10 KiB
Hiccup → Typst Mapping Reference
Hiccup Syntax
All elements follow standard Hiccup:
[:tag {attrs} children...]
- tag: Element type (keyword)
- attrs: Optional map of attributes
- children: Content (strings, nested elements, or expressions)
Element Categories
| Category | Examples | Children |
|---|---|---|
| Content elements | heading, strong, emph, block |
Display content |
| Source elements | link, image, bibliography |
link has display text; others have none |
| List elements | list, enum, table |
Each child = one item/cell |
| Container elements | figure |
Single content child (compiled as function arg) |
| Math | math |
Math expression string |
| References | ref, cite |
None |
| Code | raw |
Code string to display |
| Rules | set |
None |
| Show rules | show |
A set rule as child |
Reserved Attributes
| Attr | Used by | Purpose |
|---|---|---|
:src |
link, image, bibliography |
Source URL/path |
:label |
Any element | Makes element referenceable |
:caption |
figure |
Figure caption text |
:target |
ref |
Label to reference |
:keys |
cite |
Citation key(s) |
:element |
set, show |
Element type to configure |
:block |
math, raw |
Display as block (default: inline) |
:lang |
raw |
Code language for syntax highlighting |
Examples
;; Content element
[:heading {:level 2} "Section Title"]
;; Source element with children
[:link {:src "https://example.com"} "Click here"]
;; Source element without children
[:image {:src "diagram.png" :width "80%"}]
;; List element
[:list "First" "Second" "Third"]
;; Container with label
[:figure {:caption "Results" :label :fig/results}
[:image {:src "chart.png"}]]
;; Math (inline and block)
[:math "E = m c^2"]
[:math {:block true} "sum_(i=0)^n i"]
;; Reference and citation
[:ref {:target :fig/results}]
[:cite {:keys [:smith2020 :jones2021]}]
;; Code display
[:raw {:lang "python" :block true} "print('hello')"]
;; Set rule
[:set {:element :page :margin "2cm"}]
;; Show rule (child is the set rule to apply)
[:show {:element :heading}
[:set {:element :text :fill "blue"}]]
Audit of Current Tags
✅ Valid - Direct Typst Functions
| Hiccup | Typst | Notes |
|---|---|---|
[:heading {:level 2} "Text"] |
#heading(level: 2)[Text] |
✅ Works |
[:strong "text"] |
#strong[text] |
✅ Works |
[:emph "text"] |
#emph[text] |
✅ Works |
[:link {:src "url"} "text"] |
#link("url")[text] |
✅ Works |
[:image {:src "path.png" :width "50%"}] |
#image("path.png", width: 50%) |
✅ Works |
[:block {:inset "1em"} ...] |
#block(inset: 1em)[...] |
✅ Works |
[:bibliography {:src "refs.bib"}] |
#bibliography("refs.bib") |
✅ Works |
❌ Invalid - Don't Exist in Typst
| Bad Hiccup | Problem | Fix |
|---|---|---|
[:p "text"] |
No paragraph function | Just use strings, paragraphs are implicit |
[:item "text"] |
Not a function | Items are args to list() |
[:cell "text"] |
Not a function | Cells are args to table() |
[:div ...] |
HTML, not Typst | Use [:block ...] |
[:span ...] |
HTML, not Typst | Just inline content, or [:box ...] |
[:aside ...] |
Doesn't exist | Use [:block ...] with styling |
[:section ...] |
Doesn't exist | Just use headings |
[:doc ...] |
Doesn't exist | Top-level is just content |
[:table-row ...] |
Doesn't exist | Tables are flat |
⚠️ Needs Adjustment
| Current | Problem | Correct |
|---|---|---|
[:linebreak] |
Function name | [:linebreak] → #linebreak() ✅ actually works |
[:h-space "2em"] |
Wrong name | [:h "2em"] → #h(2em) |
[:figure [:image ...] [:caption ...]] |
Caption isn't a child | Caption goes in attrs |
[:code {:lang "py"} "..."] |
Function is raw |
[:raw {:lang "python"} "..."] |
Corrected Mappings
Lists
Typst list takes content blocks as positional args, not :item children:
;; OLD (wrong)
[:list
[:item "First"]
[:item "Second"]]
;; NEW (correct)
[:list "First" "Second"]
;; => #list([First], [Second])
;; With formatted items
[:list
[:strong "Important"]
"Regular item"]
;; => #list([#strong[Important]], [Regular item])
;; Nested
[:list
"Top level"
[:list "Nested 1" "Nested 2"]]
Typst output:
#list(
[First],
[Second],
)
Tables
Same pattern - cells are positional args:
;; OLD (wrong)
[:table {:columns 2}
[:cell "A"] [:cell "B"]
[:cell "1"] [:cell "2"]]
;; NEW (correct)
[:table {:columns 2}
"A" "B"
"1" "2"]
;; => #table(columns: 2, [A], [B], [1], [2])
;; With formatting
[:table {:columns 2}
[:strong "Header 1"] [:strong "Header 2"]
"Data 1" "Data 2"]
Typst output:
#table(
columns: 2,
[#strong[Header 1]], [#strong[Header 2]],
[Data 1], [Data 2],
)
Figures
Caption is a named parameter, not a child element:
;; OLD (wrong)
[:figure
[:image "arch.png"]
[:caption "Architecture diagram"]]
;; NEW (correct)
[:figure {:caption "Architecture diagram"}
[:image {:src "arch.png"}]]
;; => #figure(image("arch.png"), caption: [Architecture diagram])
;; With label for referencing
[:figure {:caption "Architecture" :label :fig/arch}
[:image {:src "arch.png" :width "80%"}]]
;; => #figure(image("arch.png", width: 80%), caption: [Architecture]) <fig:arch>
Typst output:
#figure(
image("arch.png", width: 80%),
caption: [Architecture],
) <fig:arch>
Math
;; Inline math (default)
[:math "x^2 + y^2"]
;; => $x^2 + y^2$
;; Block/display math
[:math {:block true} "sum_(i=0)^n i"]
;; => $ sum_(i=0)^n i $
Code/Raw
;; Inline code
[:raw "let x = 1"]
;; => `let x = 1`
;; Code block with language
[:raw {:lang "python" :block true} "print('hello')"]
;; => ```python
;; print('hello')
;; ```
Typst output:
#raw("let x = 1")
#raw(lang: "python", block: true, "print('hello')")
References and Citations
;; Reference a label
[:ref {:target :fig/arch}]
;; => @fig:arch
;; Single citation
[:cite {:keys [:smith2020]}]
;; => @smith2020
;; Multiple citations
[:cite {:keys [:smith2020 :jones2021]}]
;; => @smith2020 @jones2021
Horizontal/Vertical Space
[:h "2em"] ;; => #h(2em)
[:v "1em"] ;; => #v(1em)
Page/Document Settings
set and show rules configure document styling. Use :element attr to specify the target:
;; Set rules (affects everything after)
[:set {:element :page :paper "a4" :margin "2cm"}]
;; => #set page(paper: "a4", margin: 2cm)
[:set {:element :text :font "New Computer Modern" :size "11pt"}]
;; => #set text(font: "New Computer Modern", size: 11pt)
;; Show rules (child is the set rule to apply)
[:show {:element :heading}
[:set {:element :text :fill "blue"}]]
;; => #show heading: set text(fill: blue)
Paragraphs
There is no paragraph function. Paragraphs are created by blank lines:
;; Just strings, separated by [:parbreak] or blank content
"First paragraph."
[:parbreak]
"Second paragraph."
;; Or in raw mode, just write naturally
#t"
First paragraph.
Second paragraph.
"
Revised Full Example
(def my-doc
[;; Set rules at top
[:set {:element :page :paper "a4"}]
[:set {:element :text :font "Linux Libertine"}]
[:heading {:level 1} "My Paper"]
"This is the introduction. We discuss "
[:emph "important things"] "."
[:parbreak]
[:heading {:level 2} "Methods"]
"We used the following approach:"
[:list
"Step one"
"Step two"
[:strong "Step three (important)"]]
[:heading {:level 2} "Results"]
"See " [:ref {:target :fig/results}] " and " [:ref {:target :tab/data}] "."
[:figure {:caption "Results visualization" :label :fig/results}
[:image {:src "results.png" :width "80%"}]]
[:figure {:caption "Data table" :label :tab/data}
[:table {:columns 2}
[:strong "Input"] [:strong "Output"]
"1" "1"
"2" "4"
"3" "9"]]
"The equation is " [:math "E = m c^2"] "."
[:math {:block true} "integral_0^infinity e^(-x^2) dif x = sqrt(pi)/2"]
[:bibliography {:src "refs.bib"}]])
Compiles to:
#set page(paper: "a4")
#set text(font: "Linux Libertine")
#heading(level: 1)[My Paper]
This is the introduction. We discuss #emph[important things].
#heading(level: 2)[Methods]
We used the following approach:
#list(
[Step one],
[Step two],
[#strong[Step three (important)]],
)
#heading(level: 2)[Results]
See @fig:results and @tab:data.
#figure(
image("results.png", width: 80%),
caption: [Results visualization],
) <fig:results>
#figure(
table(
columns: 2,
[#strong[Input]], [#strong[Output]],
[1], [1],
[2], [4],
[3], [9],
),
caption: [Data table],
) <tab:data>
The equation is $E = m c^2$.
$ integral_0^infinity e^(-x^2) dif x = sqrt(pi)/2 $
#bibliography("refs.bib")
Typst Gotchas
Important Typst behaviors the compiler must handle:
-
Math variable spacing: In math mode, adjacent letters like
mcare parsed as a single variable name. To represent multiplication, add spaces:$m c^2$not$mc^2$. The compiler should insert spaces between single-letter variables. -
Referenceable tables: Tables cannot have labels directly attached. To make a table referenceable with
@label, wrap it in a#figure():#figure( table(...), caption: [...], ) <label> -
Font availability: Fonts like "Linux Libertine" may not be installed. The compiler should either bundle fonts or use fallbacks.
Summary of Design Decisions
- Removed:
:p,:item,:cell,:div,:span,:aside,:section,:doc - Lists/Tables: Children are direct content, not wrapped in
:item/:cell - Figures: Caption is an attribute; child element compiles as function argument (no
#) - Math: Use
:mathwith optional:blockattr (not:$/:$$) - Code: Use
:rawfor displayed code;#t"..."for Typst passthrough - Space: Use
:h/:vnot:h-space - References: Use
[:ref {:target :label}]and[:cite {:keys [...]}] - Settings: Use
[:set {:element :page ...}]- attrs first with:element - Paragraphs: Use
:parbreakor just content flow - Referenceable tables: Must wrap in
:figurewith:labelattribute