Skip to content

Workstations and toolingreference

A reading column beside wide tables: typography and layout for a dense docs site

This site is mostly long prose interleaved with wide tables, code blocks and Graphviz diagrams. A reading-width rule says text should stop at about 80 characters per line; the tables and diagrams need far more room than that. This page covers how the type and layout were rebuilt to serve both, what the research and the measured docs sites say, and the gate checks that now fail when either side regresses. The design identity those checks belong to is covered in One identity, two registers.

Provenance. Everything below was measured on 2026-11-10 in headless Chromium: this site at 390, 1280, 1440, 1600, 1920 and 2560px, and seven docs sites at 1920px, re-measured from a survey of thirteen. APCA contrast values come from the reference implementation, apca-w3 0.1.9. Font x-heights were read from the font files with fontTools. Study results are quoted from the papers’ own abstracts or text; where a figure came from a secondary source it says so.

TL;DR:

  • WCAG 2 contrast ratios overstate light text on dark backgrounds. Body text mixed to 78% ink passed at 9.4:1 and scored APCA Lc 69.9, under the Lc 90 that 18px of this serif needs. Full ink scores Lc 98.9.
  • Serif vs sans made no measurable legibility difference in the studies reviewed, and individual readers differ more between fonts than font classes do. The serif stayed; a side-by-side of three serifs and three sizes kept it at 18px.
  • No docs site measured lets its tables run meaningfully wider than its prose. This site needs wider tables, so it uses two widths: running text on a centred reading column of 79-95 characters, everything else filling a content column up to 72rem.
  • A paragraph break has to be bigger than a line break: line height came down from 1.75 to 1.6 and paragraphs now sit one empty line apart, up from 12px.
  • Four layouts failed before that one. The first, and the overflow bugs found while shipping the final one, are now rules in the design gate, which sweeps all 179 pages at four widths in about 4.5 minutes.
SituationLayout
Prose only, no wide tables or diagramsOne column at the reading width (Starlight’s default 45rem)
Prose plus tables that fit 700-800pxOne column for everything, as MDN, Stripe and GitHub Docs do
Prose plus tables and diagrams that need 1000px or moreTwo widths on one centre line (this site)
Side notes and figures in a marginTufte’s single left edge with full-width figures1
PropertyBeforeHow it was checked
Characters per lineabout 90 at 1280px, 202 at 2560pxcontent width over the average advance of the body face
Body colour78% ink mixed into the backgroundthe stylesheet
Body contrast, darkWCAG 9.4:1, APCA Lc 69.9apca-w3 0.1.9
Link contrast, darkAPCA Lc 57.2apca-w3 0.1.9
Paragraph gap12px at a 1.75 line height, under 0.4 of a linecomputed margins
Right sidebarhalf of all leftover width, 384px at 2560pxelement box

The content column had been widened in steps up to 96rem so that wide screens would not show empty margins. Tables and diagrams benefited; prose followed the column to 202 characters.

Contrast: APCA instead of the WCAG 2 ratio

Section titled “Contrast: APCA instead of the WCAG 2 ratio”

WCAG 2 passed both themes at its strictest level. APCA’s own documentation says the WCAG 2 math “overstates contrast for dark colors” and cannot guide a dark-mode design.2 APCA sets body-text levels by size, for a face with a 0.52 x-height: Lc 75 is the minimum for body text from 18px, and Lc 90, its preferred level, allows body text down to 14px. A face with a smaller x-height counts as a smaller size, so 18px of this serif (15.2px of the reference face) falls in the Lc 90 band, which is what the design gate requires.

PairWCAG 2APCA LcGate requires at 18px of this serif
Dark, 78% ink body9.4:1-69.990
Dark, full ink14.7:1-98.990
Dark link before--57.290
Dark link after (#deebff)-above 9190
Light, 78% ink body7.3:180.490
Light, full ink14.3:195.290

Body text is now full ink in both themes. A saturated blue cannot reach Lc 90 on the dark background, so links inside running text use a pale blue in dark mode and rely on the underline to read as links. In light mode they moved from Lc 76.0 to a dark navy at Lc 91.3. Rules, bars and titles keep the saturated blue. APCA is a draft for WCAG 3, not a standard.

A 2022 review of the legibility literature found no difference between serif and sans faces, on paper or on screen.3 A 2022 study of individual readers found reading speed varied by 35% between a reader’s fastest and slowest font, and suggested that one font does not fit all.4 Neither supports switching faces by argument, so the choice came down to x-height, which sets how large a nominal size reads.

Facex-height (glyph x / em)18px reads as
Newsreader (current)0.44115.2px of APCA’s reference face
Source Serif 40.49217.0px
Literata0.50717.5px

A side-by-side preview of the three faces at 18, 19 and 20px, light and dark, kept Newsreader. Body text now scales from 18px at 1280px to 20px at 2560px, which partly offsets the small x-height on wide screens; at 1280px it stays 18px.

The research on line length is mixed on the exact number, because studies measured different outcomes: reading speed, comprehension and preference do not peak at the same width. The guidance converges anyway. WCAG’s AAA criterion caps text at 80 characters per line.5 None of the studies reviewed tested 150 characters or more.

What established docs sites do was more useful than the studies, because they face the same tables:

Site (1920px)BodyLine heightText widthChars per lineTables and code
MDN16px1.75768px110same width as the text
Stripe16px1.63829px114same width
GitHub Docs16px1.60749px10136px wider
Cloudflare16px1.75784px103same width
Starlight16px1.75720px101same width
Rust Book16px1.45750px99code same width, tables narrower
Kubernetes16px1.50963px129same width

None of them lets a table run meaningfully wider than its prose. On these pages they accept a measure of about 100-115 characters at 16px, and wide tables scroll. This site’s tables carry four to six columns of measurements and long identifiers, and its Graphviz diagrams lose legibility when scaled to 720px, so it needs a second width.

All four were built and rejected on the same day; the first two shipped briefly and were reverted, and the last was the alternative in a side-by-side comparison.

LayoutWhat went wrong
Prose capped at 34rem, left-aligned in the 96rem columnHalf of a 2560px screen empty to the right of the text
Prose on a centred axis, tables and code breaking out on both sidesThe left edge zigzagged between prose, tables and diagrams
Each table and code block sized to its own contentEvery block had a different right edge
One 45rem column for everything (Starlight’s default)Tables and diagrams squeezed, the shape the site had moved away from

The first one passed every check the gate had at the time, including the line-length check, which measures how long a line is and not where it sits.

Running text (paragraphs, lists, quotes, asides, section headings and references) sits on a reading column centred in the content column. Tables, code blocks, diagrams and card grids fill the content column, all at one width. The page title starts at the content column’s left edge, so a long title stays on one line. The right sidebar is only as wide as its table of contents. There are two widths, centred on one line, so each page has two left edges and two right edges and no others.

Everything scales between 1280 and 2560px:

PropertyCSS
Body sizeclamp(18px, calc(16px + 0.15625vw), 20px)
Reading columnclamp(38rem, calc(24rem + 17.5vw), 52rem)
Content columnclamp(60rem, 46vw, 72rem)
Right sidebarclamp(15rem, 12.5vw, 20rem)

Measured on the live site:

ViewportBodyChars per lineReading columnContent columnSidebar
39018px45350px350px-
128018px79608px728px240px
144018.25px82636px888px240px
160018.5px83664px960px240px
192019px88720px960px240px
256020px95832px1152px320px

The measure widens past 80 characters on large screens because body size grows with it and because the docs sites above run at 100-115. The content column went through three caps on the same day: 100rem ran the tables nearly twice the width of the text, 84rem still ran them 1.75 times as wide at 2560px, and 72rem with a longer reading column keeps them at about 1.4 times.

At a 1.75 line height with a 12px paragraph gap, a paragraph break read as one more wrapped line. Line height is now 1.6 and paragraphs sit one empty line apart: 30.4px of gap on 30.4px lines at 1920px. List items step 0.35em, so a list still reads as one block. Headings get more space above than a paragraph break (36px above a second-level heading at 1920px) and less below (about 20px).

The first sweep of the real build found 20 page and width combinations that scrolled sideways or spilled, in two ways the prototype did not show. Inline code was set never to wrap, so a long command in a paragraph pushed the page sideways. It is now an inline block: a span that fits moves to the next line whole, and only a span longer than a full line wraps. Tables holding long URLs or identifiers grew past the column; table cells now keep ordinary words whole and let code break anywhere, and a build step wraps every table in a scroll container. Of 1,340 tables, 15 scroll inside their own box at 1280px and 1 at 1920px; none widens the page.

An earlier fix let every word in a cell break anywhere. It split ordinary words, column headers like MEDIAN among them, in 235 page and width combinations, and was reverted the same hour.

Second-level headings are 27px and third-level 22px on an 18-20px body: about 1.4x and 1.15x the body at 19px, and 1.23x between the two heading levels. Each gets more space above than below. Emphasis has a budget: Nielsen Norman Group’s guidance for long-form content is that highlighted text should make up no more than 30% of an article, and that long bullets still read as a wall of text.6 The gate counts bold and inline code as emphasis.

The design gate7 renders pages in Chromium and judges them. Two steps came out of this work:

StepRuleFails when
typemeasurea line of running text holds more than 100 characters at 1280, 1920 or 2560px
typecontrastAPCA Lc of text or its first link is below the level for its size, after scaling by x-height
typeemphasismore than 30% of running text is bold, marked or inline code
layoutoverflowthe page scrolls sideways at 390, 1280, 1920 or 2560px
layoutspilla block ends past the content column
layoutlopsidedat 1600px or wider, running text has one side gap at least a quarter of its column and three times the other

The type step’s limit is 100 rather than WCAG’s 80 to hold the measure chosen above, and it still fails the 150-200 character lines of a column with no measure. The layout step takes a sitemap and checks every page; on this site that is 179 pages at four widths in about 4.5 minutes. Its first run on the other apps found one real bug, a form that makes a 390px phone screen scroll sideways by 39px, and one false positive in an app’s form column, which led to the lopsided rule judging reading columns only.

  • The contrast table is the strongest evidence here: computed by the reference implementation, and the WCAG 2 versus APCA disagreement is documented by APCA itself.
  • The docs-site table is a single viewport on one page per site. It shows what those sites ship, not what reads best.
  • The 35% figure compares readers’ fastest and slowest fonts, so it argues for testing a face, not for any particular face.
  • No layout claim here is a reading-speed measurement. The layout was chosen by side-by-side comparison and verified for overflow and alignment; whether it reads faster was not tested.
docs page layouttables or diagramsneed > 800px?one column for everything(~45-48rem)notwo widths, one centre line:reading column + content columnyessweep every page at390 / 1280 / 1920 / 2560px
  1. If the tables and diagrams fit in about 800px, use one column for everything, as MDN, Stripe and Starlight do.
  2. If they need more, use two widths centred on one line: running text on a reading column, tables, code and diagrams on the content column.
  3. Either way, sweep every page at a phone width and three desktop widths for overflow before shipping, and compare alternatives side by side rather than adjusting live.
ClaimHow it was checkedStatus
APCA and WCAG valuesapca-w3 0.1.9 on each colour pairmeasured
x-heightsfontTools on the published font files, glyph x top over units per emmeasured
Docs-site widths and line heightsheadless Chromium at 1920px, one long page per sitemeasured
This site’s layout numbersheadless Chromium on the live site, six widths, three pagesmeasured
No page overflowsthe layout step over all 179 pages at four widthsmeasured
Serif vs sans, individual fontsthe cited papersdocumented, not tested here
30% emphasis budgetNielsen Norman Group guidancedocumented, not tested here
Reading speed of the new layoutnot measureddesign only
  1. D. Liepmann, “Tufte CSS,” an Edward Tufte project. https://edwardtufte.github.io/tufte-css/ ↩

  2. Myndex, “APCA in a Nutshell,” APCA documentation. https://git.apcacontrast.com/documentation/APCA_in_a_Nutshell ↩

  3. J. T. E. Richardson, “Coda: Lessons Learned,” in The Legibility of Serif and Sans Serif Typefaces, SpringerBriefs in Education, Springer, 2022. https://link.springer.com/chapter/10.1007/978-3-030-90984-0_16 ↩

  4. S. Wallace et al., “Towards Individuated Reading Experiences: Different Fonts Increase Reading Speed for Different Individuals,” ACM Transactions on Computer-Human Interaction, 2022. https://doi.org/10.1145/3502222 ↩

  5. W3C, “Understanding Success Criterion 1.4.8: Visual Presentation,” WCAG 2.2. https://www.w3.org/WAI/WCAG22/Understanding/visual-presentation.html ↩

  6. Nielsen Norman Group, “Formatting Long-Form Content,” article. https://www.nngroup.com/articles/formatting-long-form-content/ ↩

  7. E. Anugrah, “One identity, two registers,” lexicanum. https://erfi.dev/reference/house-ui-identity-as-tooling/ ↩