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.
| Situation | Layout |
|---|---|
| Prose only, no wide tables or diagrams | One column at the reading width (Starlight’s default 45rem) |
| Prose plus tables that fit 700-800px | One column for everything, as MDN, Stripe and GitHub Docs do |
| Prose plus tables and diagrams that need 1000px or more | Two widths on one centre line (this site) |
| Side notes and figures in a margin | Tufte’s single left edge with full-width figures1 |
What the page did before
Section titled “What the page did before”| Property | Before | How it was checked |
|---|---|---|
| Characters per line | about 90 at 1280px, 202 at 2560px | content width over the average advance of the body face |
| Body colour | 78% ink mixed into the background | the stylesheet |
| Body contrast, dark | WCAG 9.4:1, APCA Lc 69.9 | apca-w3 0.1.9 |
| Link contrast, dark | APCA Lc 57.2 | apca-w3 0.1.9 |
| Paragraph gap | 12px at a 1.75 line height, under 0.4 of a line | computed margins |
| Right sidebar | half of all leftover width, 384px at 2560px | element 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.
| Pair | WCAG 2 | APCA Lc | Gate requires at 18px of this serif |
|---|---|---|---|
| Dark, 78% ink body | 9.4:1 | -69.9 | 90 |
| Dark, full ink | 14.7:1 | -98.9 | 90 |
| Dark link before | - | -57.2 | 90 |
Dark link after (#deebff) | - | above 91 | 90 |
| Light, 78% ink body | 7.3:1 | 80.4 | 90 |
| Light, full ink | 14.3:1 | 95.2 | 90 |
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.
Typeface and size
Section titled “Typeface and size”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.
| Face | x-height (glyph x / em) | 18px reads as |
|---|---|---|
| Newsreader (current) | 0.441 | 15.2px of APCA’s reference face |
| Source Serif 4 | 0.492 | 17.0px |
| Literata | 0.507 | 17.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.
Line length and the wide-blocks problem
Section titled “Line length and the wide-blocks problem”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) | Body | Line height | Text width | Chars per line | Tables and code |
|---|---|---|---|---|---|
| MDN | 16px | 1.75 | 768px | 110 | same width as the text |
| Stripe | 16px | 1.63 | 829px | 114 | same width |
| GitHub Docs | 16px | 1.60 | 749px | 101 | 36px wider |
| Cloudflare | 16px | 1.75 | 784px | 103 | same width |
| Starlight | 16px | 1.75 | 720px | 101 | same width |
| Rust Book | 16px | 1.45 | 750px | 99 | code same width, tables narrower |
| Kubernetes | 16px | 1.50 | 963px | 129 | same 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.
Four layouts that failed first
Section titled “Four layouts that failed first”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.
| Layout | What went wrong |
|---|---|
| Prose capped at 34rem, left-aligned in the 96rem column | Half of a 2560px screen empty to the right of the text |
| Prose on a centred axis, tables and code breaking out on both sides | The left edge zigzagged between prose, tables and diagrams |
| Each table and code block sized to its own content | Every 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.
The two-width layout
Section titled “The two-width layout”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:
| Property | CSS |
|---|---|
| Body size | clamp(18px, calc(16px + 0.15625vw), 20px) |
| Reading column | clamp(38rem, calc(24rem + 17.5vw), 52rem) |
| Content column | clamp(60rem, 46vw, 72rem) |
| Right sidebar | clamp(15rem, 12.5vw, 20rem) |
Measured on the live site:
| Viewport | Body | Chars per line | Reading column | Content column | Sidebar |
|---|---|---|---|---|---|
| 390 | 18px | 45 | 350px | 350px | - |
| 1280 | 18px | 79 | 608px | 728px | 240px |
| 1440 | 18.25px | 82 | 636px | 888px | 240px |
| 1600 | 18.5px | 83 | 664px | 960px | 240px |
| 1920 | 19px | 88 | 720px | 960px | 240px |
| 2560 | 20px | 95 | 832px | 1152px | 320px |
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.
Paragraph rhythm
Section titled “Paragraph rhythm”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).
Tables and inline code
Section titled “Tables and inline code”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.
Hierarchy
Section titled “Hierarchy”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 checks that came out of this work
Section titled “The checks that came out of this work”The design gate7 renders pages in Chromium and judges them. Two steps came out of this work:
| Step | Rule | Fails when |
|---|---|---|
| type | measure | a line of running text holds more than 100 characters at 1280, 1920 or 2560px |
| type | contrast | APCA Lc of text or its first link is below the level for its size, after scaling by x-height |
| type | emphasis | more than 30% of running text is bold, marked or inline code |
| layout | overflow | the page scrolls sideways at 390, 1280, 1920 or 2560px |
| layout | spill | a block ends past the content column |
| layout | lopsided | at 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.
Reading the numbers
Section titled “Reading the numbers”- 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.
Decision guide
Section titled “Decision guide”- If the tables and diagrams fit in about 800px, use one column for everything, as MDN, Stripe and Starlight do.
- 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.
- 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.
Evidence
Section titled “Evidence”| Claim | How it was checked | Status |
|---|---|---|
| APCA and WCAG values | apca-w3 0.1.9 on each colour pair | measured |
| x-heights | fontTools on the published font files, glyph x top over units per em | measured |
| Docs-site widths and line heights | headless Chromium at 1920px, one long page per site | measured |
| This site’s layout numbers | headless Chromium on the live site, six widths, three pages | measured |
| No page overflows | the layout step over all 179 pages at four widths | measured |
| Serif vs sans, individual fonts | the cited papers | documented, not tested here |
| 30% emphasis budget | Nielsen Norman Group guidance | documented, not tested here |
| Reading speed of the new layout | not measured | design only |
Related docs
Section titled “Related docs”- One identity, two registers - the design identity and the gate these checks belong to
- Labelling AI use in your own work - how much of this kind of work an agent writes
References
Section titled “References”-
D. Liepmann, “Tufte CSS,” an Edward Tufte project. https://edwardtufte.github.io/tufte-css/ ↩
-
Myndex, “APCA in a Nutshell,” APCA documentation. https://git.apcacontrast.com/documentation/APCA_in_a_Nutshell ↩
-
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 ↩
-
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 ↩
-
W3C, “Understanding Success Criterion 1.4.8: Visual Presentation,” WCAG 2.2. https://www.w3.org/WAI/WCAG22/Understanding/visual-presentation.html ↩
-
Nielsen Norman Group, “Formatting Long-Form Content,” article. https://www.nngroup.com/articles/formatting-long-form-content/ ↩
-
E. Anugrah, “One identity, two registers,” lexicanum. https://erfi.dev/reference/house-ui-identity-as-tooling/ ↩