Better Typography for Docs That Engineers Actually Read

Was this helpful?

Your doc can be technically correct and still functionally useless. I’ve watched on-call engineers scroll past the answer three times because the page looked like a ransom note: 140-character lines, headings that didn’t mean anything, and code blocks that wrapped mid-flag.

This isn’t “make it pretty.” This is operational hygiene. Typography is the part of your doc that decides whether the reader arrives at the right command in 20 seconds or invents their own workaround in production.

Why typography is an SRE problem

Docs are part of the control plane. Not metaphorically—literally. Your incident response, operational runbooks, migration plans, and “this service is weird” tribal knowledge live there. If the typography is hostile, the system becomes hostile. Engineers will still ship changes, still rotate keys, still rebuild arrays. They’ll just do it from memory. That’s how you get quietly different procedures, divergent assumptions, and the kind of outages that don’t reproduce.

Typography affects three operational metrics you should care about:

  • Time-to-answer under stress. A doc that’s skimmable wins. A doc that demands reading loses.
  • Error rate during copy/paste. Wrapped code, ambiguous hyphenation, and lookalike glyphs are reliable sources of wrong commands.
  • Change safety. If headings aren’t stable anchors and line length causes reflow chaos, reviewers miss diffs and the doc drifts away from reality.

Typography won’t fix a broken storage topology. But it will stop the runbook from lying with formatting.

One quote that still holds up in ops work: “Hope is not a strategy.”Vince Lombardi. It’s not a software guy quote, which makes it even more useful. Your doc layout should not rely on hope that the reader “will notice.”

Interesting facts (because this stuff has a history)

  • Early line-length norms came from print constraints. Newspapers and books converged on moderate measures because long lines increase return-sweep errors (losing your place).
  • Typewriter conventions shaped monospace expectations. Fixed-width fonts trained generations to read aligned columns—still relevant for logs, tables, and CLI output.
  • Hyphenation used to be manual labor. Compositors inserted discretionary hyphens by hand; modern algorithms do it automatically, sometimes badly, especially for technical terms.
  • ASCII is why we still fight lookalikes. The “1/l/I” problem is ancient; the stakes changed when those characters started appearing in secrets, tokens, and hostnames.
  • Markdown inherited email-era habits. Many Markdown conventions were optimized for plain-text readability, not polished rendering—hence weird interactions with wrapping and code fences.
  • CSS hyphenation support is uneven. The hyphens property depends on language dictionaries and browser behavior; it’s not deterministic across clients.
  • Anchors became operational tooling. Once docs were navigated via URL fragments, heading text started acting like an API—changing it breaks runbooks and tickets.
  • Code font selection affects glyph clarity. Some fonts make {}, [], and () too similar at small sizes—great for aesthetics, terrible for production.

Joke #1: Typography is like monitoring: if you only notice it when it’s loud, you’re already in trouble.

Line length: the silent time sink

If your doc body text spans the full width of a 34-inch monitor, you’ve built a scanning endurance test. Long lines are not “more information.” They’re more eye travel. Eye travel costs time. Under pressure, time becomes mistakes.

What to do

  • Target 60–80 characters per line for body text. If you need a single number: 72 is a good default.
  • Use a max-width measured in characters (ch) instead of pixels. Pixels lie across fonts and devices; characters are closer to reading reality.
  • Let code be wider than prose but don’t let it wrap by default. Horizontal scrolling in code blocks is less evil than wrapped commands that change meaning.

Failure modes you’ll actually see

  • Return-sweep errors: the reader loses their place moving from the end of one long line to the start of the next, especially in dense paragraphs.
  • Skimming collapse: headings become less effective because the page becomes a wall; the reader’s strategy becomes “Ctrl+F and pray.”
  • Diff noise: if your editor wraps at one width and the site wraps at another, minor edits rewrite entire paragraphs, and reviewers stop trusting diffs.

CSS that behaves like an adult

This is the kind of CSS you put in a docs site and never touch again unless you enjoy postmortems.

cr0x@server:~$ cat docs-typography.css
:root{
  --measure: 72ch;
  --body-font: system-ui, -apple-system, "Segoe UI", Roboto, Arial, sans-serif;
  --mono-font: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
  --base-size: 16px;
  --line-height: 1.55;
}
main, article{
  max-width: var(--measure);
  margin: 0 auto;
  padding: 0 1rem;
  font-family: var(--body-font);
  font-size: var(--base-size);
  line-height: var(--line-height);
}
pre, code, kbd, samp{
  font-family: var(--mono-font);
}
pre{
  overflow-x: auto;
  white-space: pre;
  tab-size: 2;
}
code{
  font-size: 0.95em;
}

That output is a file, not a promise. The decision you make: measure prose by characters, constrain it centrally, and allow code blocks to scroll horizontally rather than wrap.

Don’t get clever with fluid type without guardrails

Fluid typography can be great. It can also quietly turn a 72ch measure into 110ch on wide displays when combined with a fluid font size. If you go fluid, cap the measure.

Headings that earn their keep

Headings are not decoration. They’re a navigation system, an outline for skimming, and an anchor API for linking from tickets, alerts, and chat. When you rename headings casually, you break people’s muscle memory. When you format headings poorly, you make the page unscannable.

Rules that keep you out of trouble

  • One page, one H1. Make it the page’s promise. If the title is vague, the rest won’t save it.
  • H2 sections must be meaningful without context. “Configuration” is not a section. “Configure TLS for outbound webhooks” is.
  • Keep heading hierarchy shallow. H2 and H3 do most of the work. H4 is a smell. H5 is a cry for help.
  • Don’t style headings so aggressively that they look like ads. High contrast and reasonable weight wins; giant gradient text loses.
  • Give headings stable IDs. If your platform auto-generates IDs from text, changing a single word breaks links. Prefer explicit IDs or an alias mechanism.

Typography choices that change behavior

Engineers skim by shape. If every heading looks the same weight as body text, the page becomes flat. If every heading is huge, the page becomes noisy. You want a clear typographic ladder: H2 is noticeably different, H3 is a sub-step, and body text stays calm.

Spacing matters more than font. The best heading font in the world can’t compensate for cramped vertical rhythm. Give headings breathing room above, tighter space below, and keep lists close to the heading they belong to.

Anchors as operational interfaces

If your incident runbook says “Go to ‘Fast rollback procedure’,” that phrase must exist, and it must be linkable. Treat heading anchors like API endpoints: stable, versioned if necessary, and not rewritten during “content refresh.”

Code fonts and code blocks: stop breaking commands

Docs fail hardest in code. People copy commands, paste them into a root shell, and hit Enter with the same confidence they use to approve a change request. Your job is to keep the text from mutating between screen and terminal.

Pick a monospace font for clarity, not vibes

Monospace fonts are infrastructure. Choose one with:

  • Clear glyph distinctions: O vs 0, l vs 1, {} vs (), and ; that doesn’t disappear.
  • Good hinting at small sizes so code doesn’t blur on low-quality displays.
  • Consistent width so tables and aligned output stay aligned.

Inline code vs code blocks

  • Inline code is for identifiers and short snippets: file paths, flags, JSON keys. Keep it short; don’t put full commands inline.
  • Code blocks are for anything you expect people to copy/paste, anything multi-line, and anything where wrapping changes meaning.

Never wrap commands by default

Wrapped code is a factory that produces broken commands with no visible error until it’s too late. It’s particularly nasty with:

  • long curl headers
  • kubectl JSONPath expressions
  • storage CLI output where columns convey semantics
  • anything with backslashes, quotes, or heredocs

Make copy/paste reliable

Use white-space: pre and horizontal scroll. If you want to help mobile users, provide a short version of the command, not soft-wrapping. Also, make sure your site doesn’t replace normal spaces with non-breaking spaces or typography “smartening” inside code blocks. That’s how you get invisible Unicode that breaks shells.

Joke #2: The only thing worse than a wrapped command is a wrapped command that still looks unwrapped.

Hyphenation: where good layout meets bad copy/paste

Hyphenation exists to make justified text look less gappy. Most technical docs should not use fully justified text. If you do, you’re inviting hyphenation problems, rivers of whitespace, and a page that looks like a misconfigured word processor.

When hyphenation helps

For narrow columns (mobile, sidebars), hyphenation can reduce awkward spacing and keep ragged-right text from looking like it fell down the stairs. But technical docs aren’t novels. They contain identifiers that should not hyphenate: function names, hostnames, flags, and compound terms that need to remain searchable.

Rules of thumb

  • Disable hyphenation in code and UI strings. Anything a reader might copy, type, or search should not be hyphenated.
  • Enable hyphenation only for prose and only if you’ve tested across browsers and your content language is set correctly.
  • Avoid justified body text. Ragged-right is fine; your readers are engineers, not typesetters.

CSS approach that won’t sabotage tokens

cr0x@server:~$ cat hyphenation.css
article{
  hyphens: auto;
}
code, pre, kbd, samp, a, .no-hyphen{
  hyphens: none;
}
h1, h2, h3{
  hyphens: manual;
}

Decision: allow the browser to hyphenate paragraphs (if you really want it), but explicitly turn it off for code, links, and anything token-like. Also: set the document language correctly. Hyphenation dictionaries depend on it.

Soft hyphens are a trap in engineering docs

A soft hyphen can sneak into copied text and ruin matching—especially in search boxes, YAML values, and shell strings. Most modern browsers don’t include soft hyphens when copying, but “most” is not a contract. If you need line breaking, use CSS. Keep the source text clean.

Fast diagnosis playbook

This is the “it feels unreadable” playbook. Use it when someone complains that docs are hard to read, hard to skim, or “the command I copied didn’t work,” and you need to find the bottleneck fast.

First: check measure and wrapping (the biggest wins)

  1. Is prose constrained to ~60–80 characters? If not, fix max-width (prefer ch).
  2. Do code blocks wrap? If yes, stop. Use horizontal scrolling and white-space: pre.
  3. Are headings visually distinct? If headings don’t pop, scanning fails. Adjust weight/size/spacing.

Second: check copy/paste integrity

  1. Are “smart quotes” or typographic substitutions applied to code? They will break shells and JSON.
  2. Are there invisible characters? NBSP, zero-width spaces, soft hyphens.
  3. Do fonts make ambiguous glyphs? If O and 0 look identical, secrets and IDs become roulette.

Third: check navigation and anchors

  1. Does the TOC reflect real structure? If it’s auto-generated but the hierarchy is chaotic, it won’t help.
  2. Are heading anchors stable? If people link to sections from tickets, changing IDs breaks operational flow.
  3. Is there an on-page search that works? If search exists but typography makes headings non-descriptive, search results are useless.

Twelve+ real tasks with commands, output, and decisions

These are practical checks you can run on a docs repo or a rendered site. Each task includes: a command, realistic output, what the output means, and the decision you make. This is how you turn “the docs feel bad” into an actionable ticket.

Task 1: Find suspiciously long lines in Markdown (prose)

cr0x@server:~$ python3 - <<'PY'
import os, re
root="docs"
limit=120
hits=0
for dp, _, files in os.walk(root):
  for f in files:
    if not f.endswith(".md"): 
      continue
    p=os.path.join(dp,f)
    with open(p,"r",encoding="utf-8") as fh:
      for i,line in enumerate(fh,1):
        if len(line.rstrip("\n"))>limit and not line.lstrip().startswith(("```","    ")):
          hits+=1
          print(f"{p}:{i}:{len(line.rstrip())}")
print("hits",hits)
PY
docs/runbooks/restore.md:44:168
docs/guide/architecture.md:112:203
hits 2

Meaning: two prose lines exceed 120 characters (not in code blocks). That often indicates either unwrapped paragraphs or pasted content that will render as wide lines.

Decision: reflow those paragraphs in source (or ensure the renderer wraps). If your renderer treats Markdown source line breaks as hard breaks, you must reflow.

Task 2: Detect wrapped code blocks caused by CSS

cr0x@server:~$ rg -n "white-space:\s*pre-wrap|white-space:\s*normal" site/assets/*.css
site/assets/main.css:418:white-space: pre-wrap;

Meaning: code blocks might be using pre-wrap which wraps long commands.

Decision: change to white-space: pre for pre elements, and allow horizontal scrolling.

Task 3: Verify computed CSS for a code block (rendered site)

cr0x@server:~$ node - <<'NODE'
const fs=require('fs');
const html=fs.readFileSync('public/index.html','utf8');
console.log(html.includes('white-space: pre-wrap') ? 'pre-wrap found' : 'no pre-wrap in HTML');
NODE
no pre-wrap in HTML

Meaning: the problematic pre-wrap likely comes from an external stylesheet, not inline HTML.

Decision: fix the CSS at the source (design system/theme), not in individual pages.

Task 4: Find smart quotes sneaking into code fences

cr0x@server:~$ python3 - <<'PY'
import re, pathlib
root=pathlib.Path("docs")
bad = ["\u2018","\u2019","\u201c","\u201d"]
for p in root.rglob("*.md"):
  t=p.read_text(encoding="utf-8",errors="ignore")
  in_code=False
  for i,line in enumerate(t.splitlines(),1):
    if line.strip().startswith("```"):
      in_code = not in_code
    if in_code and any(ch in line for ch in bad):
      print(f"{p}:{i}: smart quote in code fence")
PY
docs/runbooks/backup.md:87: smart quote in code fence

Meaning: curly quotes appear inside a code block, which can break shell commands and JSON.

Decision: replace with ASCII quotes and stop the editor/formatter from “smartening” code fences.

Task 5: Catch non-breaking spaces in code blocks (copy/paste killers)

cr0x@server:~$ python3 - <<'PY'
import pathlib
nbsp="\u00a0"
root=pathlib.Path("docs")
for p in root.rglob("*.md"):
  t=p.read_text(encoding="utf-8",errors="ignore")
  if nbsp in t:
    print(p, "contains NBSP")
PY
docs/guide/kubernetes.md contains NBSP

Meaning: NBSP exists somewhere in the Markdown. In code, it can break flags and alignment in invisible ways.

Decision: replace NBSP with regular spaces; add a lint check to prevent recurrence.

Task 6: Audit heading hierarchy (too deep, too weird)

cr0x@server:~$ python3 - <<'PY'
import pathlib, re
root=pathlib.Path("docs")
for p in root.rglob("*.md"):
  levels=[]
  for line in p.read_text(encoding="utf-8",errors="ignore").splitlines():
    m=re.match(r'^(#{1,6})\s+', line)
    if m:
      levels.append(len(m.group(1)))
  if any(l>3 for l in levels):
    print(p, "has heading level > 3:", sorted(set(levels)))
PY
docs/guide/storage.md has heading level > 3: [1, 2, 3, 4, 5]

Meaning: that file uses H4/H5; likely it’s nesting detail to compensate for poor structure.

Decision: restructure into fewer, more meaningful H2/H3 sections. Depth is rarely clarity.

Task 7: Detect unstable auto-generated anchor IDs (risk to runbooks)

cr0x@server:~$ rg -n "##\s+.*\s+\(deprecated\)|##\s+.*\s+\(old\)" docs/runbooks
docs/runbooks/restore.md:12:## Restore Procedure (old)

Meaning: headings are being renamed with qualifiers like “(old)”, which likely changes slugified IDs and breaks inbound links.

Decision: keep the heading stable and mark status elsewhere (badge, note block), or add explicit anchor IDs if your system supports it.

Task 8: Confirm code block overflow is enabled (prevents wrapping)

cr0x@server:~$ rg -n "pre\s*\{|pre\." site/assets/main.css | head
212:pre{
218:  overflow-x: auto;
219:  white-space: pre;

Meaning: CSS already uses horizontal scrolling and preserves whitespace.

Decision: good—leave it alone. If users still report wrapping, look for more specific CSS overriding it.

Task 9: Measure average line length in rendered HTML (spot “full width” layouts)

cr0x@server:~$ python3 - <<'PY'
from bs4 import BeautifulSoup
import pathlib, statistics, re
p=pathlib.Path("public/index.html")
soup=BeautifulSoup(p.read_text(encoding="utf-8",errors="ignore"),"html.parser")
text=" ".join(soup.get_text(" ").split())
chunks=re.findall(r'.{1,200}', text)
lens=[len(c) for c in chunks if len(c)>50]
print("sample chunk lengths:", lens[:8])
print("median:", int(statistics.median(lens)))
PY
sample chunk lengths: [200, 200, 200, 200, 200, 200, 200, 200]
median: 200

Meaning: the extracted text is chunked at 200 characters, so this sample is not a perfect measure—but it indicates the page has long uninterrupted text without natural breaks.

Decision: use this as a signal, not proof. Next step is visual inspection and checking CSS measure (max-width) on the container.

Task 10: Find hyphenation settings in CSS (and where they apply)

cr0x@server:~$ rg -n "\bhyphens\s*:" site/assets/*.css
site/assets/main.css:88:  hyphens: auto;
site/assets/main.css:97:  hyphens: auto;

Meaning: hyphenation is enabled somewhere, possibly broadly.

Decision: verify it’s scoped to prose containers only; disable for code, links, and token-like spans.

Task 11: Detect soft hyphens in source content

cr0x@server:~$ python3 - <<'PY'
import pathlib
shy="\u00ad"
root=pathlib.Path("docs")
count=0
for p in root.rglob("*.md"):
  t=p.read_text(encoding="utf-8",errors="ignore")
  if shy in t:
    n=t.count(shy)
    count+=n
    print(p, "soft hyphens:", n)
print("total",count)
PY
total 0

Meaning: no soft hyphens in Markdown. Good—your content isn’t already poisoned.

Decision: keep it that way by avoiding copy/paste from sources that inject soft hyphens (some PDFs, some CMS editors).

Task 12: Verify that the document language is set (hyphenation and screen readers)

cr0x@server:~$ python3 - <<'PY'
import pathlib, re
html=pathlib.Path("public/index.html").read_text(encoding="utf-8",errors="ignore")
m=re.search(r'<html[^>]*\blang="([^"]+)"', html)
print("lang=", m.group(1) if m else "missing")
PY
lang= en

Meaning: language is set. That affects hyphenation dictionaries and accessibility tooling.

Decision: ensure every page sets lang, not just the homepage template.

Task 13: Find code blocks without language tags (hurts readability and copy safety)

cr0x@server:~$ python3 - <<'PY'
import pathlib, re
root=pathlib.Path("docs")
for p in root.rglob("*.md"):
  lines=p.read_text(encoding="utf-8",errors="ignore").splitlines()
  for i,l in enumerate(lines,1):
    if l.strip()=="```":
      print(f"{p}:{i}: code fence without language")
      break
PY
docs/guide/ssl.md:55: code fence without language

Meaning: unlabeled fences reduce syntax highlighting quality and sometimes affect copy widgets or styling rules.

Decision: label fences (bash, json, yaml). Consistency helps readers and tooling.

Task 14: Check for tabs in code blocks (alignment breaks across environments)

cr0x@server:~$ python3 - <<'PY'
import pathlib
root=pathlib.Path("docs")
in_code=False
for p in root.rglob("*.md"):
  in_code=False
  for i,line in enumerate(p.read_text(encoding="utf-8",errors="ignore").splitlines(),1):
    if line.strip().startswith("```"):
      in_code=not in_code
    if in_code and "\t" in line:
      print(f"{p}:{i}: tab in code fence")
      break
PY
docs/runbooks/network.md:102: tab in code fence

Meaning: tabs inside code fences can render inconsistently; alignment-dependent output or YAML is especially fragile.

Decision: convert tabs to spaces; set tab-size in CSS if you must keep them (but don’t).

Corporate mini-stories (because this is how it fails)

1) Incident caused by a wrong assumption: “The doc site wraps code safely”

The company had a clean-looking internal docs portal. Nice theme, good search, the whole “developer experience” pitch. Runbooks were Markdown, rendered through a static site generator with a corporate CSS layer on top.

During a routine certificate rotation, a junior on-call followed the runbook and copied a long openssl command with multiple flags and file paths. The code block looked fine visually, because the theme soft-wrapped long lines and inserted a subtle indent on wrap. In the terminal, the pasted command included a newline mid-flag. The command failed, and the engineer—under mild stress—made the classic move: edited it until it ran.

It ran. It also generated a key with different parameters than intended and dropped it into a different directory than the service expected. The subsequent deploy picked up the wrong file, and a subset of instances started failing TLS handshakes. The incident didn’t take the whole service down. It just made a portion of traffic error out intermittently, which is worse because it looks like a network gremlin.

The postmortem wasn’t about the engineer. The wrong assumption was structural: “If the command is in a code block, it’s safe to copy.” It wasn’t. The fix was boring: disable wrapping in code blocks, add a copy button that copies the raw text, and lint runbooks for overly long single-line commands. They also added an explicit “Expected output” section in the runbook so the engineer could detect divergence earlier.

2) Optimization that backfired: “Let’s enable full justification and hyphenation for a ‘book-like’ feel”

A docs redesign initiative decided the internal platform looked “too wiki-ish.” Someone wanted it to feel like a polished knowledge base. They enabled justified text to create clean left and right edges, then turned on automatic hyphenation to reduce big gaps between words.

On desktop, the homepage looked… fine. On mobile and narrow split-screen layouts (a common mode during incidents), the prose hyphenated aggressively. That would have been merely annoying, except the hyphenation rules sometimes triggered on technical compound terms in headings and in-line tokens embedded in prose. Readers started searching for terms they had seen on the page and couldn’t find them because they remembered the hyphenated fragments.

The bigger issue hit runbooks: incident responders skim headings to navigate. Hyphenated headings broke that scanning pattern. People stopped using the TOC because the line breaks made section titles look like different words. And because the team used auto-generated heading anchors, small edits to heading text changed IDs, so links from older tickets broke. “The doc is lying” became a common complaint.

The rollback was straightforward but politically painful: ragged-right prose, hyphenation off by default, hyphenation allowed only in long paragraphs on narrow screens, and never in headings or anything marked as code. The redesign team learned a practical truth: typography that optimizes for aesthetics can reduce operational accuracy. The goal isn’t “book-like.” The goal is “correct under stress.”

3) Boring but correct practice that saved the day: stable anchors and conservative typography

At another place, the docs team had a habit that looked overly cautious: they treated headings and anchors as versioned interfaces. If a heading needed renaming for clarity, they would keep the old anchor as an alias, or leave the old heading as a hidden anchor target. Engineers joked that docs were getting “API governance.” They weren’t wrong.

Then an incident hit during a major platform migration. Half the company was in a war room. Multiple teams were linking runbook sections into chat threads: “jump to the ‘Emergency failover’ section,” “use the ‘Disable writes’ checklist,” “verify the ‘Storage pool health’ steps.” These links were months old, pulled from old tickets and muscle memory.

They all worked. No 404s, no “section not found,” no wasted five minutes per person hunting for the renamed heading. The site’s typography was also conservative: readable measure, strong heading contrast, code blocks that didn’t wrap, and a predictable layout where “Dangerous command” blocks looked dangerous.

Nothing about that design won awards. It did something more valuable: it reduced coordination overhead during an outage. People could trust the doc shape. When the doc shape is stable, teams spend their limited stress budget on the system, not the page.

Common mistakes: symptoms → root cause → fix

This is the stuff that shows up as “docs are bad” in feedback forms. The symptoms are real. The fixes are usually not hard. They just require someone to treat docs like production.

1) Symptom: “I copied the command and it didn’t work”

Root cause: code blocks wrap, or smart typography changed quotes/dashes, or invisible characters exist (NBSP, zero-width space).

Fix: enforce white-space: pre and overflow-x: auto; disable typographic substitutions in code; add lint checks for NBSP and smart quotes in code fences.

2) Symptom: “This page is exhausting to read”

Root cause: line length too long; insufficient line height; low contrast; dense paragraphs without structure.

Fix: constrain measure to ~72ch; set line-height ~1.5; increase paragraph spacing slightly; break long sections with meaningful H2/H3 headings.

3) Symptom: “The TOC is useless”

Root cause: headings are generic (“Overview”, “Details”), or hierarchy is too deep/erratic.

Fix: rename headings to task-oriented statements; keep most content in H2/H3; collapse or split pages that require H4/H5.

4) Symptom: “Search results don’t help”

Root cause: headings don’t contain the words users search for; critical terms are buried in prose; hyphenation/soft breaks affect remembered terms.

Fix: write headings that match user intent; include key terms early; avoid hyphenating tokens; ensure language and indexing treat code and headings sanely.

5) Symptom: “Links in old tickets go to the wrong place”

Root cause: auto-generated anchor IDs changed when headings were edited; content reorganized without redirects/aliases.

Fix: use explicit, stable IDs; add anchor aliases; treat renames like API changes with migration support.

6) Symptom: “Tables and CLI output don’t line up”

Root cause: code font not truly monospace, or proportional fonts used inside pre; tabs render inconsistently.

Fix: set a reliable monospace stack for pre/code; convert tabs to spaces; set tab-size explicitly.

7) Symptom: “Headings feel like random bold sentences”

Root cause: insufficient typographic contrast; heading spacing too tight; heading weight too light; all-caps styling reduces shape recognition.

Fix: increase size/weight difference; add vertical rhythm; avoid all-caps for long headings; ensure headings are scannable.

8) Symptom: “Mobile layout is chaos”

Root cause: code blocks try to fit the screen by wrapping; long words overflow; hyphenation on headings.

Fix: allow code to scroll horizontally; add overflow-wrap: anywhere only for prose; keep hyphenation off for headings and code.

Checklists / step-by-step plan

Checklist: typography defaults for a docs platform

  • Prose container max-width: 65–75ch.
  • Body font size: 16–18px depending on font; line-height: 1.5–1.7.
  • Ragged-right text; avoid full justification.
  • Heading ladder: H2 clearly stronger than body; H3 distinct but not loud.
  • Code blocks: white-space: pre, overflow-x: auto, copy button copies raw text.
  • Code font: a clear monospace stack; avoid fancy ligatures in operational docs.
  • Hyphenation: off globally unless you’ve tested; if enabled, scope to prose only.
  • Stable anchors: explicit IDs or alias strategy; treat heading changes like breaking changes.
  • Lint: catch NBSP, smart quotes in code, missing fence languages, tabs in YAML/code.

Step-by-step plan: fix an existing messy docs site in a week

  1. Day 1: measure and wrapping. Add max-width: 72ch to the main content container. Fix code blocks to stop wrapping.
  2. Day 2: headings and TOC. Normalize heading hierarchy. Split pages that require H4/H5 into separate pages or sections.
  3. Day 3: code integrity. Disable smart typography in code. Add lint checks for NBSP/smart quotes. Add a “copy raw” button if your platform supports it.
  4. Day 4: anchors stability. Stop relying on auto-generated IDs if you can. Add aliases for frequently linked runbook headings.
  5. Day 5: hyphenation decisions. Either disable hyphenation or scope it to prose. Confirm lang is set and consistent.
  6. Day 6: accessibility and contrast. Check color contrast for body text and headings. Make focus styles visible for keyboard navigation.
  7. Day 7: operational review. Pick three runbooks used in real incidents. Dry-run them: copy/paste commands, verify outputs, see if you can skim to the right section in 10 seconds.

How to keep it from regressing

Typography regressions are sneaky because nobody files a ticket that says “line-height dropped from 1.55 to 1.3.” They file “docs feel harder.” Prevent that with automation:

  • CSS review: changes to pre, code, heading sizes, and container width require sign-off.
  • Content lint in CI: reject NBSP and smart quotes inside code fences.
  • Visual regression tests on a handful of critical pages (runbooks).

FAQ

1) What’s the best line length for technical docs?

For prose, aim for 60–80 characters per line. If you need one default, use 72ch on the main content container. It’s a sweet spot for scanning and reduces fatigue.

2) Should code blocks wrap on mobile?

No. Wrapping changes meaning and breaks copy/paste. Let code blocks scroll horizontally. If a command is too long for mobile, provide a shorter alternative or a script file.

3) Is hyphenation good or bad?

Mostly bad in engineering docs. It can help narrow columns in prose, but it should be disabled for headings, code, links, and tokens. If you enable it, scope it tightly and test.

4) Why not justify text? It looks cleaner.

Justified text often produces uneven spacing (“rivers”) on the web, especially with narrow columns and mixed technical terms. Ragged-right is more readable and less fragile.

5) How do headings affect reliability?

Headings are navigational APIs. They power the TOC, search relevance, and inbound links from tickets. Bad headings slow incident response. Unstable anchors break operational workflows.

6) What monospace font should we use?

Pick a system monospace stack unless you have a strong reason not to. Optimize for glyph clarity and rendering at small sizes. Avoid fonts where 0/O or 1/l/I are ambiguous.

7) Should we use font ligatures in code?

In operational docs, I recommend no. Ligatures can make code look nice but can confuse readers and sometimes affect selection/copy behavior. Save them for editor themes, not runbooks.

8) How do we prevent broken anchors when headings change?

Use explicit IDs where possible. If your platform auto-generates anchors, introduce an alias mechanism (hidden anchors) for commonly linked sections. Treat heading renames as breaking changes.

9) Our docs are in Markdown. Should we hard-wrap lines at 80 characters?

Depends on tooling. Hard-wrapping can improve diffs and reviews, but it can also create weird line breaks if your renderer treats them as hard breaks. If you hard-wrap, ensure your Markdown processor uses normal paragraph wrapping semantics.

10) What’s the single highest-impact change?

Stop wrapping code blocks and constrain the prose measure. Those two changes eliminate a surprising amount of operational friction.

Conclusion: next steps that actually move the needle

Typography is a reliability feature. It determines whether the doc is usable when the reader is tired, interrupted, or trying not to make things worse. If you want docs that engineers actually read, you don’t need a rebrand. You need a few stubborn defaults and some automation.

Do this next

  1. Set your main content container to ~72ch max width and a sane line-height.
  2. Make code blocks non-wrapping with horizontal scroll. Add a copy button if you can.
  3. Disable hyphenation in headings, code, links, and token-like spans. Consider disabling it everywhere.
  4. Normalize heading hierarchy: meaningful H2s, useful H3s, minimal depth.
  5. Add CI lint checks for smart quotes, NBSP, and missing code fence languages.
  6. Pick three runbooks and dry-run them like it’s an incident. If it’s annoying in a calm room, it will be dangerous at 3 a.m.

Make the page boring. Make it consistent. Make it copy-safe. That’s how docs earn trust, and trust is the rarest resource in production systems.

← Previous
Debian 13 auditd without killing disks: practical settings for sane auditing
Next →
NVIDIA vs AMD vs Intel: what competition needs to stay sane

Leave a comment