Typst template reference
How data flows in
When a document is generated, field values are passed as a dictionary called dict. Reference any field with dict.fieldname.
// Field values passed at render time Hello, #dict.name // In expressions #let total = float(dict.amount) * 1.2
Field values are always strings. Use int(), float(), or json.decode() to convert them.
Field type annotations
Add // @type: comments to control the input shown on the Generate form and in public Forms. Without an annotation, field types are inferred from the field name.
#let data = ( name: "Alice Johnson", // @type: text dob: "15 Jan 1990", // @type: date @format: DD MMM YYYY amount: "5000", // @type: number notes: "Top performer", // @type: textarea active: "true", // @type: boolean logo: "logo.png", // @type: image brand_color: "#1a56db", // @type: color website: "https://example.com", // @type: url label: "Ship Date", // @label: Custom Label )
Date fields require @format. Writing @type: date without @format keeps the field as plain text — the format tells the system how to convert the date picker value before injecting it.
Date format tokens
Using a color field
#let data = ( brand_color: "#1a56db", // @type: color ) // Use rgb() to convert the hex value #rect(fill: rgb(data.brand_color), width: 100%)
Array fields (line items, lists)
Pass arrays as a JSON string. Decode with json.decode() then loop with for.
// dict.items = '[{"name":"Widget","qty":2,"price":9.99}]'
#let items = json.decode(dict.items)
#for item in items [
#item.name — #item.qty × #item.price
]Page setup
// A4 portrait #set page(paper: "a4", margin: (top: 2cm, bottom: 2cm, left: 2.5cm, right: 2.5cm)) // A4 landscape #set page(paper: "a4", flipped: true, margin: 1.5cm) // US Letter #set page(paper: "us-letter", margin: 1in) // Custom size #set page(width: 10cm, height: 10cm, margin: 8mm)
Text and fonts
// Document-wide default
#set text(font: "Inter", size: 11pt, fill: rgb("#1a1a1a"))
// Inline formatting
*bold* _italic_ #underline[underlined]
// Apply font to a single span
#text(font: "Playfair Display", size: 18pt, weight: "600")[Certificate of Completion]
// Apply font to multiple lines — use a scoped block
#[
#set text(font: "Playfair Display", size: 14pt)
This line uses Playfair Display.
So does this one.
And this one.
]
// Back to the document default here
// Style headings globally
#show heading.where(level: 1): it => text(font: "Playfair Display", size: 22pt, weight: "bold")[#it.body]Available fonts
Use any of these in font: "Name". Custom fonts uploaded to your Brand Kit are also available.
Colors
rgb("#e63946") // hex
rgb(230, 57, 70) // r, g, b (0–255)
luma(80%) // grayscale
color.mix(red, blue) // mix
// Usage
#set text(fill: rgb("#333333"))
#rect(fill: rgb("#f0f0f0"), stroke: rgb("#cccccc"))Images (assets)
Images must be uploaded in the Assets tab before they can be used.
// Full width
#image("logo.png", width: 100%)
// Fixed size
#image("logo.png", width: 60pt, height: 60pt, fit: "contain")
// Fit modes: "contain" | "cover" | "stretch"
// Conditionally show
#if dict.show_logo == "true" {
image("logo.png", width: 80pt)
}Layout
// Two columns
#columns(2, gutter: 1cm)[
Left column content
#colbreak()
Right column content
]
// Box / container
#block(
width: 100%,
fill: rgb("#f8f8f8"),
radius: 4pt,
inset: (x: 12pt, y: 10pt),
)[
Boxed content here
]
// Horizontal stack
#grid(
columns: (1fr, auto),
align: horizon,
)[
Left content
][
Right content
]Tables
#table(
columns: (2fr, 1fr, 1fr),
stroke: 0.5pt + rgb("#dddddd"),
fill: (_, row) => if row == 0 { rgb("#f0f0f0") } else { white },
inset: (x: 8pt, y: 6pt),
// Header row
[*Description*], [*Qty*], [*Price*],
// Data rows from array field
..{
let items = json.decode(dict.items)
items.map(item => (
[#item.description],
[#item.qty],
[$#item.price],
)).flatten()
}
)Conditionals
// Show/hide a section #if dict.show_badge == "true" [ #text(fill: green)[Verified] ] // if / else #if float(dict.balance) < 0 [ #text(fill: red)[Overdue] ] else [ #text(fill: green)[Paid] ]
Common patterns
Centered certificate heading
#set align(center) #v(3cm) #text(font: "Playfair Display", size: 28pt, weight: "bold")[ Certificate of Achievement ] #v(0.5cm) #text(size: 14pt)[Awarded to] #v(0.3cm) #text(font: "Playfair Display", size: 22pt)[#dict.recipient_name]
Invoice total row
#let items = json.decode(dict.line_items) #let total = items.fold(0.0, (acc, i) => acc + float(i.price) * float(i.qty)) Total: *$#str(calc.round(total, digits: 2))*
Page header with logo + company name
#grid(
columns: (auto, 1fr),
gutter: 12pt,
align: horizon,
)[
#image("logo.png", height: 36pt)
][
#text(font: "Inter", size: 10pt, fill: rgb("#666"))[#dict.company_name]
]
#line(length: 100%, stroke: 0.5pt + rgb("#dddddd"))
#v(0.5cm)Last updated on