Core
Primary is the anchor
Settings

How the five derived colors are placed around the anchor.

Theme builder

Shape

How solid a surface looks: one radius choice that produces two values, whether anything is outlined, and the three shadows that are the entire vocabulary.

Radius

Button
Sharprounded-none
Button
Subtlerounded-xs rounded-sm
Button
Soft · shippedrounded-md rounded-xl
Button
Roundrounded-full rounded-2xl

Elevation

surface
Flat · shippedno shadow
surface
Resting onlyshadow-sm
surface
Lift on hovershadow-sm hover:shadow-md

Radius, borders and elevation are conventions, not tokens. Tailwind already ships every utility any of them needs, so the only decision left is which ones this project uses — and that decision is made once in the theme builder and written into the generated theme-rules.md. Nothing on this page is a custom class you have to learn.


Radius

How round is the shape language?What it meansClasses
SharpSquare corners everywhere.rounded-none
SubtleBarely rounded — 2px on controls, 4px on surfaces.rounded-xs
rounded-sm
Soft
shipped default
Rounded — 6px on controls, 12px on surfaces.rounded-md
rounded-xl
RoundGenerous — pill buttons, 16px surfaces.rounded-full
rounded-2xl

One choice, two values. Picking a radius does not mean picking a card radius and then a button radius. Each option carries a control value and a surface value, and they move together:

TierClassApplies to
Controlrounded-mdButtons, inputs, chips, tabs, badges
Surfacerounded-xlCards, panels, images, dialogs

This is real in code, not a description of one. controlClasses() reads classes[0] and cardClasses() reads .at(-1), both in src/lib/theme-rules.mjs, so a button and the card it sits inside cannot disagree about the shape language.


Borders

Do surfaces get outlines?What it meansClasses
No borders — zone with grounds
shipped default
Surfaces separate by sitting on adjacent near-tones. No hairlines.none
Hairline bordersA 1px ink border at 12% on cards and inputs.border border-dark/12

The shipped choice is no borders: surfaces separate by sitting on adjacent near-tones instead. A white card on the light page ground is already a boundary, and a hairline on top of it is a second statement of the same fact. See Color → Zoning without borders for the grounds that do the work.

Ink is invisible on ink. When a border is used, it flips ladders on an inverse band — border-white/12, never border-dark/12. cardClasses() swaps the prefix rather than maintaining two class strings, so a card and its inverse twin cannot drift apart.

Reach for a hairline only when two surfaces must sit on the same ground and genuinely cannot be told apart. Never invent an opacity step outside the ladder.


Elevation

Do raised surfaces cast shadows?What it meansClasses
Flat
shipped default
No shadows. Depth comes from grounds.none
Resting only`shadow-sm` on raised surfaces; nothing on hover.shadow-sm
shadow-xl
Lift on hover`shadow-sm` at rest, `shadow-md` on hover.shadow-sm
hover:shadow-md
shadow-xl

Three levels is the entire vocabulary — nothing, resting, and overlay. There is no shadow-lg or shadow-2xl in this system, and adding one does not add a level of hierarchy; it adds an argument about which of two nearly identical shadows a given card deserves.

ClassWhere
shadow-smA raised surface at rest, if the project uses shadows at all
hover:shadow-mdLift on hover — only in the lift option, never at rest
shadow-xlAn overlay above a scrim. Available in every option, including flat

shadow-md at rest reads as a stuck hover state. It is the shadow a user’s eye has learned to expect during an interaction, so a card wearing it permanently looks like it is waiting for a click that already happened.

A shadow needs an opaque object to fall away from. Outlined and ghost controls are never raised — under a transparent control you see the shadow with no edge above it, which reads as a rendering bug rather than as depth. controlClasses() takes raised as an argument for exactly this reason, and Button passes it only for filled variants.

Layering

Stacking order is not a per-project choice. A toast that appears underneath a modal is wrong at every density, under every brand — so this is a fixed ladder, like the focus ring, and the theme builder does not offer a vote on it. Every rung is one of Tailwind’s own z-* steps.

LayerWhat sits there
z-10Sticky headers, sticky table columns
z-20Dropdowns, popovers, tooltips
z-30The scrim behind a dialog
z-40Modals and drawers — above their own scrim
z-50Toasts — above everything, including a modal

z-30 is the scrim rather than the dialog because a modal has to clear its own backdrop. Anything that must sit above a dialog and below a toast has no rung — that is the point, not an omission.


Rules

  • Two radius values, not two decisions. Controls take the first, surfaces take the last. Never pick a corner radius for one component.
  • Do not draw a border where a ground change will do. Alternate bg-light and bg-white bands; reach for a hairline only when two surfaces share a ground.
  • Flip the hairline on inverse bandsborder-white/12. Ink on a dark ground is invisible.
  • Three shadows, no more. shadow-sm at rest, hover:shadow-md only if the project lifts, shadow-xl only for overlays above a scrim.
  • Never shadow-md at rest. It reads as a stuck hover state.
  • Never raise a transparent control. Outlined and ghost buttons cast no shadow.
  • Do not add a z-index that is not on the ladder. An element that needs one is overlapping something; if it is not in the table above, reorder the markup instead.
  • Do not turn any of this into a token. --radius-card and --shadow-1 invent a bespoke surface for a value rounded-xl and shadow-sm already carry. If Tailwind ships the utility, it is a convention.