UI.MD

Spacing & scale

A 4px base unit drives a consistent spacing scale. Use 4px for tight gaps (icon-to-text) and 8px and up for structural spacing (form fields, list items, sections).

Base spacing scale

All spacing derives from --spacing: 0.25rem (4px). Bars scale in proportion to value.

ClassScaleValue
p-1
4px
p-2
8px
p-3
12px
p-4
default
16px
p-5
20px
p-6
24px
p-8
32px
p-12
48px
p-16
64px
p-20
80px

Content group coupling

4px within a group, 8px between groups. Elements that read as a single unit (a title and its lead, a label and its value) sit at 4px to signal association. Anything beyond that is structure.

Tight coupling (4px)

The paragraph sits close enough to read as the same thought: one unit, one breath. This is gap-1.

gap-1 · 4px

Loose coupling (8px)

The paragraph feels separated: the right call between groups, the wrong call inside one. This is gap-2.

gap-2 · 8px

Max widths

Width constraints for containers and panels, from small popovers to full page.

TokenUsageValue
--spacing-max-w-smPopovers, tooltips320px
--spacing-max-w-mdDialogs, small panels384px
--spacing-max-w-lgForms, medium panels512px
--spacing-max-w-xlContent columns, prose672px
--spacing-max-w-2xlWide content, two-column896px
--containerPage content maximum1540px

Reading measure

Line length governs reading rhythm. Caps are measured in ch, so a single value holds at every type tier. You may go narrower than a measure, never wider.

TokenUsageValue
max-w-measureReading copy: a 60 to 75 character measure60ch
max-w-measure-headingHeadings wrap short rather than stretch24ch

Headings break into lines rather than stretch across the page.

Body copy holds a 60 to 75 character measure so the eye returns to a predictable left edge on every line. Long lines tire the eye; short ones fragment the thought. The measure is set in ch, so the physical width tracks the type size without a separate cap per tier.

Vertical rhythm

The prose-flow utility spaces long-form text: above an element the gap scales with the type, below a heading it is half, and everything floors at 16px. Headings breathe more above than below, so each belongs to what follows.

TokenUsageValue
--rhythm-baseFloor gap between any two blocks16px
--rhythm-above-h3Above h3 through h628px
--rhythm-above-h2Above h248px
--rhythm-above-h1Above h164px
--rhythm-below-h1Below h1 (half its above-gap)32px

Trigger conditions

Each contract specifies a trigger event tied to an objective public source. The gap above this heading is large; the gap below it is half, so the heading reads as the start of this block.

Event definition

The trigger event is a measurable condition with a single binary outcome. Sub-headings take a smaller gap above than the section heading, keeping the hierarchy legible through spacing alone.

A second paragraph sits one floor-gap (16px) below the first. Body blocks never crowd and never drift.

Breakpoints

Mobile-first responsive breakpoints. Tailwind defaults, applied as min-width media queries.

BreakpointUsageValue
sm:Large phones, small tablets640px
md:Tablets, primary breakpoint768px
lg:Small laptops1024px
xl:Desktops1280px
2xl:Large desktops1536px

Shadow scale

Four elevation levels, from subtle form control depth to modal-level lift.

shadow-xs

Form controls (Input, Select, Checkbox)

shadow-sm

Subtle elevation

shadow-mddefault

Cards, elevated surfaces

shadow-lg

Modals, dialogs

Border radius

A progressive scale from subtle rounding to fully circular. Each level has a defined usage.

xs

0.25rem

Close buttons, micro elements

sm

0.375rem

Checkbox, small controls

md (default)

0.5rem

Menu items, Popover, Tooltip

lg

0.75rem

Cards, dialogs, menu popups

xl

1rem

Drawers, sheets, large panels

2xl

1.25rem

Unassigned

full

9999px

Badges, pills, switches

action-xs

0.5625rem

Controls 24px tall

action-sm

0.65625rem

Controls 28px tall

action

0.75rem

Controls 32px tall

action-lg

0.9375rem

Controls 40px tall

Z-index scale

Four rungs, no named tokens. Every overlay shares z-50 on purpose: Base UI portals them to the body in mount order, so the last one opened wins. That is what lets a Select inside a Dialog sit above it, and a Dialog opened from a menu sit above the menu. Never give an overlay its own z-index.

UtilityDescriptionValue
z-10Elevation inside one component10
z-20Sticky region in a scroll container20
z-40Page chrome (SiteHeader)40
z-50Anything portaled to the body50