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.
The same four classes, no dark: override anywhere, in both themes:
shadow-neutral-2xsshadow-neutral-smshadow-neutral-lgshadow-neutral-3xl
Not one dark: in this markup.
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:
Look closely at the dark copy: it is not a stronger black, it is a lighter neutral. On a dark surface a black shadow has nothing to darken, so depth has to come from a halo instead of a cast. That inversion is the part a hand-written pair almost always gets wrong.
The payoff:
- Depth as a name.
shadow-neutral-mdsays "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 → 3xlscale 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-tunedbox-shadowstacks.
Unlike spacing, typography, and radius, 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. Thirty-two tokens in three roles. Elevation shadows take their tint from separate shadow color tokens, which is what lets one class work in both themes. Component shadows inherit the theme through the fill color tokens they reference. Text shadows are a fixed alpha-black recipe, identical 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
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. Each row prints the layer geometry the class expands to and the tint it resolves to in each theme, and names the components that reach for it, so the ramp describes what the system does rather than what it might.
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.
Every family is tuned for one specific fill, and the inset ring is drawn in the fill's own color, so on the surface it belongs to it reads as a crisp edge rather than a visible outline. What changes between states is the outer layer, which is what each cell below names. These are surfaces carrying a box shadow rather than live controls: the page documents the shadow, not a component's color ramp.
The three states map to interaction:
default. The resting outline: a 1px inset ring plus a subtle inset highlight.hover. The ring shifts one step along the fill's own ramp and a soft outer glow appears, so the control reads as liftable.focused. An outer ring is added on top of the default. It is a halo, not the focus indicator: a colour-onlyoutline-*paints nothing on its own, so controls carryoutline-2 outline-transparentin their base and swap only the colour on focus.
These are usually wired into a component once, with the state classes layered through Tailwind variants:
Solid controls also carry a text-shadow-2xs on their label, so one button shows both shadow kinds at once. There is one more component token outside the matrix, shadow-component-icon-wrapper, for the small tinted tile behind a standalone icon.
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.
Use raw text-white on a brand fill
The label above uses plain text-white, not text-static-white. The static token flips to black under .dark, which on a fill that stays saturated in both themes gives you black text carrying a black shadow. See Static colors.
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:
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:
registry/shadows.tsdefinesshadowTokens(theneutralandcomponentfamilies) andtextShadowTokensas plain data: one mode-agnostic recipe string per token, with no light/dark pair.buildShadowCssVars()andbuildTextShadowCssVars()inregistry/config.tsturn each token into a CSS variable.styles/globals.cssdeclares those--shadow-*and--text-shadow-*variables on:root, overrides the underlying color tokens under.dark, and registers everything in@theme inline.- Tailwind v4 generates the
shadow-*andtext-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.
Shadows are not in the responsive token set (text, spacing, radius). They emit one flat value to :root with no breakpoint overrides, so depth is constant across screen sizes.
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.