Skip to content
VVVER

Design System / 04

Layout

Fluid safe-area gutters, fixed reading measures, hairline structure, asymmetric grids, and mobile source order define the page frame.

Measurements

Page gutter

--gutter-fluid · 20px at the phone floor → 40px at the desktop cap

Safe-area gutter

max(--gutter-fluid, env(safe-area-inset-left/right))

Responsive hinge

md · 768px; compositions stack below it in source order

Section measures

prose 68ch · default 1200px · wide 1440px · full uncapped

Radius

0 for brand surfaces; circles are reserved for circular controls and data marks

Hairlines

--hairline 1px · --hairline-fine 0.5px · --border-interactive 1.5px

Editorial grids

--grid-editorial-a 1fr 1.5fr 0.5fr · -b 0.75fr 1.5fr 0.75fr · --grid-index 1fr 5.75fr

Flush gutter

--gap-flush 3px for image mosaics

Stack gaps

flush 3px · xs 6px · sm 12px · md 24px · lg 48px

Vertical rhythm

--rhythm-sm 7.5svh · -md 10svh · -lg 15svh for hero/section spacing

Grid, gutter, hairline, and rhythm values are defined in tokens.css. The rhythm tiers use small-viewport units (svh), not vh.

Grid system

Three editorial column tokens establish recurring ratios; they are available templates, not a claim that every composition uses one. Each diagram applies the exact token ratio at the system's md hinge and stacks in source order below it.

--grid-editorial-a · 1fr 1.5fr 0.5fr

1fr
1.5fr
0.5fr

--grid-editorial-b · 0.75fr 1.5fr 0.75fr

0.75fr
1.5fr
0.75fr

--grid-index · 1fr 5.75fr

1fr
5.75fr

These diagrams stack below the md hinge. The tokens themselves are unresponsive values; consumers choose when to apply their ratio.

Vertical rhythm

Section spacing steps through three viewport-height tiers. The bars below use the tokens directly. Small-viewport units keep their measured height stable while mobile browser chrome changes.

--rhythm-sm · 7.5svh

--rhythm-md · 10svh

--rhythm-lg · 15svh

Small-viewport units (svh, not vh) keep mobile browser chrome from reflowing the hero as the address bar hides and shows.

Gutters & page frame

A fluid gutter sets the page edge, and container measures decide how wide the content itself reads.

Technical detail

Gutter
Grows from 20px to 40px, and yields to display safe areas when they are larger.
Measures
Container caps decide whether content reads as prose, a page, a wide canvas, or an uncapped band.

--gutter-fluid · 1.25–2.5rem

Safe content frame
gutter

Section measure ladder

Prose · 68ch
Default · 1200px
Wide · 1440px / full · uncapped

Gap-flush mosaic

The 3px flush gutter binds photographs into a single block: wide enough to read as separate frames, tight enough that they never become cards.

--gap-flush · 3px

Hairline weights

A small set of rule weights organizes the interface, separating structural lines from quieter dividers and interactive edges.

Technical detail

Structural
The 1px hairline for primary rules and separators.
Fine
The 0.5px rule, quieter for dense directories.
Interactive
Interactive edges may use the separate 1.5px token.

--hairline · 1px

Structural rules: table rows, section tops, card borders.

--hairline-fine · 0.5px

The finer rule: news-card tops and dense directory rows.

Component reference

Stack

A flex layout primitive that constrains spacing to the system scale — from the 3px mosaic gutter to the viewport-relative rhythm tiers. It spaces children and does nothing else.

Live specimen

Rendered output

Column · gap="sm"

Ink
Paper
Rules

Row · justify="between"

OneTwoThree

API reference

Inputs, types, and behavior

Inputgap
TypeStackGap = "md"
Behaviornone | flush (3px mosaic) | xs | sm | md | lg | rhythm-sm/md/lg (viewport-relative section tiers).
Inputdirection
Type"column" | "row"
BehaviorDefault "column".
Inputalign / justify
Typestring
BehaviorWord values mapped to static utilities (start, center, between, …).
Inputas / wrap
TypeElementType / boolean
BehaviorSwap the element ("section", "ul", …); wrap enables flex-wrap. No padding or margin props by design.

Component reference

Sticky stack

Sections that pin and stack as you scroll. Each panel slides over the last with an escalating z-index and a hairline top rule. Pure CSS positioning, no script — scroll the demo to see the panels stack.

Live specimen

Rendered output

01

Structure

Each panel pins at the shared top offset and is buried by the next on escalating z-index — pure CSS, no script.

02

Spacing

Each panel pins at the shared top offset and is buried by the next on escalating z-index — pure CSS, no script.

03

Source order

Each panel pins at the shared top offset and is buried by the next on escalating z-index — pure CSS, no script.

API reference

Inputs, types, and behavior

Inputtop
Typenumber = 0
BehaviorShared sticky offset in px — set it to your fixed-nav height.
Inputchildren
TypeStickyStackSection[]
BehaviorSections receive escalating z-index; per-section top / zIndex props win.
InputclassName (section)
Typestring?
BehaviorSections default to min-h-[70svh] paper — override height and padding here.

Recipes

Six compositions assembled from the primitives and grids above, each a small live render beside the markup that produced it.

Hero — media with anchored title

A media-led hero on the editorial grid; an opaque paper caption keeps the title readable without making assumptions about the image.

Woman seated on the steps of a pale green garden shed

Layout recipe · Media led

Image with anchored copy

tsx
<div className="relative overflow-hidden">
  <img alt="…" className="aspect-[16/9] w-full object-cover" src="/hero.jpg" />
  <div className="absolute inset-x-0 bottom-0 grid bg-c-white p-6 text-c-black
                  md:grid-cols-[1fr_1.5fr_0.5fr]">
    <div className="md:col-start-1 md:col-end-3">
      <p className="font-mono text-[0.62rem] uppercase text-c-black/60">Issue 01</p>
      <h1 className="text-[clamp(1.4rem,4vw,2.6rem)] uppercase leading-[0.95]">
        Image with anchored copy
      </h1>
    </div>
  </div>
</div>

Editorial header split

A two-column article header: headline left, dek and meta in the wider right column on the 0.9fr / 1.1fr split.

Headline beside a measured standfirst

Feature · Neutral specimen

The dek sits in the wider right column, just below the headline baseline — a two-column article header on the editorial split.

tsx
<header className="grid gap-8 md:grid-cols-[0.9fr_1.1fr]">
  <h1 className="text-[clamp(1.6rem,4vw,2.4rem)] uppercase leading-[0.95]">
    Headline beside a measured standfirst
  </h1>
  <div className="md:pt-2">
    <p className="font-mono text-[0.62rem] uppercase">Feature · Specimen</p>
    <p className="max-w-[46ch] leading-[1.4] text-c-black/70">The dek…</p>
  </div>
</header>

Sticky sidebar + scrolling content

A contents rail that pins while the body scrolls past it — set the sticky top to your fixed-nav height.

Overview

This neutral specimen keeps the contents rail beside a measured body column. Source order stays logical when the tracks stack.

Method

This neutral specimen keeps the contents rail beside a measured body column. Source order stays logical when the tracks stack.

Detail

This neutral specimen keeps the contents rail beside a measured body column. Source order stays logical when the tracks stack.

tsx
<div className="grid gap-6 md:grid-cols-[1fr_2fr]">
  <aside className="self-start md:sticky md:top-[var(--site-header-height,20px)]">
    <nav aria-label="Article contents">
      <Stack gap="xs">{/* section links */}</Stack>
    </nav>
  </aside>
  <article>{/* scrolling body copy */}</article>
</div>

Collection index — grid-index rows

Menu-style rows on the --grid-index ratio: a narrow index column beside a wide title column, each row a link.

tsx
<IndexTable
  columns="var(--grid-index)"        /* 1fr 5.75fr */
  rows={[
    { cells: ["01", "Editorial grid"], href: "/01" },
    { cells: ["02", "Readable measure"], href: "/02" },
  ]}
/>

Article measure

A centered prose column capped at the system’s 68ch measure for long-form body copy.

The readable measure

Body copy uses a 68ch measure. This keeps most lines long enough for a complete phrase while making the next line easy to find.

Paragraph spacing is separate from line height, so adjacent blocks remain distinct without extra rules.

tsx
<Prose className="mx-auto max-w-[68ch]">
  <h2>The readable measure</h2>
  <p>Body copy uses the system’s 68ch measure…</p>
</Prose>

Image mosaic — gap-flush

An image mosaic bound by the 3px flush gutter, so the photographs read as one block rather than separate cards.

--gap-flush · 3px

tsx
<div className="grid grid-cols-2 gap-[var(--gap-flush)] md:grid-cols-4">  {/* 3px */}
  {images.map((src) => (
    <span className="block aspect-square overflow-hidden" key={src}>
      <img alt="" className="size-full object-cover" src={src} />
    </span>
  ))}
</div>

The collection header the index recipe sits beneath

Grid + Measure + Rules + Order

Structure

Container owns measure

Section separates vertical rhythm from width. Choose prose, default, wide, or full; do not recreate those caps with page-specific numbers.

One responsive hinge

The system's own compositions change at md. Below it, source order carries meaning and tracks stack instead of shrinking.

Safe areas are part of the frame

Page gutters use the larger of the fluid token and the device inset. Full-viewport shells also use the packaged safe-area and dynamic-viewport utilities.

Media owns its crop

Media owns its crop, aspect, color, and content. Layout owns sequence and spacing; .photo-bw is an opt-in treatment, never a universal filter.

Capped measure — correct

Section separates vertical rhythm from width, so a paragraph keeps a comfortable line length and the eye finds the start of the next line without hunting back across the whole column on every wrap.

A container cap holds the line length in the comfortable reading band, however wide the viewport gets.

Uncapped measure — avoid

Section separates vertical rhythm from width, so a paragraph keeps a comfortable line length and the eye finds the start of the next line without hunting back across the whole column on every wrap.

Letting text fill the full frame stretches every line, so the eye has to travel back across the whole column to find the next one.

Example templates

Nine complete pages show editorial, collection, marketing, developer, agent, dashboard, research, and markets layouts. The research and markets examples use working charts and tables from their identity studies. Each page links back here and to the other examples.