Why Typst matters

Practical Quarto: Patterns, templates, and tooling @ posit::conf(2026)

Charlotte Wickham
&  
Mine Çetinkaya-Rundel

Case study

The Bureau of Regional Statistics publishes a quarterly release.

Same release, new requirement

The Bureau of Regional Statistics quarterly release. Every one must carry:

  • the agency mark
  • the reference period and the region
  • the release status — Provisional, Revised, or Final

Last module we built all three into an HTML title block. Now it has to be a PDF.

Your turn 1: render it as a PDF

  1. Add typst as a second format in report.qmd. Keep html:

    report.qmd
    format:
      html: default
      typst: default
  2. Render with Quarto: Preview Format… command and pick Typst.

  3. Render the HTML too. Compare the two formats.

Exercise: 04-typst/01-first-pdf

The first page of the rendered PDF. The agency logo and wordmark sit small at the top left. Regional Employment Indicators is centred below it in bold, with the subtitle Quarterly statistical release, then Bureau of Regional Statistics and the date 2026-09-14. The Summary section follows, with a sentence naming the status, period and region, a paragraph pointing at Table 1 and a green methodology notes link, a bold line reading All figures in this release are provisional, and an indented quotation about benchmarking. Then an Industry detail section with Table 1, Non-farm payroll employment by sector, and the start of a Notes section.

Why Typst?

  • Already installed. format: pdf needs a LaTeX distribution. Typst ships inside Quarto.

  • Faster. Render, look, change, look again.

  • Uses your brand. Colors, fonts, and the logo, from the same _brand.yml.

Two ways to reach metadata

Last module This module
Where you write it title-block.html, a partial report.qmd, the body
What you write $brs-release.status$ {{< meta brs-release.status >}}
What it is Pandoc template syntax a Quarto shortcode

Pandoc’s template syntax only runs when Pandoc is filling a template.

Pandoc’s template syntax →

Quarto’s meta shortcode →

Quarto writes the Typst

.qmd → .typ → .pdf

One box, labelled report.qmd and annotated you write this, holding a YAML header that sets title and format typst, then a markdown heading reading hash hash Summary in teal.

The report.qmd box on the left, an arrow labelled Pandoc, and a second box labelled report.typ holding grey boilerplate lines — a brand-color definition, a show colon doc arrow article call, a set page call — and then in teal the line equals Summary.

Three boxes in a row joined by two labelled arrows. report.qmd on the left, an arrow labelled Pandoc, report.typ in the middle holding grey boilerplate and a teal equals Summary line, an arrow labelled Typst, and report.pdf drawn as a rendered page with a logo mark, a centred title and lines of body text.

Keep the intermediate .typ:

report.qmd
format:
  typst:
    keep-typ: true

Your content translated to Typst

report.typ
= Summary
<summary>
This is a Provisional release covering Oregon for the reference period Q3 2026.

Total employment in the region rose by 4,300 jobs over the quarter, an increase of 0.4%. Sector detail is in #ref(<tbl-sectors>, supplement: [Table]), and the full method is in the #link("https://example.gov/brs/methods")[methodology notes].

#strong[All figures in this release are #emph[provisional].]

#quote(block: true)[
Benchmarking aligns the survey estimates with the annual census of employment.
]

Template contributes 431 lines above it…

Template: format: typst

A schematic of template.typ as an ordered stack of partial files: numbering.typ (number formats), definitions.typ (helpers), typst-template.typ (defines article()), a dashed header-includes slot, page.typ (#set page()), and typst-show.typ (calls article(doc)). A box labeled passed to article() as doc holds the rest: a dashed include-before slot, the body, notes.typ (footnotes), biblio.typ (bibliography), and a dashed include-after slot.

Typst syntax

Text is content

badge.typ
Provisional

The word Provisional, as plain text.

# switches into code

badge.typ
rect()


badge.typ
#rect()

The text `rect()`.

An empty rectangle with a thin black outline.

Empty parentheses: a call with no arguments.

Typst: rect →

Arguments go in the parentheses

badge.typ
#rect(fill: green)

A solid green rectangle.

Use [] to pass content arguments

badge.typ
#rect(fill: green)[Provisional]

A small green rectangle with the word Provisional inside it in black.

This is the same call:

#rect(fill: green, [Provisional])

Typst: trailing content blocks →

Your turn 2: read the Typst

  1. Render, then open report.typ. Scroll down to = Summary — everything below it is your content.

  2. Find a call — it starts with #.

  3. Search for the function in the Typst reference. Say in plain words what it does, and whether any arguments are being passed to it.

  4. Again, with another one.

Exercise: 04-typst/02-read-the-typ

You can nest content and calls

badge.typ
#rect(fill: green)[
  #text(fill: white)[Provisional]
]

A small green rectangle with the word Provisional inside it in white.

Writing Typst in a .qmd

A raw block passes straight through:

report.qmd
```{=typst}
#rect(fill: green)[Provisional]
```

Lands in report.typ untouched. Every other format ignores it.

The release status badge

report.qmd
```{=typst}
#rect(fill: brand-color.primary, radius: 3pt)[
  #text(fill: white)[
    {{< meta brs-release.status >}}
  ]
]
```

A dark green rounded rectangle with the word Provisional inside it in white type.

Set and show rules

Every element has a function

Even if not explicitly called, all layout and styling happens with functions:

Element Its function
Text text()
Heading heading()
Paragraphs par()
Page page()

A set rule changes the default

styles.typ
Provisional

#set text(fill: brand-color.primary)

Provisional

The word Provisional in black, then the word Provisional in brand green below it.

Adding show scopes it to an element

styles.typ
#show heading: set text(fill: brand-color.primary)

= Summary

Ordinary text here

== Manufacturing

Two brand green headings, Summary large and Manufacturing smaller beneath it.

A show-set rule: the show keyword, a selector, a colon, then a set rule.

Typst: styling →

.where() narrows the selector

styles.typ
#show heading.where(level: 1): set text(fill: brand-color.primary)

= Summary
== Manufacturing

A brand green Summary heading with a black Manufacturing heading beneath it.

Your .typ is already full of these

report.typ
#set page(
  paper: "us-letter",
  margin: (x: 1.25in, y: 1.25in),
  numbering: "1",
  columns: 1,
)

#set text(fill: brand-color.foreground)
#show heading: set text(font: ("Atkinson Hyperlegible",), weight: 600, )
#show link: set text(fill: rgb("#1b5e4f"), )

Your turn 3: set and show rules

In report.qmd, add a new raw block above the badge, so the rules apply to everything below.

  1. Write a set rule to loosen the paragraphs — par has a leading argument. par →

  2. Write a show-set rule to centre the section headings. align →

  3. Challenge: step 2 caught ### Manufacturing too. Narrow the selector to the top-level sections only.

Exercise: 04-typst/03-set-and-show

One answer

The middle of the rendered PDF. The Summary, Industry detail and Notes headings are centred, while the Manufacturing subsection heading stays at the left margin. Paragraph lines sit further apart than before.

report.qmd
```{=typst}
#set par(leading: 1em)
#show heading.where(level: 1): set align(center)
```

Further customization

When a raw block is not enough

You want Reach for
rules used across documents include-before: styles.typ
a header or footer on every page override page.typ
a different title block override typst-show.typ, typst-template.typ

Seed with the defaults, hand to an agent

I've added page.typ, typst-show.typ and typst-template.typ, 
unedited copies of Quarto's Typst partials, already registered in report.qmd. 
Edit the partials to rebuild the title block: 
title and subtitle alongside a right aligned the brand logo, 
the reference period and region, 
then the release status as a badge. 

With a little iteration…

A rebuilt PDF title block. Regional Employment Indicators is large and bold at the left with the subtitle beneath it, and the agency logo and wordmark at the right. Below, in small grey letter-spaced capitals, Q3 2026 dot OREGON, then a dark green badge reading PROVISIONAL in white capitals. Author and Published run beneath in two labelled columns, above a dark green rule.

Example: 04-typst/05-title-block

Wrapping up

When LaTeX is still right

  • an existing journal template — different language, no path across
  • an established math workflow built on LaTeX packages
  • HTML, when screen-reader users are a primary audience

Free with Typst

  • gt tables. Structure and CSS styling both translate. Stay in R.
  • pdf-standard: ua-1. One line, and a tagged PDF.
  • Page layout. papersize, margin, page-numbering, columns.

Questions?

Resources

Reading .typ in your editor

Tinymist Typst — syntax highlighting and completion for .typ files.

myriad-dreamin.tinymist

Open VSX · VS Code Marketplace · Docs

Where the Typst partials live

numbering.typ
definitions.typ
typst-template.typ
page.typ
typst-show.typ
notes.typ
biblio.typ

Source for a version: quarto-cli/src/resources/formats/typst/pandoc/quarto