// Docs-only stylesheet, built by the `css` script through the shared css // pipeline (.build/build-css.ts, with --no-prefix) into tmp-assets/css/; // copy-assets serves it as /css/docs.css. Tabler variables are written with // their public `--tblr-` prefix here: the inlined DocSearch stylesheet below // has variables its JS sets at runtime, so the build must not rename any. @use '@docsearch/css/dist/style.css'; // DocSearch ships its own palette — an Algolia blue plus lavender greys — and // switches themes on `html[data-theme=dark]`, which Tabler never sets. Mapping // every colour onto a Tabler token fixes both: the modal matches the docs, and // it follows the color mode through the `light-dark()` tokens. :root { --docsearch-primary-color: var(--tblr-primary); --docsearch-highlight-color: var(--tblr-primary); --docsearch-text-color: var(--tblr-body-color); --docsearch-muted-color: var(--tblr-secondary); --docsearch-secondary-text-color: var(--tblr-secondary); --docsearch-icon-color: var(--tblr-secondary); --docsearch-subtle-color: var(--tblr-border-color); --docsearch-container-background: color-mix(in srgb, var(--tblr-backdrop-bg), transparent calc(100% - 32%)); --docsearch-modal-background: var(--tblr-bg-surface); --docsearch-modal-shadow: var(--tblr-shadow-dropdown); --docsearch-searchbox-background: var(--tblr-bg-surface); --docsearch-searchbox-focus-background: var(--tblr-bg-surface); --docsearch-hit-background: var(--tblr-bg-surface-secondary); --docsearch-hit-color: var(--tblr-body-color); --docsearch-hit-shadow: none; --docsearch-footer-background: var(--tblr-bg-surface); --docsearch-key-background: var(--tblr-bg-surface-secondary); --docsearch-key-color: var(--tblr-secondary); --docsearch-key-pressed-shadow: 0 0 0 1px var(--tblr-border-color); } .DocSearch-Button { width: 100%; box-shadow: 0 0 0 1px var(--tblr-border-color); font-weight: var(--tblr-font-weight-normal); transition: all 0.2s ease-in-out; } .DocSearch-SearchBar { display: flex; padding: var(--docsearch-spacing) var(--docsearch-spacing) 0; background-color: var(--tblr-bg-surface) !important; } .DocSearch-Hit-source { background: none !important; } .DocSearch-Dropdown { background-color: var(--tblr-bg-surface) !important; } .DocSearch-Hit a { background-color: var(--tblr-bg-surface-secondary) !important; border: 1px solid var(--tblr-border-color) !important; color: var(--tblr-body-color) !important; box-shadow: none !important; &:hover { background-color: var(--tblr-bg-surface-tertiary) !important; } } .DocSearch-Hit-Container { color: var(--tblr-body-color) !important; } .DocSearch-Hit-title { color: var(--tblr-body-color) !important; } .DocSearch-Hit-path { color: var(--tblr-muted) !important; } // The selected hit is tinted like the active page in the sidebar rather than // filled with the primary color: DocSearch colors the matched words itself, and // on a solid fill they came out blue on blue. .DocSearch-Hit[aria-selected='true'] a { background-color: color-mix(in srgb, var(--tblr-primary) 8%, transparent) !important; border-color: var(--tblr-primary) !important; } .DocSearch-Modal { background-color: var(--tblr-bg-surface) !important; box-shadow: var(--tblr-shadow-dropdown) !important; } .DocSearch-Form { background-color: var(--tblr-bg-surface-secondary) !important; border: 1px solid var(--tblr-border-color) !important; } .DocSearch-Input { color: var(--tblr-body-color) !important; &::placeholder { color: var(--tblr-muted) !important; } } .DocSearch-Footer { background-color: var(--tblr-bg-surface) !important; border-top: 1px solid var(--tblr-border-color) !important; box-shadow: none !important; } .DocSearch-Button-Key { top: 0; } .DocSearch-Container { z-index: 10000; } .no-transition { * { transition: none !important; } } .example > .modal, .example > .offcanvas { display: block !important; position: relative !important; } .example > .offcanvas-backdrop { position: absolute !important; } // Highlighted code. Shiki writes the theme's own background and foreground // into a style attribute — `.shiki` comes from the docs components, and // `.astro-code` from markdown fences. Both follow Tabler's surface instead, // so only the token colors come from the shiki theme. .shiki, .astro-code { background: var(--tblr-bg-surface-dark) !important; color: var(--tblr-gray-300) !important; } code { ::selection { background: var(--tblr-primary); } } // ----------------------------------------------------------------------------- // Docs layout: muted page canvas, elevated article panel, compact sidebar // navigation, "On this page" rail and joined example/code groups. // ----------------------------------------------------------------------------- // Top navbar reads as a distinct bar on the muted canvas. Tabler's navbar // already draws its edge with an inset box-shadow, so an explicit border // would double the line. header > .navbar { background-color: var(--tblr-bg-surface); // Quiet text links: secondary until hovered, like the sidebar navigation. .docs-navbar-link { padding: 0.25rem 0.625rem; border-radius: var(--tblr-border-radius); color: var(--tblr-secondary); &:hover, &:focus-visible { color: var(--tblr-body-color); background-color: color-mix(in srgb, var(--tblr-body-color) 6%, transparent); } } } // Article panel: elevated surface holding the page content. .docs-article { background-color: var(--tblr-bg-surface); border: 1px solid var(--tblr-border-color); border-radius: var(--tblr-border-radius-lg); box-shadow: var(--tblr-shadow-xs); margin-block: 1rem; @media (min-width: 992px) { margin: 1.5rem; } } // Page footer below the article panel, aligned with the panel content. .docs-footer { margin-block-start: 2rem; padding: 0.5rem 0 2rem; @media (min-width: 992px) { margin-block-start: 4rem; margin-inline: 1.5rem; padding: 0.5rem 0 2.5rem; } } // Footer theme switcher: a segmented control whose active segment follows the // stored choice — light, dark, or auto (see the data-docs-theme inline script // in DocsLayout.astro; tabler-theme.js resolves auto to the system scheme). :root[data-docs-theme='light'] .docs-theme-toggle-light, :root[data-docs-theme='dark'] .docs-theme-toggle-dark, :root[data-docs-theme='auto'] .docs-theme-toggle-auto { color: var(--tblr-body-color); background: var(--tblr-bg-surface); border-color: var(--tblr-border-color); } // Sidebar navigation: compact rounded links. .col-docs { width: 15rem; // Section subheaders line up with the link text, not the pill edge. .subheader { padding-inline: 0.625rem; } .nav-link { border-radius: var(--tblr-border-radius); padding: 0.375rem 0.625rem; color: var(--tblr-body-color); &:hover, &:focus-visible { background-color: color-mix(in srgb, var(--tblr-body-color) 6%, transparent); } // The sidebar links had no visible focus ring at all — `.nav-link` only // resets the outline. Match the ring the buttons use. &:focus-visible { outline: 0; box-shadow: 0 0 0 var(--tblr-focus-ring-width, 0.25rem) var(--tblr-focus-ring-color); } } // Every row stays one line: a wrapped label turns the current-page pill into // a two-line block and breaks the rhythm of the list. .nav-link-title { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .nav-link-toggle { flex-shrink: 0; } // Hierarchy comes from weight, not size: a group that opens a section reads // stronger than the pages inside it. #menu .nav-link[data-bs-toggle='collapse'] { font-weight: var(--tblr-font-weight-medium); } .nav-vertical .nav { padding-inline-start: 0.5rem; margin-inline-start: 0.625rem; } // Expanded groups only switch weight; the tinted pill marks the current page. #menu .nav-link.active { color: var(--tblr-body-color); background-color: transparent; &:hover, &:focus-visible { background-color: color-mix(in srgb, var(--tblr-body-color) 6%, transparent); } } #menu .nav-link[aria-current='page'] { &, &:hover, &:focus-visible { color: var(--tblr-primary); background-color: color-mix(in srgb, var(--tblr-primary) 8%, transparent); font-weight: var(--tblr-font-weight-medium); } } } // Article typography. Docs pages are long-form reading, so the prose gets a // larger heading scale and more breathing room than the product defaults in // core's _markdown.scss. Everything targets direct children of .prose — the // markdown output — so markup inside demos keeps the product styles. .docs-page-title { font-size: 1.75rem; text-wrap: balance; } .docs-article .prose { // Heading scale: the defaults (h2 20px, h3 16px) leave h3 the same size as // body text, so subsections disappear. Margin below grows from 8px, which // glued the heading to its section while 40px separated it from the last. > h2 { margin-bottom: 1rem; font-size: 1.375rem; } > h3 { margin-bottom: 0.75rem; font-size: 1.125rem; } > p, > ul, > ol { margin-bottom: 1.25rem; } > ul li + li, > ol li + li { margin-top: 0.25em; } // Prose links keep the primary color but gain a quiet underline, so they // are findable when scanning and do not rely on color alone (WCAG 1.4.1). > :is(p, ul, ol, .docs-table) a:not(.btn) { text-decoration: underline; text-decoration-color: color-mix(in srgb, var(--tblr-primary) 40%, transparent); text-underline-offset: 3px; &:hover { text-decoration-color: var(--tblr-primary); } } // Markdown tables. The wrap-tables hast plugin (lib/satteri-wrap-tables.mjs) // puts each one in a .docs-table box, so a wide table scrolls inside the // column instead of pushing the page out. The wrapper also takes the table // out of core's `.prose > table` rule, so the whole look is defined here: // a bordered grid with a tinted header row (as on GitHub and VitePress), // full width, tighter rows, and a first column that never wraps — the // option or method name is what the reader scans for, and the description // gets whatever is left. The border sits on the box, not the table, so the // rounded corners survive a sideways scroll. > .docs-table { margin-bottom: 1.5rem; overflow-x: auto; border: 1px solid var(--tblr-border-color); border-radius: var(--tblr-border-radius); > table { width: 100%; margin: 0; font-size: 0.875rem; line-height: 1.5; border-collapse: collapse; } th, td { padding: 0.5rem 0.75rem; text-align: start; vertical-align: top; border: 1px solid var(--tblr-border-color); } // The box already draws the outer edge. tr > :first-child { border-inline-start: 0; } tr > :last-child { border-inline-end: 0; } thead tr:first-child > * { border-top: 0; } tbody tr:last-child > * { border-bottom: 0; } th { padding-top: 0.25rem; padding-bottom: 0.25rem; font-size: 0.8125rem; font-weight: var(--tblr-font-weight-semibold); color: var(--tblr-body-color); white-space: nowrap; background: var(--tblr-bg-surface-tertiary); } // Names, types and defaults stay on one line; only the last column wraps. // Every middle cell in the docs is a short label, so the browser's even // split would only spread wrapped fragments across the row. td:not(:last-child) { white-space: nowrap; } // On a phone the box scrolls sideways; without a floor the description // column would be the one squeezed into a tall sliver instead. th:last-child, td:last-child { min-width: 18rem; } td > p:last-child { margin-bottom: 0; } } // `pretty` only optimises the tail of a block, so it stays cheap on long // text; `balance` evens out all lines and suits short headings. > :is(p, li, blockquote, dd), > :is(ul, ol) li { text-wrap: pretty; } > :is(h2, h3, h4, h5, h6) { text-wrap: balance; } } // Table of contents: rail with a scrollspy indicator. #toc { border-inline-start: 1px solid var(--tblr-border-color); .nav-link { padding: 0.25rem 0.75rem; margin-inline-start: -1px; border-inline-start: 2px solid transparent; border-radius: 0; color: var(--tblr-secondary); font-size: 0.875rem; &:hover, &:focus-visible { color: var(--tblr-body-color); background-color: transparent; } &.active { color: var(--tblr-primary); border-inline-start-color: var(--tblr-primary); font-weight: var(--tblr-font-weight-medium); background-color: transparent; } &.docs-toc-sub { padding-inline-start: 1.75rem; } } } // Page tools under the table of contents: source, edit. One shape for both // links (copying the page as markdown moved to the "Copy to LLM" button above // the title, see DocsCopyMarkdownLink). .docs-page-links { display: flex; flex-direction: column; align-items: flex-start; gap: 0.5rem; } .docs-page-link { display: inline-flex; align-items: center; gap: 0.5rem; padding: 0; font-size: 0.875rem; color: var(--tblr-secondary); text-decoration: none; background: none; border: 0; .icon { width: 1rem; height: 1rem; flex-shrink: 0; } &:hover, &:focus-visible { color: var(--tblr-body-color); } &:focus-visible { outline: 0; border-radius: var(--tblr-border-radius); box-shadow: 0 0 0 var(--tblr-focus-ring-width, 0.25rem) var(--tblr-focus-ring-color); } } // Previous/next page navigation: a pair of cards on the page canvas below the // article panel, aligned with it and with the footer. .docs-pagination { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; margin-block-end: 1.5rem; @media (min-width: 992px) { margin-inline: 1.5rem; } .docs-pagination-link { display: flex; flex-direction: column; gap: 0.25rem; padding: 1rem 1.25rem; border-radius: var(--tblr-border-radius-lg); background-color: transparent; text-decoration: none; transition: background-color 0.15s ease-in-out; &:hover, &:focus-visible { background-color: color-mix(in srgb, var(--tblr-body-color) 4%, transparent); } } .docs-pagination-next { grid-column: 2; align-items: flex-end; text-align: end; } // Direction reads as an eyebrow label, so the page title carries the line. .docs-pagination-label { display: inline-flex; align-items: center; gap: 0.25rem; font-size: 0.6875rem; font-weight: var(--tblr-font-weight-medium); text-transform: uppercase; letter-spacing: 0.04em; color: var(--tblr-secondary); } .docs-pagination-title { font-weight: var(--tblr-font-weight-medium); color: var(--tblr-body-color); transition: color 0.15s ease-in-out; } // Side by side leaves no room for a longer page title on a phone. Last in the // block on purpose: it has to outrank the `grid-column: 2` rule above. @media (max-width: 575.98px) { grid-template-columns: 1fr; .docs-pagination-next { grid-column: 1; } } } // Related pages use the same card language as prev/next: a quiet bordered // surface that takes the primary color on hover, and a title that leads. .docs-related { .card { border-radius: var(--tblr-border-radius-lg); box-shadow: none; transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; &:hover, &:focus-visible { border-color: var(--tblr-primary); box-shadow: var(--tblr-shadow-xs); .h3 { color: var(--tblr-primary); } } } .card-body { padding: 1rem 1.25rem; } .h3 { margin-bottom: 0.25rem; font-size: 1rem; font-weight: var(--tblr-font-weight-medium); color: var(--tblr-body-color); transition: color 0.15s ease-in-out; } p { font-size: 0.875rem; } } // Example blocks: the demo and its code panel form one rounded group. .docs-example .example { margin: 0; } .docs-example-code .shiki { margin: 0; border-radius: var(--tblr-border-radius); } .docs-example-code-attached .shiki { border-start-start-radius: 0; border-start-end-radius: 0; } // ----------------------------------------------------------------------------- // Callouts: a tinted aside for a note, a tip or a warning inside prose. The icon // carries the state color while the text stays body color, so the block reads as // part of the page — and so a pale state like yellow never has to carry text. // ----------------------------------------------------------------------------- .callout { display: flex; gap: 0.75rem; padding: 1rem 1.25rem; margin-block: 1.5rem; font-size: 0.875rem; line-height: 1.5; color: var(--tblr-body-color); background: var(--tblr-bg-surface-secondary); border: 1px solid var(--tblr-border-color); border-radius: var(--tblr-border-radius-lg); } .callout-icon { flex: none; margin-top: 0.0625rem; color: var(--tblr-secondary); } .callout-title { margin-bottom: 0.25rem; font-weight: var(--tblr-font-weight-medium); } // The slot holds markdown, so the trailing paragraph or list brings its own // bottom margin — which would pad the callout unevenly. .callout-body > :last-child { margin-bottom: 0; } // The prose link treatment targets direct children of .prose, so it stops at the // callout. Links inside one get the same quiet underline (WCAG 1.4.1). .callout a:not(.btn) { text-decoration: underline; text-decoration-color: color-mix(in sRGB, var(--tblr-primary) 40%, transparent); text-underline-offset: 3px; &:hover { text-decoration-color: var(--tblr-primary); } } @each $state, $color in ('info': 'azure', 'tip': 'green', 'warning': 'yellow', 'danger': 'red') { .callout-#{$state} { background: var(--tblr-#{$color}-lt); border-color: color-mix(in srgb, var(--tblr-#{$color}) 24%, transparent); .callout-icon { color: var(--tblr-#{$color}); } } } // // Sticky header // // `position: sticky` only travels inside its containing block. The docs shell // has no `.page` wrapper - the header is a child of `body` - and core's // `body { height: 100% }` makes that block exactly one viewport tall, so the // header would unstick after the first screen (#2332). Let the docs body grow // with its content instead; `.page` does the same thing for the app layouts. body { height: auto; min-height: 100%; } :root { // Matches `$navbar-height` in core - the height of the docs navbar. --docs-header-height: 3.5rem; } // Anchors from the table of contents would otherwise land under the header. html { scroll-padding-top: var(--docs-header-height); } // The table of contents sticks too, so it has to clear the header (#2421). // A long list must not run past the viewport either: the rail is capped at the // space below the header and scrolls on its own (#2965). .docs-toc-sticky { top: var(--docs-header-height); max-height: calc(100vh - var(--docs-header-height)); overflow-y: auto; } // The copy button on a code block turns green while `.copied` is on it // (core/js/src/clipboard.ts). The hover and active variants are set too: the // pointer is still over the button right after a click. .btn-dark.copied { --tblr-btn-bg: var(--tblr-success); --tblr-btn-hover-bg: var(--tblr-success); --tblr-btn-active-bg: var(--tblr-success); --tblr-btn-border-color: var(--tblr-success); --tblr-btn-hover-border-color: var(--tblr-success); --tblr-btn-active-border-color: var(--tblr-success); }