Files
typlisp/typst-mapping.md
2026-01-30 09:45:24 -10:00

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:

  1. Math variable spacing: In math mode, adjacent letters like mc are 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.

  2. Referenceable tables: Tables cannot have labels directly attached. To make a table referenceable with @label, wrap it in a #figure():

    #figure(
      table(...),
      caption: [...],
    ) <label>
    
  3. Font availability: Fonts like "Linux Libertine" may not be installed. The compiler should either bundle fonts or use fallbacks.

Summary of Design Decisions

  1. Removed: :p, :item, :cell, :div, :span, :aside, :section, :doc
  2. Lists/Tables: Children are direct content, not wrapped in :item/:cell
  3. Figures: Caption is an attribute; child element compiles as function argument (no #)
  4. Math: Use :math with optional :block attr (not :$/:$$)
  5. Code: Use :raw for displayed code; #t"..." for Typst passthrough
  6. Space: Use :h/:v not :h-space
  7. References: Use [:ref {:target :label}] and [:cite {:keys [...]}]
  8. Settings: Use [:set {:element :page ...}] - attrs first with :element
  9. Paragraphs: Use :parbreak or just content flow
  10. Referenceable tables: Must wrap in :figure with :label attribute