Spacing
Spacing makes content easier to read and shows which elements belong together. All values come from tokens — no custom numbers.
Spacing is the cheapest way to make a dense financial screen legible and the easiest thing to get wrong, because every individual deviation looks harmless. The scale below is closed: if a value is not on it, it is not available.
Spacing scale
Based on a 4px base unit. Components and layouts use only values from this scale.
| Token | Value | px |
|---|---|---|
space-0 | 0 | 0 |
--ds-space-1 | 0.25rem | 4 |
--ds-space-2 | 0.5rem | 8 |
--ds-space-3 | 0.75rem | 12 |
--ds-space-4 | 1rem | 16 |
--ds-space-5 | 1.5rem | 24 |
--ds-space-6 | 2rem | 32 |
--ds-space-7 | 2.5rem | 40 |
--ds-space-8 | 3rem | 48 |
--ds-space-9 | 4rem | 64 |
--ds-space-10 | 6rem | 96 |
Why the scale goes non-linear at 5
Steps 1–4 increase by 4px. From step 5 the scale jumps to 24, 32, 40, 48, 64, 96. This is deliberate: small spacing distinguishes parts of one thing and needs fine resolution, while large spacing separates different things and needs obvious difference. A 28px gap and a 32px gap are indistinguishable to a reader, so the scale does not offer both.
The consequence is that there is no 20px. Anything that wants 20px is either a 16px relationship or a 24px one, and forcing that choice is the point.
Values are rem, not px
Every step is defined in rem, so the entire layout scales with the user's browser and OS text size. A user at 125% text size gets 125% spacing, which keeps the rhythm intact instead of cramming larger text into fixed gaps.
12px gap between stacked buttons. Each action is visually distinct.
0px gap merges buttons together. Hard to distinguish separate actions.
Where to use each token
Each spacing token has a specific role. Previews use redline annotations — red-tinted areas with boundary lines marking the exact spacing value.
| Token | Px | Usage in Senco |
|---|---|---|
space-1 | 4 | Badge dot to label, icon gap inside tight components |
space-2 | 8 | Inline button group gap (Money/Crypto tabs), FormField internal gap (label → input → helper) |
space-3 | 12 | Transaction row internal padding, compact list item padding |
space-4 | 16 | Between form fields (stacking), default component padding, screen edge margin (mobile) |
space-5 | 24 | Card padding (all sides), content area padding, grid gutter (tablet) |
space-6 | 32 | Between page sections, screen margin (desktop), grid gutter (desktop) |
space-7 | 40 | Large section dividers |
space-8 | 48 | Page section vertical spacing, hero to content gap |
space-9 | 64 | Major section breaks |
space-10 | 96 | Page top/bottom padding, empty state vertical centering |
Semantic pairings
Three relationships appear often enough that they should be reached for by name rather than by number:
| Relationship | Value | Appears in |
|---|---|---|
| Icon to its label | space-2 (8) | Buttons, status chips, nav items, list rows |
| Field to field | space-4 (16) | Every form in the product |
| Section to section | space-6 (32) | Dashboard, settings, transfer review |
If a new component needs one of these three, it uses the listed value without a separate decision. Divergence is where forms start looking like they came from different products.
Vertical rhythm on the transfer flow
The core flow is the strictest place in the system, because every step must look like the same screen advancing rather than four different screens:
| Region | Spacing |
|---|---|
| Screen edge → content | space-4 (16) mobile, space-6 (32) desktop |
| Step heading → body | space-2 (8) |
| Body → first field | space-5 (24) |
| Field → field | space-4 (16) |
| Last field → action bar | space-6 (32) |
| Action bar internal padding | space-4 (16) |
Principles
| Principle | Rule |
|---|---|
| Grouping | Related elements use smaller spacing (8px) |
| Separation | Distinct sections use larger spacing (32px+) |
| Vertical rhythm | Consistent vertical intervals between blocks |
| Component internal | Padding within components follows the scale |
| Component external | Margins between components follow the scale |
Proximity beats decoration
When two things belong together, reduce the space between them before adding a border, a background, or a divider. A card that needs a border to explain its grouping usually has the wrong internal spacing. This keeps dense screens — a nine-field address form, a transaction list — from turning into a stack of boxes.
Space belongs to the parent
A component does not add outer margin to itself. It renders its own padding and lets the container decide the gap. A component with a built-in margin-bottom is unusable in any layout that wanted a different gap, and it is the most common reason a design system ends up with space-4 and space-4 stacking into 32px nobody asked for.
Layouts express gaps with gap, not with margins on children.
Do and don't
| Do | Don't |
|---|---|
Use space-4 between every form field | Use space-4 here and space-3 there because a field "looks tall" |
Let the container set the gap with gap | Give a component its own margin-bottom |
| Tighten spacing to show grouping | Add a border to show grouping that spacing could carry |
Use space-5 for all card padding | Use 20px because 24 "felt like a lot" |
Scale spacing with rem | Mix a px margin into a rem layout |
Spacing utilities
Spacing utility classes use logical properties for RTL/LTR support:
| Class | Property | Available values |
|---|---|---|
snc-m-{n} | margin (all sides) | 0–6 |
snc-mt-{n} | margin-block-start | 0, 2, 4, 6 |
snc-mb-{n} | margin-block-end | 0, 2, 4, 6 |
snc-my-{n} | margin-block | 4, 6 |
snc-mx-auto | margin-inline: auto | — |
snc-p-{n} | padding (all sides) | 0, 2, 3, 4, 5, 6 |
snc-px-{n} | padding-inline | 4 |
snc-py-{n} | padding-block | 2, 3, 4 |
The utility set is intentionally partial. Only the steps that recur across screens are exposed; the rest are applied through component tokens. A utility for every step of the scale invites layout to be assembled from utilities rather than from components.
Logical properties, not physical
margin-block-start rather than margin-top, padding-inline rather than padding-left/right. Senco ships to markets that include right-to-left scripts, and logical properties mean an RTL layout mirrors without a second stylesheet.
Known gaps
| Item | Status |
|---|---|
space-0 | Used as a value, not defined as a --ds- variable |
| Semantic spacing conventions (inline gap, field gap, section gap) | Documented in Tokens as conventions; not declared in ds.css |
snc-px-* / snc-py-* coverage | Partial — only the values listed above exist |