- 3 Minutes Wednesdays
- Posts
- 3MW (Creating Beautiful PDF Reports with Typst and Quarto)
3MW (Creating Beautiful PDF Reports with Typst and Quarto)
Enjoy 3 Minute Wednesdays at no cost.
This newsletter is brought to you for free. If you want to advance your R skills and support my work at the same time, then you will like my paid offerings:
Guten Tag!
Many greetings from Munich, Germany. Have you ever heard of the Typst project? It is a lightweight, Markdown-inspired language designed for creating beautiful, customized PDF documents with minimal effort.
Basically, it is supposed to be like LaTeX but muuuch easier. And the coolest part? You can use Typst from within Quarto to create stunning PDF reports.
So in today’s newsletter, I’ll walk you through how to style our PDF document from last week. Let’s dive in
Set Typst as Your PDF Format
To start using Typst from Quarto, go into your .qmd
file and specify typst
as the output format in the YAML header:

If you render this now, you’ll get a similar PDF output as before.

Add Custom Styling with Typst Templates
To make our PDF look good, we’ll use template partials provided by Typst. There are two key files that we need to mention in the YAML header for that:

Both are plain text files that you can create by just taking a .txt
file and changing the extension to .typ
. So the more important question is: “How do we fill these files?” Let’s go through both files.
Filling typst-show.typ
This file defines the variables that are passed into the layout, such as the title, author, and parameters from your Quarto YAML. You see, what you you define in the Quarto YAML header isn’t automatically known to Typst. That’s why you need to transfer it. For that you use the #show
function from Typst.
There, you specify that your “article” layout (that’s the default name of the used layout) is equipped with a set of variables.

The syntax takes some getting used to. Basically, you always write

Yeah, I know. As I said, it takes some getting used to. And for drilling down nested YAML structures, you can just use a dot symbol (.
). With that we know enough Typst code to transfer title
, author
and params.species
from our Quarto YAML header to Typst.

Filling typst-template.typ
Nice. One file down, one more to go.
In the typst-template.typ
file, we define the actual article layout of our PDF report. For that, we use the #let
function.

Inside the function body (indicated by {}
), we can now set attributes of
document
(contains metadata),text
(defines text properties of the document), andpage
(defines the properties of every page in the document).
Have a look at the code and see if you can make out what it does. I like to think that it’s somewhat manageable to piece together what it does. But don’t worry. I’ll explain afterwards anyway.

So here, we have used the set
statement to specify properties of the document
, text
and page
objects. There, we’ve set some of the objects attributes (like title
, font
or paper
) to values that were
either hard-coded (like
"Source Sans Pro"
or the margins) orfilled using arguments of the
#let()
function (likeauthor
orpaper
).
Add a page
So far so good. We’ve set a couple of things. But we haven’t added any content to the layout yet. Let’s change that.
Let’s insert a page using the page()
function. In there, we can make sure that everything is left-aligned and starts at the top of the page using align()
. The syntax for that is

Add content
See that line that says “page content goes” here? That’s where we can place the content of the page. Here, let’s just try to use the body
variable from the article()
function to insert everything that is inside our Quarto document.

That should be a valid layout. Let’s try to render this. Here’s what we’ll get:

Oh no. That’s a bit dumb. Instead of understanding body
as variable, Typst things that we actually want to use a literal text. But we have used other variables just like that before, haven’t we? What’s going on?
Markdown vs evaluation context
You see, we are currently filling the align()
function inside the page()
function with content. This content is inside brackets []
where it is evaluated as Markdown. Not in any functional kind of way. And the magical signal for Typst to switch to an evaluated context is #
.
That’s also why the let()
function to define the article layout started with #
. Otherwise, Typst would think that our file is just filled with plain text and won’t evaluate anything. And the same has do be done now that we are inside a Markdown content block inside the brackets of align()
. So, let’s try this:

Ahh much better. There’s all of our content. And notice how everything fits on one page now.

Add our parameters
Similarly, we can add our parameters using the text()
function. The syntax is the same as with align()
:
First, we fill
text()
with arguments liketext(size:1.6em)
.Then, we create actual content inside the
brackets
like `text(size: 1.6em)[content].And finally we have to use
#
inside the Markdown content blocks to use functions and variables.

This will give us this terrible look.

Let’s add some vertical space with the v()
function.

And then we have way more space:

Ahh much better. Let that sink in for now. Next week, I’m showing you how to add colors to our designs.
And as always, if you have any questions, or just want to reach out, feel free to contact me by replying to this mail or finding me on LinkedIn or on Bluesky.
See you next week,
Albert 👋
Enjoyed this newsletter? Here are other ways I can help you:
Reply