Type first, then a frame that stays out of the way

Typography and layout are related, but they are not the same job. One reading rhythm at every width — then a page shell that borrows just enough from it.

Typography and layout on this site are related, but they are not the same job. Typography answers how the voice of the page sounds and how reading settles into a rhythm. Layout answers where that voice sits in the chrome — sidebar, article, footer — and how those regions rearrange as the viewport changes.

I used to blur those together. A mobile type scale and a desktop type scale. Every spacing decision dumped into the same bucket as “design system work.” A line-height tweak and a sidebar breakpoint started to feel like the same kind of problem. They are not. Type comes first here: one body size that breathes with the viewport, headings that grow when they need drama, labels that stay put, spacing from one token. Layout borrows from the type system’s units and spacing language, then does its own work.

One scale, not two

I used to pick a body size for phones, another for desktops, and jump between them at a breakpoint. That works until the in-between widths feel wrong, or until every heading needs its own pair of sizes too. clamp(MIN, VAL, MAX) replaces that habit with one expression: a preferred size that moves with the viewport, locked between a floor and a ceiling so type never gets unreadably small or theatrically large.

:root {
  --font-size: max(1rem, min(calc(0.75rem + 1.2vw), 1.5rem));
  --fs-h1: clamp(1.9rem, 1.2rem + 2.2vw, 2.7rem);
  --fs-h2: 1.75rem;
  --fs-meta: 0.875rem;
  --gap: 0.75rem;
  --lh-heading: 1.1;
  --lh-prose: calc(1.375 * 1.25);
}

h1 {
  font-size: var(--fs-h1);
  line-height: var(--lh-heading);
  text-wrap: balance;
}

p {
  font-size: var(--font-size);
  margin-bottom: 1.75rem;
  line-height: var(--lh-prose);
  text-wrap: pretty;
}

Those tokens live on :root so the whole document shares one scale.

--font-size is body type: 0.75rem + 1.2vw, clamped between 1rem and 1.5rem — 16px to 24px at a default root. The vw is the slope. As the viewport widens, that slice grows with it, so size eases instead of jumping at a breakpoint.

That is the scale. Style is what breaks from it on purpose — and amplifies it.

Style breaks the scale, and sometimes amplifies it

A fluid size alone still sounds flat if every role wears the same face and weight. IBM Plex Sans is the reading voice — body, UI, navigation, meta. Crimson Pro carries the headings. Sans for reading and chrome. Serif for titles. When those jobs blur, the page starts to sound like it cannot decide whether it is an article or a dashboard.

Weights and line-heights ride the same idea. Body sits light. Headings sit a step firmer, often through font-variation-settings so the browser delivers the weight instead of synthesizing a bold. Titles keep a tight line-height; prose gets more air; kickers track wider so they read as labels.

Titles grow from the same :root scale as body, not from a separate heading system. --fs-h1 is another root token — 1.2rem + 2.2vw between 1.9rem and 2.7rem, about 30px to 43px at a default root. Steeper vw than body, so the display title pulls ahead as the site widens, but the floors and ceilings are still rem on the root, so titles stay related to the body scale and still respect user zoom. Meta and kickers step off that motion entirely (--fs-meta: 0.875rem) so labels do not compete with prose as the screen widens.

The fluid size keeps the page readable at every width. The faces and roles decide what kind of page it feels like while it scales.

Layout borrows just enough

Layout takes two things from type: rem-based tokens and --gap. After that, its problems are structural — where the sidebar lives, how the footer lines up with the article, when the nav rearranges, how a wide image or code block stays from blowing out the measure.

Those are not typography questions. They are layout questions spoken in the same units.

Measure stays in ch — a typographic call before a layout call — so as the body scales, the column keeps roughly the same characters per line. I landed on 74ch for IBM Plex Sans by sitting with the page.

.container {
  max-width: 74ch;
  width: 100%;
  min-width: 0;
  margin: 0 auto;
}

One --gap is the shared spacing language. Heading margins and scroll offsets multiply it on the type side; the page shell multiplies it again for chrome.

h2, h3, h4 {
  margin: calc(var(--gap) * 2) 0 calc(var(--gap) / 2) 0;
  scroll-margin-top: calc(var(--gap) * 7);
}

.layout {
  display: grid;
  grid-template-columns: auto;
  grid-template-rows: auto 1fr auto;
  gap: var(--gap);
  padding: calc(var(--gap) * 2);
}

@media (min-width: 960px) {
  .layout {
    grid-template-columns: min-content auto;
    grid-template-rows: auto 1fr;
    gap: calc(var(--gap) * 3);
    padding: calc(var(--gap) * 3) calc(var(--gap) * 3) 0;
  }

  header.header {
    position: sticky;
    align-self: start;
    top: calc(var(--gap) * 7);
  }

  .footer {
    grid-column: 2 / -1;
  }
}

On small screens the shell stacks. At 960px it becomes a sidebar and content pair. The reading column keeps the 74ch measure. Sticky offset and chrome padding multiply --gap, so if the rhythm opens up later, the frame still speaks the same language. Layout rearranges chrome around the column. It does not redefine the type scale.

When something wide shows up — a float, a pre — the answer is still not “widen the article.” Protect the measure (min-width: 0 on .main and .container), let overflow scroll where it must, and keep borrowing --gap for the pulls and padding. Type set the rhythm. Layout stays out of the way.

Things to catch

Cap ch on the prose container, not on a child that switched faces — monospace and the body face disagree about how wide a character is. Keep floors and ceilings in rem so zoom still works. Add scrollbar-gutter: stable or vw-based sizes can jitter when scrollbars show up. And when the page feels tight, change --gap or its multiplier — do not invent a second spacing dialect for the chrome.

Type first — fluid size, measure, one gap, then style that knows when to break the scale. Layout borrows those units, then stays out of the way.