fdb/docs

[§] /docs/rules/scales-and-type

Scales and type

Pick type on purpose, then take every size from a short, named list: type, spacing, radius, shadow, z-index and icons.

TL;DR

  • Choose a typeface that suits the subject. One family, or two clearly different ones.
  • Type ratio 1.25 for product UI, 1.333 for editorial. Body 16px or more.
  • Lines 55 to 75 characters. Body line-height 1.5 to 1.7, display 0.9 to 1.1.
  • Spacing on the 4pt scale: 4, 8, 12, 16, 24, 32, 48, 64, 96.
  • Named z-index layers. Never z-index: 9999.

If a number is not on a scale, it is not allowed. Scales are short lists of named values that every component picks from. They are why a page feels calm even when nobody can say why.

Each scale below is copyable as tokens. Where Tailwind v4 already ships the same scale, use its defaults and do not reinvent them.

Type scale

Aa
13
Aa
16
Aa
20
Aa
25
Aa
31
Aa
39
A 1.25 scale from a 16px base. Every step is the previous one × 1.25, so the jump between neighbours always feels the same.

Use a ratio of 1.25 (major third) for product UI, where many levels sit close together. Use 1.333 (perfect fourth) for editorial and marketing pages, where fewer levels need more contrast. This guide uses 1.333.

For product UI, Tailwind's default size and line-height pairs are a sound scale. Use them as they are.

Type scale: table (11 rows)
TokenSizeLine heightUse
text-xs12px16pxLegal, badges, table meta. Never body
text-sm14px20pxSecondary text, labels, dense tables
text-base16px24pxBody text, inputs. The minimum for reading
text-lg18px28pxLead paragraph, large body
text-xl20px28pxCard titles, h4
text-2xl24px32pxh3
text-3xl30px36pxh2
text-4xl36px40pxh1 in product UI
text-5xl48px1Page hero on small screens
text-6xl60px1Marketing hero
text-7xl72px1Display, one per page at most
  • MUST keep body text at 16px or more. Inputs too: iOS Safari zooms the page when a focused input is under 16px.
  • MUST use no more than 6 sizes on a single screen. More sizes means the hierarchy is guessed.
  • SHOULD make fluid display sizes with clamp(), keeping the minimum on the scale: font-size: clamp(2.25rem, 5vw + 1rem, 4.5rem).
  • SHOULD set sizes in rem, so they follow the reader's browser font setting.

Line-height and tracking

Line-height shrinks as size grows. Big type with body line-height looks loose and falls apart.

Line-height and tracking: table (5 rows)
TokenValueUse
--leading-none1Display type 48px and up, single-line numbers
--leading-tight1.1Headings 30 to 48px
--leading-snug1.3Headings 20 to 24px, UI labels, buttons
--leading-normal1.5Body text, inputs
--leading-relaxed1.65Long-form reading at 16 to 18px
Line-height and tracking: table (4 rows)
TokenValueUse
--tracking-tighter-0.03emDisplay 48px and up
--tracking-tight-0.015emHeadings 24 to 48px
--tracking-normal0Body text. Never change it
--tracking-wide0.08emAll-caps labels only, 12px and under
  • MUST add positive tracking to any all-caps text. Capitals set at 0 look cramped.
  • MUST NOT add letter-spacing to body text.

Spacing: the 4pt scale

4
8
16
24
40
Left: a 4pt scale. Toggle to see the same layout with guessed gaps. The eye notices even when it can't say why.
Spacing: the 4pt scale: table (10 rows)
TokenpxTailwindTypical use
--space-141Icon to label, stacked label to input
--space-282Gap inside a button group, between chips
--space-3123Input padding, list row padding
--space-4164Phone gutter, card padding on phones, form field gap
--space-6246Card padding, grid gap
--space-8328Between groups inside a section
--space-124812Between sub-sections
--space-166416Section spacing on phones
--space-249624Section spacing on desktop
--space-3212832Hero padding on large screens
  • MUST use only these steps. 5, 10, 13 and 18 do not exist.
  • MUST make the gap between groups at least twice the gap inside them: 8 inside, 16 or more between.
  • SHOULD use 1px and 2px only for borders, outlines and focus-ring offsets. Those are the only off-scale values.
  • SHOULD keep component padding to 3 steps: 12, 16 and 24.

Radius scale

Tailwind v4 defaults. Pick two or three for a product and ban the rest.

Radius scale: table (9 rows)
TokenpxUse
rounded-none0Tables, full-bleed images, brutalist styles
rounded-xs2Checkboxes, tiny tags
rounded-sm4Inputs, small buttons
rounded-md6Buttons, menus
rounded-lg8Cards, popovers
rounded-xl12Large cards, sheets
rounded-2xl16Modals, feature panels
rounded-3xl24Hero media, bottom sheets on phones
rounded-full9999pxPills, avatars, icon buttons
  • MUST make nested corners concentric: inner radius = outer radius minus the padding between them. A 16px card with 8px padding holds an 8px image.
  • SHOULD use no more than 3 radii in one product, plus full.
  • SHOULD grow radius with size. A 4px chip and a 16px modal look related; a 16px chip does not.

Shadow and elevation

Shadows say how far above the page something floats. Use a few named levels, each with one job.

shadow-1

card at rest

shadow-2

card on hover

shadow-3

menu, popover

shadow-4

modal, toast

Four elevation levels. Each step is a larger, softer blur with a small, tight shadow underneath for contact. Tinted with the ink colour, never pure black.
:root {
  --shadow-1: 0 1px 2px rgb(22 20 15 / 0.08);
  --shadow-2: 0 1px 2px rgb(22 20 15 / 0.06), 0 4px 12px rgb(22 20 15 / 0.08);
  --shadow-3: 0 2px 4px rgb(22 20 15 / 0.06), 0 12px 24px rgb(22 20 15 / 0.12);
  --shadow-4: 0 4px 8px rgb(22 20 15 / 0.08), 0 24px 48px rgb(22 20 15 / 0.18);
}
Shadow and elevation: table (5 rows)
LevelTokenUse
0nonePage, sections, anything in the flow
1--shadow-1Cards at rest, inputs if you use shadows at all
2--shadow-2Hovered or dragged cards, sticky header once scrolled
3--shadow-3Dropdowns, popovers, select lists
4--shadow-4Modals, drawers, toasts
  • MUST tie elevation to z-index: a higher shadow never sits under a lower one.
  • MUST show elevation in dark mode with a lighter surface, not a stronger shadow. Shadows barely read on near-black. Material's dark theme does the same.
  • SHOULD tint shadows with the ink colour (rgb(22 20 15 / …)), never rgba(0,0,0,…).
  • SHOULD NOT combine a shadow and a border on the same card. Pick one.

Z-index: named layers

base · 0
sticky · 100
dropdown · 200
modal · 310
tooltip · 500
Each layer sits in its own band with room between. A new tooltip inside a modal still wins because tooltips have their own, higher band.
Z-index: named layers: table (8 rows)
LayerTokenValueWhat lives there
Base--z-base0Normal flow
Raised--z-raised10A card lifted over its neighbours, sticky table headers inside a section
Sticky--z-sticky100Site header, sticky sidebars, bottom tab bar
Dropdown--z-dropdown200Menus, popovers, select lists, combobox results
Overlay--z-overlay300Modal and drawer backdrops
Modal--z-modal310Dialogs, drawers, sheets
Toast--z-toast400Toasts and banners that must beat modals
Tooltip--z-tooltip500Tooltips, which can appear anywhere
  • MUST use only these tokens. z-index: 9999 or z-50 chosen by feel is a bug.
  • MUST add isolation: isolate to a component that uses z-index inside itself, so its numbers cannot leak and fight the page layers.
  • SHOULD use the native top layer where you can. <dialog> opened with showModal() and elements using the popover attribute sit above everything without any z-index.

Icon sizes

Icon sizes: table (6 rows)
SizePairs withUse
12pxtext-xsInline status dots, table sort arrows. Rare
16pxtext-sm, text-baseInline with text, inside small buttons and inputs
20pxtext-baseButtons, inputs, nav items. The UI default
24pxtext-lg and upStandalone icons, toolbars, tab bars. Material's default size
32pxheadingsFeature lists, settings rows
48pxtext-2xl and upEmpty states, onboarding
  • MUST size the icon to the line-height of the text beside it, or one step below. A 16px icon suits 14px text on a 20px line.
  • MUST give icon-only buttons a 44×44px hit area, whatever the icon size, and an aria-label.
  • SHOULD keep one stroke width across the set: 1.5px or 2px at 24px (Lucide's default is 2). Mixing icon libraries mixes stroke widths.
  • SHOULD colour icons with currentColor, so they follow the text colour in every state and theme.

Choose a voice, not a default

The fastest way to make a site look generated is to use the font every generator uses. Pick type that belongs to the subject:

  • Editorial, cultural, craft: a serif with character for display (this guide uses Newsreader), paired with a plain grotesk for text.
  • Product and tools: one well-drawn grotesk family doing everything. Weight and size make the hierarchy.
  • Playful or nostalgic: a display face with a strong period flavour, used sparingly. Never for body copy. This site uses the IBM VGA 8x16 pixel font, at multiples of 16px only.

One family, or two clearly different ones. Never two that are almost the same.

[ex] in the wild · 24

Set it for reading

Set it for reading: table (4 rows)
SettingBody textDisplay text
Line length55 to 75 characters (max-width: 65ch)Short. Break lines by meaning
Line height1.5 to 1.70.9 to 1.1
Letter spacing0Slightly negative (−0.01 to −0.035em)
Numbers in tablesfont-variant-numeric: tabular-numsn/a
50.5pxComplete, beautiful
37.9pxComplete, beautiful
28.4pxComplete, beautiful
21.3pxComplete, beautiful
16.0pxBody copy sets the rhythm for everything else.
12.0pxBody copy sets the rhythm for everything else.
Pick a ratio. Every heading size is base × ratioⁿ, so the sizes relate instead of being guessed.

Change the ratio and every setting still looks related. That relationship is what "clean" really means.

400
56px
56

Hamburgefonstiv

opsz 6 at 40px

Readable

The same axis at 12px: a caption set for reading, not for display.

opsz 72 at 40px

Readable

The same axis at 12px: a caption set for reading, not for display.

font-variation-settings: 'opsz' 56, 'wght' 400;
Newsreader has two axes: weight and optical size. Small optical sizes are wider, sturdier and more open so they survive at 12px; large ones get finer hairlines and tighter spacing. Turn off auto and set opsz 6 at 72px to see the text-cut blown up.

Variable fonts put weight and optical size on sliders. Small optical sizes are drawn wider and sturdier, so they stay readable at caption sizes. Let the size set the axis automatically.

Why it works

A scale shrinks the number of choices from infinite to about ten. The eye reads repeated intervals as order, so a page built from ten values looks intentional even to someone who cannot name a single one. It also makes reviews mechanical: a value is either on the list or it is a bug.

Sources

3 sources

On this page