typeset

Templates

A template is a document you write in. The specification is the contract it keeps: a template sets the page and the scale and nothing else, and takes its typography from the published stylesheet rather than carrying a second copy of it.

Capability matrix

What each technology can express

The specification is one vocabulary; three engines implement it, each to a different depth. This is how far each one reaches, and where a "no" says why — so you can tell which template to reach for before you download one.

CSS + Paged.jsTypst iA Writer
Every element expressible all 106 — every element the specification names has a form in CSS. How much of one reaches the page is a question about the engine, and the three rows below are where that gap is widest; 4 elements carry a fallback in spec.json naming what they degrade to where an engine cannot honour them in full — justification-justified, note-footnote, toc-entry and letter-footnote. all 106 — every element has a Typst form; 2 fall short of the full property set: quote-verse and bibliography-entry lose their runover indent, per each one's fallback in spec.json. The drop cap is set in the margin rather than wrapped, because Typst has no float. Markdown subset only — headings, body text, bold, italic, tables and footnotes; not the 106 elements the specification names, and not signature blocks, callouts, sidenotes or drop caps, which Markdown has no markup for.
Paginated two columns no — Paged.js paginates by moving content between page boxes and cannot fragment a column flow; the two-column example drops Paged.js entirely and lets the browser's own print produce the columns instead. yes, natively no — WebKit's print path ignores every CSS multi-column property, so a two-column template previews correctly on screen and exports as a single column.
Running head and folio yes, via CSS Paged Media — Chrome's own print dialog ignores the margin boxes a running head and folio need; only a paged-media engine (Paged.js, WeasyPrint, Prince) renders them. yes, natively no — iA Writer reserves the top and bottom bands itself, by setting the page margins directly; a template's own @page margin would replace that reservation rather than add to it.
True page-foot footnotes WeasyPrint and Prince only — per spec.json: “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.” yes, natively no — Markdown has no page-position construct for a note, so it degrades to an endnotes block at the end of the document.

Typst

The letter template

letter() sets a whole letter from named parts — crest, addresses, date, salutation, closing, signature, enclosures and postscript — on a 35mm page at 12pt, justified. You fill in the parts you want; the text under the #show line is the letter. Every part is optional, and a part you leave out is not set.

It lives in typeset.typ, so the import line is the same one every other document uses: #import "typeset.typ": * beside a copy of the file, or #import "@local/typeset:2.2.1": * with the implementations/ folder linked as a local Typst package.

A complete letter

#import "typeset.typ": *

#show: letter.with(
  crest: svg-recolor(read("crest-placeholder.svg"), width: 100%),
  sender: ("Adilson Carvalho", "10 Wentworth Avenue", "Surry Hills NSW 2010", "Australia"),
  addressee: ("The Registrar", "88 Rundle Street", "Adelaide SA 5000"),
  date: "27 August 2026",
  saint: "St Monica, mother of St Augustine Bishop",
  salutation: "Registrar",
  signature: "Adilson Carvalho",
  enclosures: [two specimen pages],
  postscript: [The tables were excellent.],
  accent: ("sixty-six characters",),
)

I am writing about the setting of the Institute's annual report, which arrived
this morning and which I read with more attention to its margins than to its
contents.

#quote[Narrowing the column to sixty-six characters costs one afternoon and no money.]

I would be glad to send the specification I use for my own documents.

The example letter sets a longer version of this letter part by part, with each part its own call, and gives this compact form in its opening comment. That page shows it rendered.

The parts

Every parameter of letter(), with the value it takes when you leave it out.

ParameterDefaultWhat it sets
crestnoneAn image set top right — a crest or monogram. Read an SVG drawn in one ink with svg-recolor(read("crest.svg"), width: 100%) to set it in the accent; the path resolves against your document.
sendernoneYour address: an array of lines, or line-broken content. The first line is the name, set in small caps; the rest a step smaller. Labelled From.
addresseenoneThe recipient’s address, the same shape as sender. Labelled To.
datenoneThe date line.
saintnoneThe saint of the day, set under the date and closed with , ora pro nobis. Needs a date; left out or empty, the date stands alone.
greeting"Dear"The word before the name in the salutation.
salutationnoneThe name you greet. The greeting goes before it and a comma after it, so salutation: "Fr Lai" sets “Dear Fr Lai,”
closingautoauto sets “Yours sincerely,” Words set those words and a comma, so closing: [With love] sets “With love,” none sets no closing at all — use it when you write your own.
signaturenoneYour typed name, with room above it to sign.
enclosuresnoneWhat is enclosed, set as Enc. and the text.
postscriptnoneA postscript, set after the signature as P.S. and the text.
accent()Phrases to set in the accent colour wherever they occur in the body: accent: ("good and faithful servant",). Exact and case-sensitive. Note the trailing comma for a single phrase — that is what makes it a list.
smallcaps()Phrases to set in small caps wherever they occur in the body, the same way as accent.
marginletter-marginThe page margin: 35mm on every edge by default — the standard 20mm plus a 15mm inset, which the letterhead reaches back out of.
paper"a4"The paper, by its Typst name.

Using only part of it

letter() places each part where a letter keeps it. To place them yourself, write the letter part by part: #show: letter-page sets the page and the quotation style, each part is its own call, and letter-phrases wraps the paragraphs so the accent and smallcaps phrases restyle them and nothing else. This is the form the generated letter takes, so every part is there to edit.

#import "typeset.typ": *

// The letter written part by part: letter-page sets the page, each part is
// its own call, and letter-phrases restyles phrases in the paragraphs alone.
#show: letter-page

#letter-addresses[
  #letter-address-from[
    Adilson Carvalho \
    10 Saddlers Rd, Wadalba NSW 2259
  ]
  #letter-address-to[
    Rev. Fr Lai Nguyen \
    St Anne's Chapel, Auckland
  ]
]

#letter-date("8 October 2026", saint: "St Pelagia, virgin and martyr of Antioch")

#letter-salutation[Fr Lai]

#letter-phrases(
  accent: ("good and faithful servant",),
  smallcaps: ("non sum dignus",),
)[
  I stood up right after the non sum dignus and dashed to the end of the queue.
  I pray you will one day hear Him call you _good and faithful servant_.
]

#letter-closing[In Christ]

#letter-signature[Adilson C]

Every piece letter() is built from, for writing a letter by hand:

FunctionCallWhat it sets
letter-page#show: letter-pageThe page alone: 35mm margins, 12pt, justified, no running head or folio, block quotes as letter-quote. What letter() is built on.
letter-crest#letter-crest(image)An image top right, held at the standard margin from the paper’s edge.
svg-recolorsvg-recolor(read("crest.svg"), width: 100%)An SVG with its one ink swapped for the accent — or to: any colour, from: any source colour.
letter-addresses#letter-addresses[…]The column the address blocks sit in, held at the standard margin from the paper’s edge.
letter-address-from#letter-address-from[Name \ Street]An address block labelled From.
letter-address-to#letter-address-to[Name \ Street]An address block labelled To.
letter-address-block#letter-address-block(label: [Via])[…]An address block with any label, or none.
letter-sender#letter-sender[…]Your address without a label, with more room below it.
letter-date#letter-date("8 October 2026", saint: "St Pelagia")The date, and the saint’s line under it when a saint is given.
letter-date-note#letter-date-note[…]Any other line under a date set as its own paragraph.
letter-salutation#letter-salutation[Fr Lai]
#letter-salutation("Hey,")[Fr Lai]
The greeting, the name and a comma. The greeting is Dear unless you give one first.
letter-quote#letter-quote[…]A quotation set apart by indent and colour. Inside letter-page, every #quote[…] takes this form.
letter-closing#letter-closing()
#letter-closing[With love]
The closing words and a comma — Yours sincerely when called with (). The parentheses are needed: a bare #letter-closing names the function without calling it.
letter-signature#letter-signature[Adilson C]Your typed name, with room above it to sign.
letter-enclosures#letter-enclosures[…]Enc. and what is enclosed.
letter-postscript#letter-postscript[…]P.S. and the postscript.
letter-phrases#letter-phrases(accent: ("…",), smallcaps: ("…",))[…]Restyles the listed phrases in the content it wraps, and nowhere else. Wrap the paragraphs in it when writing a letter part by part.

Changing the defaults

The greeting, the closing words and the page are parameters like any other.

#import "typeset.typ": *

// Every default is a parameter: the greeting, the closing words, and the
// page itself.
#show: letter.with(
  margin: 30mm,
  addressee: ("Fabiano and Mari", "Point Chevalier", "Auckland 1022"),
  date: "1 October 2026",
  greeting: "Hey,",
  salutation: "you two",
  closing: [With love],
  signature: "Adilson",
)

Thank you for the warm welcome, the care, the views, the coffees, the meals.

iA Writer

The letter

A letter on A4 at 35mm on all four sides. EB Garamond at 12pt, justified, filling the page the margins leave. Markdown only, so it covers headings, body text, bold, italic, tables and footnotes — its own page states what that vocabulary reaches and what it does not, including why there is no two-column iA Writer template.

Building one of your own

  • typeset.css — link it, add class="typeset", bind every face. The CSS bundle carries both.
  • typeset.typ — for anything paginated: page-foot footnotes, running heads, table-of-contents page numbers, two columns.
  • spec.json — the values themselves, if you are teaching a third engine.