Spacing

Five tokens, one content column, three breakpoints. The spacing system is deliberately small. Constraints prevent drift.

Spacing scale

Every margin, padding, and gap on the site uses one of five tokens.

TokenValuePixelsSemantic use
--space-xs0.25rem4pxMicro adjustments. Footer link padding on mobile.
--space-sm0.5rem8pxTight spacing. Post list padding, code inline padding, list margins, table cell padding, h3 margin-bottom, footnote item spacing.
--space-md1rem16pxStandard unit. Paragraph margins, wrapper horizontal padding, code block padding, share modal gap, footer colophon margin-top, footnotes heading margin-bottom.
--space-lg1.5rem24pxSection spacing. Footer padding, blockquote margin, image margin, pull quote padding, article header padding-bottom, intro padding-bottom, footnotes padding-top, share modal padding.
--space-xl3rem48pxMajor section breaks. Header margin-bottom, h2/h3 margin-top, <hr> vertical margin, wrapper vertical padding, footer margin-top, footnotes margin-top, toast bottom position, intro margin-bottom.

No in-between values exist. If a spacing decision doesn’t map cleanly to one of these five, the design needs rethinking, not a sixth token.

Content column

--content-width: 42rem

Approximately 672px at 16px base. This produces a reading column of 65—70 characters per line at body text size, the optimal range for sustained reading. This column you’re reading is set to that width.

The value uses rem rather than px so the column scales proportionally if the base font size changes (it drops from 15px to 14px at the <= 600px breakpoint). A pixel-based column would hold its width while the text shrank, breaking the character-count relationship.

The content column is non-negotiable for reading pages. No component should push content wider than --content-width.

Layout structure

The page uses a flex column inside a fixed atmospheric frame. The z-index stack controls layering:

body
  .scroll-progress            fixed, top          z-index: 100
  .site-wrapper               content column       z-index: 2
    header.site-header          flex, baseline
    main                        flex: 1
    footer.site-footer          colophon strip
  .side-rail                  fixed, right         z-index: 50
  .vignette                   fixed, full-screen   z-index: 0
  .crop-marks                 fixed, corners       z-index: 99
  .paper-grain                fixed, full-screen   z-index: 1
  .toast                      fixed, bottom-center z-index: 1000
  .share-modal-overlay        fixed, full-screen   z-index: 2000

.site-wrapper centers the content column and establishes the vertical flex layout:

max-width: var(--content-width);
margin: 0 auto;
padding: var(--space-xl) var(--space-md);
min-height: 100vh;
display: flex;
flex-direction: column;
position: relative;
z-index: 2;

main takes flex: 1 to fill remaining vertical space, pushing the footer to the bottom regardless of content length.

The atmospheric layers (vignette, grain, crop marks) sit outside the content column as fixed overlays. They create the paper environment without affecting content flow. See Atmosphere for details.

Responsive breakpoints

Three breakpoints. Each is a discrete step, not a fluid scale.

Above 768px

Full layout. Crop marks visible. Paper grain visible. Default spacing and sizing throughout.

At 768px and below

Wrapper horizontal padding increases from --space-md to --space-lg to give content more breathing room relative to screen edges. Paper grain and crop marks are hidden (performance on mobile, and the atmospheric detail is lost at small sizes anyway).

ChangeValue
.site-wrapper paddingvar(--space-xl) var(--space-lg)
.paper-graindisplay: none
.crop-marksdisplay: none

At 600px and below

The small-screen breakpoint. Base font drops to 14px (from 15px), which cascades through all rem-based values. Desktop typographic details that don’t work at small sizes are disabled.

ChangeValue
html font-size14px
.site-wrapper paddingvar(--space-lg) var(--space-md)
Homepage nameplate1.8rem (from 2.5rem)
Article h11.6rem (from 2rem)
Drop capDisabled
.post-list li paddingvar(--space-md) 0 (larger touch targets)
.footer-navjustify-content: center
.footer-nav a paddingvar(--space-sm) var(--space-sm) (larger tap targets)
.footer-settingsjustify-content: center
.accent-current22px x 22px
.accent-option20px x 20px
.shortlist-catBlock layout, width: fit-content
.share-modal paddingvar(--space-md)
.share-modal-image max-width100%
.share-modal-actions buttonmin-height: 44px

The 44px minimum button height at this breakpoint meets the touch target guidance from WCAG 2.5.8.

Do’s and don’ts

DoDon’t
Use spacing tokens for all margins, padding, gapsUse pixel values or arbitrary rem values
Keep reading content within --content-widthWiden the content column for reading pages
Test at all three breakpointsAssume desktop layout works at 600px
Let the five-token scale constrain decisionsAdd in-between values like --space-md-lg

Specimens

--space-xs
4px
--space-sm
8px
--space-md
16px
--space-lg
24px
--space-xl
48px
42rem ≈ 672px ≈ 65–70 characters per line
1080px Wide features (side rail, margin notes)
768px Layout adjusts, grain hidden
600px Mobile (14px base, drop cap disabled)