SPEC.md
The same content as prose, generated from spec.json by tools/build-spec.mjs. This is the one to paste into a model's context when you want the whole specification at once.
1# typeset — typographic specification
2
3Version 1.0.0 · updated 2026-08-28 · Adilson Carvalho
4Canonical: https://typeset.adilsoncarvalho.com
5
6A normative typographic specification for printed documents — essays, letters, reports. This file is the source of truth. CSS, Typst, LaTeX or any other implementation conforms to it; where an implementation and this file disagree, this file is right.
7
8**Target.** Paper and PDF at 100% scale. Not screens, not reflowing layouts.
9
10## Conformance
11
12- Every length is absolute. Points (pt) and millimetres (mm) mean the same thing in every engine; 1pt = 1/72in.
13- A length given in em is relative to the font size of the element it is declared on, not to the document base.
14- line_height is a multiple of the element's own font size, and defines the baseline-to-baseline advance. An engine whose leading is measured between line boxes rather than between baselines must derive its value from the resulting advance, stated per element in baseline_advance.
15- An implementation MUST reproduce every value under `properties`. It MAY add anything the target engine needs to achieve them.
16- Where an engine cannot express a property, it must fail visibly or degrade as named in `fallback` — never silently.
17- Colour is sRGB. Ink is never pure black: #000 blooms on laser output and glares in a backlit PDF.
18- Deriving leading: where the engine measures the gap between line boxes rather than between baselines, pin the line box to the band from the baseline up to 1em (in Typst: top-edge: 1em, bottom-edge: 0pt). The advance is then 1em + leading, so leading = (line_height - 1) em, independent of the font's own metrics.
19
20## Foundation
21
22### page
23
24| Property | Value |
25| --- | --- |
26| size | `A4` |
27| size mm | `210`, `297` |
28| alternate size | `Letter` |
29| margin top mm | `25` |
30| margin bottom mm | `25` |
31| margin outer mm | `22` |
32| margin inner mm | `28` |
33| duplex | Margins mirror: the inner (binding) margin is 28mm on both left and right pages, so the gutter stays put when printed double-sided. |
34| text width mm | `160` |
35
36#### running head
37
38| Property | Value |
39| --- | --- |
40| position | `top centre` |
41| content | the current level-2 heading, uppercased |
42| font | `sans` |
43| size | `8pt` |
44| tracking | `0.08em` |
45| color | `ink_faint` |
46| suppressed on | `first page` |
47
48#### folio
49
50| Property | Value |
51| --- | --- |
52| position | `bottom centre` |
53| content | `page number` |
54| font | `serif` |
55| size | `9pt` |
56| numerals | `oldstyle` |
57| color | `ink_muted` |
58| suppressed on | `first page` |
59
60### fonts
61
62| Property | Value |
63| --- | --- |
64| embedding | Font files MUST be embedded in the output. A document that resolves its fonts at print time is not reproducible. |
65
66#### serif
67
68| Property | Value |
69| --- | --- |
70| role | body text — the reading face |
71| family | `EB Garamond` |
72| license | `OFL` |
73| fallbacks | `Iowan Old Style`, `Palatino`, `Georgia`, `serif` |
74| weights | `400`, `600` |
75| styles | `normal`, `italic` |
76| features | `oldstyle figures`, `small caps`, `standard ligatures`, `kerning` |
77| note | Set no smaller than 11pt. Below that the strokes thin out on office laser output. |
78
79#### sans
80
81| Property | Value |
82| --- | --- |
83| role | headings, tables, captions, labels |
84| family | `Source Sans 3` |
85| license | `OFL` |
86| fallbacks | `Helvetica Neue`, `Helvetica`, `Arial`, `sans-serif` |
87| weights | `300`, `600`, `700` |
88| styles | `normal`, `italic` |
89| note | Humanist, so it sits with an old-style serif instead of fighting it. |
90
91#### mono
92
93| Property | Value |
94| --- | --- |
95| role | code, listings, technical identifiers |
96| family | `IBM Plex Mono` |
97| license | `OFL` |
98| fallbacks | `SF Mono`, `Menlo`, `Consolas`, `monospace` |
99| weights | `400`, `600` |
100| styles | `normal`, `italic` |
101| features | `ligatures disabled` |
102| note | Monospaced faces run optically large beside a serif; every size below is already corrected for it. |
103
104### scale
105
106| Property | Value |
107| --- | --- |
108| ratio | `1.2` |
109| base | `11pt` |
110
111#### steps
112
113| Property | Value |
114| --- | --- |
115| xs | `8pt` |
116| sm | `9.5pt` |
117| base | `11pt` |
118| h4 | `12pt` |
119| h3 | `14pt` |
120| h2 | `18pt` |
121| h1 | `24pt` |
122
123### rhythm
124
125| Property | Value |
126| --- | --- |
127| line height | `1.45` |
128| line height tight | `1.2` |
129| baseline advance | `15.95pt` |
130| space | `11pt` |
131| space note | One unit of vertical space is 11pt — one line of base leading, near enough. Every gap below is a multiple of it. |
132| measure | `33em` |
133| measure mm | `126` |
134| measure chars | `66` |
135| measure note | The single largest legibility lever. Past about 75 characters the return sweep fails and readers re-read lines without noticing; below 45 the eye fixates too often. |
136
137#### measure variants
138
139| Property | Value |
140| --- | --- |
141| narrow | `27em` |
142| wide | `40em` |
143
144### color
145
146#### ink
147
148| Property | Value |
149| --- | --- |
150| hex | `#1a1a1a` |
151| role | `body text` |
152
153#### ink muted
154
155| Property | Value |
156| --- | --- |
157| hex | `#5a5a5a` |
158| role | captions, attributions, secondary text |
159
160#### ink faint
161
162| Property | Value |
163| --- | --- |
164| hex | `#8a8a8a` |
165| role | labels, list markers, running heads |
166
167#### rule
168
169| Property | Value |
170| --- | --- |
171| hex | `#c9c4bd` |
172| role | hairlines, table body rules |
173
174#### rule strong
175
176| Property | Value |
177| --- | --- |
178| hex | `#6f6a64` |
179| role | table header and footer rules, block accents |
180
181#### wash
182
183| Property | Value |
184| --- | --- |
185| hex | `#f4f1ec` |
186| role | code and callout backgrounds |
187
188#### accent
189
190| Property | Value |
191| --- | --- |
192| hex | `#7a1f1f` |
193| role | drop caps, note markers, fleurons |
194| note | Oxblood. Chosen to read as a dark grey when printed in greyscale. |
195
196## Templates
197
198A template sets the page and the scale. Everything under `foundation` and `sections` still applies; a template states only what it overrides, and what it forbids.
199
200Default: `single-column`
201
202### Single column
203
204`single-column`
205
206**Use for.** Essays, letters, reports, memoranda — anything read continuously from start to finish.
207
208**page.** as foundation.page
209
210**scale.** as foundation.scale
211
212**rhythm.** as foundation.rhythm
213
214**justification.** optional
215
216#### notes
217
218- The default. Nothing to opt into.
219
220### Two column, equal
221
222`two-column` · opt-in: `typeset--two-column`
223
224**Use for.** Papers, journal articles, newsletters, technical notes — documents that are scanned and referenced as much as read, and where a figure is worth more than an uninterrupted argument.
225
226#### derivation
227
228| Property | Value |
229| --- | --- |
230| text width mm | `160` |
231| column gap mm | `6` |
232| column width mm | `77` |
233| floor | `45` |
234| conclusion | A 77mm column carries 40 characters at the 11pt base — below the 45-character floor this spec sets for a line of prose. The base size therefore DROPS to 9.5pt, which restores 47. This is arithmetic, not preference: an implementation that keeps 11pt in two columns violates the measure rule, which is the rule everything else in this spec is downstream of. |
235| recompute when | The page size, the margins or the gutter change. Characters per line = 0.524 x column_mm x (11 / base_pt). |
236
237**characters per line**
238
239| Property | Value |
240| --- | --- |
241| 11pt | `40` |
242| 10.5pt | `42` |
243| 10pt | `44` |
244| 9.5pt | `47` |
245| 9pt | `49` |
246
247#### page
248
249| Property | Value |
250| --- | --- |
251| size | `A4` |
252| margin top mm | `25` |
253| margin bottom mm | `25` |
254| margin outer mm | `22` |
255| margin inner mm | `28` |
256| columns | `2` |
257| column widths | equal — the two columns are the same width, always |
258| column gap mm | `6` |
259| column width mm | `77` |
260| column rule | none by default; an optional 0.5pt hairline in `rule` where the columns need separating |
261| column balance | the final page balances its columns to equal height |
262| lines per column | `52` |
263| running head | as foundation.page — spans the full text width, not a column |
264| folio | `as foundation.page` |
265
266#### scale
267
268| Property | Value |
269| --- | --- |
270| ratio | `1.2` |
271| base | `9.5pt` |
272| note | Every step comes down. A 24pt heading inside a 77mm column takes three lines to say two words; 18pt takes two. The h1 keeps display size because it spans both columns. |
273
274**steps**
275
276| Property | Value |
277| --- | --- |
278| xs | `7pt` |
279| sm | `8.5pt` |
280| base | `9.5pt` |
281| h4 | `9.5pt` |
282| h3 | `11pt` |
283| h2 | `13pt` |
284| h1 | `20pt` |
285
286#### rhythm
287
288| Property | Value |
289| --- | --- |
290| line height | `1.4` |
291| baseline advance | `13.3pt` |
292| space | `9.5pt` |
293| measure | the column — 77mm, not a character count |
294| measure chars | `47` |
295| note | Leading tightens with the measure: a shorter line needs less vertical separation to keep the return sweep unambiguous. |
296
297#### requirements
298
299- Justification with hyphenation is MANDATORY, not optional. At 47 characters a ragged right edge produces a visibly serrated column and word gaps wide enough to read as rivers. This is the one place the spec removes a choice it otherwise offers.
300- The last line of a paragraph is still flush left. Two columns make a stretched last line more visible, not less.
301- Balance the columns on the final page. A last page with one full column and one empty third reads as a printing error.
302- A spanning element must span BOTH columns fully or neither. An element that spans one and a half columns has no correct reading order.
303- Keep a spanning element at the top or the bottom of the page, never mid-column. Interrupting both columns in the middle forces the reader to find their place twice.
304
305#### spanning
306
307| Property | Value |
308| --- | --- |
309| always | `title block`, `subtitle`, `byline`, `dateline`, `abstract`, `heading 1`, `colophon`, `bibliography heading` |
310| optional | `figure`, `table`, `code block`, `pull quote` |
311| never | `paragraph`, `list`, `blockquote`, `callout`, `heading 2 and below`, `endnotes` |
312| note | A wide figure or table opts in per instance. The default is column-width, because a spanning element costs a break in both columns. |
313
314#### forbidden
315
316| Property | Value |
317| --- | --- |
318| sidenote | There is no margin to put it in. Use a footnote or an endnote. |
319| dropcap | A three-line cap at 3.05em is 29pt in a 77mm column — a quarter of the column width for one letter. Open with small caps instead. |
320| measure variants | narrow and wide are meaningless: the column is the measure. |
321
322#### element overrides
323
324**h1**
325
326| Property | Value |
327| --- | --- |
328| size | `20pt` |
329| spans | `both columns` |
330| space after | `9.5pt` |
331
332**h2**
333
334| Property | Value |
335| --- | --- |
336| size | `13pt` |
337| space before | `19pt` |
338| space after | `4.75pt` |
339
340**h3**
341
342| Property | Value |
343| --- | --- |
344| size | `11pt` |
345| space before | `19pt` |
346| space after | `4.75pt` |
347
348**h4**
349
350| Property | Value |
351| --- | --- |
352| size | `9.5pt` |
353| space before | `19pt` |
354| space after | `4.75pt` |
355
356**paragraph**
357
358| Property | Value |
359| --- | --- |
360| size | `9.5pt` |
361| line height | `1.4` |
362| space after | `0` |
363| first line indent | `1.25em` |
364| note | Indented paragraphs are the default here, not an option: a blank line costs 3% of a column. |
365
366**blockquote**
367
368| Property | Value |
369| --- | --- |
370| size | `9pt` |
371| indent left | `9.5pt` |
372
373**table**
374
375| Property | Value |
376| --- | --- |
377| size | `8pt` |
378| line height | `1.3` |
379
380**codeblock**
381
382| Property | Value |
383| --- | --- |
384| size | `7.5pt` |
385| line height | `1.4` |
386
387**figure caption**
388
389| Property | Value |
390| --- | --- |
391| size | `8pt` |
392
393**footnote**
394
395| Property | Value |
396| --- | --- |
397| size | `8pt` |
398| note | Scoped to the page, not the column: one notes area spanning both columns at the page foot. |
399
400**endnotes**
401
402| Property | Value |
403| --- | --- |
404| size | `8.5pt` |
405
406**pullquote**
407
408| Property | Value |
409| --- | --- |
410| size | `12pt` |
411| note | Column width by default. Spanning both columns turns it into a divider, which is a different and heavier thing. |
412
413**abstract**
414
415| Property | Value |
416| --- | --- |
417| size | `9pt` |
418| max width | the full text width, set apart above the columns |
419
420## Elements
421
422### Structure
423
424#### Headings
425
426`headings`
427
428- Sans against the serif body, so hierarchy reads by contrast rather than by size alone.
429- Below level 4 the size stops changing and the register shifts instead — small caps, then italic. Six distinct levels inside a 24pt–11pt range.
430- Space above is four times the space below: a heading belongs to what follows it.
431- A heading MUST NOT be the last thing on a page. This is the most common defect in printed documents.
432
433##### Heading 1 — document title
434
435| Property | Value |
436| --- | --- |
437| font | `sans` |
438| weight | `300` |
439| size | `24pt` |
440| line height | `1.2` |
441| baseline advance | `28.8pt` |
442| tracking | `-0.015em` |
443| color | `ink` |
444| align | `left` |
445| space before | `0` |
446| space after | `5.5pt` |
447| break after | `avoid` |
448| break inside | `avoid` |
449| wrap | `balanced` |
450
451##### Heading 2 — section
452
453| Property | Value |
454| --- | --- |
455| font | `sans` |
456| weight | `600` |
457| size | `18pt` |
458| line height | `1.2` |
459| baseline advance | `21.6pt` |
460| tracking | `-0.01em` |
461| color | `ink` |
462| align | `left` |
463| space before | `22pt` |
464| space after | `5.5pt` |
465| break after | `avoid` |
466| break inside | `avoid` |
467| wrap | `balanced` |
468| sets running head | yes |
469
470##### Heading 3 — subsection
471
472| Property | Value |
473| --- | --- |
474| font | `sans` |
475| weight | `600` |
476| size | `14pt` |
477| line height | `1.2` |
478| baseline advance | `16.8pt` |
479| color | `ink` |
480| align | `left` |
481| space before | `22pt` |
482| space after | `5.5pt` |
483| break after | `avoid` |
484
485##### Heading 4
486
487| Property | Value |
488| --- | --- |
489| font | `sans` |
490| weight | `600` |
491| size | `12pt` |
492| line height | `1.2` |
493| baseline advance | `14.4pt` |
494| color | `ink` |
495| align | `left` |
496| space before | `22pt` |
497| space after | `5.5pt` |
498| break after | `avoid` |
499
500##### Heading 5
501
502| Property | Value |
503| --- | --- |
504| font | `serif` |
505| weight | `600` |
506| size | `11pt` |
507| variant | `all small caps` |
508| tracking | `0.06em` |
509| color | `ink` |
510| space before | `22pt` |
511| space after | `5.5pt` |
512| break after | `avoid` |
513
514> No size change from body — the register carries the level.
515
516##### Heading 6
517
518| Property | Value |
519| --- | --- |
520| font | `serif` |
521| weight | `400` |
522| style | `italic` |
523| size | `11pt` |
524| color | `ink` |
525| space before | `22pt` |
526| space after | `5.5pt` |
527| break after | `avoid` |
528
529##### Run-in heading
530
531| Property | Value |
532| --- | --- |
533| display | inline with the first line of its paragraph |
534| separator | ` · ` |
535| separator color | `ink_faint` |
536| space before | `0` |
537| space after | `0` |
538
539> For dense reports where a full heading line is too much vertical cost.
540
541#### Section numbering
542
543`numbering` · opt-in: `typeset--numbered`
544
545- Numbers are generated by a counter, never typed. A typed number drifts the moment a section moves.
546- Reports are numbered; essays and letters are not. It must be one switch.
547
548##### Level-2 number
549
550| Property | Value |
551| --- | --- |
552| format | `1` |
553| separator | `two spaces` |
554| color | `ink_faint` |
555| numerals | `lining tabular` |
556| resets | `the level-3 counter` |
557
558##### Level-3 number
559
560| Property | Value |
561| --- | --- |
562| format | `1.1` |
563| separator | `two spaces` |
564| color | `ink_faint` |
565| numerals | `lining tabular` |
566
567### Prose
568
569#### Paragraphs
570
571`paragraphs`
572
573- Two conventions, one switch. Spaced paragraphs suit documents that get skimmed. Indented paragraphs with no gap suit continuous prose and are the convention of nearly every printed book.
574- The first paragraph after any heading, block or break is flush left: the thing above it has already marked the start.
575
576##### Paragraph — spaced (default)
577
578| Property | Value |
579| --- | --- |
580| font | `serif` |
581| weight | `400` |
582| size | `11pt` |
583| line height | `1.45` |
584| baseline advance | `15.95pt` |
585| numerals | `oldstyle proportional` |
586| color | `ink` |
587| space after | `11pt` |
588| first line indent | `0` |
589| orphans | `2` |
590| widows | `2` |
591| max width | `33em` |
592
593##### Paragraph — indented (opt-in: `typeset--indented`)
594
595| Property | Value |
596| --- | --- |
597| space after | `0` |
598| first line indent | `1.5em` |
599| first line indent after block | `0` |
600
601> Applies after a heading, blockquote, figure or section break, and to the first paragraph of the document.
602
603#### Alignment: ragged or justified
604
605`justification` · opt-in: `typeset--justified`
606
607- Ragged right is the DEFAULT, and it is a choice rather than an absence — a document states it. Justification buys a clean right edge at the cost of uneven word spacing; a ragged setting keeps the spacing even and gives up the edge. Neither is more correct, but the cost lands differently by document.
608- Choose ragged right for a letter, a memorandum, a short note — anything addressed to a person rather than to a readership. Justification reads as institutional, and its even edge is the visual signature of print that was set for strangers.
609- Choose justification for continuous prose at a full measure: an essay, a report, a paper. It is mandatory in two columns, where a ragged edge at 47 characters serrates the column.
610- These are a single decision. Justification without hyphenation opens rivers of white space; hyphenation without justification breaks words for no gain. Take both or neither.
611- Hyphenation is per-language and requires the document language to be declared. An English dictionary applied to Portuguese produces confident nonsense.
612- The last line of a paragraph is NEVER stretched. State this explicitly — a paginating engine fragments the text, so the visual last line stops looking like the end of a paragraph and gets justified. It does not reproduce in an unpaginated preview.
613- Justification applies to prose only. It MUST NOT reach a subtitle, byline, caption, address block, table cell, heading or listing. In an engine where alignment inherits, every such block declares its own alignment rather than relying on an exception list.
614- Alignment inherits, and so does last-line alignment. A block that sets its own alignment — a centred pull quote, a centred section break — must also set its own last-line alignment, or the document's justification flushes its last line (its only line, for a one-liner) to the left.
615
616##### Ragged right (default) (opt-in: `typeset--ragged — names the default; no class needed to get it`)
617
618| Property | Value |
619| --- | --- |
620| align | `left` |
621| align last line | `left` |
622| hyphenation | `manual — never automatic` |
623| line breaking | high effort: avoid a very short last line, and even out the right edge |
624| applies to | `paragraph`, `list item`, `blockquote`, `definition description`, `callout` |
625
626> Hyphenation exists to serve justification. Without justification a hyphen breaks a word for no gain, so it stays off.
627
628> The rag itself is the thing to judge: an even rag reads as deliberate, a rag with one very short line and one very long one reads as an accident.
629
630##### Justified
631
632| Property | Value |
633| --- | --- |
634| align | `justify` |
635| align last line | `left` |
636| justify method | `inter-word` |
637| hyphenation | `automatic` |
638| hyphenation min word chars | `6` |
639| hyphenation min chars before break | `3` |
640| hyphenation min chars after break | `3` |
641| max consecutive hyphens | `2` |
642| applies to | `paragraph`, `list item`, `blockquote`, `definition description`, `callout` |
643
644##### Never justified
645
646| Property | Value |
647| --- | --- |
648| align | `left` |
649| hyphenation | `manual` |
650| applies to | `all headings`, `subtitle`, `byline`, `dateline`, `abstract`, `attribution`, `epigraph`, `verse`, `caption`, `figure caption`, `address block`, `table cell`, `table header`, `code block`, `table of contents`, `bibliography`, `endnotes`, `sidenote`, `letterhead`, `salutation`, `closing`, `signature`, `enclosures`, `postscript`, `definition term` |
651
652#### Drop cap
653
654`dropcap`
655
656- For the opening of an essay or a chapter, and nowhere else.
657- The size and line height are tuned together to cover exactly three lines. Change the leading and the cap must be retuned.
658- The opening words go in small caps. Without them the jump from 33pt to 11pt is too abrupt and the eye skips the first line.
659
660##### Drop cap
661
662| Property | Value |
663| --- | --- |
664| font | `serif` |
665| weight | `400` |
666| size | `3.05em` |
667| size pt | `33.55pt` |
668| line height | `0.86` |
669| color | `accent` |
670| float | `left` |
671| lines covered | `3` |
672| padding right | `0.06em` |
673| padding top | `0.02em` |
674| first line indent | `0` |
675
676##### Opening words
677
678| Property | Value |
679| --- | --- |
680| variant | `all small caps` |
681| tracking | `0.04em` |
682| extent | the first two to four words |
683
684#### Inline emphasis
685
686`inline`
687
688- Nested emphasis FLIPS to roman rather than compounding. This is correct typographic behaviour and most engines get it wrong by default.
689- Superscripts and subscripts must not disturb the line's leading.
690- Underline is reserved for insertions. On paper it is a typewriter's substitute for italic, and there is no reason to imitate a typewriter.
691
692##### Bold
693
694| Property | Value |
695| --- | --- |
696| weight | `600` |
697
698> For the load-bearing clause.
699
700##### Italic
701
702| Property | Value |
703| --- | --- |
704| style | `italic` |
705
706> For a title or a term of art.
707
708##### Nested italic
709
710| Property | Value |
711| --- | --- |
712| style | `normal` |
713
714##### Small caps
715
716| Property | Value |
717| --- | --- |
718| variant | `all small caps` |
719| tracking | `0.05em` |
720| numerals | `oldstyle` |
721
722> Named entities, acronyms, stage directions.
723
724##### Abbreviation
725
726| Property | Value |
727| --- | --- |
728| variant | `all small caps` |
729| tracking | `0.05em` |
730| underline | `0.5pt dotted` |
731| underline color | `ink_faint` |
732
733##### Superscript
734
735| Property | Value |
736| --- | --- |
737| size | `0.72em` |
738| baseline shift | `+0.45em` |
739| line height | `0` |
740| affects leading | no |
741
742##### Subscript
743
744| Property | Value |
745| --- | --- |
746| size | `0.72em` |
747| baseline shift | `-0.22em` |
748| line height | `0` |
749| affects leading | no |
750
751##### Deletion
752
753| Property | Value |
754| --- | --- |
755| decoration | `line-through` |
756| color | `ink_muted` |
757
758##### Insertion
759
760| Property | Value |
761| --- | --- |
762| decoration | `underline` |
763| underline offset | `0.15em` |
764
765##### Highlight
766
767| Property | Value |
768| --- | --- |
769| background | `wash` |
770| bleed | `2pt beyond the glyphs` |
771
772##### Key
773
774| Property | Value |
775| --- | --- |
776| font | `sans` |
777| size | `0.85em` |
778| padding | `0.15em 0.35em` |
779| border | `0.5pt rule` |
780| border bottom | `1.5pt rule` |
781| radius | `2pt` |
782
783#### Inline code
784
785`code-inline`
786
787##### Inline code
788
789| Property | Value |
790| --- | --- |
791| font | `mono` |
792| size | `0.86em` |
793| ligatures | `disabled` |
794| background | `wash` |
795| padding | `0.1em 0.28em` |
796| radius | `2pt` |
797| wrap | `may break within a word` |
798
799> The 0.86em corrects for the monospace face running optically large beside the serif.
800
801> Ligatures off: an arrow rendered as one glyph is charming in an editor and wrong in a document that quotes source.
802
803#### Links in print
804
805`links`
806
807- Paper has no hover and no click. A URL that carries information must be printed.
808
809##### Link
810
811| Property | Value |
812| --- | --- |
813| color | `inherit` |
814| decoration | `underline` |
815| underline thickness | `0.5pt` |
816| underline offset | `0.14em` |
817| skip descenders | yes |
818
819##### Printed URL
820
821| Property | Value |
822| --- | --- |
823| content | the href in parentheses, after the link text |
824| font | `mono` |
825| size | `0.82em` |
826| color | `ink_muted` |
827| wrap | `may break anywhere` |
828| applies to | external (http/https) links only |
829| excluded | `internal anchors`, `mailto:`, `links marked bare` |
830
831#### Numerals
832
833`figures-numeric`
834
835- Three kinds, three jobs. Getting this wrong is the most visible amateur tell in a set document, and it is one setting.
836- Old-style figures have ascenders and descenders and sit inside the x-height. Lining figures are uniform cap height. Tabular figures share one advance width, which is the only reason a column of numbers can align.
837
838##### In running prose
839
840| Property | Value |
841| --- | --- |
842| numerals | `oldstyle proportional` |
843
844##### In headings and display
845
846| Property | Value |
847| --- | --- |
848| numerals | `lining proportional` |
849
850##### In tables and columns
851
852| Property | Value |
853| --- | --- |
854| numerals | `lining tabular` |
855
856##### Fractions
857
858| Property | Value |
859| --- | --- |
860| form | `diagonal` |
861
862### Blocks
863
864#### Quotations
865
866`quotes`
867
868- Four jobs, four treatments. A block quote is evidence. An epigraph opens a chapter. A pull quote is display type lifted from the body. Verse preserves the poet's line breaks.
869- A pull quote repeats text that already appears in the body, so it MUST NOT be the only place a claim appears — a skimming reader would get the claim without its qualification.
870- Attribution always sits outside the quotation. Inside, it becomes part of what was said.
871
872##### Block quote
873
874| Property | Value |
875| --- | --- |
876| font | `serif` |
877| size | `10.5pt` |
878| line height | `1.45` |
879| color | `ink` |
880| space before | `13.75pt` |
881| space after | `13.75pt` |
882| indent left | `16.5pt` |
883| border left | `1pt rule` |
884| break inside | `avoid` |
885| last child space after | `0` |
886
887##### Attribution
888
889| Property | Value |
890| --- | --- |
891| display | `block` |
892| align | `left` |
893| space before | `5.5pt` |
894| size | `9.5pt` |
895| style | `normal` |
896| color | `ink_muted` |
897| prefix | `em dash and a space` |
898
899##### Epigraph
900
901| Property | Value |
902| --- | --- |
903| font | `serif` |
904| style | `italic` |
905| size | `10pt` |
906| color | `ink_muted` |
907| align | `left` |
908| block alignment | `flush right` |
909| max width | `24em` |
910| border | `none` |
911| space after | `22pt` |
912
913> Cite inside an epigraph is roman, not italic — the surrounding block is already italic.
914
915##### Pull quote
916
917| Property | Value |
918| --- | --- |
919| font | `sans` |
920| weight | `300` |
921| size | `15pt` |
922| line height | `1.3` |
923| align | `centre` |
924| wrap | `balanced` |
925| border top | `1.5pt rule_strong` |
926| border bottom | `0.5pt rule` |
927| padding top | `11pt` |
928| padding bottom | `11pt` |
929| space before | `16.5pt` |
930| space after | `16.5pt` |
931
932##### Verse
933
934| Property | Value |
935| --- | --- |
936| font | `serif` |
937| size | `11pt` |
938| align | `left` |
939| line breaks | `preserved as authored` |
940| indent left | `22pt` |
941| runover indent | `1.5em` |
942| border | `none` |
943
944> Runover lines indent further than the verse line they continue, so a wrapped line cannot be mistaken for a new one.
945
946#### Lists
947
948`lists`
949
950- The marker's colour, weight and width must be under control. A grey marker with black text reads as one thing; a black marker competes with the first word.
951- Runover lines align to the text, never to the marker.
952- Two levels of nesting is the limit worth designing for.
953- Definition lists are underused: they are the right shape for a glossary, a set of terms, or the field-and-value blocks of a letter.
954
955##### Unordered list
956
957| Property | Value |
958| --- | --- |
959| marker | `·` |
960| marker color | `ink_muted` |
961| marker weight | `700` |
962| marker column width | `1.4em` |
963| indent | `1.4em` |
964| item space after | `2.75pt` |
965| space after | `11pt` |
966
967##### Unordered list, nested
968
969| Property | Value |
970| --- | --- |
971| marker | `–` |
972| marker weight | `400` |
973| space before | `2.75pt` |
974
975##### Ordered list
976
977| Property | Value |
978| --- | --- |
979| marker | `1.` |
980| marker color | `ink_muted` |
981| numerals | `lining tabular` |
982| marker column width | `1.4em` |
983| indent | `1.4em` |
984| item space after | `2.75pt` |
985
986> Tabular numerals so 9 and 10 align.
987
988##### Ordered list, nested
989
990| Property | Value |
991| --- | --- |
992| marker | `a.` |
993
994##### Tight list (opt-in: `ts-list-tight`)
995
996| Property | Value |
997| --- | --- |
998| item space after | `0` |
999
1000> For enumerations that are not prose.
1001
1002##### Definition term
1003
1004| Property | Value |
1005| --- | --- |
1006| font | `sans` |
1007| weight | `600` |
1008| size | `9.5pt` |
1009| align | `left` |
1010| space before | `6.6pt` |
1011| space before first | `0` |
1012| break after | `avoid` |
1013
1014##### Definition description
1015
1016| Property | Value |
1017| --- | --- |
1018| font | `serif` |
1019| size | `11pt` |
1020| indent left | `16.5pt` |
1021| space after | `0` |
1022
1023#### Tables
1024
1025`tables`
1026
1027- Rules, not grids. Vertical rules are almost never needed — the columns already read as columns, and every added line is ink competing with data.
1028- Three horizontal rules only: above the header, below the header, below the body.
1029- A table is scanned, not read. Sans at 9.5pt; the serif's job is reading.
1030- Numbers align on their right edge, on tabular figures. Units go in the header, not in every cell.
1031- A table that spans pages MUST repeat its header on each. Without it, page two of a table is unreadable.
1032
1033##### Table
1034
1035| Property | Value |
1036| --- | --- |
1037| font | `sans` |
1038| size | `9.5pt` |
1039| line height | `1.35` |
1040| numerals | `lining tabular` |
1041| width | `full measure` |
1042| space before | `13.75pt` |
1043| space after | `13.75pt` |
1044| border collapse | yes |
1045
1046##### Header cell
1047
1048| Property | Value |
1049| --- | --- |
1050| font | `sans` |
1051| weight | `600` |
1052| size | `8pt` |
1053| case | `uppercase` |
1054| tracking | `0.07em` |
1055| color | `ink_muted` |
1056| align | `left` |
1057| border bottom | `1pt rule_strong` |
1058| padding | `0.45em 0.7em 0.45em 0` |
1059| repeats across pages | yes |
1060
1061##### Body cell
1062
1063| Property | Value |
1064| --- | --- |
1065| align | `left` |
1066| vertical align | `baseline` |
1067| border bottom | `0.5pt rule` |
1068| padding | `0.45em 0.7em 0.45em 0` |
1069| last row border bottom | `1pt rule_strong` |
1070
1071##### Numeric cell
1072
1073| Property | Value |
1074| --- | --- |
1075| align | `right` |
1076| numerals | `lining tabular` |
1077
1078##### Footer cell
1079
1080| Property | Value |
1081| --- | --- |
1082| weight | `600` |
1083| border bottom | `none` |
1084
1085##### Caption
1086
1087| Property | Value |
1088| --- | --- |
1089| position | `above the table` |
1090| align | `left` |
1091| font | `sans` |
1092| size | `9.5pt` |
1093| color | `ink` |
1094| space after | `0.5em` |
1095| label | small caps, weight 600, tracking 0.06em |
1096| max width | `none` |
1097
1098##### Row
1099
1100| Property | Value |
1101| --- | --- |
1102| break inside | `avoid` |
1103
1104##### Zebra striping (opt-in: `ts-table-zebra`)
1105
1106| Property | Value |
1107| --- | --- |
1108| background | `wash on even body rows` |
1109
1110> For wide, dense tables only. Not a default.
1111
1112#### Code blocks
1113
1114`codeblock`
1115
1116- Paper cannot scroll. Lines wrap; they are never clipped.
1117- A long listing MUST be allowed to split across pages. Forbidding the break pushes a blank page ahead of it.
1118
1119##### Code block
1120
1121| Property | Value |
1122| --- | --- |
1123| font | `mono` |
1124| size | `8.5pt` |
1125| line height | `1.45` |
1126| background | `wash` |
1127| border left | `2pt rule_strong` |
1128| padding | `0.8em 1em` |
1129| space before | `13.75pt` |
1130| space after | `13.75pt` |
1131| wrap | `soft-wrap long lines` |
1132| tab size | `2` |
1133| break inside | avoid on screen, allowed in print |
1134
1135#### Figures & captions
1136
1137`figures`
1138
1139- A caption is not a title: it goes below the figure.
1140- A figure and its caption stay on one page. This is the one place a no-break rule is unambiguously right.
1141- The caption carries a small-caps label so a cross-reference in the text has something to point at.
1142
1143##### Figure
1144
1145| Property | Value |
1146| --- | --- |
1147| space before | `16.5pt` |
1148| space after | `16.5pt` |
1149| break inside | `avoid` |
1150| image max width | `full measure` |
1151
1152##### Figure caption
1153
1154| Property | Value |
1155| --- | --- |
1156| position | `below the figure` |
1157| font | `sans` |
1158| size | `9.5pt` |
1159| line height | `1.4` |
1160| color | `ink_muted` |
1161| align | `left` |
1162| space before | `0.5em` |
1163| border top | `0.5pt rule` |
1164| padding top | `0.4em` |
1165| label | small caps, weight 600, colour ink, tracking 0.06em |
1166
1167#### Callouts
1168
1169`callouts`
1170
1171- A callout is an aside the reader may skip without losing the argument. If skipping it would lose the argument, it is a paragraph.
1172- Two variants only. A document with five callout colours has none.
1173
1174##### Callout
1175
1176| Property | Value |
1177| --- | --- |
1178| font | `serif` |
1179| size | `10pt` |
1180| background | `wash` |
1181| border | `0.5pt rule` |
1182| border left | `2.5pt rule_strong` |
1183| padding | `0.75em 1em` |
1184| space before | `13.75pt` |
1185| space after | `13.75pt` |
1186| break inside | `avoid` |
1187
1188##### Callout title
1189
1190| Property | Value |
1191| --- | --- |
1192| font | `sans` |
1193| weight | `700` |
1194| size | `8pt` |
1195| case | `uppercase` |
1196| tracking | `0.09em` |
1197| color | `ink_muted` |
1198| space after | `0.35em` |
1199
1200##### Callout — warning
1201
1202| Property | Value |
1203| --- | --- |
1204| border left | `2.5pt accent` |
1205| title color | `accent` |
1206
1207#### Section breaks
1208
1209`breaks`
1210
1211- A blank line cannot survive a page break: if the break falls on the gap, the reader never learns the scene changed. Over a long document this is a certainty, not an accident. The mark MUST be visible.
1212- Four registers, one per kind of division.
1213
1214##### Break — three asterisks
1215
1216| Property | Value |
1217| --- | --- |
1218| content | `* * *` |
1219| size | `10pt` |
1220| tracking | `0.6em` |
1221| color | `ink_faint` |
1222| align | `centre` |
1223| space before | `16.5pt` |
1224| space after | `16.5pt` |
1225| break after | `avoid` |
1226
1227> The default, for a change of scene in prose.
1228
1229##### Break — asterism
1230
1231| Property | Value |
1232| --- | --- |
1233| content | `⁂` |
1234| size | `14pt` |
1235| color | `ink_faint` |
1236| align | `centre` |
1237
1238> A heavier division: a change of part.
1239
1240##### Break — fleuron
1241
1242| Property | Value |
1243| --- | --- |
1244| content | `❦` |
1245| size | `12pt` |
1246| color | `accent` |
1247| align | `centre` |
1248
1249> Where the document can afford ornament.
1250
1251##### Break — rule
1252
1253| Property | Value |
1254| --- | --- |
1255| border top | `0.5pt rule` |
1256| content | `none` |
1257
1258> Where it cannot. Reports.
1259
1260### Apparatus
1261
1262#### Footnotes, endnotes & sidenotes
1263
1264`notes`
1265
1266- Bottom-of-page footnotes require an engine that can measure the page while laying it out. Typst and LaTeX do this natively. WeasyPrint 53+ and Prince implement the CSS footnote model. No browser does.
1267- Where footnotes are unavailable the note text MUST still live in the document, degrading to a numbered endnotes block — never to a lost note.
1268- Sidenotes are the better default where the page can afford the margin: no page-break arithmetic, and the reader never leaves the sentence they are in.
1269- The reference mark must not disturb the leading of the line it sits on.
1270
1271##### Reference mark
1272
1273| Property | Value |
1274| --- | --- |
1275| content | `the note number` |
1276| size | `0.7em` |
1277| baseline shift | `+0.45em` |
1278| line height | `0` |
1279| color | `accent` |
1280| numerals | `lining` |
1281| decoration | `none` |
1282| brackets | `none` |
1283| affects leading | no |
1284
1285##### Footnote
1286
1287| Property | Value |
1288| --- | --- |
1289| position | foot of the page carrying the mark |
1290| font | `serif` |
1291| size | `9.5pt` |
1292| line height | `1.35` |
1293| separator | a rule above the notes area |
1294
1295Where unsupported: an endnotes block at the end of the document
1296
1297##### Endnotes block
1298
1299| Property | Value |
1300| --- | --- |
1301| font | `serif` |
1302| size | `9.5pt` |
1303| line height | `1.4` |
1304| align | `left` |
1305| space before | `22pt` |
1306| border top | `0.5pt rule` |
1307| padding top | `11pt` |
1308| list indent | `1.8em` |
1309| item space after | `0.4em` |
1310| heading | none — the rule above the block is the only mark it needs |
1311
1312> No 'NOTES' label. In a one-page document the rule plus the numbered list already reads as a notes area, and a heading over two lines of small print is heavier than the thing it labels.
1313
1314##### Sidenote (opt-in: `typeset--sidenotes`)
1315
1316| Property | Value |
1317| --- | --- |
1318| font | `sans` |
1319| size | `8pt` |
1320| line height | `1.4` |
1321| color | `ink_muted` |
1322| align | `left` |
1323| width | `11em` |
1324| position | the right margin, 2em past the text column's own right edge |
1325| vertical offset | -0.3em from the marker's line |
1326| marker prefix | the note number in accent, weight 600 |
1327| measure when active | `27em` |
1328| reserved margin | 14em — the gap plus the note, with an em to spare |
1329
1330> Anchor the note to the text column's right edge, not to the measure. The measure is a maximum: where the reserved margin is the binding constraint the column is narrower than it, and a note offset from the measure lands on top of the text.
1331
1332> Sidenotes are positioned, not flowed: a long note followed closely by another will overlap it. Keep each note short and consecutive notes apart — one per paragraph, and not in every paragraph. An implementation cannot fix this, and should not pretend to; it is a constraint on the writing.
1333
1334#### Table of contents
1335
1336`toc`
1337
1338- Page numbers require the engine to resolve a cross-reference after pagination. Where it cannot, the entry MUST omit the number rather than print a wrong one — a TOC with wrong numbers is worse than one with none.
1339
1340##### Entry
1341
1342| Property | Value |
1343| --- | --- |
1344| font | `sans` |
1345| size | `9.5pt` |
1346| align | `left` |
1347| space after | `0.35em` |
1348| decoration | `none` |
1349| leader | dotted 0.5pt rule, colour rule, filling the space between title and number |
1350| leader baseline offset | `-0.15em` |
1351| page number | `resolved after pagination` |
1352| page number numerals | `lining tabular` |
1353| page number color | `ink_muted` |
1354
1355Where unsupported: leaders render, page numbers are omitted
1356
1357##### Entry — level 2
1358
1359| Property | Value |
1360| --- | --- |
1361| indent left | `1.5em` |
1362
1363#### Bibliography
1364
1365`bibliography`
1366
1367- Hanging indent, always. The surname is what is being scanned, so it must be the leftmost thing on the entry.
1368
1369##### Entry
1370
1371| Property | Value |
1372| --- | --- |
1373| font | `serif` |
1374| size | `9.5pt` |
1375| line height | `1.4` |
1376| align | `left` |
1377| hanging indent | `1.8em` |
1378| space after | `0.55em` |
1379| marker | `none` |
1380| break inside | `avoid` |
1381| title style | `italic` |
1382
1383### Documents
1384
1385#### Front matter
1386
1387`frontmatter`
1388
1389- A colophon closes the document, recording how it was made and in what type. It is the one piece of matter that is purely for pleasure, and the cheapest thing here to include.
1390
1391##### Title block
1392
1393| Property | Value |
1394| --- | --- |
1395| space after | `33pt` |
1396| padding bottom | `11pt` |
1397| border bottom | `0.5pt rule` |
1398
1399##### Subtitle
1400
1401| Property | Value |
1402| --- | --- |
1403| font | `sans` |
1404| weight | `300` |
1405| size | `14pt` |
1406| line height | `1.25` |
1407| color | `ink_muted` |
1408| align | `left` |
1409| wrap | `balanced` |
1410| space after | `11pt` |
1411
1412##### Byline
1413
1414| Property | Value |
1415| --- | --- |
1416| font | `serif` |
1417| size | `11pt` |
1418| variant | `all small caps` |
1419| tracking | `0.08em` |
1420| align | `left` |
1421| space after | `0` |
1422
1423##### Dateline
1424
1425| Property | Value |
1426| --- | --- |
1427| font | `sans` |
1428| size | `9.5pt` |
1429| color | `ink_muted` |
1430| align | `left` |
1431| space before | `0.2em` |
1432
1433##### Abstract
1434
1435| Property | Value |
1436| --- | --- |
1437| font | `serif` |
1438| size | `10pt` |
1439| line height | `1.45` |
1440| color | `ink_muted` |
1441| align | `left` |
1442| max width | `30em` |
1443| space after | `22pt` |
1444| label | ABSTRACT — sans, 700, 8pt, uppercase, tracking 0.1em, colour ink_faint, on its own line |
1445
1446##### Colophon
1447
1448| Property | Value |
1449| --- | --- |
1450| font | `serif` |
1451| style | `italic` |
1452| size | `9.5pt` |
1453| color | `ink_muted` |
1454| align | `left` |
1455| max width | `26em` |
1456| space before | `33pt` |
1457| padding top | `11pt` |
1458| border top | `0.5pt rule` |
1459| break before | `avoid` |
1460
1461#### Letter
1462
1463`letter`
1464
1465- A letter is set ragged right. It is addressed to a person, and justification reads as institutional.
1466- The sender block is address data, not a masthead: one style throughout, at body size, in the reading face. Nothing bold, nothing in the sans. A personal letter does not announce itself.
1467- Address blocks are line-broken data, not prose: tighter leading, never justified, line breaks as authored.
1468- The signature is the typed name with room above it to sign. No ruled line — a rule is a form to be filled in.
1469- A letter carries no running head and no folio.
1470- Footnotes belong in a letter as much as in an essay. Where the engine cannot set them at the page foot, the rule plus the numbered note at the end of a one-page letter reads as a footnote area regardless.
1471
1472##### Letter page
1473
1474| Property | Value |
1475| --- | --- |
1476| margin top mm | `32` |
1477| margin bottom mm | `28` |
1478| margin sides mm | `25` |
1479| running head | `none` |
1480| folio | `none` |
1481
1482> The deeper top margin puts the letterhead where an envelope window expects it.
1483
1484##### Sender block
1485
1486| Property | Value |
1487| --- | --- |
1488| font | `serif` |
1489| weight | `400` |
1490| size | `11pt` |
1491| line height | `1.35` |
1492| color | `ink` |
1493| align | `left` |
1494| line breaks | as authored — name, street, suburb and postcode, country |
1495| space after | `27.5pt` |
1496
1497> Every line is the same style, including the name. Setting the name larger or bolder turns a letter into stationery.
1498
1499> Omit what you do not use. A personal letter needs no email address and no telephone number.
1500
1501##### Address block
1502
1503| Property | Value |
1504| --- | --- |
1505| font | `serif` |
1506| size | `11pt` |
1507| line height | `1.35` |
1508| style | `normal` |
1509| align | `left` |
1510| line breaks | `as authored` |
1511| space after | `16.5pt` |
1512| label | sans, 8pt, uppercase, tracking 0.1em, colour ink_faint, on its own line |
1513
1514##### Date line
1515
1516| Property | Value |
1517| --- | --- |
1518| numerals | `oldstyle` |
1519| align | `left` |
1520| space after | `16.5pt` |
1521
1522##### Line under the date
1523
1524| Property | Value |
1525| --- | --- |
1526| font | `serif` |
1527| style | `italic` |
1528| size | `11pt` |
1529| color | `ink` |
1530| align | `left` |
1531| space before | 0.1em — it belongs to the date, and takes no gap of its own |
1532| space after | `16.5pt` |
1533
1534> A dedication, a feast, a devotion — whatever the writer puts under the date. Italic, at body size, immediately beneath.
1535
1536##### Salutation
1537
1538| Property | Value |
1539| --- | --- |
1540| align | `left` |
1541| space after | `11pt` |
1542
1543##### Closing
1544
1545| Property | Value |
1546| --- | --- |
1547| align | `left` |
1548| space before | `16.5pt` |
1549
1550##### Signature block
1551
1552| Property | Value |
1553| --- | --- |
1554| font | `serif` |
1555| size | `11pt` |
1556| align | `left` |
1557| space before | `33pt — the room to sign` |
1558| rule | `none` |
1559| break inside | `avoid` |
1560| signature image max height | `16mm` |
1561
1562> The typed name only. A ruled line above a name is a form; a letter is not a form.
1563
1564##### Enclosures
1565
1566| Property | Value |
1567| --- | --- |
1568| font | `serif` |
1569| size | `11pt` |
1570| align | `left` |
1571| space before | `22pt` |
1572| label | same style as the text it introduces — no small caps, no weight change |
1573
1574> Same reasoning as the postscript: "Enc." introduces a sentence, it does not head a section.
1575
1576##### Postscript
1577
1578| Property | Value |
1579| --- | --- |
1580| font | `serif` |
1581| size | `11pt` |
1582| align | `left` |
1583| space before | `11pt` |
1584| label | same style as the text it introduces — no small caps, no weight change |
1585
1586> A postscript is a sentence that happens to begin with "P.S.". The label is not a heading.
1587
1588##### Footnote
1589
1590| Property | Value |
1591| --- | --- |
1592| marker | as the note marker in Apparatus — superscript, accent, no brackets |
1593| position | foot of the page where the engine can; otherwise after the signature block |
1594| font | `serif` |
1595| size | `9.5pt` |
1596| separator | 0.5pt rule above the notes area |
1597| heading | `none` |
1598
1599Where unsupported: a numbered note after the signature, under a rule — which on a one-page letter is the page foot anyway
1600
1601#### Pagination utilities
1602
1603`utilities`
1604
1605- The small set of overrides reached for while proofing a real document. Every one of them is a manual decision about a specific page, not a style.
1606
1607##### Break before
1608
1609| Property | Value |
1610| --- | --- |
1611| effect | start this element on a new page |
1612
1613> Chapter openings.
1614
1615##### Break after
1616
1617| Property | Value |
1618| --- | --- |
1619| effect | start the next element on a new page |
1620
1621##### Keep together
1622
1623| Property | Value |
1624| --- | --- |
1625| effect | never split this element across pages |
1626
1627> A short table, a signature block, a callout.
1628
1629##### No hyphenation
1630
1631| Property | Value |
1632| --- | --- |
1633| hyphenation | `manual` |
1634
1635##### Tie
1636
1637| Property | Value |
1638| --- | --- |
1639| effect | `no line break inside` |
1640
1641> A figure with its unit, an initial with a surname, a section mark with its number.
1642
1643##### Print only / screen only
1644
1645| Property | Value |
1646| --- | --- |
1647| effect | present in one medium, absent in the other |
1648
1649> The URL list a page needs and a screen does not; the navigation a screen needs and a page does not.
1650
1651##### Exact colour
1652
1653| Property | Value |
1654| --- | --- |
1655| effect | backgrounds and rules survive the output device's ink-saving |
1656
1657> Without it a print dialog can strip every rule in a table.
1658
1659---
1660
1661Generated from spec.json by tools/build-spec.mjs. Do not edit this file — edit spec.json and regenerate.