TITLE: Typst template DATE: 2026-01-31 AUTHOR: John L. Godlee ==================================================================== In a previous post I created a LaTeX template to keep track of my preferred configuration for typesetting notes and reports. I still use LaTeX a lot, but I've been getting interested in Typst, a modern document typesetting system that could be a viable replacement. Typst fixes a lot of issues that made LaTeX annoying to use: [a previous post]: /posts/personal/2019-10-05-sty-latex.md [Typst]: https://typst.app/ - LaTeX installations are huge, 3.5 -- 5 GB. - Compilation of large documents is slow. My PhD thesis took around 5 minutes to compile from scratch. - Error messages are cryptic. - The syntax of LaTeX macros is arcane. In order to learn how Typst works, I tried to re-create my LaTeX document template. I also experimented with a few new features. These are the basic verbs of Typst: - set allows you to globally define the options for an existing element. - let is for defining new elements. - show allows you to customise an existing element. I will go through the template line by line: Firstly import external packages. * means import all functions from these packages. Compared to LaTeX, I need far fewer packages to create a nice looking document. #import "@preview/booktabs:0.0.4": * // booktabs style tables #import "@preview/zero:0.6.0": * // similar to siunitx Define options for the template, which can be set in the document which calls the template. // mynotes.typ #let project( title: "", authors: (), line_numbers: false, draft_mode: false, anonymous: false, spacing: "normal", body) = { Basic document settings, including the title, authors, page geometry and fonts. 2.54 cm margins matches the default for Microsoft Word documents. Pages are numbered, with numbers at the bottom of the page in the centre. The font is "CMU Serif", which was designed by Donald Knuth for TeX. // Set document metadata set document(title: title, author: authors) // Page geometry set page( paper: "a4", margin: ( top: 2.54cm, bottom: 2.54cm, left: 2.54cm, right: 2.54cm ), numbering: "1", number-align: center, ) // Font set text( font: "CMU Serif", size: 11pt, lang: "en", region: "gb" ) Adjust paragraph and heading spacing. Colour hyperlinks. Bump the bibliography to a new page. // Heading numbering set heading( numbering: "1." ) // Paragraph spacing set par( spacing: 0.55cm ) // Heading spacing show heading: set block( above: 1.5em, below: 1.0em ) // Heading text size show heading: set text(size: 1.1em) // Hyperlink colour show link: set text(fill: rgb("#336666")) // Bibliography on a new page show bibliography: it => { pagebreak() it } Define a toggle for line numbering. If the option "line_numbers: true" when the template is called, the document will have line numbers. // Line number toggle show: it => { if line_numbers { set par.line(numbering: "1") it } else { it } } This is one of the new features, a toggle which adds a "DRAFT" watermark across the page. // Draft mode toggle show: it => { if draft_mode { set page(background: rotate(45deg, text(80pt, fill: rgb("EEEEEE"))[DRAFT])) it } else { it } } Another new feature to toggle the spacing of the document. Useful if I want to print and annotate a draft. // Spacing toggle show: it => { if spacing == "compact" { set par(leading: 0.5em, justify: true) set page(margin: 1.5cm) it } else if spacing == "wide" { set par(leading: 1.5em) it } else { it } } Another new feature to anonymise the authors by replacing the names with a black bar. // Control for potentially multiple authors let authors = if type(authors) == str { (authors,) } else { authors } // Anonymous author names toggle show: it => { if anonymous { for author in authors { show author: name => box(fill: black, radius: 1pt, hide(name)) } it } else { it } } // Title and author formatting align(center)[ #block(text(size: 2em, weight: "bold")[#title]) #text(size: 1em)[ #{ let names = authors.join(", ", last: ", and ") if anonymous { // Optional redaction of author names box(fill: black, radius: 1pt, hide(names)) } else { names } } ] #v(1em) ] Set the style for tables and figures. // Reset table style set table( stroke: none, gutter: 0pt, inset: 0.5em, ) // Use booktabs default style show table: it => { booktabs-default-table-style(it) } // Move table captions above, but keep image captions below show figure.where(kind: table): set figure.caption(position: top) // Left-align figure captions show figure.caption: it => { block(width: 90%, align(left)[ #it ]) } // Add space between the caption and the table show figure.where(kind: table): set block(spacing: 1.5em) // Render content body } Define a new variable to highlight TODO items in red with a pale red background and prefixed with "TODO:". // Define todo function #let todo(body) = { highlight(fill: red.lighten(80%))[ #text(fill: red.darken(20%), weight: "bold")[TODO:] #body ] } Configure the zero package, which formats numbers. // Configure {zero} #set-group( size: 3, separator: ",", threshold: (integer: 4, fractional: calc.inf), ) Then in the main document I can do this to call the template if the template is in the project directory: #import "mynotes.typ": * #show: project.with( title: "Testing the Typst template", authors: ("John L. Godlee"), line_numbers: false, draft_mode: false, anonymous: false, spacing: "normal", ) I can also put the template here to call it from anywhere: Library/Application Support/typst/ └── packages └── local └── mynotes └── 0.1.0 ├── mynotes.typ └── typst.toml The typst.toml looks like this: [package] name = "mynotes" version = "0.1.0" entrypoint = "mynotes.typ" Then I can call it in a document like so: #import "@local/mynotes:0.1.0": * #show: project.with( title: "Testing the Typst template", authors: ("John L. Godlee"), line_numbers: false, draft_mode: false, anonymous: false, spacing: "normal", ) ![Screenshot of Typst document.](https://johngodlee.xyz/img_full/typst_template/typst1.png ) ![Screenshot of Typst document.](https://johngodlee.xyz/img_full/typst_template/typst2.png ) To streamline the editing process I am using the chomosuke/typst-preview.nvim neovim plugin, which lets you compile Typst documents inside neovim, and provides a live preview of the document in a web browser. It relies on the tinymist LSP. This is the config I'm using with Lazy.nvim: [chomosuke/typst-preview.nvim]: https://github.com/chomosuke/typst-preview.nvim [tinymist]: https://github.com/Myriad-Dreamin/tinymist -- Typst return { "chomosuke/typst-preview.nvim", ft = "typst", -- Only load when opening Typst files version = "1.*", build = function() require("typst-preview").update() end, config = function() require("typst-preview").setup({ debug = false, render_on_save = false, follow_cursor = true, invert_colors = "auto", }) end, }