Reference notes. Source retained; historical claims may need rechecking.
Documentation design - the didactic-page layer
The layer Kennedy leaves open. Kennedy's rules (kennedy/) is about app screens - one job, one control, one state to manage. Frank's pages are didactic documents: teaching pages, one-pagers, explainers read calmly by UN partners and government teams. This digest is the missing body - how a document reads well - and it sits under the same two lenses as everything in this folder: the Teletubbies principle (the page speaks to the eye before it is read) and the Kennedy method (one job, hierarchy by de-emphasis, grayscale first, colour last, double the whitespace).
All CSS below is hand-written on house-token variables - no framework, no dependency - because a self-contained teaching page must stay one file.
The four sources - verdict
On Semantic UI: the framework is 2013-era, jQuery-based, and effectively dead - its own community redirects to the live fork
Fomantic-UI. Neither belongs inside a teaching page (≈500 KB CSS+JS + jQuery for a one-pager is the wrong tool). But its element vocabulary - segment, message, step, statistic - is the grammar of a good document. We stole the ideas as a dozen lines of CSS each. See §Blocks.
Named exemplars - the doc sites that read well
LaTeX.css - a single <link> turns plain semantic HTML into a book-like page: serif type, centred title block, numbered contents, one red accent. Proof that typography and spacing alone - zero components - can look finished. The closest reference to what Frank needs.
MDN Web Docs - the reference everyone converged on. Persistent "In this article" rail, breadcrumb above the title, soft-tinted status callouts, grey inline code chips. Reads well because the page never makes you guess where you are.
Stripe docs - three panes (nav · prose · matching example), colour used only as a functional signal. Borrow selectively - the community also called it "getting a little too technical."
Astro Starlight - grouped collapsible sidebar, "On this page" control, coloured-left-border admonitions (note/tip/caution/danger). The clean reference for the orientation patterns.
GitHub Markdown CSS - the "semi-nice baseline" everyone measures against.
- Vercel · Tailwind · Prisma · Supabase · Radix · Anthropic docs - named as the current frontier (dev-tool companies competing hard on doc quality). Take the layout discipline, not the specific typeface.
- Confluence - named only as the anti-example: wiki chrome, not a designed page. What NOT to imitate.
A. Reading - how prose itself reads well
1. Reading measure (line length). Cap body text at 66–72 characters, never full viewport width. Short lines let the eye find the return path; wide lines lose it on every wrap.
.prose { max-width: 68ch; margin-inline: auto; }
2. Two body regimes. Reading prose at 18px / 1.6; dense reference (tables, specs, definitions) at 14–16px / 1.4. A teaching paragraph and a data table are different reading modes - they should look different. Never mix them in one block.
.prose { font-size: 18px; line-height: 1.6; }
.dense { font-size: 15px; line-height: 1.4; }
3. Generous paragraph rhythm. ~24–32px between paragraphs, more before headings - roughly double the browser default. Silence between ideas reads as "organised," not "empty." This is the whitespace half of Kennedy.
.prose p { margin-bottom: 1.6em; }
.prose h2 { margin-top: 3em; }
4. Three fonts, no more - and each has a job. A serif or humanist sans for body, a plain sans for UI/labels/nav, a monospace for anything literal (code, IDs, field names). The font tells the eye "prose" vs "control" vs "literal value" before the words are read - pure Teletubbies. Set once at :root.
:root { --font-body:'Georgia',serif; --font-ui:system-ui,sans-serif; --font-mono:'SF Mono',monospace; }
B. Orientation - never let a long page lose the reader
5. "On this page" rail. A short list of the page's own <h2>s, sticky beside the content; collapses to a <details> toggle on narrow screens. Answers "where am I / how much is left" without scrolling blind.
.on-this-page { position: sticky; top: 2rem; font-size: 14px; }
.on-this-page a { display:block; padding:.25rem 0; color:var(--ink-soft); text-decoration:none; }
.on-this-page a:hover { color:var(--accent); }
@media (max-width:800px){ .on-this-page{ position:static; } } /* wrap in <details> here */
6. Anchor links on headings, revealed on hover. A quiet # appears next to a heading on hover, giving every section a citable address ("see the section on X"). Each heading needs an id.
h2 .anchor { opacity:0; margin-left:.4em; color:var(--ink-soft); text-decoration:none; }
h2:hover .anchor, h2:focus-within .anchor { opacity:1; }
7. The top menu (the Studio pattern, made a rule 03-08-2026 by Frank's verdict). Every multi-section document opens with ONE sticky bar: left, the collection name in the accent color plus › the page's name (uppercase, small); right, the links: the document's own sections for a long single page, or the collection's sibling pages for a booklet. One line, wraps on phones, no logo clutter. It replaces the old "slim sticky header" and complements the on-this-page rail (short documents can then drop the rail). Proven across the whole UI-UX Studio; the ready component is .topnav in the kit.
.topnav { position:sticky; top:0; z-index:60; background:var(--paper); border-bottom:1px solid var(--line); }
.topnav-inner { display:flex; justify-content:space-between; align-items:center; gap:var(--space-3); flex-wrap:wrap; }
.topnav .brand { font-weight:700; font-size:var(--scale-0); letter-spacing:.08em; text-transform:uppercase; }
.topnav .brand .home { color:var(--accent); }
8. Container / reading column. Cap the page width, and cap the prose column tighter inside it. Kennedy's "generous whitespace" as CSS.
.container { max-width:1120px; margin:0 auto; padding:0 1.5rem; }
.text-column { max-width:700px; margin:0 auto; }
C. Blocks - the document's vocabulary (stolen from Semantic UI, made native)
9. Segment - the topic block. A bordered, padded box grouping related content; optionally raised with a soft shadow. The single most useful idea - it turns a wall of text into visibly separate topics before a word is read (Teletubbies test).
.segment { background:#fff; border:1px solid var(--line); border-radius:4px; padding:1.5rem 2rem; margin:1.5rem 0; }
.segment.raised { box-shadow:0 2px 6px rgba(0,0,0,.08); }
10. Callout / admonition - the one place colour earns its keep. A tinted box with a left accent, pulling out one sentence the reader must not miss. Everything else stays grayscale; the callout is the scarce accent (Kennedy). 2–3 meanings max - never a rainbow.
.callout { border-left:4px solid var(--accent,#888); background:#f7f7f7; padding:.9rem 1.2rem; margin:1.25rem 0; border-radius:0 4px 4px 0; }
.callout.info { --accent:#2b6cb0; background:#eef4fb; }
.callout.warning { --accent:#b7791f; background:#fbf3e6; }
.callout.success { --accent:#2f855a; background:#eaf7ef; }
11. Steps - the procedure row. A row of numbered stages, each with a title and short description. Turns "first… then… finally" into something scannable in two seconds. The shape for every "how this works" page.
.steps { display:flex; border:1px solid var(--line); border-radius:4px; overflow:hidden; }
.step { flex:1; padding:1rem 1.25rem; border-right:1px solid var(--line); }
.step:last-child { border-right:none; }
.step .num { font-size:1.4rem; font-weight:600; color:#999; }
.step.active .num { color:var(--ink); }
.step .title{ font-weight:600; margin-top:.25rem; }
.step .desc { color:var(--ink-soft); font-size:.9rem; }
12. Statistic - the big number. A large numeral with a small caption. Lands the one figure a page wants remembered ("47 countries", "3 steps", "€0 cost"). De-emphasis by contrast: everything around it stays quiet.
.stat .value { font-size:2.75rem; font-weight:700; line-height:1; color:var(--ink); }
.stat .label { font-size:.85rem; text-transform:uppercase; letter-spacing:.05em; color:var(--ink-soft); margin-top:.35rem; }
13. Definition pairs. Term + one-line meaning, structurally paired. Didactic pages constantly need "here is a word, here is what it means" - native <dl> says it structurally, which a callout can't. Use the real tags.
dl.desc-list dt { font-weight:600; margin-top:1rem; }
dl.desc-list dd { margin:.15rem 0 0 0; color:var(--ink-soft); }
14. Divider - named section break. A rule, optionally with a word centred in it ("Step 2", "Or"). Splits a long page without a full heading.
.divider { display:flex; align-items:center; color:#999; margin:2rem 0; }
.divider::before, .divider::after { content:""; flex:1; border-bottom:1px solid var(--line); }
.divider span { padding:0 .75rem; font-size:.85rem; }
15. Label - the status tag. A tiny rounded tag ("Draft", "Required", "Step 2 of 4"). One grey + one accent cover a document's needs - skip Semantic's dozen variants.
.label { padding:.2em .6em; border-radius:3px; font-size:.75rem; background:#eee; color:var(--ink-soft); }
.label.accent { background:color-mix(in srgb,var(--accent) 15%,white); color:var(--accent); }
16. Code / example blocks - label, don't just colour. A small language tag or filename above the block, monospace, horizontal scroll (never wrap - wrapping destroys the indentation, which is itself information). A block distinguished by background colour alone is invisible without colour vision.
figure.code figcaption { font:12px var(--font-mono); color:var(--ink-soft); background:#f2f2f0; padding:.3rem .8rem; border:1px solid var(--line); border-bottom:none; border-radius:4px 4px 0 0; }
figure.code pre { margin:0; overflow-x:auto; padding:1rem; background:#fafafa; border:1px solid var(--line); border-radius:0 0 4px 4px; }
17. Tables - sticky header, zebra rows, scroll in their own box. Long reference tables keep the header visible; alternate tint aids row-tracking; on small screens the table scrolls sideways inside its wrapper rather than squeezing (the same rule Frank applies to artifacts).
.table-wrap { overflow-x:auto; }
.table-wrap thead th { position:sticky; top:0; background:var(--paper); }
.table-wrap tbody tr:nth-child(even) { background:#faf9f7; }
18. Before/after & two-column comparison. Wrong-way beside right-way, or old beside new, in matched columns with equal visual weight. Comparison is instant in two columns and slow in prose - the eye sees the difference before reading why (Teletubbies).
.compare { display:grid; grid-template-columns:1fr 1fr; gap:2rem; }
@media (max-width:700px){ .compare{ grid-template-columns:1fr; } }
D. Three meta-rules that make all the above cheap to change
19. Palette as variables, up front. One :root block, not hex scattered through the file. Then "one accent colour" or "double the whitespace" is a two-line edit, not a search-and-replace. (Frank's house rule is light theme only - so no prefers-color-scheme; but the variable discipline the dark-mode sites use is worth keeping.)
:root { --ink:#1a1a1a; --ink-soft:#666; --paper:#fdfdfb; --line:#e4e4e0; --accent:#b3261e; }
20. Grayscale-first review. Every pattern above must carry its hierarchy in black/white/grey; colour is added only where it means something (a warning, a link, a status). Review by dropping filter:grayscale(1) on <body> - anything that loses hierarchy was leaning on colour to do structural work that size, weight and space should have done. This is the Kennedy method, made a checkable step.
21. No stray value in an inline style - snap it to the nearest token, and scope the class. token-audit.py reads inline style= attributes as well as the <style> block, so a hand-tuned .82rem or .7rem is a violation wherever it sits. Two things to know when clearing them (learned on documentation-compendium.html, 03-08-2026):
- Snap, never invent. A stray
.7rem becomes var(--space-3), not a new --space-0-7 token. The scale is 5 steps by duplication; a value that "needs" to sit between two of them is a value nobody will miss. Expect a 3-5px shift and check it - line counts and wrap points must not move.
- The specificity trap. The kit sets
.prose p and .prose ul (0,1,1). A new page-level class alone (.demo-lead, 0,1,0) loses to it and the margin silently stays at the prose rhythm. Scope it: .prose .demo-lead { margin: 0 0 var(--space-3); }. Same reason .layout main.prose is written that way.
- Room kept for a badge is a sum, not a step. A padding that exists to keep text clear of an absolutely placed element (the
.ext badge on all.html) is a derived quantity: badge width, plus its offset, plus clearance. Rounding it to the scale (3.4rem to 4rem, 04-09-2026) moved the wrap point of 8 tiles. Write it as calc() of tokens, which the audit accepts, then measure the line counts again: the text column must not get narrower, and a column even one pixel wider can still un-wrap a line that sat on the edge. Snapping .65rem to --scale-0 grows a badge by a quarter, so re-check its room in the same pass.
Verify by measuring, not by eye: read getBoundingClientRect() and the computed style of every touched element before and after, and confirm the line count of each paragraph is identical.
Anti-patterns - what makes a document tiring
- Full-width body text - no measure cap; the eye loses its place on every wrap.
- Colour-only signalling - a code block or status told apart purely by hue; invisible without colour vision or on a poor screen.
- Font tourism - 3+ display fonts on one page (the "tired of Geist" complaint is over-designed type, not the font).
- Loud saturated callouts - filled high-chroma boxes for routine notes; everything screams, so nothing lands.
- No local orientation on a long page - no rail, no sticky header, no anchors; a scroll of unknown length.
- Reading prose and dense reference at the same size/weight - a paragraph and a data table fighting for one register.
- Word-wrapped code - long lines wrap and break the code's own structure.
- Confluence-style wiki chrome / marketing-page hero type - nav, tags, "last edited by", oversized display headings competing with the content for the first five seconds. A document's job is to be read calmly, not to sell.
The semantic-naming lesson (free, no framework)
Semantic UI's real contribution isn't its CSS - it's the naming discipline: classes read like English (raised segment, info message, three column grid), so markup is self-documenting. Adopt the habit for hand-written pages: name a div class="callout warning" or class="step active", never class="box-3 c-blue". Anyone opening the file later - Frank in a year, or a partner's developer - reads intent straight off the tag. Costs nothing, needs no framework.
Delta vs our rules
CONFIRMS
- Reading measure + two body regimes + double rhythm = the typography rules already in the system doc, now with named px/ch numbers for documents specifically.
- Callout / statistic / steps = colour and size as scarce, meaningful signal - the Kennedy de-emphasis rule made into concrete blocks.
- Grayscale-first review = our existing grayscale check, now with the filter:grayscale(1) mechanic.
- Segment / comparison / big-number all pass the Teletubbies 5-second test by construction - the eye sees structure before reading.
ADDS (new to the folder - Kennedy didn't cover documents)
- The orientation kit - on-this-page rail, hover anchors, sticky header. Kennedy has nothing on long-read navigation; teaching pages need it.
- The block vocabulary as native CSS - segment, callout, steps, statistic, description list, divider, label, labelled code block. A ready shelf to lift from, on house tokens.
- The "borrow the vocabulary, refuse the dependency" rule - the honest answer to "should we adopt Semantic UI / Bootstrap / Tailwind": no - steal the idea, hand-write the CSS, keep the page one file.
- The semantic-naming habit - self-documenting class names, portable to every page.