Foundations
Spacing and layout
The 4px spacing scale, semantic gaps, grids, breakpoints, comfortable and compact density, touch targets and safe areas.
Spacing follows a 4px base with an 8px rhythm between blocks. Every value is a token: use --ds-space-* and never write a new pixel value. Small steps (2px, 4px) are for optical alignment inside a component; gaps between blocks use multiples of 8px.
Spacing scale
--ds-space-00--ds-space-0-50.125rem--ds-space-10.25rem--ds-space-1-50.375rem--ds-space-20.5rem--ds-space-2-50.625rem--ds-space-30.75rem--ds-space-3-50.875rem--ds-space-41rem--ds-space-4-51.125rem--ds-space-51.25rem--ds-space-61.5rem--ds-space-71.75rem--ds-space-82rem--ds-space-102.5rem--ds-space-123rem--ds-space-field-stack0.5remLabel → input → help (8 px).--ds-space-field-group1.5remBetween field groups and between form sections (24 px).--ds-space-section2remBetween large blocks in a form/detail view (32 px).--ds-space-card-x1.125remHorizontal padding of portal cards.--ds-space-card-y1rem--ds-space-panel2remDesktop form/detail content padding; 1.25rem at ≤ 640 px.--ds-space-toolbar-gap1rem 1.5remRow × column gap of the page toolbar.The 2px and 4px steps (--ds-space-0-5, --ds-space-1) keep icons and inline text aligned. Steps --ds-space-2 (8px), -4 (16px), -6 (24px), -8 (32px) and -12 (48px) build the gaps between parts.
Semantic gaps
Semantic tokens name the purpose of a gap. Reach for these before the raw scale.
| Token | Value | Use for |
|---|---|---|
--ds-space-field-stack |
8px | Label, control and helper text inside one field |
--ds-space-field-group |
24px | Between groups of fields in a form |
--ds-space-section |
32px | Between large sections of a page |
--ds-space-card-y × --ds-space-card-x |
16 × 18px | Card padding |
--ds-space-panel |
32px | Panel and large dialog padding |
--ds-space-toolbar-gap |
16px rows × 24px columns | Gaps in a toolbar |
| Variable | Value | Notes |
|---|---|---|
--ds-space-0 | 0 | |
--ds-space-0-5 | 0.125rem | |
--ds-space-1 | 0.25rem | |
--ds-space-1-5 | 0.375rem | |
--ds-space-2 | 0.5rem | |
--ds-space-2-5 | 0.625rem | |
--ds-space-3 | 0.75rem | |
--ds-space-3-5 | 0.875rem | |
--ds-space-4 | 1rem | |
--ds-space-4-5 | 1.125rem | |
--ds-space-5 | 1.25rem | |
--ds-space-6 | 1.5rem | |
--ds-space-7 | 1.75rem | |
--ds-space-8 | 2rem | |
--ds-space-10 | 2.5rem | |
--ds-space-12 | 3rem | |
--ds-space-field-stack | 0.5rem | Label → input → help (8 px). |
--ds-space-field-group | 1.5rem | Between field groups and between form sections (24 px). |
--ds-space-section | 2rem | Between large blocks in a form/detail view (32 px). |
--ds-space-card-x | 1.125rem | Horizontal padding of portal cards. |
--ds-space-card-y | 1rem | |
--ds-space-panel | 2rem | Desktop form/detail content padding; 1.25rem at ≤ 640 px. |
--ds-space-toolbar-gap | 1rem 1.5rem | Row × column gap of the page toolbar. |
Sizes
| Variable | Value | Notes |
|---|---|---|
--ds-size-target | 2.75rem | Minimum touch target for all controls (44 px). |
--ds-size-target-compact | 2.25rem | Action icons in dense desktop tables (36 px). Not for touch screens. |
--ds-size-control-lg | 3rem | Master form input (48 px). |
--ds-size-icon-sm | 1rem | |
--ds-size-icon-md | 1.125rem | Navigation icon (18 px). |
--ds-size-icon-lg | 1.25rem | |
--ds-size-icon-xl | 2rem | Dropzone icon, account avatar. |
--ds-size-avatar | 2.25rem | |
--ds-size-sidebar | 16rem | |
--ds-size-sidebar-rail | 4.75rem | |
--ds-size-topbar | 4.75rem | |
--ds-size-auth-panel | 24rem | |
--ds-size-dialog-sm | 34rem | |
--ds-size-dialog-lg | 54rem | |
--ds-size-panel | 24rem | Notification panel. |
--ds-size-popover-calendar | 22rem | |
--ds-size-menu | 15rem | |
--ds-size-measure | 65ch | Paragraph reading width. |
--ds-size-table-max-height | min(68dvh, 44rem) |
- Sidebar 16rem, collapsed rail 4.75rem, top bar 4.75rem.
- Dialogs 34rem (small) and 54rem (large). Below 640px a dialog fills the screen.
- Reading measure
65ch(--ds-size-measure). - Scrolling tables are capped at
min(68dvh, 44rem)so sticky headers stay visible.
Grids
Layouts use CSS grid with minmax(min(100%, X), 1fr), so a column is never wider than its container, even at 320px.
| Class | Behaviour |
|---|---|
.ds-kpis |
auto-fill, columns of at least 13.5rem |
.ds-grid-2 |
auto-fit, columns of at least 28rem; one column when narrow |
.ds-grid-3 |
auto-fit, columns of at least 20rem |
.ds-field-grid |
Two columns, one column at 640px and below |
View HTML code
<div class="ds-kpis">
<div class="ds-kpi"><span class="ds-text-subtle">Open invoices</span><span class="ds-num" style="font-size:var(--ds-type-metric-size);font-weight:var(--ds-type-metric-weight)">42</span></div>
<div class="ds-kpi"><span class="ds-text-subtle">Outstanding</span><span class="ds-num" style="font-size:var(--ds-type-metric-size);font-weight:var(--ds-type-metric-weight)">18,420.00</span></div>
<div class="ds-kpi"><span class="ds-text-subtle">Active projects</span><span class="ds-num" style="font-size:var(--ds-type-metric-size);font-weight:var(--ds-type-metric-weight)">7</span></div>
</div>Prefer container queries when a component must adapt to the space it receives rather than to the viewport.
Breakpoints
| Variable | Value | Notes |
|---|---|---|
--ds-breakpoint-xs | 360px | |
--ds-breakpoint-sm | 640px | Phone: tables become labeled rows, actions drop to their own row. |
--ds-breakpoint-md | 768px | |
--ds-breakpoint-lg | 1024px | Sidebar becomes a fixed column (drawer open). |
--ds-breakpoint-xl | 1280px |
| Width | What changes |
|---|---|
| 640px | Tables become labelled cards, inputs stay at 16px, page headers and dialog footers stack |
| 1024px | The sidebar is fixed on the left (a drawer below), the collapse button appears, .ds-main padding grows to 32px |
| 1180px | The toolbar stacks: text first, actions below and right-aligned |
Check 320px, 390px, 768px and 1280px in both languages. Do not add a breakpoint when 640, 1024 or 1180 is enough; record a component-specific breakpoint on that component's page.
Density
- Comfortable is the default: controls are 44px tall on touch and spacing follows the scale above.
- Compact is for dense desktop tables and toolbars. Wrap the region in
.ds-dense: small buttons and chips drop to--ds-size-target-compact(36px) and keep a hit area of at least 24×24px. Do not use it on touch devices or for the only way to do something.
View HTML code
<div style="display:flex;flex-wrap:wrap;align-items:center;gap:1.5rem">
<div style="display:flex;gap:.5rem"><button class="btn btn-sm">Comfortable</button><button class="btn btn-sm btn-primary">Save</button></div>
<div class="ds-dense" style="display:flex;gap:.5rem"><button class="btn btn-sm">Compact</button><button class="btn btn-sm btn-primary">Save</button></div>
</div>Touch targets
- At least 24×24px everywhere (WCAG 2.5.8) and 44×44px (
--ds-size-target) on(pointer: coarse). - Compact sizes keep the hit area with padding or a pseudo-element, not by shrinking the target.
- Leave 8px between adjacent targets so a thumb hits only one.
Safe areas
On devices with notches and rounded corners, add viewport-fit=cover to the viewport meta tag and pad fixed edges with the environment insets:
.bottom-bar {
padding-block-end: max(var(--ds-space-3), env(safe-area-inset-bottom));
padding-inline: max(var(--ds-space-4), env(safe-area-inset-left), env(safe-area-inset-right));
}Apply this to fixed bars, bottom navigation and sheets. Never place a primary action inside the inset.
Usage
- Use logical properties (
margin-inline-start,padding-block,inset-inline-end) sodir="rtl"works without edits. - Group related items closer than unrelated ones: 8px inside a field, 24px between fields, 32px between sections.
- Do not nest cards more than one level; use a hairline instead of a second box.
- Use
gapbetween siblings instead of margins on each child.
Tokens
--ds-space-*, --ds-size-*, --ds-breakpoint-*, --ds-size-target, --ds-size-target-compact, --ds-size-measure.