defuss-shadcn / Guides / Sizing / density
Density
Compactness is an explicit whitespace policy—not a global zoom factor. Keep typography, geometry and target sizes independent.
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
| Quantity | Density behavior |
|---|---|
| Numeric gaps, padding, margins; stack gap; default container gutters | Scaled. |
| Numeric widths, heights, square sizes, icon dimensions | Unchanged. |
| Fonts and line heights | Unchanged. |
| Literal px spacing, automatic margins, percentage sizes | Unchanged. |
| Existing button/input recipes and their fixed dimensions | Unchanged until explicitly migrated. |
| Interaction target minima | Independent 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:
| Component | What the density scales |
|---|---|
| Card | Header / content / footer section paddings |
| Table | Head, cell and caption padding |
| Accordion | Trigger and panel padding |
| Steps | Step and content gaps (size ladder owns indicator size) |
| Timeline | Per-item rhythm (connector gap + trailing space) |
| Tree View | Row padding-block (indent is structural, never scaled) |
| Sidebar | Link rows and the content frame |
| Command | Palette rows, search wrapper, group headings |
| Context Menu | Container padding and item rows |
| Sortable | List gap and item padding |
| Pagination | Inter-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