Semantic color tokens for theming, dark mode, and consistent UI.
Create UI ships a single color system built on semantic tokens. Instead of reaching for a raw color like red-600 and remembering to add a dark-mode override, you name the role a color plays (bg-error-base, text-body, border-primary-weak) and the system resolves the right value for the active theme.
One system, many themes: every component is styled once against these tokens, and a theme decides what each token resolves to. Swapping the brand color or flipping to dark mode never touches component code.
Why semantic tokens
The same surface, written two ways:
The payoff:
- Intent over value.
bg-error-basesays what it is.bg-red-600makes the next reader guess. - Dark mode for free. Each token already carries a light and a dark value. You almost never write a
dark:color override again. - One place to retheme. Change a token mapping once and every component that uses it updates. The brand
primaryfamily is just an alias you can repoint. - Consistency by default. A fixed ramp (
weakest → strongest) keeps spacing between shades predictable across the whole product.
Tokens are exposed as ordinary Tailwind color utilities, so they work everywhere a color does: bg-*, text-*, border-*, outline-*, ring-*, fill-*, divide-*, and so on.
How it works
A token flows through three layers before it reaches your markup.
1. Primitive scales are the raw palette: gray, red, blue, indigo, and the rest, each with shades 50–950 plus alpha-* steps. These are defined once in registry/tokens.ts and rarely used directly in product code.
2. Semantic tokens map a role to a light and a dark primitive:
3. CSS variables and Tailwind utilities. The build emits each token as a --color-* variable, remaps it under .dark, and re-exports it to Tailwind through @theme:
The result is a single class that already knows about both themes:
Dark mode is driven by a .dark class on an ancestor (usually <html>). Everything inside it resolves the dark value automatically, so you set the theme once at the root and never thread it through components.
The strength ramp
Most families share one vocabulary, ordered from lightest to most prominent:
weakest → weak → base → strong → strongest
weakest/weak: tinted backgrounds and subtle fills.base: the solid, default color (buttons, icons, indicators).strong/strongest: text, emphasis, and high-contrast detail on top of the weak fills.
A common pairing is a weakest background with strong text and a weak border, which stays legible in both themes:
Background and text
Neutral surfaces use a longer ramp built for layering. Lower steps are page and card backgrounds; higher steps are borders, dividers, and strong UI chrome. The ramp inverts in dark mode, so weakest is the lightest surface in light mode and the darkest in dark mode.
Text has its own roles. strongest is for headings and high-emphasis copy, body for comfortable reading, and placeholder / disabled for de-emphasized states. Watch how a single set of classes reads correctly in both themes:
Strongest, for headings
Body text uses text-body for long-form reading.
Placeholder and helper text.
Disabled text.
Strongest, for headings
Body text uses text-body for long-form reading.
Placeholder and helper text.
Disabled text.
Status and brand families
Each family below carries the full weakest → strongest ramp and is meant for a specific kind of meaning. Reach for the family that matches intent, not the one that happens to be the right hue.
Primary
Your brand and primary actions. primary is an alias, so a theme can repoint the whole family to any hue without changing components.
Error
Destructive actions, failures, and invalid input.
Success
Confirmations and completed states.
Warning
Non-blocking cautions that need attention.
Info
Neutral, informational messaging and tips.
Verified, Feature, Highlighted, Away, Stable
Accent families for product-specific meaning: a verified badge, a feature callout, a highlighted promotion, an away presence dot, a stable or neutral status. They follow the same ramp, so usage is identical to the families above.
Static colors
Static tokens are theme-aware foreground anchors for sitting on top of colored or inverted surfaces. static-white is white in light mode and flips to black in dark mode, static-black does the opposite. That flip is what keeps text legible on an accent fill, since the accent itself gets lighter in dark mode.
Each static color also exposes alpha-* steps (static-white-alpha-16, static-black-alpha-24, ...) for translucent overlays such as spinner tracks and hairlines on top of color. They appear over a checkerboard in the token reference below so the transparency itself reads.
Overlay and interaction
These tokens are alpha tints used for state and depth, not solid fills.
scrim/scrim-strong: dimming behind modals, drawers, and popovers.hover/active: subtle wash for interactive states on light surfaces.hover-inverted/active-inverted: the same idea for dark or colored surfaces.
Guidelines
Do reach for the semantic token that matches intent. Avoid raw primitives and manual dark: color overrides in product and site code.
- Use
basefor solid fills,weakest/weakfor tinted backgrounds,strong/strongestfor text and emphasis. - Reserve raw primitive scales (
bg-red-600) for the rare case a design genuinely needs a one-off hue with no semantic role. - Let the root
.darkclass do the work. If you find yourself writingdark:bg-*for color, there is almost always a token that already handles it.
Token reference
Every semantic token with its light and dark value. These land in your globals.css automatically when you set up Create UI, ready to use as Tailwind utilities.
The groups below name where each token is used most, not where it can be used. Every token is an ordinary color value, so it works with any utility regardless of its group: bg-strong and text-strong both exist, and a text role like body can paint a background just as a background step can color text. Reach for the name whose meaning fits, in whatever utility you need.
| Token | Light | Dark |
|---|---|---|
| Static | ||
| static-white | var(--color-white) | var(--color-black) |
| static-black | var(--color-black) | var(--color-white) |
| static-white-alpha-0 | var(--color-white-alpha-0) | var(--color-black-alpha-0) |
| static-white-alpha-8 | var(--color-white-alpha-8) | var(--color-black-alpha-8) |
| static-white-alpha-16 | var(--color-white-alpha-16) | var(--color-black-alpha-16) |
| static-white-alpha-24 | var(--color-white-alpha-24) | var(--color-black-alpha-24) |
| static-white-alpha-32 | var(--color-white-alpha-32) | var(--color-black-alpha-32) |
| static-white-alpha-48 | var(--color-white-alpha-48) | var(--color-black-alpha-48) |
| static-white-alpha-64 | var(--color-white-alpha-64) | var(--color-black-alpha-64) |
| static-white-alpha-80 | var(--color-white-alpha-80) | var(--color-black-alpha-80) |
| static-black-alpha-0 | var(--color-black-alpha-0) | var(--color-white-alpha-0) |
| static-black-alpha-8 | var(--color-black-alpha-8) | var(--color-white-alpha-8) |
| static-black-alpha-16 | var(--color-black-alpha-16) | var(--color-white-alpha-16) |
| static-black-alpha-24 | var(--color-black-alpha-24) | var(--color-white-alpha-24) |
| static-black-alpha-32 | var(--color-black-alpha-32) | var(--color-white-alpha-32) |
| static-black-alpha-48 | var(--color-black-alpha-48) | var(--color-white-alpha-48) |
| static-black-alpha-64 | var(--color-black-alpha-64) | var(--color-white-alpha-64) |
| static-black-alpha-80 | var(--color-black-alpha-80) | var(--color-white-alpha-80) |
| static | var(--color-white) | var(--color-black) |
| Background | ||
| weakest | var(--color-neutral-50) | var(--color-neutral-900) |
| weak | var(--color-neutral-100) | var(--color-neutral-800) |
| light | var(--color-neutral-200) | var(--color-neutral-700) |
| medium | var(--color-neutral-300) | var(--color-neutral-500) |
| heavy | var(--color-neutral-700) | var(--color-neutral-300) |
| strong | var(--color-neutral-800) | var(--color-neutral-200) |
| strongest | var(--color-neutral-950) | var(--color-neutral-50) |
| Text | ||
| disabled | var(--color-neutral-300) | var(--color-neutral-600) |
| placeholder | var(--color-neutral-400) | var(--color-neutral-500) |
| body | var(--color-neutral-600) | var(--color-neutral-300) |
| Primary | ||
| primary-weakest | var(--color-primary-100) | var(--color-primary-alpha-16) |
| primary-weak | var(--color-primary-300) | var(--color-primary-alpha-32) |
| primary-base | var(--color-primary-500) | var(--color-primary-400) |
| primary-strong | var(--color-primary-600) | var(--color-primary-500) |
| primary-strongest | var(--color-primary-800) | var(--color-primary-700) |
| Error | ||
| error-weakest | var(--color-red-50) | var(--color-red-alpha-16) |
| error-weak | var(--color-red-100) | var(--color-red-alpha-24) |
| error-base | var(--color-red-600) | var(--color-red-500) |
| error-strong | var(--color-red-700) | var(--color-red-400) |
| error-strongest | var(--color-red-900) | var(--color-red-200) |
| Success | ||
| success-weakest | var(--color-green-50) | var(--color-green-alpha-16) |
| success-weak | var(--color-green-100) | var(--color-green-alpha-24) |
| success-base | var(--color-green-600) | var(--color-green-500) |
| success-strong | var(--color-green-700) | var(--color-green-400) |
| success-strongest | var(--color-green-900) | var(--color-green-200) |
| Warning | ||
| warning-weakest | var(--color-orange-50) | var(--color-orange-alpha-16) |
| warning-weak | var(--color-orange-100) | var(--color-orange-alpha-24) |
| warning-base | var(--color-orange-600) | var(--color-orange-500) |
| warning-strong | var(--color-orange-700) | var(--color-orange-400) |
| warning-strongest | var(--color-orange-900) | var(--color-orange-200) |
| Info | ||
| info-weakest | var(--color-blue-50) | var(--color-blue-alpha-16) |
| info-weak | var(--color-blue-100) | var(--color-blue-alpha-24) |
| info-base | var(--color-blue-600) | var(--color-blue-500) |
| info-strong | var(--color-blue-700) | var(--color-blue-400) |
| info-strongest | var(--color-blue-900) | var(--color-blue-200) |
| Verified | ||
| verified-weakest | var(--color-sky-50) | var(--color-sky-alpha-16) |
| verified-weak | var(--color-sky-100) | var(--color-sky-alpha-24) |
| verified-base | var(--color-sky-600) | var(--color-sky-500) |
| verified-strong | var(--color-sky-700) | var(--color-sky-400) |
| verified-strongest | var(--color-sky-900) | var(--color-sky-200) |
| Feature | ||
| feature-weakest | var(--color-indigo-50) | var(--color-indigo-alpha-16) |
| feature-weak | var(--color-indigo-100) | var(--color-indigo-alpha-24) |
| feature-base | var(--color-indigo-600) | var(--color-indigo-500) |
| feature-strong | var(--color-indigo-700) | var(--color-indigo-400) |
| feature-strongest | var(--color-indigo-900) | var(--color-indigo-200) |
| Highlighted | ||
| highlighted-weakest | var(--color-fuchsia-50) | var(--color-fuchsia-alpha-16) |
| highlighted-weak | var(--color-fuchsia-100) | var(--color-fuchsia-alpha-24) |
| highlighted-base | var(--color-fuchsia-600) | var(--color-fuchsia-500) |
| highlighted-strong | var(--color-fuchsia-700) | var(--color-fuchsia-400) |
| highlighted-strongest | var(--color-fuchsia-900) | var(--color-fuchsia-200) |
| Away | ||
| away-weakest | var(--color-yellow-50) | var(--color-yellow-alpha-16) |
| away-weak | var(--color-yellow-100) | var(--color-yellow-alpha-24) |
| away-base | var(--color-yellow-600) | var(--color-yellow-500) |
| away-strong | var(--color-yellow-700) | var(--color-yellow-400) |
| away-strongest | var(--color-yellow-900) | var(--color-yellow-200) |
| Stable | ||
| stable-weakest | var(--color-neutral-50) | var(--color-neutral-alpha-16) |
| stable-weak | var(--color-neutral-100) | var(--color-neutral-alpha-24) |
| stable-base | var(--color-neutral-600) | var(--color-neutral-500) |
| stable-strong | var(--color-neutral-700) | var(--color-neutral-400) |
| stable-strongest | var(--color-neutral-900) | var(--color-neutral-200) |
| Overlay | ||
| scrim | var(--color-black-alpha-48) | var(--color-black-alpha-64) |
| scrim-strong | var(--color-black-alpha-64) | var(--color-black-alpha-80) |
| hover | var(--color-black-alpha-8) | var(--color-white-alpha-8) |
| active | var(--color-black-alpha-16) | var(--color-white-alpha-16) |
| hover-inverted | var(--color-white-alpha-8) | var(--color-black-alpha-8) |
| active-inverted | var(--color-white-alpha-16) | var(--color-black-alpha-16) |
| Shadow | ||
| shadow-neutral-xs | var(--color-black-alpha-8) | var(--color-neutral-800) |
| shadow-neutral-sm | var(--color-black-alpha-16) | var(--color-neutral-800) |
| shadow-neutral-md | var(--color-black-alpha-24) | var(--color-neutral-800) |