Spacing
Spacing and sizing tokens, fixed and scalable tokens usage.
The Experience System is based on Tailwind’s spacing model. The theme sets a single base step:
@theme {
--spacing: 0.25rem;
}pnpm add @by/experience-systemThat --spacing value is the unit Tailwind uses for numeric spacing utilities (p-4, gap-3, m-6, w-20, etc.): each step is a multiple of this base.
In addition to Tailwind utility classes the --spacing() function can be used to retrieve spacing values in CSS. For example:
.my-element {
margin: --spacing(4);
}So default (non-scaled) spacing = ordinary Tailwind utilities whose resolved size is fixed for a given theme; it does not automatically change when the app switches “density.”
Density and scaling factor
Beside of standard Tailwind spacing tokens Fractal experience system provides a set of scalable spacing tokens to address density modes switching. Spacing density is controlled by data-ds-spacing (set by ThemeProvider’s spacing prop). It defines --scale-density variable that is used to calculate scalable spacings.
There are 3 density modes:
- standard — full density (
--scale-density: 1). - compact — layout spacing tightens to 87.5% of the same token (
--scale-density: 0.875). - ultra-compact — layout spacing tightens to 75%, and typography scales down as well (
--scale-density: 0.75).
[data-ds-spacing='standard'] {
--scale-density: 1;
--scale-typography: 1;
--scale-line-height: 1;
}
[data-ds-spacing='compact'] {
--scale-density: 0.875;
--scale-typography: 1;
--scale-line-height: 1;
}
[data-ds-spacing='ultra-compact'] {
--scale-density: 0.75;
--scale-typography: 0.875;
--scale-line-height: 0.875;
}Scaled spacing utilities (*-scaled-*)
The experience system adds utilities such as p-scaled-4, gap-scaled-6, px-scaled-3, size-scaled-8, etc. They apply the same numeric scale as normal Tailwind spacing, but multiply by --scale-density:
@utility p-scaled-* {
padding: calc(var(--scale-density, 1) * --spacing(--value(number)));
padding: calc(var(--scale-density, 1) * --spacing(--value([number])));
}So for a given class (e.g. p-scaled-4), standard vs compact vs ultra-compact produces proportionally smaller padding/margin/gap without changing the class name.
Library components already use this pattern where internal rhythm should follow density (for example accordion trigger padding and avatar size use -scaled- utilities in the codebase).
When to use scaled vs default spacing
Prefer scaled for
-
List, tree, and table items
- Reduce vertical padding inside each row.
- Decrease spacing between rows.
- Slightly reduce supporting/icon sizes, but keep the overall row hit area at or near your accessibility minimum.
- Cell padding: noticeably tighter in compact mode, especially for numeric or pivot tables where users value more rows over extra whitespace.
Outcome: more rows on screen in compact mode without making individual rows fiddly to hit.
-
Controls’ visual bodies (but not their hit areas)
- Buttons, segmented controls, chips, toggles, etc. can visually compact:
- Narrower padding inside buttons; slightly smaller icon size.
- Tighter spacing between controls in toolbars and button groups.
- For checkboxes/radios, keep the click/tap target large but reduce surrounding margins.
Keyboard-heavy/pro environments often benefit from a more compact look while remaining usable with the mouse.
-
Chrome and structural elements
- These can scale quite a bit without directly affecting interaction safety:
- Page header, toolbars, tab bars: reduce height and padding, as they are used frequently and benefit from fitting more actions on a single line.
- Navigation rails, sidebars: slightly reduced padding, item spacing, and icon size.
- Cards, panels, accordions: smaller internal padding and card gaps.
- Tag/chip-like filters: smaller visual chips with tighter spacing, but preserved hover/click hit areas.
Users perceive this as “more working area” with the same tools.
-
Margins, gutters, and whitespace
- Global layout spacing is one of the least risky knobs:
- Reduce margins around content regions.
- Tighten gaps in grid layouts and between sections.
- Keep a minimum whitespace baseline to avoid a cluttered look, but bring everything a bit closer together.
What shouldn’t be scaled
Here are categories where aggressive scaling tends to hurt UX more than help.
-
Minimum interactive hit areas
- Accessibility minimums per platform shouldn’t be violated:
- Touch: ~44–48 logical pixels square.
- Desktop: could be more dense, but click targets should be comfortable. WCAG AA minimum for desktop is 24px.
-
Primary typography
- Key identifiers: IDs, names, statuses, and key numeric values should not drop below main body text size. If space is tight, prefer truncation with tooltip/hover detail over shrinking font sizes.
- Error and validation text: these should remain clearly readable; density should never make error states easy to miss.
You can slightly reduce line height and paragraph spacing in compact mode, but preserve legibility and scan-ability. Headings may shrink a bit, but keep your typographic hierarchy intact.
Density is not the same thing as a “small font mode”; users who want smaller/larger fonts usually expect a separate setting.
-
High-risk actions
- Preserve interaction safety for high-risk actions:
- Destructive buttons: these should not be shrunk so much that users mis-hit them. Often they keep more padding and a distinct placement (e.g., separated from dense inline action icons).
- Inline actions (edit/delete/etc.) in dense rows: keep sufficient spacing between them to avoid mis-clicks, even in compact tables.
-
Key flow elements
- Wizard steps, important forms, and onboarding flows should remain relatively comfortable. Density controls are mainly for “workhorse” views: dashboards, grids, logs, queues, etc.
- Admin or configuration screens with heavy explanation text shouldn’t be compressed to the point where labels wrap awkwardly and layout becomes noisy.
If you change the representation (card → row), that’s a component swap, not just density scaling.
-
Brand-critical elements and icons
- Density modes shouldn’t shrink these below their minimum.
- Icons can scale somewhat, but once they reach the smallest size (16px) where details are still clear, hold them constant and only adjust spacing around them.
-
Information-heavy components where legibility is paramount
- Charts, dense tables with lots of numeric data, complex diagrams:
- If shrinking fonts or cell height makes scanning hard or increases misreads, don’t scale these aggressively.
- Instead, consider alternative patterns: horizontal scrolling, column pinning, column visibility toggles, or zoom/fit controls.
Custom scaling
Tailwind custom variants let you spell different utilities per density (e.g. standard:p-6 compact:p-3) instead of one scaled utility.
Use this when:
- Design needs non-uniform changes between modes (not a single multiply factor).
- Specific regions need different discrete steps than
*-scaled-*would produce. - Apps can also define semantic tokens that vary by
[data-ds-spacing](as in the “Defining New Tokens” doc), e.g. different--spacing-card-paddingper mode — useful for named spacing that is not just “token × scale.”