Theming
View as MarkdownTimeless ships CSS in three tiers — the layer order, behavior, and the look — and only the last is optional. Loading CSS covers which is which. This page is about the last one: how to change how Timeless looks, or replace its appearance entirely.
There are 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 uiTokenGroups from @timelessui/components, so you can drive
a theme editor or a token audit from the same list this page is generated from. The export names the
vocabulary, not one theme’s values: Atmosphere fills these names in, and a theme of your own fills
in the same ones.
| 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(#5c6069, #a5a7b0) |
--ui-fg-subtle | light-dark(#80848d, #8e9099) |
--ui-line | light-dark(#d7d9de, #343641) |
--ui-line-strong | light-dark(#b8bcc5, #505360) |
--ui-accent | light-dark(#0056bd, #66b0ff) |
--ui-accent-hover | light-dark(#004299, #8ec5ff) |
--ui-accent-active | light-dark(#004ea9, #7db9ff) |
--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, #fb8f8f) |
--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-tooltip | 0 1px 1px rgb(0 0 0 / 12%), 0 2px 6px 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 the Atmosphere theme
Section titled “Styling without the Atmosphere theme”The theme is the optional tier. Import tokens.css and core.css and no theme at all, and every
component stays positioned, structurally intact, and operable while looking like nothing in
particular — which is exactly what you want when the look is yours to supply. See
Loading CSS for the imports and the Tailwind caveat.
What you must keep is core. It is not a look: it is what anchors a Select surface to its trigger, keeps a closed menu off screen, and keeps a filtered option hidden. Dropping it leaves components that render but misbehave, which is worse than unstyled.
The public anatomy is the contract, and it is unchanged whether the theme is loaded or not:
.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 targets the same anatomy — Utility CSS and Tailwind works one through end to end:
[data-ui-part~='trigger'] { /* your own trigger styling */}
ui-tabs [role='tab'][aria-selected='true'] { /* your own selected-tab styling */}You can also replace the theme wholesale rather than opt out of it. themes/atmosphere/ is one
directory of plain stylesheets, one per component, plus its own tokens.css holding the token
values this page documents. A second theme is a sibling of the same shape.
Never target data-ui-internal-*. Those are private runtime hooks and change without notice.