Five hues times seven roles, generated from three numbers per hue; plus the static scales for radius, type, motion and spacing.

Last updated

Every design decision in Apogee is a CSS variable. Components read tokens; they never own a colour or a size. Theming is an override in one place, not a find-and-replace across components.

Colour: hues × roles

Colour has two axes.

Hues name intent: primary (brand), neutral, critical, positive, warning.

Roles name what a colour is for. Every hue gets the same seven:

Role Variable What it is
Fill --background-{hue} The solid colour: buttons, badges, selected states.
Faded fill --background-{hue}-faded A translucent tint for soft chips, alerts and hover rows. Alpha over the page, so it works on any surface.
Hovered fill --background-{hue}-highlighted The fill under the pointer.
Hovered faded fill --background-{hue}-faded-highlighted The tint under the pointer.
Border --border-{hue} and -faded Outlines and rings; the faded form for tinted surfaces.
Foreground --foreground-{hue} Text and icons of that hue on the page. Passes AA on the page and on the faded fill.
On-colour --on-{hue} Text on the solid fill. White or black, whichever passes AA.

neutral adds a few more: --foreground-neutral-faded (secondary text), --background-page and -page-faded, --background-elevation-base, -raised, -overlay (surfaces by height), --background-disabled, --foreground-disabled, --border-disabled, and --background-inverted with --on-inverted (tooltips, badges on media).

Backgrounds and on-colours

--background-primary--on-primary
--background-primary-faded--foreground-primary
--background-primary-highlighted--on-primary
--background-primary-faded-highlighted--foreground-primary
--background-neutral--on-neutral
--background-neutral-faded--foreground-neutral
--background-neutral-highlighted--on-neutral
--background-neutral-faded-highlighted--foreground-neutral
--background-critical--on-critical
--background-critical-faded--foreground-critical
--background-critical-highlighted--on-critical
--background-critical-faded-highlighted--foreground-critical
--background-positive--on-positive
--background-positive-faded--foreground-positive
--background-positive-highlighted--on-positive
--background-positive-faded-highlighted--foreground-positive
--background-warning--on-warning
--background-warning-faded--foreground-warning
--background-warning-highlighted--on-warning
--background-warning-faded-highlighted--foreground-warning

Page, elevation, disabled, inverted

--background-page
--background-page-faded
--background-elevation-base
--background-elevation-raised
--background-elevation-overlay
--background-disabled
--background-disabled-faded
--background-inverted

Borders

--border-primary
--border-primary-faded
--border-neutral
--border-neutral-faded
--border-critical
--border-critical-faded
--border-positive
--border-positive-faded
--border-warning
--border-warning-faded
--border-disabled

Foregrounds on the page

--foreground-primary
--foreground-neutral
--foreground-critical
--foreground-positive
--foreground-warning
--foreground-neutral-faded

One short name, the right role per utility

Tailwind’s namespaced theme keys map each role to the utility that needs it, so you type one hue name and get the correct role:

<button class="bg-primary text-on-primary hover:bg-primary-highlighted">Save</button>
<a class="text-primary">A link</a>
<div class="border border-primary-faded bg-primary-faded text-primary">A tinted callout</div>
Utility Reads
bg-{hue} --background-{hue}
text-{hue} --foreground-{hue}
text-on-{hue} --on-{hue}
border-{hue}, outline-{hue} --border-{hue}
bg-page, bg-elevation-raised, bg-inverted … the matching --background-*
ring-{hue}, divide-{hue}, from-{hue} … --color-{hue} alias of the fill

Opacity modifiers like bg-primary/10 work on the alias forms; prefer the generated -faded roles, which were tuned for contrast.

Changing the brand

The colours are generated. Edit the five inputs in scripts/hues.mjs (a Tailwind colour name such as 'blue-700', a grey family such as 'slate', or an OKLCH object; see Color) and run:

npm run build:tokens      # rewrites the generated blocks in registry/tokens/theme.css
npm run check:contrast    # fails if any text or ring pair drops below AA

From a project that copied Apogee in, point both scripts at your files:

node node_modules/@ava-ui/apogee/scripts/build-tokens.mjs src/styles/apogee.css ./hues.mjs
node node_modules/@ava-ui/apogee/scripts/check-contrast.mjs src/styles/apogee.css

(hues.mjs is a copy of the package’s file with your values; apogee.css is the tokens file init wrote.) Every build records its inputs on one line at the end of the light block, /* hues {…} */. Leave out ./hues.mjs and the script regenerates from that line, so you can edit the values there instead. Never edit the lines between the @generated markers by hand; the next build overwrites them. The generator derives the dark theme, the faded tints, the hover steps and the on-colours, and the contrast gate measures every pair it produces, in both themes, with alpha composited over the page.

To override a single token at runtime (a client’s brand on one site), set the variable in your own CSS after the theme import and re-run the gate.

Dark mode

[data-theme="dark"] on any ancestor flips the generated block. Section’s theme prop uses it to recolour a band of the page. Surfaces step up in lightness with elevation in dark mode; shadows become a faint top highlight. See Dark mode.

Skins

A skin is an optional file that redefines three inert variables (--shadow-control, --highlight-control, --shadow-field) under [data-skin]. See Skins.

Elevation

Token Light Dark
--shadow-outline Hairline shadow for cards at rest Same, deeper black
--shadow-raised Popovers, hovered cards Same, deeper black
--shadow-overlay Dialogs, command palettes Same, deeper black
--overlay 50% black scrim 65% black scrim

Utilities: shadow-outline, shadow-raised, shadow-overlay.

Radius

Token Value Utility
--radius-sm 4px rounded-sm
--radius-md 6px rounded-md
--radius-lg 10px rounded-lg
--radius-xl 14px rounded-xl

Motion

Token Value
--duration-rapid 100ms
--duration-fast 150ms
--duration-medium 250ms
--duration-slow 400ms
--ease-standard cubic-bezier(0.2, 0, 0, 1)
--ease-accelerate cubic-bezier(0.4, 0, 1, 1)
--ease-decelerate cubic-bezier(0, 0, 0.2, 1)

See Motion for the wiring and the reduced-motion rule.

Responsive props

A few layout props accept a value per breakpoint instead of a single value: direction, gap, align, justify and wrap on Box and Stack; cols and gap on Grid; colSpan and rowSpan on GridItem; size on Text. Write { base: 'column', md: 'row' }; keys are Tailwind’s breakpoints (sm 640px, md 768px, lg 1024px, xl 1280px), mobile first.

Every other prop takes one value; use class with Tailwind’s md: prefixes for the rest. The classes behind responsive props are generated into utils/responsive-tables.ts so Tailwind sees each one.

Type and spacing

Three font stacks: --font-sans (body and UI), --font-mono (code), --font-serif (quotations; the device’s default serif). Override any of them in your own @theme.

Headings use fluid clamp() tokens (--text-display, --text-h1 … --text-h6). Page rhythm uses --spacing-gutter and --spacing-section-*. Component internals keep Tailwind’s fixed numeric scale. See Typography and Spacing.

Reference

Every variable the tokens file defines, read from the file at build time.

Backgrounds (57)

TokenLightDark
--background-primaryoklch(0.488 0.243 264.376)oklch(0.488 0.1944 264.376)
--background-primary-fadedoklch(0.98 0.02 264.376)oklch(0.24 0.0198 264.376)
--background-primary-highlightedoklch(0.5392 0.243 264.376)oklch(0.5392 0.1944 264.376)
--background-primary-faded-highlightedoklch(0.488 0.243 264.376 / 0.06)oklch(0.488 0.1944 264.376 / 0.08)
--background-criticaloklch(0.577 0.245 27.325)oklch(0.5769 0.196 27.325)
--background-critical-fadedoklch(0.98 0.02 27.325)oklch(0.24 0.02 27.325)
--background-critical-highlightedoklch(0.5193 0.245 27.325)oklch(0.5192 0.196 27.325)
--background-critical-faded-highlightedoklch(0.577 0.245 27.325 / 0.06)oklch(0.5769 0.196 27.325 / 0.08)
--background-warningoklch(0.828 0.189 84.429)oklch(0.7889 0.1512 84.429)
--background-warning-fadedoklch(0.98 0.0378 84.429)oklch(0.24 0.0154 84.429)
--background-warning-highlightedoklch(0.7452 0.189 84.429)oklch(0.71 0.1512 84.429)
--background-warning-faded-highlightedoklch(0.828 0.189 84.429 / 0.06)oklch(0.7889 0.1512 84.429 / 0.08)
--background-positiveoklch(0.527 0.154 150.069)oklch(0.519 0.1232 150.069)
--background-positive-fadedoklch(0.98 0.02 150.069)oklch(0.24 0.0126 150.069)
--background-positive-highlightedoklch(0.4743 0.154 150.069)oklch(0.4671 0.1232 150.069)
--background-positive-faded-highlightedoklch(0.527 0.154 150.069 / 0.06)oklch(0.519 0.1232 150.069 / 0.08)
--background-neutraloklch(0.94 0.0047 213.5)oklch(0.32 0.0038 213.5)
--background-neutral-fadedoklch(0.55 0.0208 213.5 / 0.07)oklch(0.8 0.0134 213.5 / 0.1)
--background-neutral-highlightedoklch(0.9118 0.0047 213.5)oklch(0.3608 0.0038 213.5)
--background-neutral-faded-highlightedoklch(0.55 0.0208 213.5 / 0.13)oklch(0.8 0.0134 213.5 / 0.16)
--background-disabledoklch(0.95 0.004 213.5)oklch(0.28 0.0169 213.5)
--background-disabled-fadedoklch(0.98 0.0016 213.5)oklch(0.225 0.0146 213.5)
--background-elevation-baseoklch(1 0 213.5)oklch(0.2 0.0134 213.5)
--background-elevation-raisedoklch(1 0 213.5)oklch(0.22 0.0144 213.5)
--background-elevation-overlayoklch(1 0 213.5)oklch(0.24 0.0153 213.5)
--background-pageoklch(1 0 213.5)oklch(0.16 0.0113 213.5)
--background-page-fadedoklch(0.98 0.0016 213.5)oklch(0.18 0.0124 213.5)
--background-invertedoklch(0.24 0.0153 213.5)oklch(0.96 0.0032 213.5)
--background-color-primaryvar(--background-primary)—
--background-color-primary-fadedvar(--background-primary-faded)—
--background-color-primary-highlightedvar(--background-primary-highlighted)—
--background-color-primary-faded-highlightedvar(--background-primary-faded-highlighted)—
--background-color-neutralvar(--background-neutral)—
--background-color-neutral-fadedvar(--background-neutral-faded)—
--background-color-neutral-highlightedvar(--background-neutral-highlighted)—
--background-color-neutral-faded-highlightedvar(--background-neutral-faded-highlighted)—
--background-color-criticalvar(--background-critical)—
--background-color-critical-fadedvar(--background-critical-faded)—
--background-color-critical-highlightedvar(--background-critical-highlighted)—
--background-color-critical-faded-highlightedvar(--background-critical-faded-highlighted)—
--background-color-positivevar(--background-positive)—
--background-color-positive-fadedvar(--background-positive-faded)—
--background-color-positive-highlightedvar(--background-positive-highlighted)—
--background-color-positive-faded-highlightedvar(--background-positive-faded-highlighted)—
--background-color-warningvar(--background-warning)—
--background-color-warning-fadedvar(--background-warning-faded)—
--background-color-warning-highlightedvar(--background-warning-highlighted)—
--background-color-warning-faded-highlightedvar(--background-warning-faded-highlighted)—
--background-color-pagevar(--background-page)—
--background-color-page-fadedvar(--background-page-faded)—
--background-color-elevation-basevar(--background-elevation-base)—
--background-color-elevation-raisedvar(--background-elevation-raised)—
--background-color-elevation-overlayvar(--background-elevation-overlay)—
--background-color-disabledvar(--background-disabled)—
--background-color-disabled-fadedvar(--background-disabled-faded)—
--background-color-invertedvar(--background-inverted)—
--background-color-overlayvar(--overlay)—

Foregrounds (7)

TokenLightDark
--foreground-primaryoklch(0.52 0.243 264.376)oklch(0.75 0.2066 264.376)
--foreground-criticaloklch(0.52 0.245 27.325)oklch(0.75 0.2083 27.325)
--foreground-warningoklch(0.52 0.189 84.429)oklch(0.75 0.1606 84.429)
--foreground-positiveoklch(0.52 0.154 150.069)oklch(0.75 0.1309 150.069)
--foreground-neutraloklch(0.24 0.0153 213.5)oklch(0.96 0.0032 213.5)
--foreground-neutral-fadedoklch(0.52 0.021 213.5)oklch(0.74 0.0162 213.5)
--foreground-disabledoklch(0.65 0.0191 213.5)oklch(0.45 0.0208 213.5)

Borders and outlines (28)

TokenLightDark
--border-primaryoklch(0.448 0.243 264.376)oklch(0.588 0.1944 264.376)
--border-primary-fadedoklch(0.94 0.02 264.376)oklch(0.3 0.0198 264.376)
--border-criticaloklch(0.537 0.245 27.325)oklch(0.6769 0.196 27.325)
--border-critical-fadedoklch(0.94 0.02 27.325)oklch(0.3 0.02 27.325)
--border-warningoklch(0.788 0.189 84.429)oklch(0.8889 0.1512 84.429)
--border-warning-fadedoklch(0.94 0.0378 84.429)oklch(0.3 0.0154 84.429)
--border-positiveoklch(0.487 0.154 150.069)oklch(0.619 0.1232 150.069)
--border-positive-fadedoklch(0.94 0.02 150.069)oklch(0.3 0.0126 150.069)
--border-neutraloklch(0.2 0.0134 213.5 / 0.12)oklch(0.95 0.004 213.5 / 0.12)
--border-neutral-fadedoklch(0.2 0.0134 213.5 / 0.08)oklch(0.95 0.004 213.5 / 0.08)
--border-disabledoklch(0.2 0.0134 213.5 / 0.06)oklch(0.95 0.004 213.5 / 0.06)
--border-color-primaryvar(--border-primary)—
--border-color-primary-fadedvar(--border-primary-faded)—
--outline-color-primaryvar(--border-primary)—
--outline-color-focusvar(--border-primary)—
--border-color-neutralvar(--border-neutral)—
--border-color-neutral-fadedvar(--border-neutral-faded)—
--outline-color-neutralvar(--border-neutral)—
--border-color-criticalvar(--border-critical)—
--border-color-critical-fadedvar(--border-critical-faded)—
--outline-color-criticalvar(--border-critical)—
--border-color-positivevar(--border-positive)—
--border-color-positive-fadedvar(--border-positive-faded)—
--outline-color-positivevar(--border-positive)—
--border-color-warningvar(--border-warning)—
--border-color-warning-fadedvar(--border-warning-faded)—
--outline-color-warningvar(--border-warning)—
--border-color-disabledvar(--border-disabled)—

On-colours (6)

TokenLightDark
--on-primaryoklch(1 0 0)oklch(1 0 0)
--on-criticaloklch(1 0 0)oklch(1 0 0)
--on-warningoklch(0 0 0)oklch(0 0 0)
--on-positiveoklch(1 0 0)oklch(1 0 0)
--on-neutraloklch(0 0 0)oklch(1 0 0)
--on-invertedoklch(1 0 213.5)oklch(0.16 0.0113 213.5)

Tailwind colour bridge (6)

TokenLightDark
--color-neutralvar(--border-neutral)—
--color-primaryvar(--background-primary)—
--color-criticalvar(--background-critical)—
--color-positivevar(--background-positive)—
--color-warningvar(--background-warning)—
--color-pagevar(--background-page)—

Shadows and gradients (8)

TokenLightDark
--press-overlayoklch(0 0 0 / 0.08)oklch(1 0 0 / 0.1)
--shadow-outline0 1px 2px -0.5px rgb(0 0 0 / 0.06), 0 2px 3px -1px rgb(0 0 0 / 0.06)0 1px 2px rgb(0 0 0 / 0.4)
--shadow-raised0 8px 12px -4px rgb(0 0 0 / 0.08), 0 1px 2px rgb(0 0 0 / 0.1)0 8px 16px -4px rgb(0 0 0 / 0.5), 0 2px 4px -1px rgb(0 0 0 / 0.4)
--shadow-overlay0 16px 24px -4px rgb(0 0 0 / 0.12), 0 4px 8px -2px rgb(0 0 0 / 0.08), 0 1px 2px rgb(0 0 0 / 0.1)0 20px 32px -8px rgb(0 0 0 / 0.6), 0 8px 12px -4px rgb(0 0 0 / 0.45), 0 1px 3px rgb(0 0 0 / 0.4)
--shadow-control0 0 #0000—
--shadow-field0 0 #0000—
--gradient-controlnone—
--shadow-surface0 0 #0000—

Radius (4)

TokenLightDark
--radius-sm4px—
--radius-md6px—
--radius-lg10px—
--radius-xl14px—

Motion (7)

TokenLightDark
--ease-standardcubic-bezier(0.2, 0, 0, 1)—
--ease-acceleratecubic-bezier(0.4, 0, 1, 1)—
--ease-deceleratecubic-bezier(0, 0, 0.2, 1)—
--duration-rapid100ms—
--duration-fast150ms—
--duration-medium250ms—
--duration-slow400ms—

Type (30)

TokenLightDark
--text-color-primaryvar(--foreground-primary)—
--text-color-on-primaryvar(--on-primary)—
--text-color-neutralvar(--foreground-neutral)—
--text-color-neutral-fadedvar(--foreground-neutral-faded)—
--text-color-on-neutralvar(--on-neutral)—
--text-color-criticalvar(--foreground-critical)—
--text-color-on-criticalvar(--on-critical)—
--text-color-positivevar(--foreground-positive)—
--text-color-on-positivevar(--on-positive)—
--text-color-warningvar(--foreground-warning)—
--text-color-on-warningvar(--on-warning)—
--text-color-disabledvar(--foreground-disabled)—
--text-color-on-invertedvar(--on-inverted)—
--font-sansui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'—
--font-monoui-monospace, SFMono-Regular, Menlo, Consolas, 'Liberation Mono', 'Courier New', monospace—
--font-serifui-serif, Georgia, Cambria, 'Times New Roman', Times, serif—
--text-displayclamp(2.5rem, 1.6rem + 4.5vw, 4.5rem)—
--text-display--line-height1.05—
--text-h1clamp(2rem, 1.5rem + 2.5vw, 3rem)—
--text-h1--line-height1.1—
--text-h2clamp(1.625rem, 1.35rem + 1.4vw, 2.25rem)—
--text-h2--line-height1.15—
--text-h3clamp(1.375rem, 1.2rem + 0.9vw, 1.75rem)—
--text-h3--line-height1.2—
--text-h4clamp(1.125rem, 1.05rem + 0.4vw, 1.375rem)—
--text-h4--line-height1.3—
--text-h5clamp(1rem, 0.97rem + 0.2vw, 1.125rem)—
--text-h5--line-height1.4—
--text-h6clamp(0.875rem, 0.85rem + 0.15vw, 1rem)—
--text-h6--line-height1.45—

Spacing and layout (5)

TokenLightDark
--spacing-gutterclamp(1rem, 0.55rem + 2.25vw, 2rem)—
--spacing-section-smclamp(2rem, 1.4rem + 3vw, 3.5rem)—
--spacing-sectionclamp(3rem, 2rem + 5vw, 6rem)—
--spacing-section-lgclamp(4rem, 2.57rem + 4.76vw, 6rem)—
--spacing-section-xlclamp(5rem, 3rem + 10vw, 12rem)—

Other (17)

TokenLightDark
--overlayoklch(0 0 0 / 0.5)oklch(0 0 0 / 0.65)
--navbar-height0px—
--apogee-base-pagevar(--background-page)—
--apogee-base-neutralvar(--foreground-neutral)—
--apogee-base-neutral-text-fadedvar(--foreground-neutral-faded)—
--apogee-base-bordervar(--border-neutral)—
--apogee-base-border-fadedvar(--border-neutral-faded)—
--apogee-base-neutral-fadedvar(--background-neutral-faded)—
--apogee-base-neutral-faded-highlightedvar(--background-neutral-faded-highlighted)—
--apogee-base-primaryvar(--background-primary)—
--apogee-base-primary-highlightedvar(--background-primary-highlighted)—
--apogee-base-on-primaryvar(--on-primary)—
--apogee-base-primary-textvar(--foreground-primary)—
--apogee-base-primary-bordervar(--border-primary)—
--highlight-controltransparent—
--default-transition-durationvar(--duration-fast)—
--default-transition-timing-functionvar(--ease-standard)—