Theme
On this page (7)

§Opt in to interface density

Density changes whitespace, not display resolution. The presets are adjustable design choices: compact 0.75, comfortable 1, spacious 1.25. They inherit into descendants; an inner density attribute starts a new density context. Every code block on this page shows exactly the classes that render the demo above it - only the demo's decorative colors are omitted.

§What changes—and what does not

QuantityDensity behavior
Numeric gaps, padding, margins; stack gap; default container guttersScaled.
Numeric widths, heights, square sizes, icon dimensionsUnchanged.
Fonts and line heightsUnchanged.
Literal px spacing, automatic margins, percentage sizesUnchanged.
Existing button/input recipes and their fixed dimensionsUnchanged until explicitly migrated.
Interaction target minimaIndependent CSS-pixel constraints.

§A realistic compact surface

The same settings panel under two density contexts. In compact the p-4 panel padding and the stack's default gap scale to 0.75×, in spacious to 1.25× - typography, button heights and border radii stay identical. That is the density contract on real UI: a whitespace policy, not a zoom.

§Real world: a dense data table

The same four records - name, role, last modified - rendered twice with identical markup; only the density attribute changes. In compact the panel's p-4 padding, the stack gap and each row's gap-2 shrink to 0.75×; in spacious they grow to 1.25×. The avatars (size-8), the font sizes and the ms-auto date alignment do not move: density scales the whitespace between and around the rows, never the type or the geometry. That is what makes a compact mode safe to offer on data-heavy screens - rows pack tighter without shrinking text below readability.

§Components with native density

Beyond the utility scale above, the components whose information density a user actually dials in implement data-density themselves - set it on the component root and their internal whitespace scales with the same 0.75 / 1 / 1.25 ratio, comfortable being identical to the unsized default. Each component page shows the three steps side by side:

ComponentWhat the density scales
CardHeader / content / footer section paddings
TableHead, cell and caption padding
AccordionTrigger and panel padding
StepsStep and content gaps (size ladder owns indicator size)
TimelinePer-item rhythm (connector gap + trailing space)
Tree ViewRow padding-block (indent is structural, never scaled)
SidebarLink rows and the content frame
CommandPalette rows, search wrapper, group headings
Context MenuContainer padding and item rows
SortableList gap and item padding
PaginationInter-cell gap only (size ladder owns cell dimensions)

Components already covered by a dedicated size ladder (Button, Input, Badge, …) deliberately stay out: their compactness is the size axis, and stacking a second whitespace axis on the same padding would make the two fight. Density stays the policy for collections and surfaces; sizes stay the policy for controls.

§Customize a context

.dense-inspector { --layout-density: 0.9; }
/* Component-owned opt-in recipe. No changes to existing .btn rules. */
.inspector-action {
  min-inline-size: 44px;
  min-block-size: 44px;
  padding-inline: calc(var(--size-base) * 3 * var(--layout-density));
}

Use a valid nonnegative number. Numeric spacing clamps negative density to zero; invalid CSS token types are not runtime-validated. Named size aliases stay geometric. Do not express density by changing the root font size or devicePixelRatio.

§Pointer capability is a separate policy

.inspector-action {
  min-inline-size: 44px;
  min-block-size: 44px;
}
@media (any-pointer: coarse) {
  .inspector-action { padding-inline: 1rem; }
}

The example already keeps a 44px target baseline; the media query only adds breathing room. any-pointer: coarse can detect an available coarse pointer on a hybrid device, but it is not a complete description of a user’s access needs. Keep controls usable without that signal. Media Queries: available pointer capabilities

The AA target-size criterion is 24 × 24 CSS pixels with exceptions; the enhanced AAA criterion uses 44 × 44 with exceptions. Check actual target geometry, not just icon dimensions. WCAG 2.2: Target Size (Minimum), AA · WCAG 2.2: Target Size (Enhanced), AAA

Comments, ideas or improvements? Edit this page's source on GitHub