Spacing

One 4px-based / 8-point scale, and one rule about how to use it. Every gap on every arXiv page comes from these seven steps — no off-scale values (14, 26, …). Written spec: DESIGN-POLICIES.md (Spacing); tokens: --ds-space-1…--ds-space-12 in design-system.css.

The scale

Seven steps, each roughly 1.5× the last. The gaps in the bars below are the token values — the page pulls them live from --ds-space-*. Production code uses them exactly as written — --ds-space-4 everywhere, with no per-codebase renaming.

TokenValueDrawn at size
--ds-space-10.25rem (4px)
--ds-space-20.5rem (8px)
--ds-space-30.75rem (12px)
--ds-space-41rem (16px)
--ds-space-61.5rem (24px)
--ds-space-82rem (32px)
--ds-space-123rem (48px)
Relevant code
/* A stop is used as written: a token, in rem, never a pixel value */
.ds-card {
  padding: var(--ds-space-4) var(--ds-space-6);   /* 16px 24px at a 16px root */
}
--ds-space-1
4px. Tightest — label→value, icon→text.
--ds-space-2
8px. Closely related inline items.
--ds-space-3
12px. Related stacked elements.
--ds-space-4
16px. Default gap between elements in a section.
--ds-space-6
24px. Sub-section separation / card padding.
--ds-space-8
32px. Larger internal padding.
--ds-space-12
48px. Breathing space between major sections.

The proximity rule

The gap between sections should be clearly larger than the gap within one — aim for ~3× (e.g., 16px within, 48px between). This is what makes grouping read without borders or boxes. Same content, same words, both examples below — only the spacing differs.

Note

Every gap in this demo sits on a container as gap, so no child carries a margin of its own.

Proximity applied

✓ Two groups, read instantly

SubmitterA. Palomares
Submitted14 Jul 2026
Primary categorygr-qc
LicenseCC BY 4.0

One flat gap (16px everywhere)

✗ One undifferentiated list

SubmitterA. Palomares
Submitted14 Jul 2026
Primary categorygr-qc
LicenseCC BY 4.0
Relevant code
/* One named break per kind of gap, on the container, never on a child */
.record       { display: grid; gap: var(--ds-space-section); }   /* between groups */
.record-group { display: grid; gap: var(--ds-space-block); }     /* between rows in a group */
.record-row   { display: grid; gap: var(--ds-space-tight); }     /* a label and its value */
--ds-space-tight
8px. Inside a group: a label and its value, an icon and its text, two buttons that belong together.
--ds-space-block
16px. Between blocks in a section. Twice tight.
--ds-space-section
48px. Between sections. Three times block, so that between is never mistaken for within.

The layout tokens

Three tokens set the shape of every page: the width of the content column, the gutter that keeps it off the viewport edge, and the space the container puts between one region and the next. The container that reads them is documented on Organizing content.

Note

The demo is a real container, so the tracks it draws are the live token values.

A band that runs edge to edge. Its own padding is --ds-gutter, so this text still sits on the content track.

The next region. The container leaves --ds-space-section above it, and the ground on either side of this card is the gutter.

Relevant code
<div class="ds-container">
  <header class="ds-page-header">…</header>
  <section>…</section>   <!-- --ds-space-section above it, from the container -->
  <section>…</section>
</div>
/* A page that needs a different width changes the one token;
   it never adds a second width beside it. Here: a dense internal
   tool with tables that cannot compress. */
:root { --ds-width-page: 1080px; }
--ds-width-page
850px. The width of the content track, and the one width on a page: prose, tables, demo blocks, code and callouts all share it. An element never declares its own.
--ds-gutter
--ds-space-6, 24px. The least space between the content track and the viewport edge. A .ds-full band uses the same token as its inline padding, so the band and the column cannot drift apart.
--ds-space-section
48px. The container's row-gap: the space between one direct child and the next. A section carries no margin of its own.

Accessibility essentials

  • Spacing does not shrink a control. Every button, link and other control keeps a hit area of at least 24 by 24 CSS pixels (WCAG 2.2, 2.5.8), however tight the gap between it and its neighbours. Grow the hit area with padding where the visible control is smaller than that.
  • Spacing survives zoom. The scale is in rem, so every gap grows with the reader's text size (WCAG 1.4.4). At 400% page zoom the page is 320 pixels wide (WCAG 1.4.10): do not set a gap or a width that forces the page to scroll sideways.
  • Spacing is not a structure. Proximity tells a sighted reader what belongs together, and a screen reader does not see it. Where a group has a name, give it a heading, a <fieldset> or a landmark, so the grouping is in the markup as well as in the gap.

Rules

Stay on the scale

Reach for a token, never a hand-picked pixel. If a layout seems to want 14px or 26px, it wants an existing step (12 or 24) — or the design is fighting the grid. New values are a change to DESIGN-POLICIES, not a local override.

Between ≈ 3× within

Grouping is carried by contrast in spacing, not by lines. Reach for the named break — --ds-space-block within, --ds-space-section between — rather than the raw steps they map to. Block is 2× tight and section is 3× block; below about 1.5× the eye cannot tell the two apart and the grouping silently fails. When a border feels necessary to separate two blocks, first try widening the gap.

Everything in rem, spacing included

The scale is expressed in rem, so spacing scales with the reader's text size. A fixed gap beside growing text compresses the rhythm exactly for the person who asked for more room, and the ratio between within and between is what carries meaning, so it has to survive.

Name the break, not the value

Pages use --ds-space-tight (inside a group), --ds-space-block (between blocks in a section) or --ds-space-section (between sections) — never a pixel value, and never a raw --ds-space-N. The raw scale is the palette; these are the roles, the same way colours are named for what they do rather than what hue they are. Changing what a break means then moves every page together.

Containers own the space between regions

A section does not know what follows it, so it cannot know how far away that should be. Put the space on the container as gap, not on each child as a margin: gap never collapses, cannot be undone by a child, and needs no last-child resets. If a system needs :last-child { margin-bottom: 0 }, that is not a fix, it is a symptom that the space is owned by the wrong thing.

Prose flow inside a section stays element-owned, because a heading's space depends on its own size, which a container cannot know. Two mechanisms, at two clearly separated levels, never competing at the same one.

Gap takes no scale of its own

“How far apart do these sit” means the same thing whether the space comes from a margin or a gap, and whether it runs down the page or across it. So gap between siblings uses the rhythm above, on either axis.

One exception: the space between a glyph and its label inside a single control is part of the control rather than layout. Use --ds-gap-glyph (0.5em), which scales with that control's own type — the same exemption already given to button padding.

Buttons placed as bare siblings are spaced by the HTML whitespace between the tags — narrower than any deliberate value, and it looks like a bug because it is one. Use .ds-btn-group, or --stack for the vertical axis and --end for a form's action area.

Namespaced in production

There is one name per stop, --ds-space-6, and every codebase uses it exactly as written. No per-repository prefixes and no mapping tables: a mapping kept in another repo is invisible to everything here.