v0.5

Shadows

Semantic shadow tokens for elevation, component states, and depth.

Create UI ships one shadow system. Every shadow is a token, applied with a single utility class that carries its full layer stack. You name the role you need (shadow-neutral-md, shadow-component-primary-hover, text-shadow-sm) instead of hand-writing a multi-layer box-shadow and a separate dark-mode override.

Why shadow tokens

A convincing shadow is never one layer. Elevation is a stack of casts at different blurs and offsets, and on a dark surface a black shadow vanishes, so you keep a dark-mode copy in sync too. The same raised card, written two ways:

// Raw: spell out every layer, then maintain a dark-mode copy by hand.
<div className="shadow-[0_4px_6px_-1px_#0000001a,0_2px_4px_-2px_#0000001a] dark:shadow-[0_4px_6px_-1px_#0000005c,0_2px_4px_-2px_#0000005c]" />
 
// Token: name the elevation, the layer stack and the dark tint come with it.
<div className="shadow-neutral-md" />

The payoff:

  • Depth as a name. shadow-neutral-md says "raised card." A raw layer stack makes the next reader reverse-engineer the intent.
  • Dark mode for free. The tint lives in a color token that already flips, so a shadow stays believable on a dark surface without a dark: override.
  • One ramp, even steps. A fixed 2xs → 3xl scale keeps the jump between elevations consistent across the whole product.
  • States without the math. Focus rings, hover glows, and inset borders are tokens too (shadow-component-*), so a control's whole outline is a few class names instead of hand-tuned box-shadow stacks.

The three roles

  • Elevation (shadow-neutral-*): soft drop shadows that lift a surface off the page. Cards, popovers, dropdowns, and modals climb this ramp.
  • Component (shadow-component-*): inset borders and focus rings that draw the outline of an interactive control across its default, hover, and focused states. Solid controls also carry a text-shadow-2xs on their label, so one button shows both shadow kinds at once.
  • Text (text-shadow-*): a small ramp (2xs, xs, sm, md, lg) for legibility on text that sits over a colored or photographic fill.

All three are color-aware: the tint comes from separate shadow color tokens, which is what lets a single class work in both themes. More on that under Color and dark mode.

Usage

Apply a token class to any element. The class sets the whole shadow.

// Elevation: lift a card off the page
<div className="bg-static shadow-neutral-md rounded-xl" />
 
// Component: a control that changes shadow per state
<button className="shadow-component-primary-default hover:shadow-component-primary-hover" />
 
// Text: legibility on a colored fill
<span className="text-static-white text-shadow-sm" />

Unlike spacing and typography, shadows are fixed at every breakpoint. Depth and focus rings read the same on a phone and a desktop, so there is nothing responsive to manage.

Elevation

The neutral ramp is one continuous scale from a 1px hairline to a deep, far-cast shadow. Pick the step by how far off the page the surface should feel: a resting card sits low, a popover floats above it, a modal sits highest.

shadow-neutral-2xs
shadow-neutral-xs
shadow-neutral-sm
shadow-neutral-md
shadow-neutral-lg
shadow-neutral-xl
shadow-neutral-2xl
shadow-neutral-3xl
TokenUse for
shadow-neutral-2xsHairline lift, a 1px seam under a sticky bar
shadow-neutral-xsResting inputs, button groups, low chrome
shadow-neutral-smCards and tiles at rest
shadow-neutral-mdRaised cards, small popovers
shadow-neutral-lgDropdowns, menus, comboboxes
shadow-neutral-xlPopovers and floating panels
shadow-neutral-2xlDialogs and modals
shadow-neutral-3xlThe highest, most-detached overlays

Component

Component shadows draw the outline of an interactive control. Each one stacks an inset ring (the border), a 2px inset highlight (the top bevel), and, on the raised states, an outer glow or focus outline. They come as a matrix of six families across three states.

The specimen below renders the real Button, pinned to each state, so every swatch shows the box shadow and the label's text-shadow-2xs at the same time.

primary
default
shadow-component-primary-defaulttext-shadow-2xs
hover
shadow-component-primary-hovertext-shadow-2xs
focused
shadow-component-primary-focusedtext-shadow-2xs
neutral
default
shadow-component-neutral-defaulttext-shadow-2xs
hover
shadow-component-neutral-hovertext-shadow-2xs
focused
shadow-component-neutral-focusedtext-shadow-2xs

The three states map to interaction:

  • default. The resting outline: a 1px inset ring plus a subtle inset highlight.
  • hover. The ring brightens and a soft outer glow appears, so the control reads as liftable.
  • focused. An outer outline ring is added on top of the default, signalling keyboard focus.
FamilySurface it sits onUse for
primaryThe brand fillPrimary buttons and key actions
neutralA light or white surfaceDefault buttons, inputs, controls
invertedA dark or inverted surfaceControls on a dark fill
errorThe error fillDestructive actions, invalid input
successThe success fillConfirm actions, valid input
infoThe info fillInformational controls

The specimen shows primary and neutral. The same default/hover/focused matrix exists for inverted, error, success, and info (for example shadow-component-error-hover), each tuned to its own fill.

These are usually wired into a component once, with the state classes layered through Tailwind variants:

<button className="bg-primary-base shadow-component-primary-default hover:shadow-component-primary-hover focus-visible:shadow-component-primary-focused">
  Save changes
</button>

Text

Text shadows add a touch of depth for legibility, most often on label text that sits over a colored or photographic fill. The ramp runs from a 1px seam to a soft three-layer drop. Solid buttons already use the smallest step (text-shadow-2xs) on their label.

Create UItext-shadow-2xs
Create UItext-shadow-xs
Create UItext-shadow-sm
Create UItext-shadow-md
Create UItext-shadow-lg
TokenUse for
text-shadow-2xsA 1px crispening seam
text-shadow-xsLight separation from the fill
text-shadow-smSoft drop on small label text
text-shadow-mdMore pronounced drop on larger type
text-shadow-lgThe deepest, for display type

Color and dark mode

Elevation shadows do not hard-code their color. They reference shadow color tokens, and those tokens are what invert between themes:

registry/tokens.ts
"shadow-neutral-xs": {
  light: "var(--color-black-alpha-8)",
  dark: "var(--color-neutral-800)",
},

In light mode the shadow is a soft black alpha, so it reads as a cast shadow. In dark mode it resolves to a neutral tone, so depth stays visible instead of vanishing into the dark surface. You set the theme once at the root and every shadow follows. See the Colors page for the full token reference.

Under the hood

The shadow system is declarative, with a single source of truth:

  1. registry/shadows.ts defines shadowTokens (the neutral and component families) and textShadowTokens as plain data, each with a light and dark value.
  2. buildShadowCssVars() and buildTextShadowCssVars() in registry/config.ts turn each token into a CSS variable.
  3. styles/globals.css declares those --shadow-* and --text-shadow-* variables on :root, overrides the underlying color tokens under .dark, and registers everything in @theme inline.
  4. Tailwind v4 generates the shadow-* and text-shadow-* utilities from the theme.

Because the tint lives in separate color tokens, the shadow definitions themselves are identical in light and dark. Only the color flips, which is why a single class covers both themes.

When a project runs createui init, the CLI emits the same variables into the consumer's stylesheet, so the shadow system travels with the components.