v0.5

Colors

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:

// Raw colors: you hand-manage every theme and every state.
<div className="bg-red-600 text-white dark:bg-red-500 dark:text-neutral-50" />
 
// Semantic tokens: name the intent, theming is handled for you.
<div className="bg-error-base text-static-white" />

The payoff:

  • Intent over value. bg-error-base says what it is. bg-red-600 makes 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 primary family is just an alias you can repoint.
  • Consistency by default. A fixed ramp (weakest → strongest) keeps spacing between shades predictable across the whole product.

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:

registry/tokens.ts
"primary-base": {
  light: "var(--color-primary-500)",
  dark: "var(--color-primary-400)",
},

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:

styles/globals.css
:root {
  --color-primary-base: var(--color-primary-500);
}
.dark {
  --color-primary-base: var(--color-primary-400);
}
 
@theme inline {
  --color-primary-base: var(--color-primary-base);
}

The result is a single class that already knows about both themes:

<div className="bg-primary-base" />

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:

weakest
weak
base
strong
strongest

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.

weakest
weak
light
medium
heavy
strong
strongest

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:

Light

Strongest, for headings

Body text uses text-body for long-form reading.

Placeholder and helper text.

Disabled text.

Dark

Strongest, for headings

Body text uses text-body for long-form reading.

Placeholder and helper text.

Disabled text.

<h2 className="text-strongest">Heading</h2>
<p className="text-body">Body copy that stays readable in light and dark.</p>
<span className="text-placeholder">Optional</span>

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.

weakest
weak
base
strong
strongest
Light
Dark

Error

Destructive actions, failures, and invalid input.

weakest
weak
base
strong
strongest
Light
Your payment could not be processed.
Dark
Your payment could not be processed.

Success

Confirmations and completed states.

weakest
weak
base
strong
strongest
Light
Changes saved successfully.
Dark
Changes saved successfully.

Warning

Non-blocking cautions that need attention.

weakest
weak
base
strong
strongest
Light
Your trial ends in 3 days.
Dark
Your trial ends in 3 days.

Info

Neutral, informational messaging and tips.

weakest
weak
base
strong
strongest
Light
We have updated our terms of service.
Dark
We have updated our terms of service.

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.

weakest
weak
base
strong
strongest
weakest
weak
base
strong
strongest
weakest
weak
base
strong
strongest
weakest
weak
base
strong
strongest
weakest
weak
base
strong
strongest

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.

Light
static-white text on a primary surface
static-black text on an inverted surface
Dark
static-white text on a primary surface
static-black text on an inverted surface

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.
// Dialog backdrop
<div className="bg-scrim" />
 
// Row hover on a light surface
<button className="hover:bg-hover active:bg-active" />

Guidelines

// Do
<div className="bg-error-base text-static-white" />
 
// Avoid: raw color, manual theme handling, no shared meaning
<div className="bg-red-600 text-white dark:bg-red-500" />
  • Use base for solid fills, weakest / weak for tinted backgrounds, strong / strongest for 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 .dark class do the work. If you find yourself writing dark: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.

TokenLightDark
Static
static-whitevar(--color-white)var(--color-black)
static-blackvar(--color-black)var(--color-white)
static-white-alpha-0var(--color-white-alpha-0)var(--color-black-alpha-0)
static-white-alpha-8var(--color-white-alpha-8)var(--color-black-alpha-8)
static-white-alpha-16var(--color-white-alpha-16)var(--color-black-alpha-16)
static-white-alpha-24var(--color-white-alpha-24)var(--color-black-alpha-24)
static-white-alpha-32var(--color-white-alpha-32)var(--color-black-alpha-32)
static-white-alpha-48var(--color-white-alpha-48)var(--color-black-alpha-48)
static-white-alpha-64var(--color-white-alpha-64)var(--color-black-alpha-64)
static-white-alpha-80var(--color-white-alpha-80)var(--color-black-alpha-80)
static-black-alpha-0var(--color-black-alpha-0)var(--color-white-alpha-0)
static-black-alpha-8var(--color-black-alpha-8)var(--color-white-alpha-8)
static-black-alpha-16var(--color-black-alpha-16)var(--color-white-alpha-16)
static-black-alpha-24var(--color-black-alpha-24)var(--color-white-alpha-24)
static-black-alpha-32var(--color-black-alpha-32)var(--color-white-alpha-32)
static-black-alpha-48var(--color-black-alpha-48)var(--color-white-alpha-48)
static-black-alpha-64var(--color-black-alpha-64)var(--color-white-alpha-64)
static-black-alpha-80var(--color-black-alpha-80)var(--color-white-alpha-80)
staticvar(--color-white)var(--color-black)
Background
weakestvar(--color-neutral-50)var(--color-neutral-900)
weakvar(--color-neutral-100)var(--color-neutral-800)
lightvar(--color-neutral-200)var(--color-neutral-700)
mediumvar(--color-neutral-300)var(--color-neutral-500)
heavyvar(--color-neutral-700)var(--color-neutral-300)
strongvar(--color-neutral-800)var(--color-neutral-200)
strongestvar(--color-neutral-950)var(--color-neutral-50)
Text
disabledvar(--color-neutral-300)var(--color-neutral-600)
placeholdervar(--color-neutral-400)var(--color-neutral-500)
bodyvar(--color-neutral-600)var(--color-neutral-300)
Primary
primary-weakestvar(--color-primary-100)var(--color-primary-alpha-16)
primary-weakvar(--color-primary-300)var(--color-primary-alpha-32)
primary-basevar(--color-primary-500)var(--color-primary-400)
primary-strongvar(--color-primary-600)var(--color-primary-500)
primary-strongestvar(--color-primary-800)var(--color-primary-700)
Error
error-weakestvar(--color-red-50)var(--color-red-alpha-16)
error-weakvar(--color-red-100)var(--color-red-alpha-24)
error-basevar(--color-red-600)var(--color-red-500)
error-strongvar(--color-red-700)var(--color-red-400)
error-strongestvar(--color-red-900)var(--color-red-200)
Success
success-weakestvar(--color-green-50)var(--color-green-alpha-16)
success-weakvar(--color-green-100)var(--color-green-alpha-24)
success-basevar(--color-green-600)var(--color-green-500)
success-strongvar(--color-green-700)var(--color-green-400)
success-strongestvar(--color-green-900)var(--color-green-200)
Warning
warning-weakestvar(--color-orange-50)var(--color-orange-alpha-16)
warning-weakvar(--color-orange-100)var(--color-orange-alpha-24)
warning-basevar(--color-orange-600)var(--color-orange-500)
warning-strongvar(--color-orange-700)var(--color-orange-400)
warning-strongestvar(--color-orange-900)var(--color-orange-200)
Info
info-weakestvar(--color-blue-50)var(--color-blue-alpha-16)
info-weakvar(--color-blue-100)var(--color-blue-alpha-24)
info-basevar(--color-blue-600)var(--color-blue-500)
info-strongvar(--color-blue-700)var(--color-blue-400)
info-strongestvar(--color-blue-900)var(--color-blue-200)
Verified
verified-weakestvar(--color-sky-50)var(--color-sky-alpha-16)
verified-weakvar(--color-sky-100)var(--color-sky-alpha-24)
verified-basevar(--color-sky-600)var(--color-sky-500)
verified-strongvar(--color-sky-700)var(--color-sky-400)
verified-strongestvar(--color-sky-900)var(--color-sky-200)
Feature
feature-weakestvar(--color-indigo-50)var(--color-indigo-alpha-16)
feature-weakvar(--color-indigo-100)var(--color-indigo-alpha-24)
feature-basevar(--color-indigo-600)var(--color-indigo-500)
feature-strongvar(--color-indigo-700)var(--color-indigo-400)
feature-strongestvar(--color-indigo-900)var(--color-indigo-200)
Highlighted
highlighted-weakestvar(--color-fuchsia-50)var(--color-fuchsia-alpha-16)
highlighted-weakvar(--color-fuchsia-100)var(--color-fuchsia-alpha-24)
highlighted-basevar(--color-fuchsia-600)var(--color-fuchsia-500)
highlighted-strongvar(--color-fuchsia-700)var(--color-fuchsia-400)
highlighted-strongestvar(--color-fuchsia-900)var(--color-fuchsia-200)
Away
away-weakestvar(--color-yellow-50)var(--color-yellow-alpha-16)
away-weakvar(--color-yellow-100)var(--color-yellow-alpha-24)
away-basevar(--color-yellow-600)var(--color-yellow-500)
away-strongvar(--color-yellow-700)var(--color-yellow-400)
away-strongestvar(--color-yellow-900)var(--color-yellow-200)
Stable
stable-weakestvar(--color-neutral-50)var(--color-neutral-alpha-16)
stable-weakvar(--color-neutral-100)var(--color-neutral-alpha-24)
stable-basevar(--color-neutral-600)var(--color-neutral-500)
stable-strongvar(--color-neutral-700)var(--color-neutral-400)
stable-strongestvar(--color-neutral-900)var(--color-neutral-200)
Overlay
scrimvar(--color-black-alpha-48)var(--color-black-alpha-64)
scrim-strongvar(--color-black-alpha-64)var(--color-black-alpha-80)
hovervar(--color-black-alpha-8)var(--color-white-alpha-8)
activevar(--color-black-alpha-16)var(--color-white-alpha-16)
hover-invertedvar(--color-white-alpha-8)var(--color-black-alpha-8)
active-invertedvar(--color-white-alpha-16)var(--color-black-alpha-16)
Shadow
shadow-neutral-xsvar(--color-black-alpha-8)var(--color-neutral-800)
shadow-neutral-smvar(--color-black-alpha-16)var(--color-neutral-800)
shadow-neutral-mdvar(--color-black-alpha-24)var(--color-neutral-800)