Skip to content

Spacing

The spacing scale and the functional layout, content and control roles.

Choose spacing by the relationship between elements: layout organizes independent blocks, content connects parts of one block, and control spacing separates associated actions. Inline and block refer to writing axes, independently of the CSS display type.

01Spacing scale

The shared scale is calculated from --space-unit, independently of typography. Its eight steps use multipliers 1, 2, 3, 4, 6, 8, 12 and 16, preserving the existing rhythm. Functional tokens select these steps for a shared role; component-specific spacing can use the scale directly. Zero and layout keywords such as auto need no token.

TokenRoleDefinition
--space-unitBase unit from which the spacing scale is calculated.0.25rem
--space-1Step 1 of the shared spacing scale.var(--space-unit)
--space-2Step 2 of the shared spacing scale.calc(var(--space-unit) * 2)
--space-3Step 3 of the shared spacing scale.calc(var(--space-unit) * 3)
--space-4Step 4 of the shared spacing scale.calc(var(--space-unit) * 4)
--space-5Step 5 of the shared spacing scale.calc(var(--space-unit) * 6)
--space-6Step 6 of the shared spacing scale.calc(var(--space-unit) * 8)
--space-7Step 7 of the shared spacing scale.calc(var(--space-unit) * 12)
--space-8Step 8 of the shared spacing scale.calc(var(--space-unit) * 16)

Change the unit on :root to adjust the whole scale. A descendant override does not recompute inherited steps or functional aliases; for a local composition, override the relevant functional token instead. This is a spacing rhythm, not a universal geometric grid: strokes, cross clearances, icon drawings and Page's viewport clearance remain independent. Do not reconstruct arbitrary legacy values as unit multipliers.

02Functional spacing

Choose a role by the relationship between the elements, then padding for an inset or gap for a separation. Each role has an inline and a block axis; they share one value today so that a later design can adjust one axis, such as narrower horizontal insets on phones.

TokenRoleDefinition
--layout-padding-inlineInline inset from the frame's edges; the gutter that aligns sections and the shell.var(--space-6)
--layout-padding-blockBlock inset inside sections and the shell.var(--space-6)
--layout-gap-inlineInline separation between independent blocks.var(--space-6)
--layout-gap-blockBlock separation between independent blocks.var(--space-6)
--content-padding-inlineInline inset inside a contained surface, such as a card or panel.var(--space-4)
--content-padding-blockBlock inset inside a contained surface.var(--space-4)
--content-gap-inlineInline separation between related parts of one block.var(--space-4)
--content-gap-blockBlock separation between related parts of one block.var(--space-4)
--control-padding-inlineInline inset of buttons, fields and other text controls.var(--space-4)
--control-padding-blockBlock inset of buttons, fields and other text controls.var(--space-3)
--control-gap-inlineInline separation within a control group: a control and what it carries, or controls acting together.var(--space-2)
--control-gap-blockBlock separation within a control group.var(--space-2)

Choose the role

  • layout: independent blocks, or the frame around them. Sections and their inset, an article beside a sidebar, Header and Footer insets.
  • content: related parts of one block. The title, text and footer of a card; paragraphs; the fields of a form.
  • control: a control with what it carries, or controls acting together. A label, its field and its help; a row of buttons; the options of a fieldset.
  • Components own their inset; parents own the separation between children. .stack, .row and .formatted add no container padding.
  • Section owns outer spacing and defaults to layout gaps. Jointive layouts keep zero gap: Grid owns cell boundaries and each cell's content owns its inset. Page margins remain internal to Page.

Local exceptions

Some spacing is not a relationship between elements; it stays in the owning component, using the --space-* scale. Keep these cases local rather than forcing a role:

  • Lists of destinations, such as navigation links and tabs.
  • The internal arrangement of one control, such as the distance between a button's label and its arrow.
  • Drawing geometry and optical adjustments, such as the theme switch capsule or a centered touch target.
  • Short pairings inside reading content, such as a caption under an image.
  • Table cells, which are slots in a dense grid rather than contained surfaces.

Compose and override

Use component defaults, a named preset or the stack and row classes before writing spacing by hand. Heading uses --space-8 above its title and the layout insets on its sides and below.

<script>
  import { Section, Heading } from "@dg/ui";
</script>

<Heading title="Collection" />
<Section padding="no-bottom">
  <p>Content aligned with the frame.</p>
</Section>
<Section gap="content">
  <p>Related content.</p>
  <p>Another related part.</p>
</Section>

When writing CSS by hand, inline is horizontal and block is vertical. The two-value gap shorthand takes the row gap first: write the block token, then the inline token. Margins and paddings use the token of their own logical axis.

.collection {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 16rem), 1fr));
  gap: var(--layout-gap-block) var(--layout-gap-inline);
}

.actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--control-gap-block) var(--control-gap-inline);
}

Override the functional token directly on the owning element. Negative inline margins matching the layout inset can extend a specimen to the rails; a one-off value is not a reason to introduce a new token.

distributed·gallery