Space tokens
The generated reference for the root spacing value, the numeric steps derived from it, the named roles, the target floor and the four mode thresholds.
Overview
Everything the space system knows, in one generated table: the root value, the
steps, the named roles that map onto them, the touch-target floor, the minimum
separation between adjacent targets, and the four container thresholds that
define the responsive modes. Generated from tokens/space.json by
scripts/build-tokens.mts.
This is the table most often opened while building rather than while reading,
because the Used by column answers the question that actually comes up. That
question is not "how big is section" but "what else assumes section, and will
I break it".
How it works
tokens/space.json holds four groups.
The root. A single value from which Tailwind derives its whole spacing scale. The product theme sets it above the default; the docs chrome leaves it there. Everything in the steps group is a multiple of it, and the generated table shows the multiple as well as the resolved value.
The named roles. hairline through page, each mapped to a step. Components
consume roles; the numeric steps exist underneath them. See
Spacing scale.
The floors. --opsin-target-minimum, the minimum touch target, and the
minimum separation between adjacent interactive elements. These are the only
values in the space system that are not multiples of the root. They are absolute
physical minimums and they do not move when the product's density is retuned.
That is the point of them.
The mode thresholds. The container widths at which compact, medium,
expanded and wide begin, plus the per-mode gutter and the line-measure cap.
These are consumed by container queries, not media queries, everywhere except the
shell. See Responsive modes.
Using it
Look up the role, not the number. The resolved value changes with the root; the role does not.
Check Used by before changing anything. Space is the most widely consumed
token family in the system, and it is arithmetically coupled to
radius through the nesting rule. Changing a padding
changes a corner.
Notice which rows are absolute. The two floors are deliberately not derived. If you are theming density and a floor moved with it, something is wrong: a product cannot make its own touch targets smaller by choosing a tighter density.
Read the thresholds as container widths. They are not device sizes and not
viewport breakpoints. A component in a 300-point pane on a 1600-point display is
in compact, and the table is describing the pane.
Tokens
| Token | What it controls | Used by |
|---|---|---|
| --opsin-space-00rem | 0px. | no component |
| --opsin-space-10.25rem | Gap between an icon and its label. | badge, button, callout, care-card, checkbox, combobox, dialog, dose-tracker, empty-state, field, goal-ring, log-sheet, menu, questionnaire, radio-group, range-bar, result-card, scale-input, scroll-area, segmented-control, select, sheet, slider, source-citation, status-pill, stepper, surface, symptom-picker, tab-bar, tabs, timeline-entry, toast, tooltip, trend-sparkline |
| --opsin-space-20.5rem | Gap between tightly related lines. For the separation between two interactive targets see targets.separation, which is the same number and carries its scope. | alert-banner, avatar, badge, body-map, button, callout, combobox, consent-sheet, dialog, disclaimer-note, dose-tracker, empty-state, field, icon-button, log-sheet, menu, metric-tile, number-field, popover, progress, questionnaire, radio-group, range-bar, range-legend, reading-input, relative-time, scale-input, score-dial, scroll-area, segmented-control, select, sheet, skeleton, slider, spinner, status-pill, surface, symptom-picker, tab-bar, table, tabs, textarea, timeline-entry, toast, tooltip, trend-sparkline, visually-hidden |
| --opsin-space-30.75rem | Inner padding of a compact control. | accordion, alert-banner, button, card, checkbox, combobox, consent-sheet, dialog, disclaimer-note, divider, empty-state, field, log-sheet, menu, metric-tile, popover, progress, radio-group, reading-input, result-card, scroll-area, segmented-control, select, sheet, slider, status-pill, stepper, surface, symptom-picker, tab-bar, table, tabs, textarea, timeline-entry, toast, tooltip, value |
| --opsin-space-41rem | The default gap between elements inside a card. | accordion, avatar, badge, button, callout, care-card, consent-sheet, dialog, divider, empty-state, goal-ring, link, log-sheet, menu, popover, range-legend, result-card, scale-input, score-dial, segmented-control, sheet, surface, switch, tabs, timeline-entry, toast |
| --opsin-space-51.25rem | Card inner padding on a phone. | avatar, button, consent-sheet, dialog, questionnaire, sheet, slider, switch, symptom-picker |
| --opsin-space-61.5rem | Card inner padding on a wide screen; gap between cards. | avatar, card, dialog, disclaimer-note, empty-state, field, log-sheet, skeleton, stepper, switch, tab-bar, trend-sparkline |
| --opsin-space-82rem | Gap between sections within a screen. | avatar, body-map, disclaimer-note, popover, range-bar, reading-input, sheet, stepper |
| --opsin-space-102.5rem | Space above a section heading. | avatar, sheet, skeleton, switch |
| --opsin-space-123rem | Gap between major regions of a screen. | avatar, trend-sparkline |
| --opsin-space-164rem | Top of a screen below the safe area; the space a consent sheet leaves above its first question. | dialog, skeleton |
| --opsin-space-205rem | Empty-state vertical rhythm. | no component |
| --opsin-space-246rem | The largest step. Beyond this, use a layout, not a gap. | no component |
| --opsin-space-px0.0625rem | Hairline borders only. | body-map, trend-sparkline |
| --opsin-space-0-50.125rem | Optical nudges. Not a layout step. | badge, dose-tracker, radio-group, range-legend, result-card, scroll-area, segmented-control, status-pill, stepper, switch, tab-bar, tabs, term, trend-sparkline |
| --opsin-target-minimum2.75rem | The opsinjs floor for any interactive control, applied to the hit area rather than to the visible box. | accordion, body-map, button, card, checkbox, combobox, consent-sheet, dialog, disclaimer-note, field, icon-button, menu, metric-tile, number-field, popover, questionnaire, radio-group, reading-input, scale-input, segmented-control, select, sheet, slider, source-citation, switch, symptom-picker, tab-bar, tabs, textarea, toast, tooltip |
| --opsin-target-comfortable3rem | The default for a primary action in the product theme. | dialog |
| --opsin-target-generous3.5rem | A single primary action on a consent, escalation or emergency surface, where a mis-tap has a real cost. | no component |
| --opsin-target-separation0.5rem | The minimum gap between two adjacent targets whose visible boxes are smaller than 44px. It is a floor for undersized targets only. Two targets that both meet the 44px minimum are outside its scope, and separating those is governed by the differing cost of a mis-tap between them rather than by a fixed gap. | alert-banner, card, care-card, consent-sheet, result-card |
| --opsin-gutter-phone16px | The screen gutter in the phone responsive mode. | no component |
| --opsin-gutter-tablet24px | The screen gutter in the tablet responsive mode. | no component |
| --opsin-gutter-wide32px | The screen gutter in the wide responsive mode. | no component |
| --opsin-measure-tight45ch | A caption or a legend. | dialog, empty-state, goal-ring, popover, tooltip |
| --opsin-measure-comfortable66ch | The maximum line length for prose anywhere in the product, including a disclaimer nobody wants to read. | disclaimer-note, result-card, score-dial, sheet, source-citation, term |
| --opsin-measure-wide80ch | Code and machine output only. | no component |
| --opsin-graphic-dial17rem | The maximum width of the ScoreDial arc. This is 16em against the product body of 1.0625rem, which is 17rem and lands on the 4px grid at 68 steps, and it yields to w-full when the column is narrower. | goal-ring, score-dial |
| --opsin-safe-topenv(safe-area-inset-top, 0px) | The top safe-area inset, for installed web apps where browser chrome does not protect the edge. | no component |
| --opsin-safe-rightenv(safe-area-inset-right, 0px) | The right safe-area inset, for installed web apps where browser chrome does not protect the edge. | sheet |
| --opsin-safe-bottomenv(safe-area-inset-bottom, 0px) | The bottom safe-area inset, for installed web apps where browser chrome does not protect the edge. | dialog, sheet |
| --opsin-safe-leftenv(safe-area-inset-left, 0px) | The left safe-area inset, for installed web apps where browser chrome does not protect the edge. | sheet |
Accessibility impact
- Publishing the floors as tokens is what makes them auditable. A target minimum that lives in one component's stylesheet cannot be checked; one that lives in a generated table can be, and a regression against it fails the build.
- The mode thresholds are the reflow contract. SC 1.4.10 is tested at 320 CSS
pixels, which is inside
compact; publishing the threshold makes it possible to say exactly which layout a reader at 400% zoom gets. - Derived spacing scales with text; absolute floors do not need to. Both behaviours are correct and the table distinguishes them, which is the only way a reviewer can tell a bug from a decision.
- The table is a real table, with a header row and navigable cells, as every generated table on this site is.
Related
- Spacing scale says what each role is for.
- Density and touch covers where the two absolute floors come from and how they are measured.
- Reference → Tokens has the same rows alongside every other family.
Responsive modes
Four named modes, the rule that a wider screen shows more of the story rather than a bigger version of it, and why a component queries its container.
Iconography
One icon library, roles rather than filenames, and the rule that an icon never carries status on its own in a system whose category axis is glyph-driven.