Reusable elements

In Typst, #let is your primary building block for reusable formatting and construction. Use it to avoid repeating the same patterns.

#let banner(message, note) = [
  #strong(message)
  #v(0.3em)
  #quote(note)
]

#banner("Reusable functions", "Define UI once and reuse it for many places.")

Reusable function in action

“If you update this definition, both call sites update automatically.”

Output-aware formatting

Calepin exposes #calepin.elements.target() to pick content for HTML, static outputs, or a fallback. This example uses colors to make output differences explicit. In Typst 0.15, HTML color output was not fully supported, so HTML branches use explicit CSS via html.elem().

#let html-color-label(body, color) = html.elem("span", attrs: (style: "color: " + color + ";"))[
  #body
]

#calepin.elements.target(
  html: () => [#html-color-label([#strong("HTML")], "blue") output branch],
  paged: () => [#text(fill: green)[#strong("PDF/SVG")] output branch],
  fallback: () => [#text(fill: gray)[#strong("Fallback")] output branch],
)

The rendered result:

HTML output branch

Built-in elements

The namespace includes a compact set of reusable elements for cards, galleries, columns, tabs, and lightbox media. They are designed to demonstrate output-aware patterning in practical notebook websites.

card

calepin.elements.card keeps one piece of content boxed consistently across formats.

#let callout = calepin.elements.card[
  #heading(level: 3)[Reusable card]
  A card wraps content with matching style in HTML and paged output.
]
#callout

Reusable card

A card wraps content with matching style in HTML and paged output.

calepin.elements.gallery accepts image items as tuples or dictionaries and handles local image metadata automatically in static outputs. In HTML output, it can activate lightbox behavior.

#calepin.elements.gallery(
  (
    ("../assets/flowers_01.jpg", "First flower", [First flower]),
    ("../assets/flowers_04.jpg", "Fourth flower", [Fourth flower]),
    ("../assets/flowers_02.jpg", "Second flower", [Second flower]),
    ("../assets/flowers_03.jpg", "Third flower", [Third flower]),
    ("../assets/flowers_05.jpg", "Fifth flower", [Fifth flower]),
  ),
  columns: 3,
  max-width: 42em,
)

Columns

calepin.elements.columns is output-aware, so the same call produces a plain Pico .grid for HTML and a grid(...) for paged output. Columns are equal-width; pass the paged-output column count as an integer. By default, each item is wrapped in a <div> so plain Typst blocks stay together as one column; use wrap: false when the items already render as standalone HTML elements such as cards.

#calepin.elements.columns(
  columns: 2,
  wrap: false,
  [
    #calepin.elements.card[
      #heading(level: 3)[Left]
      Use an equal-width two-column layout for related content.
    ]
  ],
  [
    #calepin.elements.card[
      #heading(level: 3)[Right]
      Paged output renders this via Typst `grid(...)`; HTML uses `<div class="grid">` by default.
    ]
  ],
)

Left

Use an equal-width two-column layout for related content.

Paged output renders this via Typst grid(...); HTML uses <div class="grid"> by default.

You can also request more than two columns:

#calepin.elements.columns(
  columns: 4,
  wrap: true,
  [One], [Two], [Three]
)
One
Two
Three

Tabs

calepin.elements.tabs renders Web Awesome tabs in HTML and lists each enabled panel in paged output. Use calepin.elements.tabs[...] as the group and calepin.elements.tab("Label", active: true)[...] for each panel. Panel names are generated automatically; pass name: "..." only when you need a stable custom panel id. Fenced code chunks inside tabs are still discovered and executed.

#calepin.elements.tabs[
  #calepin.elements.tab("R", active: true)[
This tab shows R code:

```r
x <- c(1, 2, 3, 4, 5)
mean(x)
```
  ]

  #calepin.elements.tab("Python")[
This tab shows Python code:

```python
x = [1, 2, 3, 4, 5]
sum(x) / len(x)
```
  ]
]
R

This tab shows R code:

x <- c(1, 2, 3, 4, 5)
mean(x)
[1] 3
Python

This tab shows Python code:

x = [1, 2, 3, 4, 5]
sum(x) / len(x)
3.0

lightbox-image(...) and lightbox-video(...) produce browser-only interactive media wrappers in HTML while degrading gracefully in paged output.

#calepin.elements.lightbox-image(
  "editor-image",
  "../assets/screenshot_notebook.png",
  "Notebook screenshot",
  width: 16em,
)
#calepin.elements.lightbox-video(
  "editor-video",
  "../assets/calepin_vscode.mp4",
  poster: "../assets/calepin_vscode-thumb.png",
  width: 16em,
)
Notebook screenshot

For lower-level browser-only components, see Custom web elements.