Tutorial2 days ago

1. Installing Typst, and the Two Characters That Break Every First Document

Typst installed in 2.20 seconds and compiled its first PDF in 90 milliseconds. Then we typed a Markdown heading into it and watched the whole thing fall over.

The WJS Desk

Sep 22, 2026 · 9 min read

Photo by Erik Mclean on Pexels

By the end of this part you will have a working Typst install, a compiled PDF, a live preview loop that rebuilds in 80 milliseconds, and a clear idea of the two characters that make every new Typst document fail. One of those failures is loud. The other one exits 0, writes a perfectly valid PDF, and hands you garbage.

Here are the numbers we measured on a stock Apple Silicon Mac running macOS 26.5.1 (build 25F80) with Homebrew 7.0.6. We timed everything with /usr/bin/time -p, which is why the numbers have two decimal places rather than a vibe.

StepTimeNotes
brew install typst2.20 s20.4 MB bottle, cold download
First compile of a one page file1.61 sIncludes the initial font scan
Every compile after that0.09 sFive consecutive runs, 0.09 to 0.10 s
Disk footprint43 MB22 files, zero Homebrew dependencies

What Typst actually is, and why we bothered

Typst is a markup-based typesetting system written in Rust. You write plain text with light markup, run a compiler, and get a PDF. That description also fits LaTeX, which is exactly the point: Typst exists because the people who built it thought LaTeX was solving a 1978 problem with 1978 ergonomics.

The repo sits at 56,184 stars, 1,719 forks and 449 contributors, under Apache-2.0. It was created on 24 September 2019, so this is not a three week old rocket. The current release is v0.15.1, published on 17 July 2026, and we counted 125 commits to main in the 90 days before we started writing. There is a company behind it (typst.app, with a jobs page and a public forum), and the compiler you are about to install is the open core of that business. We will come back to what that means for you in part four.

We picked it because the pitch is testable. "Faster than LaTeX" is a claim you can put a stopwatch on, and over the next four parts we do exactly that, against a real LaTeX engine, on the same source content.

The first LaTeX engine we tested took 8 minutes and 43 seconds to compile a one page document. Typst took 1.61 seconds, and that was its slow run.

Prerequisites, honestly

You need a terminal, Homebrew, and about ten minutes. That is genuinely the whole list. You do not need an account, an API key, a LaTeX distribution, a Python environment or Node. Typst ships as a single static binary that links against five macOS system libraries and nothing else.

We checked that last claim rather than repeating it:

brew deps typst
otool -L /opt/homebrew/bin/typst

brew deps typst printed nothing at all. otool -L listed five entries, all of them CoreFoundation, CoreServices, Security, libiconv and libSystem. For comparison, when we installed Tectonic later in this course as our LaTeX baseline, Homebrew pulled in 23 dependencies including glib, harfbuzz, cairo and icu4c.

Install it

brew install typst
typst --version

That printed typst 0.15.1 (unknown commit). The install took 2.20 seconds of wall clock on our run, downloading a 20.4 MB bottle and unpacking 43 MB into the Cellar. We ran it twice: once with the bottle already sitting in the Homebrew download cache (4.72 s, most of it spent fetching manifests) and once after deleting the cached bottle to force a real download (2.20 s). Your number depends on your connection, but the shape holds: this is a seconds install, not a minutes install.

Pro tip: if you do not use Homebrew, the release page at github.com/typst/typst/releases has prebuilt archives for macOS, Linux and Windows. It is one binary. You can drop it anywhere on your PATH and it will work, because there is nothing else to install.

Your first document, and the timings that matter

Make a scratch directory and write a file. We used /tmp/wjs-typst for everything in this course, so nothing we did touched a real project.

mkdir -p /tmp/wjs-typst
cd /tmp/wjs-typst
cat > hello.typ <<'EOF'
= Hello from a terminal

This is a paragraph. Typst does smart quotes: "like this".

#lorem(40)
EOF
typst compile hello.typ

That produced hello.pdf, 12,340 bytes, one page. The first run took 1.61 seconds because Typst walks your system font directories and builds a cache. Every run after that took 0.09 seconds. We ran it five times in a row to be sure: 0.09, 0.09, 0.10, 0.09, 0.09.

Two details in that tiny file are worth pointing at. = starts a heading, not #. And #lorem(40) is a function call, which is what the hash actually means. Hold that thought for about ninety seconds.

Typst will also write PNG and SVG from the same source, which is how we produced every figure we looked at while testing:

typst compile hello.typ hello.png
typst compile hello.typ hello.svg

The same one page document came out as 12,340 bytes of PDF, 58,708 bytes of PNG at the default 144 PPI, and 67,202 bytes of SVG. If your document has more than one page, PNG and SVG export needs a page number template in the filename, or you get error: cannot export multiple images without a page number template. We hit that one by accident and the fix is to write page-{p}.png instead of page.png.

What broke: the hash, in both directions

Here is the file we wrote on purpose, because it is what anyone arriving from Markdown will type:

cat > markdown-habits.typ <<'EOF'
# My Title

This is **bold** and this is *italic*.

- item one
- item two
EOF
typst compile markdown-habits.typ

Three things happened. First, a hard error with exit code 1:

error: expected expression
  ┌─ markdown-habits.typ:1:1
  │
1 │ # My Title
  │  ^

In Typst, # switches into code mode. # My is not a heading, it is an attempt to evaluate an expression called My. Headings are =, ==, ===. Second, **bold** produced a warning rather than bold text:

warning: no text within stars
  = hint: using multiple consecutive stars (e.g. **) has no additional effect

Single stars are bold in Typst, and underscores are italic. Double stars are an empty strong block wrapped around nothing. Third, the bullet list worked fine, because that one happens to match.

Now the direction nobody warns you about. We wrote this:

cat > nohash.typ <<'EOF'
= Test
lorem(30)
EOF
typst compile nohash.typ

This is the one that costs you an afternoon. No error. No warning. Exit code 0. A valid PDF. And the body of the page is the literal text lorem(30), because without the hash it is just words. We confirmed it by also exporting to HTML and grepping the output, and there it was: lorem(30) sitting in a paragraph tag. Forgetting the hash in front of a function call is a silent failure, and you only catch it by looking at the output.

The mental model that fixes both: prose is the default, and # is the door into code. = is structure. Once that clicks, most of the Markdown muscle memory stops hurting.

Fonts: 513 on your Mac, four in a container

This is the finding we did not expect from a first hour with a tool, and it is the reason this section exists instead of a section about colours.

typst fonts | wc -l
typst fonts --ignore-system-fonts

On our Mac, typst fonts listed 513 font families. With --ignore-system-fonts, it listed exactly four: DejaVu Sans Mono, Libertinus Serif, New Computer Modern, and New Computer Modern Math. Those four are compiled into the binary. Everything else came from macOS.

Now ask for a font that does not exist:

typst compile badfont.typ

You get warning: unknown font family: helvetica neue bold condensed nope, a finished PDF, and exit code 0. We checked the exit code separately with echo $? rather than trusting the pipe, and it really is 0.

So we ran the experiment. One source file asking for Helvetica Neue, compiled twice:

typst compile helv.typ helv-mac.png
typst compile --ignore-system-fonts helv.typ helv-ci.png
md5 helv-mac.png helv-ci.png

Two different files. 84,479 bytes against 77,852 bytes, different checksums, visibly different typefaces, both exit 0, and only the second run printed a warning. That is a build that succeeds on your laptop and quietly produces a different document on a CI runner. We will come back to this in part four with the fix, which is --font-path plus checked-in font files, but you should know about it on day one.

The loop you will actually work in

Nobody compiles by hand. Typst has a watch mode that recompiles on save and can open the result in your PDF viewer:

typst watch hello.typ --open

We measured this on a deliberately large document, 200 pages of generated text and maths, because a 90 ms build on one page proves nothing. Watch mode prints its own timings. The first build took 171.77 ms, and the next four edits recompiled in 114.69 ms, 78.31 ms, 81.04 ms and 80.21 ms. That is incremental compilation working: the numbers fall as the cache warms, on a document 200 times longer than the one we started with.

Confirmed: sub-100 ms rebuilds on a 200 page document, measured by the compiler itself, on a 2026 consumer laptop with no configuration.

Common mistakes

  • Using # for headings. It is =. The hash means "evaluate this".
  • Forgetting # before a function call. Silent. Your function name ends up in the document as text.
  • Using **double stars** for bold. Single stars. Double stars warn and do nothing.
  • Trusting a clean exit. Typst warns about missing fonts and still writes the PDF. If you care which typeface shipped, read the warnings.
  • Exporting multiple pages to PNG without a template. Use out-{p}.png, not out.png.
  • Expecting HTML export to just work. It exists, it needs --features html, and the compiler itself prints "do not rely on this feature for production use cases". We tried it: a level one heading came out as <h2>, not <h1>. The tracking issue is typst/typst#5512.

How to back out

This is the cheapest rollback we have written up in a while, and it is a real reason to try the thing:

brew uninstall typst
rm -rf ~/Library/Caches/typst

That is it. There is no TeX tree, no system font install, no registry of packages under /usr/local, no daemon. Typst keeps downloaded packages in ~/Library/Caches/typst, which was 3.8 MB by the end of our testing, and the binary in the Cellar. Delete both and the machine is exactly as it was. Your .typ files are plain text and stay where you left them.

What we did not test in this part

Everything here is macOS 26.5.1 on Apple Silicon, with Typst 0.15.1 from Homebrew. We did not test Windows, Linux, Intel Macs, or the typst.app web editor, which is a separate hosted product with a free tier that we deliberately stayed out of because this course is about the terminal. The font count of 513 is our machine, not a constant. The 2.20 second install is our connection, not a promise.

Next

Part two is where Typst stops being a nicer Markdown and starts being the reason to switch: the scripting language. We take a real CSV pulled from the GitHub REST API, build a table and a chart out of it, and hit the type error that every single person hits on their first CSV.

Share

Typst installed in 2.20 seconds and compiled a page in 90 ms. Then we found the failure that exits 0, produces a PDF, and renders differently on your laptop than in CI. #Typst #OpenSource #Rust #LaTeX

Never miss a ship

The best stuff that shipped this week, delivered every Thursday. Free, no spam. We read all the boring stuff so you get the fun parts.

Keep reading