Skip to content

Loading CSS

View as Markdown

Timeless CSS ships as plain stylesheets in three tiers. There is no build step, no preprocessor, and no configuration — but two of the three tiers are required, and it is worth knowing which.

Tier Required Holds
tokens.css Yes The cascade-layer order, and color-scheme
core.css, or core/<component>.css Yes Behavior: anchoring, box participation, scrolling, native control resets
themes/atmosphere.css, or per component No The look: colour, type, spacing, shadow, radius, motion

Core is the tier people are surprised by. It is not styling — it is the implementation of “the surface opens beside its trigger”, “the closed menu is not on screen”, and “the filtered option is gone”. Without it a Select opens unanchored and a filtered option stays visible, which is a broken component rather than an unstyled one.

For prototypes, documents, and anything where request granularity does not matter, import the theme — it pulls in the two required tiers itself:

import '@timelessui/components/css/themes/atmosphere.css'

For applications, import the required tiers plus the exact component a route renders:

import '@timelessui/components/css/tokens.css'
import '@timelessui/components/css/core/button.css'
import '@timelessui/components/css/core/dialog.css'
import '@timelessui/components/css/themes/atmosphere/tokens.css'
import '@timelessui/components/css/themes/atmosphere/button.css'
import '@timelessui/components/css/themes/atmosphere/dialog.css'

Both paths produce the same public classes, custom properties, layers, and state selectors. The only difference is how much CSS reaches the browser. Each component page lists every stylesheet that component needs under Styling, core files included.

There is no default theme. themes/atmosphere.css names the theme it loads, and a second theme would be a sibling of the same shape — themes/<name>.css beside themes/<name>/.

tokens.css declares the order of the three Timeless layers:

@layer ui.tokens, ui.components, ui.utilities;

Layers are ordered by first encounter, so whichever stylesheet is parsed first gets the lower-priority layers. Load a component stylesheet before tokens.css and the order is established by that file instead, which is how a rule you wrote in ui.utilities ends up losing to a component.

themes/atmosphere.css imports tokens first on your behalf. When importing granularly, put tokens.css first yourself.

tokens.css also sets color-scheme: light dark, which is what makes light-dark() resolve to the branch the reader asked for. Drop it and every scheme-dependent token silently returns its light value.

See Theming for what those layers mean in practice and which tokens and component variables you can override.

The theme tier is genuinely optional, and replacing it is the supported path — for a design system of your own, for Tailwind, or for any utility-class or CSS-in-JS setup. Import the two required tiers and nothing else:

import '@timelessui/components/css/tokens.css'
import '@timelessui/components/css/core.css'

Every component is then positioned, structurally intact, and operable, with no appearance of ours: UA colours, no radius, no shadow, the browser’s default face. Style the public anatomy with whatever you like — Theming covers the selectors that are yours to target.

A colour, radius, shadow, or type utility can never lose to Timeless on this path, and not by convention: core is forbidden from declaring any of those properties, and a build check proves it. The only properties core does declare are the behavioural ones — display, overflow, position, the inset family, appearance and friends — so those are the only conflicts possible, and they are conflicts you would be picking deliberately.

Two things to expect on this path. Sizing stays in the theme, so a few components sit at their content size rather than a designed one — a Sheet panel is content-height instead of full-height until you give it a size, and a Select surface is as wide as its widest option instead of as wide as its trigger, because min-inline-size: anchor-size(width) is a design decision. And core carries no affordances, so there is no hover highlight and no focus ring until you add them.

Target size comes with the sizing, which makes it yours. Without the theme, seven components fall under the 24×24 CSS pixels WCAG 2.2 SC 2.5.8 asks for — the colour picker’s channel sliders, the number stepper’s step buttons, pagination’s page links, and the recipes built from them. A control can still conform by being spaced rather than large, which is why core does not force a floor on you: a dense tool that wants small targets is a legitimate design, and picking between the two is exactly the kind of decision the theme tier holds. Whichever you pick, decide it rather than inheriting it. apps/e2e records the seven and fails on an eighth, so this list stays honest.