Theming
Timeless has three styling surfaces, in order of how often you should reach for them:
- Design tokens — global values every component reads. Change these to rebrand everything.
- Component variables — per-component custom properties. Change these to restyle one component.
- Your own CSS — public classes, plain attributes, and
data-ui-partselectors. Change these when you want something the first two cannot express.
There is no configuration file and no build step. All three are plain CSS.
Cascade layers come first
Section titled “Cascade layers come first”tokens.css opens with the layer order, so every Timeless rule lands in a named layer:
@layer ui.tokens, ui.components, ui.utilities;Unlayered CSS beats layered CSS, whatever the specificity. So your own stylesheet wins over
component styles by default — a single class overrides a Timeless rule with three selectors, and you
never need !important:
/* Wins over .ui-button, no matter how specific the component rule is. */.checkout-button { border-radius: 0;}Put your CSS in ui.utilities when you want it layered but still above the components, and import
Timeless styles before your own so the layer order is established first.
Design tokens
Section titled “Design tokens”Tokens are CSS custom properties on :root, all prefixed --ui-. Redefine any of them in your own
:root block, or scope them to a subtree to theme part of a page:
:root { --ui-accent: oklch(58% 0.19 265); --ui-radius-control: 0.25rem; --ui-font-sans: 'Inter Variable', system-ui, sans-serif;}
.marketing-section { --ui-bg-surface: #fffaf0;}The names and groups are exported as atmosphereTokenGroups from @timelessui/components, so you
can drive a theme editor or a token audit from the same list this page is generated from.
| Token | Value |
|---|---|
--ui-bg-page | light-dark(#f7f7f8, #111113) |
--ui-bg-surface | light-dark(#ffffff, #19191d) |
--ui-bg-surface-raised | light-dark(#f1f2f4, #24242a) |
--ui-bg-surface-subtle | light-dark(#f9f9fa, #151519) |
--ui-fg | light-dark(#17171a, #f4f4f5) |
--ui-fg-muted | light-dark(#666a73, #a5a7b0) |
--ui-fg-subtle | light-dark(#80848d, #8e9099) |
--ui-line | light-dark(#d7d9de, #343641) |
--ui-line-strong | light-dark(#b8bcc5, #505360) |
--ui-accent | #0064d8 |
--ui-accent-hover | #0045b7 |
--ui-accent-active | #005cd7 |
--ui-accent-soft | light-dark(rgb(0 122 255 / 10%), rgb(0 122 255 / 18%)) |
--ui-success | light-dark(#0f7134, #4ade80) |
--ui-success-soft | light-dark(rgb(15 113 52 / 10%), rgb(74 222 128 / 14%)) |
--ui-warning | light-dark(#805500, #facc15) |
--ui-warning-soft | light-dark(rgb(128 85 0 / 12%), rgb(250 204 21 / 14%)) |
--ui-danger | light-dark(#b91c1c, #f87171) |
--ui-danger-soft | light-dark(rgb(185 28 28 / 10%), rgb(248 113 113 / 14%)) |
--ui-focus | color-mix(in oklab, var(--ui-accent), transparent 25%) |
Control fills
Section titled “Control fills”| Token | Value |
|---|---|
--ui-bg-control | light-dark(#ececef, #25262b) |
--ui-bg-control-hover | light-dark(#e1e2e6, #30323a) |
--ui-bg-control-active | light-dark(#d6d7dc, #383b44) |
--ui-bg-control-muted | light-dark(#f0f1f3, #202126) |
--ui-bg-accent | #0064d8 |
--ui-bg-accent-hover | color-mix(in oklab, #0064d8 78%, #0045b7) |
--ui-bg-accent-active | color-mix(in oklab, #0064d8 70%, #0050ad) |
--ui-bg-danger | #cf2034 |
--ui-bg-danger-hover | color-mix(in oklab, #cf2034 82%, #a91427) |
--ui-bg-danger-active | color-mix(in oklab, #cf2034 74%, #93101f) |
Radius
Section titled “Radius”| Token | Value |
|---|---|
--ui-radius-xs | 0.25rem |
--ui-radius-sm | 0.375rem |
--ui-radius-md | 0.5rem |
--ui-radius-lg | 0.625rem |
--ui-radius-xl | 0.875rem |
--ui-radius-control | 0.875rem |
--ui-radius-pill | 999px |
Shadow
Section titled “Shadow”| Token | Value |
|---|---|
--ui-shadow-control | 0 1px 1px rgb(0 0 0 / 10%), 0 2px 5px rgb(0 0 0 / 8%), inset 0 1px 0 … |
--ui-shadow-control-hover | 0 1px 1px rgb(0 0 0 / 10%), 0 2px 6px rgb(0 0 0 / 9%), inset 0 1px 0 … |
--ui-shadow-control-active | inset 0 1px 2px rgb(0 0 0 / 16%), inset 0 1px 0 rgb(255 255 255 / 12%) |
--ui-shadow-control-accent | 0 1px 1px rgb(0 0 0 / 10%), 0 2px 5px rgb(0 122 255 / 12%), inset 0 1… |
--ui-shadow-control-danger | 0 1px 1px rgb(0 0 0 / 10%), 0 2px 5px rgb(255 51 71 / 14%), inset 0 1… |
--ui-shadow-outline-control | 0 1px 1px light-dark(rgb(0 0 0 / 8%), rgb(0 0 0 / 28%)), 0 2px 5px li… |
--ui-shadow-floating | 0 1px 1px rgb(0 0 0 / 10%), 0 8px 24px rgb(0 0 0 / 10%) |
--ui-shadow-inset | inset 0 1px 2px light-dark(rgb(0 0 0 / 8%), rgb(0 0 0 / 26%)) |
| Token | Value |
|---|---|
--ui-space-1 | 0.25rem |
--ui-space-2 | 0.375rem |
--ui-space-3 | 0.5rem |
--ui-space-4 | 0.75rem |
--ui-space-5 | 1rem |
Typography
Section titled “Typography”| Token | Value |
|---|---|
--ui-font-sans | Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, '… |
--ui-font-mono | 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, ui-monospace, m… |
Motion
Section titled “Motion”| Token | Value |
|---|---|
--ui-duration-fast | 120ms |
--ui-ease-standard | cubic-bezier(0.2, 0, 0, 1) |
Effects
Section titled “Effects”| Token | Value |
|---|---|
--ui-disabled-opacity | 0.56 |
--ui-checkerboard | linear-gradient( 45deg, var(--ui-checkerboard-ink) 25%, #0000 25% 75%… |
--ui-checkerboard-ink | light-dark(rgb(0 0 0 / 16%), rgb(255 255 255 / 16%)) |
Light and dark
Section titled “Light and dark”tokens.css sets color-scheme: light dark and expresses every scheme-dependent token with
light-dark():
--ui-bg-surface: light-dark(#ffffff, #19191d);That means the platform picks the scheme and no JavaScript is involved. To force one scheme, set
color-scheme on the root or on any subtree:
/* Always light, even when the OS asks for dark. */.invoice-preview { color-scheme: light;}When you override a scheme-dependent token, keep using light-dark() so both schemes stay correct:
:root { --ui-bg-page: light-dark(#fdfdfd, #0d0d10);}Some tokens are deliberately scheme-independent — brand fills such as --ui-bg-accent are the same
in both schemes, and their hover and active steps are derived with color-mix() in OKLab:
--ui-bg-accent-hover: color-mix(in oklab, #0064d8 78%, #0045b7);Component variables
Section titled “Component variables”Each component documents the custom properties it reads from its own root. They are listed in the Styling section of every component page. Set them wherever you want the change to apply:
/* Every button in the app. */.ui-button { --ui-button-radius: 0.375rem;}
/* One button. */.checkout-button { --ui-button-bg: var(--ui-success); --ui-button-height: 3rem;}Component variables are the right tool when a token would be too broad: --ui-radius-control rounds
every control, while --ui-button-radius rounds only buttons.
Styling without Timeless CSS
Section titled “Styling without Timeless CSS”Components stay usable with no Timeless stylesheet at all. The public anatomy is the contract:
.ui-*classes identify CSS component roots.data-ui-*attributes carry contract-declared configuration on those roots.- Plain attributes configure
ui-*custom-element hosts. data-ui-partidentifies authored anatomy.- Native attributes, ARIA, and platform pseudo-classes carry state.
So a utility-class or CSS-in-JS project can skip the stylesheets and target the same anatomy:
[data-ui-part~='trigger'] { /* your own trigger styling */}
ui-tabs [role='tab'][aria-selected='true'] { /* your own selected-tab styling */}Never target data-ui-internal-*. Those are private runtime hooks and change without notice.