Reusable card
A card wraps content with matching style in HTML and paged output.
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.”
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
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.
cardcalepin.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.
]
#calloutcalepin.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,
)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.
]
],
)You can also request more than two columns:
#calepin.elements.columns(
columns: 4,
wrap: true,
[One], [Two], [Three]
)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)
```
]
]This tab shows R code:
x <- c(1, 2, 3, 4, 5)
mean(x)
[1] 3This tab shows Python code:
x = [1, 2, 3, 4, 5]
sum(x) / len(x)
3.0lightbox-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,
)For lower-level browser-only components, see Custom web elements.