A Quarto filter that renders TikZ diagrams as inline SVG in the DOM — real
<path>/<text> elements the page's CSS sees and styles directly — instead of
opaque <img> files. Built to solve the two classic pain points of TikZ in
Quarto: size coordination with surrounding text, and light/dark theming.
Each {.tikz} code block is compiled latex → dvisvgm (text kept as SVG
<text> with embedded WOFF2 subsets of the TeX fonts), then post-processed:
- Size:
width/heightattributes are stripped (onlyviewBoxremains) and the SVG is sized inem(TeX pt width ÷ base font size). Diagrams — geometry and labels together — scale with the surrounding font size. - Color: the default ink is a sentinel (
#010101) thatteacup.cssmaps tocurrentColor, so labels and strokes inherit the page text color. Named palette colors (accent,accent2–accent5,muted,canvas) are sentinels remapped to CSS custom properties (--teacup-accent, …) via attribute selectors. Dark mode is therefore pure CSS; no re-rendering. - Scoping: dvisvgm names embedded font subsets and text classes identically
in every SVG (
cmr10,text.f0). Inline<style>is document-global, so fonts and selectors are renamed/scoped per diagram (hash-derived id) to prevent one diagram's font subset from shadowing another's. - Caching: compiled SVGs are cached in
_teacup-cache/keyed by SHA-1 of the generated TeX source. - PDF builds: for
latexoutput the TikZ passes through as raw LaTeX.
quarto add fNBU/teacupThis installs the extension under the _extensions subdirectory of your
project. If you're using version control, check that directory in. Requires
Quarto ≥ 1.4 and the TeX toolchain listed under Requirements below.
---
filters: [teacup]
---
```{.tikz}
\draw[->, thick, accent] (0,0) -- (3,1) node[right] {$u$};
```Bare drawing commands are wrapped in tikzpicture automatically; a full
\begin{tikzpicture}…\end{tikzpicture} is also accepted.
Block attributes: width="30%" (or any CSS width) overrides the computed em
width; extra classes and an #id are carried onto the <svg> element.
Document metadata under teacup::
preamble:extra LaTeX preamble (string or list)font-size:base font size in pt: 10, 11 or 12 (default 10; article class ignores other values, which would desynchronize the em conversion)cache:cache directory (default_teacup-cache)engine:latex(default, lightest) ordvilualatex(full unicode input; needstexlive-luatex)
Override the custom properties in your theme SCSS/CSS:
body.quarto-dark { --teacup-accent: #ffc04d; }Defaults for body.quarto-light / body.quarto-dark ship in teacup.css.
The sentinel-remapping rules themselves are generated from PALETTE in
teacup.lua at render time and injected into the document head — PALETTE
is the single source of truth for sentinel hexes and palette names.
- Shadings don't theme. Gradient color stops (
\shade,\shadedraw) keep their compiled colors: the sentinel remapping targetsfill/strokeattributes, notstop-color, so a shading between palette colors will not follow--teacup-*overrides in dark mode. Solid fills and strokes theme as documented. - Sentinel hexes are reserved. A user color that happens to equal a
sentinel hex (see
PALETTEinteacup.lua, e.g.#FDFDFC=canvas) is indistinguishable from the palette color and gets remapped —#FDFDFCbecomes transparent unless themed. Use the palette names, or any hex not in the sentinel table. \tikzexternalizeis incompatible. Theexternallibrary tries to manage its own compilation cache, which cannot work inside teacup's per-block standalone compiles; it fails the render with a confusing LaTeX error. Remove it for HTML builds — teacup already caches compiled diagrams by content hash.
Minimal Debian/Ubuntu install:
apt install texlive-pictures dvisvgm(texlive-pictures provides pgf/TikZ and pulls texlive-latex-base,
texlive-latex-recommended (for xcolor), texlive-base and
texlive-binaries — about 210 MB total. Neither texlive-latex-extra nor
texlive-luatex is needed: the template uses article, not standalone,
and the default engine is plain latex.)
dvisvgm≥ 3.0 (3.1+ not required;--currentcoloris not used)- Quarto ≥ 1.4
Common commands via just:
just test— unit tests (test/unit.lua, pure Lua post-processing, no TeX needed) plus end-to-end tests (test/e2e.sh, renderstest/fixtures/and asserts on the HTML: inline SVGs, em sizing, width override, font/style scoping, single CSS injection, cache reuse, and useful LaTeX error output)just example— render the demo;just preview— render and open itjust clean— remove rendered outputs and caches
The unit tests load the filter with TEACUP_TEST=1, which makes it expose
its internals as teacup_internals.
quarto render example.qmd — three diagrams exercising em-sizing, palette
colors, math labels, and a width override. Toggle the page's dark mode to see
diagrams follow the theme.
See CONTRIBUTING.md. MIT-licensed (LICENSE).