test.noahglynn.com

Typesetting

Setting body text so the rag works and the page reads.

Left-aligned text has a ragged right edge. Whether that edge helps or hurts reading comes down to a few decisions: how wide the line runs, where the last line falls, and which words are allowed to break. These are the decisions we apply by default, each one rendered live by your browser.

Every example below is real. Your browser is doing the wrapping; the CSS that controls it sits beside each one. Nothing here runs a model or a query. The quality checker described near the end runs offline, not on this page.

The whole point, in one comparison

The same paragraph, set two ways.

Left: the browser default, with the line allowed to run the full width of its container and no help on the last line. Right: the recipe applied. Same words, same font.

Browser default

A line that runs too long makes the eye lose its place on the return sweep, while a line too short chops the phrasing into fragments. The measure is the first decision, and most of the rag follows from it. Set the width first, then deal with the last line and the words that should not break.

Recipe applied

A line that runs too long makes the eye lose its place on the return sweep, while a line too short chops the phrasing into fragments. The measure is the first decision, and most of the rag follows from it. Set the width first, then deal with the last line and the words that should not break.

What changed: the measure is capped near 62 characters so the line length sits in the comfortable reading range, and the prose uses a paragraph-aware wrap that smooths the last line instead of leaving whatever the greedy line-by-line algorithm produced.

The techniques

One decision at a time.

Five techniques, in the order we apply them. Constrain the measure first, because most of the rag follows from it. Then improve the last line, balance the display type, and protect the two places a line should never break.

01Constrain the measure

Line length is the first decision and the one that matters most. Bringhurst puts the comfortable range at roughly 45 to 75 characters, with about 66 as the ideal for a single column; Tschichold frames it as eight to twelve words. A fixed pixel width drifts out of that range as the font size changes, so we cap the measure in character units and keep a pixel value only as the outer bound.

Too wide, about 90 characters

Past about seventy-five characters the return sweep gets unreliable: the eye leaves the end of one line and has to hunt for the start of the next, and on a long measure it sometimes lands on the wrong one. The line below is set near ninety characters so the strain is visible.

Held to 62 characters

Past about seventy-five characters the return sweep gets unreliable: the eye leaves the end of one line and has to hunt for the start of the next, and on a long measure it sometimes lands on the wrong one. The line here is held to sixty-two characters.

/* the hairline marks 62ch */
p { max-width: 62ch; }   /* keep 640px as the outer bound */

02Improve the last line and the rag

Browsers wrap text greedily, one line at a time, taking as many words as fit and pushing the rest down. That often strands the last line. The pretty value asks the engine to look at the paragraph as a whole and trade a little space on earlier lines to fix the end. It is the default we put on running prose.

text-wrap: pretty, applied to this very paragraph

Every paragraph of body copy on this page already carries this setting, which is why the right edge stays even and the final line rarely drops to a single stranded word. Resize the window and the wrap recomputes.

p { text-wrap: pretty; }

03Balance the display lines

Headings and other short blocks read better when their lines are close to equal length. The balance value evens them out, so a two-line heading does not leave a short stub on the second line. We put it on headings and short callouts, not on long body copy, where it is the wrong tool.

Without balance: a long line, then a short one

A heading that has to wrap onto a second line of text

text-wrap: balance

A heading that has to wrap onto a second line of text

h1, h2, h3 { text-wrap: balance; }

04Bind the last two words

A single word alone on the final line is an orphan, the oldest fault in the rag. Where pretty does not catch it, a non-breaking space between the last two words ties them together so they wrap as a unit. Unlike the wrap heuristics, the bind is honored identically across rendering engines, which is why we keep it as the backstop.

Without the bind, at a fixed narrow width

A non-breaking space ties the last two words so the line never ends on one stranded word.

With a non-breaking space before the final word

A non-breaking space ties the last two words so the line never ends on one stranded word.

the line never ends on one stranded word.

05Hold compounds together

An ordinary hyphen is a valid break point, so a hyphenated compound can split across two lines and read as two fragments. A non-breaking hyphen, the character at code point U+2011, keeps the compound whole. We use it on names, model numbers, and compounds that should never come apart.

Ordinary hyphen, free to break

The part number on the tag reads PX-27-A and must stay intact.

Non-breaking hyphen, U+2011

The part number on the tag reads PX‑27‑A and must stay intact.

PX‑27‑A   /* U+2011, the non-breaking hyphen */

See it move

Pull the measure and watch the rag.

This paragraph is real text. The slider changes nothing but its width; your browser recomputes the wrap each time. The reading below reports the width in characters and inspects the final line, the same way the offline checker does.

62 characters wide

Drag the measure narrow enough and the final word drops to a line of its own; widen it and the line lengthens until the return sweep starts to strain. Somewhere in the comfortable middle the rag settles and the paragraph reads without effort.

Move the slider to inspect the last line.

The quality check

What the checker looks for.

Once the recipe is applied, a tool called textwrap‑check inspects the result. It runs offline, across two rendering engines, at several narrow widths. It is not running on this page. On each block of prose it measures these things.

Orphanhard fault
A last line of a single word. The fault the bind in technique four exists to prevent.
Runtmoderate
A last line that is very short by character count or by width, even if it holds more than one word.
Long last lineminor
On a block of three or more lines, a last line that runs nearly the full measure, leaving no visual close to the paragraph.
Rag shapemoderate
The silhouette of the line ends: a wedge that drifts in one direction, a moon that bulges, a stairstep of near-equal lines, or a soft and varied edge, which is the one to want.
Rag spreadminor
How far the line ends vary from the measure, reported as a percentage so a hard, blocky rag is told apart from a soft one.
Hanging function wordhard fault
A heading line that ends on a stranded article, preposition, or conjunction. The balance value cannot see phrasing, so this is checked on its own.
OpenType presenceminor
Whether ligatures and kerning are on, plus a probe for figure style. The system font carries no old-style figures, so that part is informational.
Vertical rhythmreport only
The dominant line stride and how many block gaps sit off it. Reported, never graded: the web does not need a strict baseline grid.

The signals roll into one grade of pass, minor, or review. Orphans and hanging function words are hard faults that push to review. Runts and a bad rag shape are moderate and land at minor. The long last line and OpenType flags are minor on their own.

Source boundary

Shown and withheld.

This page is built on a private tooling system. It shows the method and the rendered result, and keeps the internals out.

Shown

  • The CSS recipe, in full and copyable.
  • The names of the techniques and the rules behind them.
  • The dimensions the checker measures.
  • The source authors the rules come from.

Withheld

  • The checker's source code and its local file paths.
  • The private project pages it has graded.
  • The surrounding harness and how it is wired.
  • Any credentials or tokens.

Appendix

The recipe, the sources, and the edge cases.

The buildable block

/* body prose */
p {
  max-width: 62ch;     /* measure near 66 ideal, 640px outer bound */
  line-height: 1.55;
  text-wrap: pretty;   /* smooth the last line and the rag */
}

/* display type */
h1, h2, h3 { text-wrap: balance; }

/* the two backstops, in the markup */
/* bind the last two words:  one stranded word. */
/* hold a compound whole:    PX‑27‑A   (U+2011) */

Where the rules come from

Bringhurst
The measure of 45 to 75 characters, about 66 as the single-column ideal, and the discipline of the rag.
Tschichold
Line length as eight to twelve words, and the orphan as a fault to remove.
Hochuli
The soft rag against the hard one: the shape of the edge, not only its faults.
Lupton
The working vocabulary of orphans, runts, and rag.
Rutter
Translating the print rules into CSS for the screen, where the engine wraps greedily.

Edge cases worth knowing

The bind can read as a false orphanA block bound with a non-breaking space under the pretty value can flag as an orphan in the system WebKit runner, which strands the word as if the space were ignored. A recent real iPhone renders the same block correctly, so confirm on a real device before chasing it.

A real orphan needs a width changeWhen the stranded word is physically wider than the column, no bind will help. That orphan is real on every engine and calls for a wider measure or a different break, not a non-breaking space.

Old-style figures are a no-op hereThe system font has no detectable old-style figures, so the figure-style setting does nothing until a web font with them is in use.