Design system

The colors, type, space and motion every component is built from.

Color

Scales

Every color comes from a Radix Colors scale of 12 steps. Text uses the solid steps; fills and edges use the translucent alpha versions, so they read the same on any surface.

gray
red
amber
green
blue

Roles

Components ask for a role, never a step, so every role flips with the theme. Each status has a base for icons and short labels, FillSubtle and Fill tints, and an Edge: dangerFillSubtle, successEdge and so on.

textPrimarygray-12Aa
textSecondarygray-11Aa
textMutedgray-10, dark 10–11Aa
fillSubtlegray-a2
fillgray-a3
fillStronggray-a4
edgegray-a4
edgeStronggray-a6
accentgray-12
dangerredAa
successgreenAa
warningamberAa
infoblueAa
TokenFor
textPrimaryContent
textSecondarySupporting copy
textMutedLabels and hints
fillSubtleHover
fillSelected
fillStrongPressed
edgeBorders and dividers
edgeStrongEdges that have to hold their own
accentThe one solid fill, with onAccent text
backgroundThe page
dangerErrors and destructive actions
successCompleted actions
warningSomething needs attention
infoNeutral news that isn't an error

Keep body text neutral on a status tint. The base color marks the icon or label that carries the meaning.

Typography

Components set size and weight and inherit the font family. This site sets Inter; use it or your own. Every size shares one line height, lineHeights.row, so a row of mixed sizes still centers in a control.

Inter

ABCDEFGHIJKLMNOPQRSTUVWXYZ

abcdefghijklmnopqrstuvwxyz

0123456789 !?&@#%

smQuiet, crisp, and finished.14px / 18px
xsQuiet, crisp, and finished.13px / 18px
xxsQuiet, crisp, and finished.12px / 18px
TokenValueFor
fontSizes.sm14pxControls and body copy
fontSizes.xs13pxSecondary text
fontSizes.xxs12pxThe smallest labels
fontWeights.regular400Body
fontWeights.medium500Labels and titles
fontWeights.semibold600Emphasis; nothing is heavier

Anything that changes in place (counts, timers, prices, table columns) sets fontVariantNumeric: "tabular-nums", so digits don't shift.

Layout

Space

A 4px grid, with 2px for hairline gaps.

TokenValue
xxxs2px
xxs4px
xs8px
sm12px
md16px
lg24px
xl48px

Radius

sm for controls, md for surfaces, full for pills. Nested corners are concentric: md around sm at space.xxs.

xs4px
sm8px
md12px
full9999px

Size

Controls take a fixed height, so a button and an input in one row line up.

controlXs24px
controlSm28px
controlMd32px
controlLg36px
TokenValueFor
controlXxs20pxInline controls
icon16pxIcons beside sm text
iconSm14pxIcons beside xs text
iconXs12pxIcons beside xxs text
hitArea40pxThe smallest target a pointer gets

Shadow

TokenFor
controlUnder a bordered control
popoverFloating surfaces; it draws their edge, so they need no border

Motion

Under media.reducedMotion, fades stay and movement goes: things that travel land at once.

Easing

Each curve over durations.move. Slow it down to see the shape.

cubic-bezier(0.23, 1, 0.32, 1)300ms × 4 = 1200ms

Slow down
4×
TokenFor
outEntrances and presses
inOutThings traveling across the screen
overshootSmall elements that should feel alive
layoutA surface growing or shrinking to fit
crossfadeOne state replacing another in place

Color changes on hover use the CSS keyword ease. Nothing uses ease-in.

Duration

Interface motion stays under 300ms. A spinner is the exception.

TokenValueFor
hover150msColor changes on hover
press160msScale on :active
popover180msSmall surfaces that open from a trigger
move300msIndicators and thumbs that travel
layout300msA surface growing or shrinking to fit
crossfade300msCross-fades between states
spin1sOne turn of a spinner

Press

Pressables scale down on :active, on easings.out over durations.press. The smaller the target, the bigger the give.

TokenValueFor
iconscale(0.95)Icon buttons
linkscale(0.97)Buttons
rowscale(0.99)Full-width rows