Templating
Theme layouts are MiniJinja templates. The HTML layouts (layouts/site.html and layouts/document.html) and the paged layout (layouts/pdf.typ) all use the same engine, so the syntax on this page applies to both. The HTML templates and PDF templates pages document the values each layout receives.
Syntax
MiniJinja has two kinds of tags.
Interpolation prints a value:
<title>{{ doc.title }}</title>Statements control flow. Loops repeat a block:
{% for file in css %}
<style>
{{ file.content }}
</style>
{% endfor %}Conditionals show a block only when a value is set:
{% if site.logo %}
<img src="{{ site.logo }}" alt="{{ site.logo_alt }}">
{% endif %}Includes pull in another template file, which is how themes share partials:
{% include "partials/site-footer.html" %}The MiniJinja syntax reference covers the rest: filters, tests, macros, and more.
Context
Each layout receives a context: a set of named values you reference with {{ }}. The available names depend on the target.
- HTML layouts receive
site,css,js,doc,theme,target,vars, and more. See HTML templates. - The paged layout receives
doc,theme,target, andvars. See PDF templates.
Both targets receive theme, target, and vars.
Variables
Pass project-specific template values with --set vars.<name>=...:
calepin compile notebook.typ --set vars.course="Econ 101" --set vars.semester="Fall 2026"
These values are available as a top-level vars in both HTML and paged layouts. Document-level calepin.setup(vars: ...) values are merged into the same map, and CLI --set vars.<name>=... values take precedence. In HTML templates, vars sits at the top level, not under site:
<p>{{ vars.course }}, {{ vars.semester }}</p>In a layouts/pdf.typ paged layout, read the same values and emit Typst:
#let course = "{{ vars.course }}"