No Leed component reads a color directly. Every one of them reads a --component-* custom property, and each of those resolves through a three-deep var() fallback chain. One rung of that chain belongs to you: --site-*. Set the names you care about in your own :root, leave the rest alone, and every component that reads them follows.
The resolution chain
The shape is identical for every color Leed paints:
--component-primary-color: var(
--docs-primary-color, /* set by the active documentation color theme */
var(--site-primary-color, /* set by YOU, in your own :root */
var(--default-primary-color)) /* Leed's floor, in defaults.css */
);Three rungs, resolved left to right, first defined value wins:
| Rung | Who sets it | Where | Should you? |
|---|---|---|---|
--docs-* | The color-theme-* utility on <html> | docs/themes/<name>.css, or your own custom theme | Only by writing a documentation theme |
--site-* | You | a :root block in your own tailwind/ CSS | Yes — this is the supported surface |
--default-* | Leed | defaults.css, with its own dark override | No |
Because --docs-* sits above --site-*, a documentation page with a color theme applied overrides your site palette inside the docs — which is the point of a theme. Everywhere else on the site, --site-* is the top of the chain.
flowchart LR C["--component-X<br/><i>read by every Leed component</i>"] D["--docs-X<br/>set by the color-theme utility<br/><i>docs pages only</i>"] S["--site-X<br/>set by YOU, in :root<br/><i>your supported surface</i>"] F["--default-X<br/>defaults.css<br/><i>light + dark floor</i>"] C -->|"first choice"| D D -.->|"undefined ⇒ fall through"| S S -.->|"undefined ⇒ fall through"| F D --> OUT["painted value"] S --> OUT F --> OUT
A handful of --component-* names read a differently named --docs-* token as their first choice — --component-anchor-text-color reads --docs-body-link-color, and both --component-current-location-text-color and --component-copied-content-bg-color read --docs-primary-color. That only matters if you are writing a documentation theme; from the --site-* rung the mapping is name-for-name.
Setting tokens
Tokens go in an ordinary :root block in a file under tailwind/, imported from tailwind/site.config.css. They are custom-property declarations, so they do not need to win a cascade fight — leave the file unlayered or put it in layer(base), whichever your entry file already does. Copy only the lines you care about.
/* tailwind/site/theme.css */
:root {
color-scheme: light dark;
--site-primary-color: var(--color-pink-700);
--site-primary-color-hover: var(--color-pink-800);
--site-text-color: var(--color-neutral-600);
--site-bg-color: var(--color-white);
--site-border-color: var(--color-neutral-300);
--site-header-1-color: var(--color-neutral-900);
@media (prefers-color-scheme: dark) {
--site-text-color: var(--color-neutral-300);
--site-bg-color: var(--color-neutral-950);
--site-border-color: var(--color-neutral-700);
--site-header-1-color: var(--color-neutral-50);
}
}The 45 tokens
Defaults are written as the Tailwind color names defaults.css uses. A — in the dark column means the light value carries into dark unchanged. Where a default is another token, the chain is given.
Brand and primary
| Token | What it colors | Light default | Dark default |
|---|---|---|---|
--site-primary-color | The accent: buttons, active states, the copy-confirmation ground | blue-500 | blue-400 |
--site-primary-color-hover | The accent under the pointer | blue-600 | blue-500 |
--site-primary-light | A lighter accent; the sidebar hover wash is mixed from it | blue-400 | blue-300 |
Page surface and text
| Token | What it colors | Light default | Dark default |
|---|---|---|---|
--site-bg-color | The page ground — and the diagram zoom stage | white | slate-950 |
--site-text-color | Body text | gray-700 | slate-400 |
--site-fixed-body-bg-color | The ground behind fixed/overlay body content | slate-950 | — |
--site-border-color | Generic borders and rules | slate-300 | slate-500 |
--site-card-bg-color | The ground behind an API reference card | transparent | — |
--site-step-light-color | The connector between numbered steps | gray-200 | slate-800 |
Headings
| Token | What it colors | Light default | Dark default |
|---|---|---|---|
--site-header-1-color | h1 | gray-900 | gray-200 |
--site-header-2-color | h2 | gray-900 | gray-200 |
--site-header-3-color | h3 | gray-800 | gray-100 |
--site-header-4-color | h4 | gray-800 | gray-100 |
--site-header-5-color | h5 | gray-800 | slate-100 |
--site-header-6-color | h6 | gray-800 | slate-100 |
Links and heading anchors
| Token | What it colors | Light default | Dark default |
|---|---|---|---|
--site-body-link-color | Links in the article body | --component-primary-color | — |
--site-body-link-hover-color | Body link, hover | --component-primary-color-hover | — |
--site-body-link-active-color | Body link, :active | --component-primary-color-hover | — |
--site-anchor-text-color | The # anchor beside a heading | sky-600 | — |
--site-anchor-text-color-hover | Heading anchor, hover | sky-700 | sky-400 |
--site-anchor-text-color-active | Heading anchor, :active | sky-900 | — |
Navigation and current location
| Token | What it colors | Light default | Dark default |
|---|---|---|---|
--site-current-location-text-color | The text of the page you are on, in the sidebar and TOC | sky-800 | sky-500 |
--site-current-location-bg-color | The ground of the current TOC entry | slate-50 | slate-800 |
--site-sidebar-link-bg-color | The resting ground of every sidebar row | transparent | — |
--site-sidebar-link-hover-bg-color | A sidebar row under the pointer | a 7% wash of --component-primary-light | — |
--site-sidebar-link-current-bg-color | The sidebar row for the current page | falls through to the hover token | — |
--site-menu-bg-color | Menu and dropdown grounds | white | slate-900 |
--site-header-bg-color | The site header band | gray-100 | slate-900 |
--site-header-border-color | The rule under the header | gray-500 | slate-200 |
Code and math
| Token | What it colors | Light default | Dark default |
|---|---|---|---|
--site-code-bg-color | The outer code-block well | gray-100 | slate-900 |
--site-code-inner-bg-color | The inner <code> surface | white | slate-950 |
--site-code-border-color | The code-block border (used at 10% alpha) | gray-950 | white |
--site-code-bg-color-selection | Selected text inside a code block | blue-100 | blue-800 |
--site-code-math-text-color | Rendered block and inline math | gray-700 | slate-400 |
Search
| Token | What it colors | Light default | Dark default |
|---|---|---|---|
--site-search-button-bg-color | The search trigger in the header | white | slate-950 |
--site-search-modal-bg-color | The search dialog ground | white | slate-950 |
--site-search-result-bg-color | The resting ground of one result row | transparent | — |
Buttons, pagination, footer, copy
| Token | What it colors | Light default | Dark default |
|---|---|---|---|
--site-button-secondary-bg-color | Secondary button ground | gray-100 | gray-600 |
--site-button-secondary-bg-hover-color | Secondary button, hover | gray-200 | gray-700 |
--site-pagination-icon-color | Previous/next arrows | gray-500 | gray-400 |
--site-pagination-icon-color-hover | Previous/next arrows, hover | gray-700 | gray-200 |
--site-footer-social-icons-color | Footer social icons | --component-text-color | — |
--site-footer-social-icons-hover-color | Footer social icons, hover | gray-500 | gray-400 |
--site-copied-content-bg-color | The “Copied” confirmation ground | pink-800 | — |
--site-copied-content-text-color | The “Copied” confirmation text | white | — |
All 45 names as one copy-pasteable :root block
Paste this into a file under tailwind/, delete every line you do not want, and give the survivors real values. Anything you delete falls through to Leed’s default, which already has a dark twin.
:root {
color-scheme: light dark;
/* brand */
--site-primary-color: ;
--site-primary-color-hover: ;
--site-primary-light: ;
/* surface */
--site-bg-color: ;
--site-text-color: ;
--site-fixed-body-bg-color: ;
--site-border-color: ;
--site-card-bg-color: ;
--site-step-light-color: ;
/* headings */
--site-header-1-color: ;
--site-header-2-color: ;
--site-header-3-color: ;
--site-header-4-color: ;
--site-header-5-color: ;
--site-header-6-color: ;
/* links and heading anchors */
--site-body-link-color: ;
--site-body-link-hover-color: ;
--site-body-link-active-color: ;
--site-anchor-text-color: ;
--site-anchor-text-color-hover: ;
--site-anchor-text-color-active: ;
/* navigation */
--site-current-location-text-color: ;
--site-current-location-bg-color: ;
--site-sidebar-link-bg-color: ;
--site-sidebar-link-hover-bg-color: ;
--site-sidebar-link-current-bg-color: ;
--site-menu-bg-color: ;
--site-header-bg-color: ;
--site-header-border-color: ;
/* code and math */
--site-code-bg-color: ;
--site-code-inner-bg-color: ;
--site-code-border-color: ;
--site-code-bg-color-selection: ;
--site-code-math-text-color: ;
/* search */
--site-search-button-bg-color: ;
--site-search-modal-bg-color: ;
--site-search-result-bg-color: ;
/* buttons, pagination, footer, copy */
--site-button-secondary-bg-color: ;
--site-button-secondary-bg-hover-color: ;
--site-pagination-icon-color: ;
--site-pagination-icon-color-hover: ;
--site-footer-social-icons-color: ;
--site-footer-social-icons-hover-color: ;
--site-copied-content-bg-color: ;
--site-copied-content-text-color: ;
/* layout — not a color */
--site-header-height: ;
@media (prefers-color-scheme: dark) {
/* re-declare only the ones that differ */
}
}Every token above has a separate dark default, applied by the single media query described in Dark Mode. The 17 built-in color themes that supply the --docs-* rung above these are cataloged in Themes, Fonts and Code Themes.
Five tokens that bypass the chain and are read raw
A small set of properties is read without a --component-* wrapper. Two consequences: their names carry no --site- prefix, and leaving them unset leaves them undefined rather than inherited — an undefined custom property in a color position makes the whole declaration invalid at computed-value time, so the element gets no paint at all rather than a fallback.
| Token | Read by | What happens if unset |
|---|---|---|
--api-snippets-bg-color | docs/api.css — the small mono chips beside endpoints, parameters and enum values | The chip renders with no ground; text sits directly on the page |
--api-select-list-item-bg-color-hover | docs/api.css — the server/language select list in the API reference | No hover feedback on the list rows |
--menu-footer-header-text-color | docs/docs.css — the bold column headings in the documentation footer menu | The heading falls back to inherited color |
--form-border-color / --form-border-color-hover | Occupies the middle rung of --component-form-border-color, but unprefixed | Nothing breaks — there is a --default-form-border-color floor (gray-300 light, white dark). Listed because the name surprises people |
--form-download-spinner-text | utils/utilities.css — the spinner icon inside a form download button | The spinner falls back to inherited color |
--site-header-height
The one non-color token in the contract, and the token that makes in-page anchors land correctly.
| Token | Type | Default | What reads it |
|---|---|---|---|
--site-header-height | length | 0px | html { scroll-padding-top } in defaults.css |
html {
scroll-padding-top: calc(
var(--height-header, var(--site-header-height, 0px)) + var(--height-toc, 0px)
);
}--height-header and --height-toc are published at runtime by documentation.js, on documentation pages only. Everywhere else both are undefined — which is exactly why the fallback exists, and why a non-documentation site with a fixed header has to declare the value itself:
:root {
--site-header-height: 68px;
}Measure it; do not guess. Open a page, select the header element, and read its rendered height including its border. If the number is wrong, every in-page anchor lands off by the difference — which is a quiet, permanent papercut across an entire documentation set, since deep links to a heading are how most readers arrive at a section.
--component-bg-color and the diagram zoom stage
One paragraph, because the failure only appears after a click and only in one scheme.
Every Mermaid diagram is click-to-zoom. The full-screen viewer paints its stage var(--component-bg-color, #ffffff) and clones the diagram into it — so the diagram resolves its own var() colors there, against whatever the page’s tokens say. A diagram themed for ink-on-parchment, dropped onto a hard white stage on a dark page, is dark-on-dark and close to unreadable.
Setting --site-bg-color in both schemes is enough to make --component-bg-color resolve correctly and the stage carry the same ground the diagram had in the document. The rest of diagram theming — the palette file, which keys accept a var(), and why — is in Theming Diagrams.
Tokens versus overrides
The dividing line is short. If what you want to change is a color, set a token. If it is structure — spacing, radius, geometry, layout — write a rule in a layer(components) file, as described in Cascade Layers and Overriding Leed. And if the thing you are fighting is painted by a theme utility, neither an override nor a --site-* token reaches it: set the --docs-* token instead, because the utility is unlayered and outranks anything layered you could write.
Readers arriving here because an anchor scrolled its heading under the header want the --site-header-height section above; the reading side of that behavior is described in Documentation Reading Experience.