<?xml version="1.0" encoding="UTF-8"?>
<rss  xmlns:atom="http://www.w3.org/2005/Atom" 
      xmlns:media="http://search.yahoo.com/mrss/" 
      xmlns:content="http://purl.org/rss/1.0/modules/content/" 
      xmlns:dc="http://purl.org/dc/elements/1.1/" 
      version="2.0">
<channel>
<title>Mickaël CANOUIL</title>
<link>https://mickael.canouil.fr/blog.html</link>
<atom:link href="https://mickael.canouil.fr/blog.xml" rel="self" type="application/rss+xml"/>
<description>Posts on Quarto extensions and filters, Typst templates, R packages, and the tooling that keeps technical writing reproducible.</description>
<language>en-gb</language>
<image>
<url>https://mickael.canouil.fr/assets/images/og-image.png</url>
<title>Mickaël CANOUIL</title>
<link>https://mickael.canouil.fr/blog.html</link>
</image>
<generator>quarto-1.11.5</generator>
<lastBuildDate>Mon, 05 Oct 2026 00:00:00 GMT</lastBuildDate>
<item>
  <title>An Extension Can Ship a Whole Quarto Project Type</title>
  <link>https://mickael.canouil.fr/posts/2026-10-05-quarto-atelier-project-type/</link>
  <description><![CDATA[ 

<!--
@license MIT
@copyright 2026 Mickaël Canouil
@author Mickaël Canouil
-->
Skip to main content





<p>I maintain more than forty Quarto extensions. For a long time, a single <code>template.qmd</code> or <code>example.qmd</code> was all the documentation each of them had. This post shows how I gave every one of them a proper documentation website, without having to maintain the styling of forty websites.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="featured.png" class="lightbox" data-gallery="quarto-lightbox-gallery-1"><img src="https://mickael.canouil.fr/posts/2026-10-05-quarto-atelier-project-type/featured.png" class="img-featured img-fluid quarto-figure quarto-figure-center figure-img" alt="Card on an off-white background, under a dark navy band reading &quot;Quarto,
project types&quot; in beige. The title &quot;An Extension Can Ship a Whole Quarto
Project Type&quot; in dark navy sits above a gold rule. Below, a navy tile
labelled \_extension.yml on the left has three gold lines fanning out to
a grid of twelve cream site tiles on the right, each showing the line
&quot;type: atelier&quot;.
" width="600"></a></p>
</figure>
</div>
<section id="extension-documentation" class="level2">
<h2 class="anchored" data-anchor-id="extension-documentation">Extension Documentation</h2>
<p>A Quarto extension usually comes with a <code>template.qmd</code> or an <code>example.qmd</code>. It shows the extension at work, and for years it was the only documentation mine had. But it is one document. The usage, the options, and the examples all end up on the same long page.</p>
<p>So I decided to give each extension a proper documentation website. The pages were not the problem, since each extension needs its own content anyway. The styling and the chrome were. A good Quarto website needs a navbar, a footer, a theme, edit and issue links, Open Graph and Twitter card tags, search, and some accessibility fixes. That is close to a hundred lines of YAML, plus a few style sheets and HTML files, before the first page. I did not want to keep forty copies of that in step by hand.</p>
</section>
<section id="the-atelier-project-type" class="level2">
<h2 class="anchored" data-anchor-id="the-atelier-project-type">The <code>atelier</code> Project Type</h2>
<p>Most Quarto extensions contribute a filter, a shortcode, or a format. An extension can also contribute a project type<sup>1</sup>. It inherits from one of the base types (<code>default</code>, <code>website</code>, or <code>book</code>), and it sets the default values for <code>_quarto.yml</code>.</p>
<p><a href="https://github.com/mcanouil/quarto-atelier"><code>quarto-atelier</code></a> is such an extension. I made it for the documentation websites of my extensions, so the styling and the chrome live in one place instead of forty. Its project type is called <code>atelier</code>, and it is a <code>website</code> underneath. A site that uses it writes very little:</p>
<div class="code-with-filename">
<div class="code-with-filename-file">
<pre><strong>_quarto.yml</strong></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" data-filename="_quarto.yml" style="background: #f1f3f5;"><pre class="sourceCode yaml code-with-copy"><code class="sourceCode yaml"><span id="cb1-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">project</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb1-2"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">  </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">type</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> atelier</span></span>
<span id="cb1-3"></span>
<span id="cb1-4"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">website</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb1-5"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">  </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">title</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"My Extension"</span></span>
<span id="cb1-6"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">  </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">site-url</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> https://example.org/my-extension</span></span>
<span id="cb1-7"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">  </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">repo-url</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> https://github.com/example/my-extension</span></span>
<span id="cb1-8"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">  </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">navbar</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb1-9"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">left</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb1-10"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">      </span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">href</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> index.qmd</span></span>
<span id="cb1-11"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">        </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">text</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> Home</span></span></code></pre></div></div>
</div>
<p>The title, the URLs, and the navbar items belong to the site. Everything else comes from the extension.</p>
</section>
<section id="inherited-configuration" class="level2">
<h2 class="anchored" data-anchor-id="inherited-configuration">Inherited Configuration</h2>
<p>From those few lines, the site inherits:</p>
<ul>
<li><code>_site</code> as the output directory, and edit and issue links to the repository.</li>
<li>Open Graph and Twitter card tags, with <code>en_GB</code> as the locale and a large image card.</li>
<li>Page navigation, a back to top link, a table of contents, and copy buttons on code blocks.</li>
<li>An <code>llms.txt</code> file, and a search overlay in the navbar.</li>
<li>A footer, and a theme that reads the site’s <code>_brand.yml</code> for its colours in light and dark mode.</li>
<li>A filter that adds the <code>og:type</code> and <code>og:url</code> tags, which Quarto leaves out.</li>
<li>Six small HTML scripts, among them a skip link and a few accessibility fixes.</li>
</ul>
<p>Here are two of those sites: the documentation of <code>quarto-atelier</code> itself, and the one of <a href="https://m.canouil.dev/quarto-typst-render/"><code>quarto-typst-render</code></a>. They share the same layout, sidebar, table of contents, and repository links. The colours differ, because each site has its own <code>_brand.yml</code>.</p>
<div class="tabset-margin-container"></div><div class="panel-tabset">
<ul class="nav nav-tabs"><li class="nav-item"><a class="nav-link active" id="tabset-1-1-tab" data-bs-toggle="tab" data-bs-target="#tabset-1-1" aria-controls="tabset-1-1" aria-selected="true" href="">Light</a></li><li class="nav-item"><a class="nav-link" id="tabset-1-2-tab" data-bs-toggle="tab" data-bs-target="#tabset-1-2" aria-controls="tabset-1-2" aria-selected="false" href="">Dark</a></li></ul>
<div class="tab-content">
<div id="tabset-1-1" class="tab-pane active" aria-labelledby="tabset-1-1-tab">
<div class="quarto-layout-panel" data-layout-ncol="2">
<div class="quarto-layout-row">
<div class="quarto-layout-cell" style="flex-basis: 50.0%;justify-content: flex-start;">
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="assets/atelier-light.webp" class="lightbox" data-gallery="quarto-lightbox-gallery-2" title="The quarto-atelier documentation."><img src="https://mickael.canouil.fr/posts/2026-10-05-quarto-atelier-project-type/assets/atelier-light.webp" class="hero-art img-fluid figure-img" alt="Home page of the Atelier documentation website in light mode, with a sidebar holding the site title, a GitHub widget, a search field, and the Home, Reference, Examples, and Changelog links, the page text in the centre, and the table of contents with edit and issue links on the right. Accents are teal on a light grey background."></a></p>
<figcaption>The <code>quarto-atelier</code> documentation.</figcaption>
</figure>
</div>
</div>
<div class="quarto-layout-cell" style="flex-basis: 50.0%;justify-content: flex-start;">
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="assets/typst-render-light.webp" class="lightbox" data-gallery="quarto-lightbox-gallery-3" title="The quarto-typst-render documentation."><img src="https://mickael.canouil.fr/posts/2026-10-05-quarto-atelier-project-type/assets/typst-render-light.webp" class="hero-art img-fluid figure-img" alt="Home page of the Typst Render documentation website in light mode, with the same layout as the Atelier site: a sidebar with the site title, a GitHub widget, a search field, and the page links, the page text in the centre, and the table of contents on the right. Accents are green on a warm cream background."></a></p>
<figcaption>The <code>quarto-typst-render</code> documentation.</figcaption>
</figure>
</div>
</div>
</div>
</div>
</div>
<div id="tabset-1-2" class="tab-pane" aria-labelledby="tabset-1-2-tab">
<div class="quarto-layout-panel" data-layout-ncol="2">
<div class="quarto-layout-row">
<div class="quarto-layout-cell" style="flex-basis: 50.0%;justify-content: flex-start;">
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="assets/atelier-dark.webp" class="lightbox" data-gallery="quarto-lightbox-gallery-4" title="The quarto-atelier documentation."><img src="https://mickael.canouil.fr/posts/2026-10-05-quarto-atelier-project-type/assets/atelier-dark.webp" class="hero-art img-fluid figure-img" alt="Home page of the Atelier documentation website in dark mode, with the same sidebar, page text, and table of contents as in light mode. Accents are light teal on a near-black background."></a></p>
<figcaption>The <code>quarto-atelier</code> documentation.</figcaption>
</figure>
</div>
</div>
<div class="quarto-layout-cell" style="flex-basis: 50.0%;justify-content: flex-start;">
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="assets/typst-render-dark.webp" class="lightbox" data-gallery="quarto-lightbox-gallery-5" title="The quarto-typst-render documentation."><img src="https://mickael.canouil.fr/posts/2026-10-05-quarto-atelier-project-type/assets/typst-render-dark.webp" class="hero-art img-fluid figure-img" alt="Home page of the Typst Render documentation website in dark mode, with the same layout as the Atelier site. Accents are mint green and salmon on a dark grey background."></a></p>
<figcaption>The <code>quarto-typst-render</code> documentation.</figcaption>
</figure>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<p>I checked this on a test site with only the lines above. The rendered page had the <code>og:locale</code>, <code>og:type</code>, and <code>twitter:card</code> tags, the skip link, and the edit and issue links beside the table of contents. The <code>_site</code> folder had an <code>llms.txt</code> next to the pages.</p>
<p>One tag needs one more key. Quarto does not pass the <code>website</code> block to filters, so the filter cannot read <code>site-url</code> there. For <code>og:url</code>, the site repeats its URL under the extension name:</p>
<div class="code-with-filename">
<div class="code-with-filename-file">
<pre><strong>_quarto.yml</strong></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb2" data-filename="_quarto.yml" style="background: #f1f3f5;"><pre class="sourceCode yaml code-with-copy"><code class="sourceCode yaml"><span id="cb2-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">extensions</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb2-2"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">  </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">atelier</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb2-3"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">site-url</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> https://example.org/my-extension</span></span></code></pre></div></div>
</div>
<div class="highlight">
<p><strong>One release, every site:</strong> when I change a default in <code>quarto-atelier</code>, every site gets it at its next extension update.</p>
</div>
<p>Each site keeps its own copy of the extension under <code>_extensions/</code>, so the update is not instant. In each repository, a scheduled GitHub Actions <a href="https://github.com/mcanouil/quarto-workflows/blob/main/.github/workflows/quarto-extensions-updates.yml">workflow</a> runs <a href="https://github.com/mcanouil/quarto-extensions-updater">Quarto Extensions Updater</a> once a month, the action I wrote about in <a href="../../posts/2025-12-12-quarto-extensions-updater/index.html">an earlier post</a>. It compares the installed extensions with the <a href="https://m.canouil.dev/quarto-extensions/">Quarto extensions registry</a>, using the same core library as <a href="https://github.com/mcanouil/quarto-wizard">Quarto Wizard</a>. When <code>quarto-atelier</code> has a new release, it opens a pull request with the release notes, so every update is a change I can review before it lands.</p>
</section>
<section id="overriding-the-defaults" class="level2">
<h2 class="anchored" data-anchor-id="overriding-the-defaults">Overriding the Defaults</h2>
<p>The site can still change any inherited value. Quarto merges the defaults of the project type with the site’s <code>_quarto.yml</code>, and the site wins.</p>
<p>For example, <code>atelier</code> puts the search in the navbar as an overlay. The documentation site of <code>quarto-badge</code> has no navbar, only a sidebar. So it moves the search there:</p>
<div class="code-with-filename">
<div class="code-with-filename-file">
<pre><strong>_quarto.yml</strong></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb3" data-filename="_quarto.yml" style="background: #f1f3f5;"><pre class="sourceCode yaml code-with-copy"><code class="sourceCode yaml"><span id="cb3-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">website</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb3-2"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">  </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">search</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb3-3"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">location</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> sidebar</span></span>
<span id="cb3-4"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">type</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> textbox</span></span></code></pre></div></div>
</div>
<p>The merge works key by key. On my test site, I replaced only the centre of the footer. The left part of the footer, which I did not touch, stayed as <code>atelier</code> set it.</p>
</section>
<section id="limitations" class="level2">
<h2 class="anchored" data-anchor-id="limitations">Limitations</h2>
<p>There are two limits to keep in mind.</p>
<p>First, lists are added together, not replaced. If a site adds its own script to <code>include-after-body</code>, it gets that script and the five from <code>atelier</code>. A site cannot remove one inherited script or filter. If a site needs that, the default does not belong in the shared project type.</p>
<p>Then, every site moves with the extension. That is the point, but it also means a bad default reaches all of them. The monthly pull requests are the safety net, because each site takes the change through its own review.</p>
</section>
<section id="your-own-project-type" class="level2">
<h2 class="anchored" data-anchor-id="your-own-project-type">Your Own Project Type</h2>
<p><code>atelier</code> carries my own choices, from the locale to the footer. It is built for my sites, not for yours. But if you maintain several Quarto websites, I encourage you to write a project type of your own, under your own name.</p>
<p><code>atelier</code> can serve as a starting point. The <code>contributes:</code> block in its <code>_extension.yml</code> holds the project type and the HTML format it uses, and the rest of the folder holds the files they point to. The source is on <a href="https://github.com/mcanouil/quarto-atelier"><iconify-icon inline="" icon="fa7-brands:github" aria-label="GitHub" title="GitHub"></iconify-icon> GitHub</a>, and the <a href="https://m.canouil.dev/quarto-atelier/">documentation site</a> is built with the project type itself.</p>
<p>To try it, or to read it inside a project of your own, install it with Quarto:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb4" data-filename="bash" style="background: #f1f3f5;"><pre class="sourceCode bash cw-auto code-with-copy"><code class="sourceCode bash"><span id="cb4-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">quarto</span> add mcanouil/quarto-atelier</span></code></pre></div></div>
<p>It needs Quarto 1.9.36 or later. Or install it from your editor with <a href="https://m.canouil.dev/quarto-wizard/">Quarto Wizard</a>:</p>
<div class="btn-group" aria-label="Quarto Wizard installer options">
<p><a href="vscode://mcanouil.quarto-wizard/install?repo=mcanouil/quarto-atelier" class="btn btn-outline-primary" title="Install quarto-atelier in Visual Studio Code using the Quarto Wizard extension.">Visual Studio Code</a> <a href="positron://mcanouil.quarto-wizard/install?repo=mcanouil/quarto-atelier" class="btn btn-outline-primary" title="Install quarto-atelier in Positron using the Quarto Wizard extension.">Positron</a></p>
</div>
<p>Happy publishing!</p>


</section>


<a onclick="window.scrollTo(0, 0); return false;" id="quarto-back-to-top"><i class="bi bi-arrow-up"></i> Back to top</a><div id="quarto-appendix" class="default"><section id="footnotes" class="footnotes footnotes-end-of-document"><h2 class="anchored quarto-appendix-heading">Footnotes</h2>

<ol>
<li id="fn1"><p>See the Quarto documentation on <a href="https://quarto.org/docs/extensions/project-types.html">Project Types</a>.↩︎</p></li>
</ol>
</section><section class="quarto-appendix-contents" id="quarto-reuse"><h2 class="anchored quarto-appendix-heading">Reuse</h2><div class="quarto-appendix-contents"><div><a rel="license" href="https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en_gb">CC BY-NC-SA 4.0</a></div></div></section><section class="quarto-appendix-contents" id="quarto-citation"><h2 class="anchored quarto-appendix-heading">Citation</h2><div><div class="quarto-appendix-secondary-label">BibTeX citation:</div><pre class="sourceCode code-with-copy quarto-appendix-bibtex"><code class="sourceCode bibtex">@misc{canouil2026,
  author = {CANOUIL, Mickaël},
  title = {An {Extension} {Can} {Ship} a {Whole} {Quarto} {Project}
    {Type}},
  date = {2026-10-05},
  url = {https://mickael.canouil.fr/posts/2026-10-05-quarto-atelier-project-type/},
  langid = {en-GB}
}
</code></pre><div class="quarto-appendix-secondary-label">For attribution, please cite this work as:</div><div id="ref-canouil2026" class="csl-entry quarto-appendix-citeas">
CANOUIL, M. (2026-10-05). An Extension Can Ship a Whole Quarto Project
Type. <em>Mickael.canouil.fr</em>. <a href="https://mickael.canouil.fr/posts/2026-10-05-quarto-atelier-project-type/">https://mickael.canouil.fr/posts/2026-10-05-quarto-atelier-project-type/</a>
</div></div></section></div> ]]></description>
  <category>quarto</category>
  <category>extensions</category>
  <category>website</category>
  <category>documentation</category>
  <guid>https://mickael.canouil.fr/posts/2026-10-05-quarto-atelier-project-type/</guid>
  <pubDate>Mon, 05 Oct 2026 00:00:00 GMT</pubDate>
  <media:content url="https://mickael.canouil.fr/posts/2026-10-05-quarto-atelier-project-type/featured.png" medium="image" type="image/png" height="76" width="144"/>
</item>
<item>
  <title>Quarto Wizard 3.3: Typst Preview and Schema Validation</title>
  <link>https://mickael.canouil.fr/posts/2026-09-07-quarto-wizard-3-3/</link>
  <description><![CDATA[ 

<!--
@license MIT
@copyright 2026 Mickaël Canouil
@author Mickaël Canouil
-->
Skip to main content





<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="featured.png" class="lightbox" data-gallery="quarto-lightbox-gallery-1"><img src="https://mickael.canouil.fr/posts/2026-09-07-quarto-wizard-3-3/featured.png" class="img-featured img-fluid quarto-figure quarto-figure-center figure-img" alt="A card titled Quarto Wizard 3.3, with the line &quot;Typst preview, beside
the editor&quot; and the Typst wordmark under it. Beside the title, an editor
window holds a Typst cell on the left and the preview panel on the
right, where the cell compiles to a scatter plot of penguin flipper
length against body mass.
" width="600"></a></p>
</figure>
</div>
<p><strong><span class="quarto-wizard" title="Quarto Wizard Logo" aria-label="Quarto Wizard Logo"></span> Quarto Wizard 3.3</strong> adds a preview for Typst blocks, and publishes the Lua validator that an extension can use to check its own options. This post shows both.</p>
<div class="highlight">
<p><strong><span class="quarto-wizard" title="Quarto Wizard Logo" aria-label="Quarto Wizard Logo"></span> Quarto Wizard 3.3</strong> is available from the <a href="https://marketplace.visualstudio.com/items?itemName=mcanouil.quarto-wizard">VS Code Marketplace</a> and the <a href="https://open-vsx.org/extension/mcanouil/quarto-wizard">Open VSX Registry</a>.</p>
</div>
<section id="preview-a-typst-block" class="level2">
<h2 class="anchored" data-anchor-id="preview-a-typst-block">Preview a Typst Block</h2>
<p>Writing Typst inside a Quarto document used to mean rendering the whole document to see one small block. Quarto Wizard 3.3 compiles the block under the cursor and shows the image beside the editor. It uses the Typst binary that ships inside Quarto, so there is nothing else to install.</p>
<p>Put the cursor inside a Typst block, then run <strong>Quarto Wizard: Preview Typst Block</strong>, or select the code lens above the block. The cell below draws the penguins with <a href="https://m.canouil.dev/gribouille/">Gribouille</a>, and the image beside it is what the render produces.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="assets/media/typst-preview-panel-dark.webp" class="lightbox" data-gallery="quarto-wizard-dark"><img src="https://mickael.canouil.fr/posts/2026-09-07-quarto-wizard-3-3/assets/media/typst-preview-panel-dark.webp" title="Typst Block Preview" class="dark-content img-fluid hero-art quarto-figure quarto-figure-center figure-img" alt="A Quarto document with a Typst cell on the left and the Quarto Wizard
preview panel on the right, showing the scatter plot of penguin flipper
length against body mass that the cell compiles to.
"></a></p>
</figure>
</div>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="assets/media/typst-preview-panel-light.webp" class="lightbox" data-gallery="quarto-wizard-light"><img src="https://mickael.canouil.fr/posts/2026-09-07-quarto-wizard-3-3/assets/media/typst-preview-panel-light.webp" title="Typst Block Preview" class="light-content img-fluid hero-art quarto-figure quarto-figure-center figure-img" alt="A Quarto document with a Typst cell on the left and the Quarto Wizard
preview panel on the right, showing the scatter plot of penguin flipper
length against body mass that the cell compiles to.
"></a></p>
</figure>
</div>
<p>The panel then follows the cursor, and it compiles again after each edit. Move the cursor to another block, of any of the three kinds, and the panel shows that one instead.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="assets/media/typst-preview-loop-dark.webp" class="lightbox" data-gallery="quarto-wizard-dark"><img src="https://mickael.canouil.fr/posts/2026-09-07-quarto-wizard-3-3/assets/media/typst-preview-loop-dark.webp" title="Typst Block Preview" class="dark-content img-fluid hero-art quarto-figure quarto-figure-center figure-img" alt="The same document and preview panel, where the cursor visits a Typst
cell, a raw block and a plain block in turn, and the compiled image
follows every edit and every move.
"></a></p>
</figure>
</div>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="assets/media/typst-preview-loop-light.webp" class="lightbox" data-gallery="quarto-wizard-light"><img src="https://mickael.canouil.fr/posts/2026-09-07-quarto-wizard-3-3/assets/media/typst-preview-loop-light.webp" title="Typst Block Preview" class="light-content img-fluid hero-art quarto-figure quarto-figure-center figure-img" alt="The same document and preview panel, where the cursor visits a Typst
cell, a raw block and a plain block in turn, and the compiled image
follows every edit and every move.
"></a></p>
</figure>
</div>
<p>The panel writes a failure over the last good image of the same block. The preview therefore does not go empty while you type.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="assets/media/typst-preview-error-dark.webp" class="lightbox" data-gallery="quarto-wizard-dark"><img src="https://mickael.canouil.fr/posts/2026-09-07-quarto-wizard-3-3/assets/media/typst-preview-error-dark.webp" title="A Failure in the Typst Preview" class="dark-content img-fluid hero-art quarto-figure quarto-figure-center figure-img" alt="The preview panel showing a compiler message about an invalid number
suffix, with the last good image of the block still under it.
"></a></p>
</figure>
</div>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="assets/media/typst-preview-error-light.webp" class="lightbox" data-gallery="quarto-wizard-light"><img src="https://mickael.canouil.fr/posts/2026-09-07-quarto-wizard-3-3/assets/media/typst-preview-error-light.webp" title="A Failure in the Typst Preview" class="light-content img-fluid hero-art quarto-figure quarto-figure-center figure-img" alt="The preview panel showing a compiler message about an invalid number
suffix, with the last good image of the block still under it.
"></a></p>
</figure>
</div>
<p>The full compiler output goes to the Quarto Wizard log.</p>
<div class="callout callout-style-default callout-tip callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
Tip
</div>
</div>
<div class="callout-body-container callout-body">
<p><code>quartoWizard.typstPreview.debounceMs</code> sets how long an edit waits before the preview follows it. Its default is 300 milliseconds. Set it to <code>0</code> to compile on every keystroke, and raise it when a block is slow to compile.</p>
</div>
</div>
<section id="where-the-image-appears" class="level3">
<h3 class="anchored" data-anchor-id="where-the-image-appears">Where the Image Appears</h3>
<p><code>quartoWizard.typstPreview.surface</code> selects where the image is shown.</p>
<ul>
<li><code>panel</code>, the default, shows the image beside the editor. The panel scrolls, so a tall image is never cut.</li>
<li><code>hover</code> shows the image when the pointer rests on a block.</li>
<li><code>off</code> shows no image, and offers no code lens.</li>
</ul>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="assets/media/typst-preview-hover-dark.webp" class="lightbox" data-gallery="quarto-wizard-dark"><img src="https://mickael.canouil.fr/posts/2026-09-07-quarto-wizard-3-3/assets/media/typst-preview-hover-dark.webp" title="Typst Block Preview in a Hover" class="dark-content img-fluid hero-art quarto-figure quarto-figure-center figure-img" alt="A hover card over a Typst block, showing the compiled image of that
block above the block itself.
"></a></p>
</figure>
</div>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="assets/media/typst-preview-hover-light.webp" class="lightbox" data-gallery="quarto-wizard-light"><img src="https://mickael.canouil.fr/posts/2026-09-07-quarto-wizard-3-3/assets/media/typst-preview-hover-light.webp" title="Typst Block Preview in a Hover" class="light-content img-fluid hero-art quarto-figure quarto-figure-center figure-img" alt="A hover card over a Typst block, showing the compiled image of that
block above the block itself.
"></a></p>
</figure>
</div>
<p>A hover cannot scroll, so the image is scaled down to <code>quartoWizard.typstPreview.maxHeight</code>.</p>
</section>
<section id="three-kinds-of-block" class="level3">
<h3 class="anchored" data-anchor-id="three-kinds-of-block">Three Kinds of Block</h3>
<p>Three fences are spelled almost the same way, and Quarto treats each one differently. The preview treats them differently too, and the code lens above each block says which kind it is.</p>
<table class="caption-top table">
<thead>
<tr class="header">
<th>Fence</th>
<th>What Quarto does with it</th>
<th>What the preview compiles</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>```typst</code></td>
<td>Highlights it, and never executes it.</td>
<td>The body, under the preview’s own page setup and colours.</td>
</tr>
<tr class="even">
<td><code>```{=typst}</code></td>
<td>Passes it to the Typst output untouched.</td>
<td>The body, under every raw block above it in the document.</td>
</tr>
<tr class="odd">
<td><code>```{typst}</code></td>
<td>Executes it through the <a href="https://m.canouil.dev/quarto-typst-render/">typst-render</a> extension.</td>
<td>The body, under the options and the colours of that extension.</td>
</tr>
</tbody>
</table>
<p>A <code>{typst}</code> cell needs <code>typst-render</code> installed in the project. Without it the preview says so and shows nothing. An image compiled with guessed options is not the image the render produces, which is worse than no image at all.</p>
<p>Quarto Wizard installs it for you, in the folder you pick:</p>
<p><a href="vscode://mcanouil.quarto-wizard/install?repo=mcanouil/quarto-typst-render" class="btn btn-primary btn-sm">Install in VS Code</a> <a href="positron://mcanouil.quarto-wizard/install?repo=mcanouil/quarto-typst-render" class="btn btn-primary btn-sm">Install in Positron</a></p>
<p>The other two kinds need no extension.</p>
</section>
<section id="colours" class="level3">
<h3 class="anchored" data-anchor-id="colours">Colours</h3>
<p>A plain block and a raw block take their colours from <code>quartoWizard.typstPreview.foreground</code> and <code>quartoWizard.typstPreview.background</code>. Both accept three forms. <code>auto</code>, the default, follows the editor theme. <code>none</code> writes no colour, so Typst uses its own black on white. Any other value is a Typst colour expression written as it stands, for example <code>rgb("#ff9800")</code>.</p>
<p>A <code>{typst}</code> cell keeps the colour contract of <code>typst-render</code> instead, so the image matches the render and not the editor. A cell that resolves a colour from a brand needs a side of that brand. <strong>Quarto Wizard: Switch Typst Preview Brand Mode</strong> shows the other side, and the panel header always says which side it used.</p>
</section>
<section id="paths-and-the-compile-root" class="level3">
<h3 class="anchored" data-anchor-id="paths-and-the-compile-root">Paths and the Compile Root</h3>
<p>The preview sends the block to Typst through standard input. A path inside the block therefore resolves against the compile root, and not against the file that holds the block.</p>
<p>The preview uses the root that a render uses. For a <code>{typst}</code> cell, that is the <code>root</code> value under <code>extensions.typst-render</code>, whose default is the directory of the document. A cell also compiles with the <code>font-path</code>, the <code>package-path</code> and the <code>input</code> values of the extension. A plain block and a raw block always use the directory of the document.</p>
</section>
<section id="open-the-file-a-path-points-to" class="level3">
<h3 class="anchored" data-anchor-id="open-the-file-a-path-points-to">Open the File a Path Points To</h3>
<p>A <code>file:</code> option and a <code>preamble:</code> option name a file. Hold <kbd>Ctrl</kbd>, or <kbd>Cmd</kbd> on macOS, and select the value to open that file in the editor.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="assets/media/typst-option-link-dark.webp" class="lightbox" data-gallery="quarto-wizard-dark"><img src="https://mickael.canouil.fr/posts/2026-09-07-quarto-wizard-3-3/assets/media/typst-option-link-dark.webp" title="Opening a File a Typst Option Names" class="dark-content img-fluid hero-art quarto-figure quarto-figure-center figure-img" alt="A Typst cell whose preamble option value is underlined as a link, with
the tooltip that names the file and says Cmd and click.
"></a></p>
</figure>
</div>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="assets/media/typst-option-link-light.webp" class="lightbox" data-gallery="quarto-wizard-light"><img src="https://mickael.canouil.fr/posts/2026-09-07-quarto-wizard-3-3/assets/media/typst-option-link-light.webp" title="Opening a File a Typst Option Names" class="light-content img-fluid hero-art quarto-figure quarto-figure-center figure-img" alt="A Typst cell whose preamble option value is underlined as a link, with
the tooltip that names the file and says Cmd and click.
"></a></p>
</figure>
</div>
<p>The link appears only when the file is there. A path that leads to no file gets a warning instead. <code>file:</code> works in a cell only, while <code>preamble:</code> also works in the front matter of a document, and under <code>extensions.typst-render</code> in <code>_quarto.yml</code> or <code>_metadata.yml</code>.</p>
</section>
<section id="the-limits-of-the-preview" class="level3">
<h3 class="anchored" data-anchor-id="the-limits-of-the-preview">The Limits of the Preview</h3>
<p>The preview is an authoring aid, and it is not a render. The limits below are the limits of this first version, and most of them can be lifted later.</p>
<div class="callout callout-style-default callout-important callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
Important
</div>
</div>
<div class="callout-body-container callout-body">
<p>The preview differs from a render in two ways, and it says so beside the image when the difference applies. A raw block reaches Typst through the document template during a render, and the template contributes imports and rules that the preview does not apply yet. The preview also compiles one block on its own, so a plain block that uses a value from another block fails.</p>
</div>
</div>
<p>The preview shows the first page of a block of several pages, it always compiles to SVG, and it does not yet see the payload that the R and Python helpers of <code>typst-render</code> produce during a render.</p>
<p>One limit is deliberate and stays. The preview is off in an untrusted workspace, because it runs a compiler over the content of that workspace.</p>
</section>
</section>
<section id="one-schema-two-validators" class="level2">
<h2 class="anchored" data-anchor-id="one-schema-two-validators">One Schema, Two Validators</h2>
<p>The other half of 3.3 is for people who write extensions. It is about <code>_schema.yml</code>, the file where an extension declares every option, shortcode argument, format key, attribute and class that it accepts. If you only use extensions, this part changes nothing for you, beyond better messages from the extensions that adopt it.</p>
<p>Quarto Wizard has read that file for a while, to offer completion, hover documentation and diagnostics. A value of the wrong type, or a key the extension does not declare, is reported while you write.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="assets/media/schema-diagnostics-dark.webp" class="lightbox" data-gallery="quarto-wizard-dark"><img src="https://mickael.canouil.fr/posts/2026-09-07-quarto-wizard-3-3/assets/media/schema-diagnostics-dark.webp" title="Extension Schema Diagnostics" class="dark-content img-fluid hero-art quarto-figure quarto-figure-center figure-img" alt="A \_quarto.yml file where two extension options carry a squiggle, and
the Problems panel below reports a wrong type and an unknown option.
"></a></p>
</figure>
</div>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="assets/media/schema-diagnostics-light.webp" class="lightbox" data-gallery="quarto-wizard-light"><img src="https://mickael.canouil.fr/posts/2026-09-07-quarto-wizard-3-3/assets/media/schema-diagnostics-light.webp" title="Extension Schema Diagnostics" class="light-content img-fluid hero-art quarto-figure quarto-figure-center figure-img" alt="A \_quarto.yml file where two extension options carry a squiggle, and
the Problems panel below reports a wrong type and an unknown option.
"></a></p>
</figure>
</div>
<p>That covers the editor only. Someone who writes an option by hand in <code>_quarto.yml</code>, with no editor to help, still reaches the render with a wrong value. So 3.3 publishes the Lua validator that reads the same file during the render.</p>
<div class="highlight">
<p><strong>Two artefacts, two jobs:</strong> the meta-schema JSON validates your <code>_schema.yml</code> while you write it, and the Lua module validates the metadata of your user while Quarto renders.</p>
</div>
<p>The meta-schema is the file you point <code>$schema</code> at.</p>
<div class="code-with-filename">
<div class="code-with-filename-file">
<pre><strong>_extensions/my-extension/_schema.yml</strong></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" data-filename="_extensions/my-extension/_schema.yml" style="background: #f1f3f5;"><pre class="sourceCode yaml code-with-copy"><code class="sourceCode yaml"><span id="cb1-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">$schema</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> https://m.canouil.dev/quarto-wizard/assets/schema/v2/extension-schema.json</span></span>
<span id="cb1-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">options</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb1-3"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">  </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">theme</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb1-4"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">type</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> string</span></span>
<span id="cb1-5"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">description</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Colour theme for the extension output."</span></span>
<span id="cb1-6"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">default</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> light</span></span>
<span id="cb1-7"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">enum</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">[</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">light</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">,</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> dark</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">,</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> auto</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">]</span></span>
<span id="cb1-8"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">  </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">count</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb1-9"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">type</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> integer</span></span>
<span id="cb1-10"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">default</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span></span>
<span id="cb1-11"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">minimum</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb1-12"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">maximum</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">100</span></span></code></pre></div></div>
</div>
<p>The module is published as <a href="https://m.canouil.dev/quarto-wizard/assets/schema/v2/schema.lua" target="_blank" rel="noopener noreferrer"><code>schema.lua</code></a>. Its version is the version of the vocabulary, so the v2 module implements the v2 meta-schema. Vendor it into your extension, because a render must not need network access.</p>
<div class="code-with-filename">
<div class="code-with-filename-file">
<pre><strong>_extensions/my-extension/my-extension.lua</strong></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="annotated-cell-2" data-filename="_extensions/my-extension/my-extension.lua" style="background: #f1f3f5;"><pre class="sourceCode lua code-annotation-code code-with-copy code-annotated"><code class="sourceCode lua"><a class="code-annotation-anchor" data-target-cell="annotated-cell-2" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-2-1" class="code-annotation-target"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">local</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">schema</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">require</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">quarto</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">utils</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>resolve_path<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'_modules/schema.lua'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">):</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">gsub</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'%.lua$'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">''</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">))</span></span>
<span id="annotated-cell-2-2"></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-2" data-target-annotation="2" onclick="event.preventDefault();">2</a><span id="annotated-cell-2-3" class="code-annotation-target"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">local</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">options</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">schema</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>validate_options<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-2-4">  <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">meta</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-2-5">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'my-extension'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-2-6">  <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">quarto</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">utils</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>resolve_path<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'_schema.yml'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-2-7"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
</div>
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-2" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-2" data-code-lines="1" data-code-annotation="1">Load the vendored copy by its absolute path. Lua keys its module cache by the name you pass, so <code>require('_modules.schema')</code> would hand the second extension of a render the copy of the first one.</span>
</dd>
<dt data-target-cell="annotated-cell-2" data-target-annotation="2">2</dt>
<dd>
<span data-code-cell="annotated-cell-2" data-code-lines="3" data-code-annotation="2">Read <code>meta.extensions.my-extension</code>, test every value against the schema, and return the options with the defaults applied.</span>
</dd>
</dl>
<p>Errors and warnings go through the Quarto logger, and a schema that cannot be read never stops the render. Narrower entry points exist for a shortcode call, for the attributes of an element, and for a format block. The <a href="https://m.canouil.dev/quarto-wizard/reference/schema-validator.html">Using the Lua Validator</a> page, added to the Quarto Wizard documentation in this release, describes each one.</p>
<p>The v2 meta-schema also accepts <code>uniqueItems</code> now. The validator already enforced that keyword, so a schema that used it was rejected by the meta-schema and accepted by the module. Both agree again.</p>
</section>
<section id="other-fixes" class="level2">
<h2 class="anchored" data-anchor-id="other-fixes">Other Fixes</h2>
<p>3.4 follows straight after 3.3, and it is about the two features above, so the list below holds both releases.</p>
<ul>
<li>An extension in a repository whose default branch is <code>master</code> installs without writing <code>@master</code>. The branch is read from GitHub when the repository has no release, no tag and no registry entry.</li>
<li>A Typst block written between two <code>---</code> lines that open no front matter is found. The rule the preview follows is the rule Pandoc applies.</li>
<li>The search for Quarto projects no longer collects search processes. It applies your <code>files.exclude</code> and <code>search.exclude</code> settings, and it stops when a newer search replaces it.</li>
<li>The preview reads a document again when that document takes the name of an untitled document you closed. It used to show you the blocks of the one before.</li>
<li>A fenced block that a tab indents inside a blockquote is found, and a body line keeps the indent you wrote.</li>
<li>A YAML diagnostic points at the key or the value it is about, rather than at the whole line, and an option written in flow style is checked as well.</li>
<li>The Lua validator corrects two checks, accepts a name written with a hyphen where the schema declares it with an underscore, and reports a spelling that two attributes both declare. That last one used to leave the choice to the validator.</li>
</ul>
<p>The changelog holds the full list, for <a href="https://m.canouil.dev/quarto-wizard/changelog.html#version-3-3">3.3</a> and for <a href="https://m.canouil.dev/quarto-wizard/changelog.html#version-3-4">3.4</a>.</p>
<p>Happy writing!</p>


</section>

<a onclick="window.scrollTo(0, 0); return false;" id="quarto-back-to-top"><i class="bi bi-arrow-up"></i> Back to top</a><div id="quarto-appendix" class="default"><section class="quarto-appendix-contents" id="quarto-reuse"><h2 class="anchored quarto-appendix-heading">Reuse</h2><div class="quarto-appendix-contents"><div><a rel="license" href="https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en_gb">CC BY-NC-SA 4.0</a></div></div></section><section class="quarto-appendix-contents" id="quarto-citation"><h2 class="anchored quarto-appendix-heading">Citation</h2><div><div class="quarto-appendix-secondary-label">BibTeX citation:</div><pre class="sourceCode code-with-copy quarto-appendix-bibtex"><code class="sourceCode bibtex">@misc{canouil2026,
  author = {CANOUIL, Mickaël},
  title = {Quarto {Wizard} 3.3: {Typst} {Preview} and {Schema}
    {Validation}},
  date = {2026-09-07},
  url = {https://mickael.canouil.fr/posts/2026-09-07-quarto-wizard-3-3/},
  langid = {en-GB}
}
</code></pre><div class="quarto-appendix-secondary-label">For attribution, please cite this work as:</div><div id="ref-canouil2026" class="csl-entry quarto-appendix-citeas">
CANOUIL, M. (2026-09-07). Quarto Wizard 3.3: Typst Preview and Schema
Validation. <em>Mickael.canouil.fr</em>. <a href="https://mickael.canouil.fr/posts/2026-09-07-quarto-wizard-3-3/">https://mickael.canouil.fr/posts/2026-09-07-quarto-wizard-3-3/</a>
</div></div></section></div> ]]></description>
  <category>quarto</category>
  <category>vscode</category>
  <category>positron</category>
  <category>extensions</category>
  <category>typst</category>
  <category>quarto-wizard</category>
  <guid>https://mickael.canouil.fr/posts/2026-09-07-quarto-wizard-3-3/</guid>
  <pubDate>Mon, 07 Sep 2026 00:00:00 GMT</pubDate>
  <media:content url="https://mickael.canouil.fr/posts/2026-09-07-quarto-wizard-3-3/featured.png" medium="image" type="image/png" height="76" width="144"/>
</item>
<item>
  <title>Gribouille 0.7.0: A Theme from Your Brand, and Plots That Fit</title>
  <link>https://mickael.canouil.fr/posts/2026-08-25-gribouille-0-7/</link>
  <description><![CDATA[ 

<!--
@license MIT
@copyright 2026 Mickaël Canouil
@author Mickaël Canouil
-->
Skip to main content





<p><a href="https://github.com/mcanouil/gribouille">Gribouille</a> 0.7.0 is about two things: the colours a plot uses, and the room it takes. A theme can now be built from a parsed <a href="https://posit-dev.github.io/brand-yml/"><code>_brand.yml</code></a>, so the figures in a document follow the same brand as the prose around them. The rest of the release is layout. A plot draws at any size that gives it room to draw, it keeps to the <code>width</code> and <code>height</code> you set, and it says how much room it needs when it cannot. The one thing to watch when you upgrade is that some plots which used to draw past their canvas now fail instead.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="featured.png" class="lightbox" data-gallery="quarto-lightbox-gallery-1"><img src="https://mickael.canouil.fr/posts/2026-08-25-gribouille-0-7/featured.png" class="img-featured img-fluid quarto-figure quarto-figure-center figure-img" alt="Featured card for the Gribouille 0.7.0 release on a cream paper
background. On the left, the Gribouille wordmark sits above the tagline
&quot;Create elegant graphics with the Grammar of Graphics for Typst.&quot;, a
small red &quot;v0.7.0 release&quot; pill, and the URL m.canouil.dev/gribouille.
On the right, a slightly rotated bordered card frames three panels, one
per change in the release. Across the top, a short block of brand
colours becomes, through an arrow labelled &quot;theme-brand()&quot;, a penguin
scatter drawn on the brand's pale blue paper with red, teal, and amber
points. Bottom left, a rising unemployment line carrying an open
arrowhead at its last point, with a gold double-headed span along the
bottom of the panel. Bottom right, a sentence of prose holding two small
line charts, each no taller than the words around it.
" width="600"></a></p>
</figure>
</div>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Note</span>At a glance
</div>
</div>
<div class="callout-body-container callout-body">
<ul>
<li><a href="https://github.com/mcanouil/gribouille">Gribouille</a> 0.7.0 on Typst Universe: <code>#import "@preview/gribouille:0.7.0": *</code>. Typst 0.15.0 or newer.</li>
<li><a href="https://m.canouil.dev/gribouille/reference/themes/theme-brand.html"><code>theme-brand()</code></a> turns a <a href="https://posit-dev.github.io/brand-yml/"><code>_brand.yml</code></a> into a theme, and any theme now carries a <code>palette</code> that discrete colour and fill scales use by default.</li>
<li><a href="https://m.canouil.dev/gribouille/reference/helpers/arrow.html"><code>arrow()</code></a> puts arrowheads on <code>geom-segment</code>, <code>geom-curve</code>, <code>geom-spoke</code>, <code>geom-path</code>, <code>geom-line</code>, and <code>geom-step</code>.</li>
<li>A plot keeps to the <code>width</code> and <code>height</code> you set, down to one that fits in a line of prose. Tick labels, strip bands, and side legends take their room from inside the canvas.</li>
<li><a href="https://m.canouil.dev/gribouille/reference/scales/breaks-log.html"><code>breaks-log()</code></a> breaks a log scale on powers of ten, <a href="https://m.canouil.dev/gribouille/reference/helpers/format-log.html"><code>format-log()</code></a> writes <code>1000</code> as <code>10^3</code>, and <a href="https://m.canouil.dev/gribouille/reference/guides/guide-axis-logticks.html"><code>guide-axis-logticks()</code></a> adds two rows of smaller ticks below.</li>
<li>A boxplot of sixty-four thousand rows draws in about 2 seconds, where every earlier version took nearly thirty.</li>
<li><iconify-icon inline="" icon="octicon:alert-16" aria-label="Breaking change" title="Breaking change"></iconify-icon> <strong>Breaking:</strong> <code>arrow</code> on the three text geoms takes an <code>arrow()</code> specification in place of a boolean, and <code>arrow-length</code> is gone.</li>
<li><iconify-icon inline="" icon="octicon:alert-16" aria-label="Breaking change" title="Breaking change"></iconify-icon> <strong>Breaking:</strong> a plot whose labels, strips, or legends cannot fit its canvas fails with the room it needs instead of drawing past the edge.</li>
</ul>
</div>
</div>
<p>Every figure in this post is a real, freshly compiled plot.</p>
<section id="breaking-changes" class="level2" data-number="1">
<h2 data-number="1" class="anchored" data-anchor-id="breaking-changes"><span class="header-section-number">1</span> <iconify-icon inline="" icon="octicon:alert-16" aria-label="Breaking change" title="Breaking change"></iconify-icon> Breaking changes</h2>
<p>Four things change, and only the first is a rename. In the other three, a plot used to draw the wrong thing and now says so.</p>
<div class="callout callout-style-default callout-warning callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Warning</span>Migrate these names
</div>
</div>
<div class="callout-body-container callout-body">
<ul>
<li>Arrowheads are a specification now. <code>arrow: true</code> on <code>geom-text</code>, <code>geom-label</code>, and <code>geom-typst</code> becomes <code>arrow: arrow()</code>, and <code>arrow-length: 6pt</code> becomes <code>arrow: arrow(length: 6pt)</code>. The <code>arrow-length</code> parameter is removed.</li>
<li>Tick labels turn by -90 to 90 degrees, on <code>guide-axis()</code>, <code>guide-axis-logticks()</code>, <code>guide-axis-theta()</code>, and <code>axis-text</code>. Past a quarter turn they read upside down, so anything outside that range fails.</li>
<li>The theta axis of a <code>coord-radial()</code> plot reads its ticks and its arc from the scale that carries the sweep, <code>y</code> on a pie rather than <code>x</code>. Its minor ticks move to <code>axis-ticks-minor</code>, so a theme that styled the angular axis through <code>axis-line</code> alone must set <code>axis-ticks</code> too.</li>
<li>A plot that cannot fit the size it was asked for fails, saying how much room is missing and what to change. That covers tick labels reaching past the panel, a strip label wider than its panel, and a legend on a plot side.</li>
</ul>
</div>
</div>
</section>
<section id="a-theme-from-your-brand" class="level2" data-number="2">
<h2 data-number="2" class="anchored" data-anchor-id="a-theme-from-your-brand"><span class="header-section-number">2</span> A theme from your brand</h2>
<p><a href="https://posit-dev.github.io/brand-yml/"><code>brand.yml</code></a> is a small standard for writing a brand’s colours, fonts, and logos in one YAML file that several tools read. Quarto reads it, Shiny reads it, and the figures were the part that did not follow. <a href="https://m.canouil.dev/gribouille/reference/themes/theme-brand.html"><code>theme-brand()</code></a> closes that gap.</p>
<p>It takes the dictionary Typst’s <code>yaml()</code> returns, not a path, because Typst reads a relative path from the file that calls <code>yaml</code>. So you read the file, and <code>theme-set()</code> makes the result the default for every plot below it:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" data-filename="typst" style="background: #f1f3f5;"><pre class="sourceCode typst cw-auto code-with-copy"><code class="sourceCode typst"><span id="cb1-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>theme-set<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>theme-brand<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>yaml<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"_brand.yml"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)))</span></span></code></pre></div></div>
<p>The brand’s <code>foreground</code>, <code>background</code>, and <code>primary</code> become the theme’s <code>ink</code>, <code>paper</code>, and <code>accent</code>, and the gridlines, axis lines, and strips follow from that pair as they do under <code>theme-minimal()</code>. The roles left over, <code>secondary</code>, <code>tertiary</code>, <code>success</code>, <code>info</code>, <code>warning</code>, and <code>danger</code>, make the palette, in that order, with any repeated colour dropped.</p>
<p>The figure below writes the brand as a Typst dictionary, with the keys the standard defines, so you can read it here.</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="annotated-cell-2" data-filename="typst" style="background: #f1f3f5;"><pre class="sourceCode typst cw-auto code-annotation-code code-with-copy code-annotated"><code class="sourceCode typst"><span id="annotated-cell-2-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">brand</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-2-2">  color: <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-2" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-2-3" class="code-annotation-target">    palette: <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-2-4">      slate: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#0B2545"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-2-5">      mist: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#E7EEF6"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-2-6">      midnight: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#0A1622"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-2-7">      frost: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#DCE6F2"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-2-8">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-2" data-target-annotation="2" onclick="event.preventDefault();">2</a><span id="annotated-cell-2-9" class="code-annotation-target">    foreground: <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>light: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"slate"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> dark: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"frost"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-2-10">    background: <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>light: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"mist"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> dark: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"midnight"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-2-11">    primary: <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>light: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#D1495B"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> dark: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#FF7A8A"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-2" data-target-annotation="3" onclick="event.preventDefault();">3</a><span id="annotated-cell-2-12" class="code-annotation-target">    secondary: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#00798C"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-2-13">    tertiary: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#EDAE49"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-2-14">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-2-15"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-2-16"></span>
<span id="annotated-cell-2-17"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>plot<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-2-18">  data<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> penguins<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-2-19">  mapping<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> aes<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"flipper-len"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"body-mass"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"species"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-2-20">  layers<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>geom-point<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> alpha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.85</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> stroke<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">none</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),),</span></span>
<span id="annotated-cell-2-21">  labels<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> labels<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-2-22">    title<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Body Mass Against Flipper Length"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-2-23">    x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Flipper Length (mm)"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-2-24">    y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Body Mass (g)"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-2-25">    fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Species"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-2-26">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-2" data-target-annotation="4" onclick="event.preventDefault();">4</a><span id="annotated-cell-2-27" class="code-annotation-target">  theme<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> theme-brand<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>brand<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-2-28">  width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">12cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-2-29">  height<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">7cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-2-30"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
<div class="cell-annotation">
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-2" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-2" data-code-lines="3" data-code-annotation="1">A semantic colour may be a hex string, the name of a <code>color.palette</code> entry, or an alias of another entry. All three resolve.</span>
</dd>
<dt data-target-cell="annotated-cell-2" data-target-annotation="2">2</dt>
<dd>
<span data-code-cell="annotated-cell-2" data-code-lines="9" data-code-annotation="2">A colour may also carry <code>light</code> and <code>dark</code> variants, as a brand that supports both schemes does.</span>
</dd>
<dt data-target-cell="annotated-cell-2" data-target-annotation="3">3</dt>
<dd>
<span data-code-cell="annotated-cell-2" data-code-lines="12" data-code-annotation="3">A colour with no variants is used in both modes.</span>
</dd>
<dt data-target-cell="annotated-cell-2" data-target-annotation="4">4</dt>
<dd>
<span data-code-cell="annotated-cell-2" data-code-lines="27" data-code-annotation="4"><code>mode: "light"</code> or <code>mode: "dark"</code> picks which side of those variants to use, and <code>"light"</code> is the default. This post sets it from the page colour in its preamble, so the figure follows the colour-scheme toggle.</span>
</dd>
</dl>
</div>
<div>
<div class="light-content">
<div style="text-align: center;">
<p><a href="../../posts/2026-08-25-gribouille-0-7/./assets/typst-render/brand-light.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-2" title="Scatter plot of penguin body mass against flipper length on the brand's pale blue paper with navy ink, solid points filled by species in the brand's red, teal, and amber, with a species legend on the right."><img src="https://mickael.canouil.fr/posts/2026-08-25-gribouille-0-7/assets/typst-render/brand-light.svg" class="img-fluid hero-art" alt="Scatter plot of penguin body mass against flipper length on the brand's pale blue paper with navy ink, solid points filled by species in the brand's red, teal, and amber, with a species legend on the right."></a></p>
</div>
</div>
<div class="dark-content">
<div style="text-align: center;">
<p><a href="../../posts/2026-08-25-gribouille-0-7/./assets/typst-render/brand-dark.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-3" title="Scatter plot of penguin body mass against flipper length on the brand's pale blue paper with navy ink, solid points filled by species in the brand's red, teal, and amber, with a species legend on the right."><img src="https://mickael.canouil.fr/posts/2026-08-25-gribouille-0-7/assets/typst-render/brand-dark.svg" class="img-fluid hero-art" alt="Scatter plot of penguin body mass against flipper length on the brand's pale blue paper with navy ink, solid points filled by species in the brand's red, teal, and amber, with a species legend on the right."></a></p>
</div>
</div>
</div>
</div>
<div class="highlight">
<p><strong>One brand, one document.</strong> The prose and the plots read their colours from the same file.</p>
</div>
<p>The palette <code>theme-brand()</code> derives is nothing special: <code>palette</code> is a theme key like any other, so any theme takes one.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb2" data-filename="typst" style="background: #f1f3f5;"><pre class="sourceCode typst cw-auto code-with-copy"><code class="sourceCode typst"><span id="cb2-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>theme-set<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>theme-minimal<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb2-2">  palette<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>rgb<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#D1495B"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span> rgb<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#00798C"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span> rgb<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#EDAE49"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)),</span></span>
<span id="cb2-3"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">))</span></span></code></pre></div></div>
<p>Set it once and every discrete colour and fill scale in the document draws from it, which saves repeating <code>scale-colour-manual(values: ...)</code> on each plot. Three rules cover the rest:</p>
<ul>
<li>A scale that sets its own <code>palette:</code> keeps it.</li>
<li>The default is <code>palette: auto</code>, which is the Okabe-Ito palette, as before.</li>
<li>Continuous scales are not affected, and stay on viridis.</li>
</ul>
<p>Brand colours are chosen to sit well together, and data colours need the opposite, so when a brand’s roles are too close to tell apart, pass <code>palette: none</code>:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb3" data-filename="typst" style="background: #f1f3f5;"><pre class="sourceCode typst cw-auto code-with-copy"><code class="sourceCode typst"><span id="cb3-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>theme-brand<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>brand<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> palette<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">none</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span></code></pre></div></div>
<p>The plot then keeps the brand’s paper, ink, and accent, and draws the data in Okabe-Ito.</p>
<div class="callout callout-style-default callout-warning callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Warning</span>Accessibility: A brand is not checked for contrast
</div>
</div>
<div class="callout-body-container callout-body">
<p>Nothing verifies that the brand’s foreground and background contrast with each other. A brand that pairs two dark colours gives you a theme you cannot read, and Gribouille draws it exactly as asked.</p>
</div>
</div>
</section>
<section id="arrowheads-on-the-lines" class="level2" data-number="3">
<h2 data-number="3" class="anchored" data-anchor-id="arrowheads-on-the-lines"><span class="header-section-number">3</span> Arrowheads on the lines</h2>
<p>A line often shows a direction. Until now, showing it meant drawing the head yourself, in Typst, on top of the plot. Six line geoms take an <code>arrow</code> instead: <code>geom-segment</code>, <code>geom-curve</code>, <code>geom-spoke</code>, <code>geom-path</code>, <code>geom-line</code>, and <code>geom-step</code>. The three text geoms take it too, in place of the plain <code>arrow: true</code> they had before.</p>
<p><a href="https://m.canouil.dev/gribouille/reference/helpers/arrow.html"><code>arrow()</code></a> describes the head. <code>length</code> is how long each wing is, <code>angle</code> how far the wings open from the line, <code>ends</code> which end carries the head, and <code>type</code> whether it is an open V or a filled triangle.</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="annotated-cell-5" data-filename="typst" style="background: #f1f3f5;"><pre class="sourceCode typst cw-auto code-annotation-code code-with-copy code-annotated"><code class="sourceCode typst"><span id="annotated-cell-5-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">teal</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> rgb<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#1F7A8C"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-5-2"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">gold</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> rgb<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#B5830A"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-5-3"></span>
<span id="annotated-cell-5-4"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>plot<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-5-5">  data<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> economics<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-5-6">  mapping<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> aes<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"date"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"unemploy"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-5-7">  layers<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-5-8">    geom-line<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-5-9">      linewidth<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-5-10">      colour<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> teal<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-5" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-5-11" class="code-annotation-target">      arrow<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> arrow<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>length<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">8pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> angle<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">20</span>deg<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-5-12">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-5-13">    geom-segment<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-5-14">      data<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">((</span>x: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2008-01-01"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> y: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">6900</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> xend: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2009-10-01"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> yend: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">6900</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),),</span></span>
<span id="annotated-cell-5-15">      mapping<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> aes<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"x"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"y"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> xend<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"xend"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> yend<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"yend"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-5-16">      colour<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> gold<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-5-17">      linewidth<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.8pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-5" data-target-annotation="2" onclick="event.preventDefault();">2</a><span id="annotated-cell-5-18" class="code-annotation-target">      arrow<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> arrow<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>length<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">6pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> ends<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"both"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> type<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"closed"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-5-19">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-5-20">    geom-text<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-5-21">      data<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">((</span>x: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2008-11-01"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> y: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">6900</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> label: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"21 months"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),),</span></span>
<span id="annotated-cell-5-22">      mapping<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> aes<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"x"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"y"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> label<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"label"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-5-23">      colour<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> gold<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-5-24">      size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">9pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-5-25">      nudge-y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">600</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-5-26">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-5-27">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-5-28">  scales<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> scales<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> scale-date<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>date-format<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"[year]-[month repr:numerical]"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)),</span></span>
<span id="annotated-cell-5-29">  labels<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> labels<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-5-30">    title<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Unemployment Through the 2008 Recession"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-5-31">    x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Date"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-5-32">    y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Unemployed (thousands)"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-5-33">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-5-34">  theme<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> theme-minimal<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(),</span></span>
<span id="annotated-cell-5-35">  width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">12cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-5-36">  height<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">7cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-5-37"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
<div class="cell-annotation">
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-5" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-5" data-code-lines="11" data-code-annotation="1">The default <code>ends: "last"</code> puts one head at the end of the line. On a multi-point geom the ends are the ends of the whole group, not of every join, so a twenty-four-point line carries one head and not twenty-three.</span>
</dd>
<dt data-target-cell="annotated-cell-5" data-target-annotation="2">2</dt>
<dd>
<span data-code-cell="annotated-cell-5" data-code-lines="18" data-code-annotation="2"><code>ends: "both"</code> heads each end, and <code>type: "closed"</code> fills the triangle instead of stroking a V.</span>
</dd>
</dl>
</div>
<div>
<div class="light-content">
<div style="text-align: center;">
<p><a href="../../posts/2026-08-25-gribouille-0-7/./assets/typst-render/arrow-light.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-4" title="Line chart of the number of unemployed in thousands from January 2008 to December 2009, rising from about 7,700 to a peak of 15,352 in October 2009 and carrying an open arrowhead at its last point, with a gold double-headed arrow spanning 21 months along the bottom of the panel."><img src="https://mickael.canouil.fr/posts/2026-08-25-gribouille-0-7/assets/typst-render/arrow-light.svg" class="img-fluid hero-art" alt="Line chart of the number of unemployed in thousands from January 2008 to December 2009, rising from about 7,700 to a peak of 15,352 in October 2009 and carrying an open arrowhead at its last point, with a gold double-headed arrow spanning 21 months along the bottom of the panel."></a></p>
</div>
</div>
<div class="dark-content">
<div style="text-align: center;">
<p><a href="../../posts/2026-08-25-gribouille-0-7/./assets/typst-render/arrow-dark.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-5" title="Line chart of the number of unemployed in thousands from January 2008 to December 2009, rising from about 7,700 to a peak of 15,352 in October 2009 and carrying an open arrowhead at its last point, with a gold double-headed arrow spanning 21 months along the bottom of the panel."><img src="https://mickael.canouil.fr/posts/2026-08-25-gribouille-0-7/assets/typst-render/arrow-dark.svg" class="img-fluid hero-art" alt="Line chart of the number of unemployed in thousands from January 2008 to December 2009, rising from about 7,700 to a peak of 15,352 in October 2009 and carrying an open arrowhead at its last point, with a gold double-headed arrow spanning 21 months along the bottom of the panel."></a></p>
</div>
</div>
</div>
</div>
<p>The head takes the colour and the thickness of the line it sits on, so it stays in step when a mapped <code>colour</code> or <code>linewidth</code> changes it. It also stays solid under a dashed <code>linetype</code>, because a dashed head looks like a rendering fault, not like a style.</p>
</section>
<section id="plots-that-fit-the-size-you-asked-for" class="level2" data-number="4">
<h2 data-number="4" class="anchored" data-anchor-id="plots-that-fit-the-size-you-asked-for"><span class="header-section-number">4</span> Plots that fit the size you asked for</h2>
<p>This is the long part of the release, and the one you are most likely to notice. A Gribouille plot has always taken a <code>width</code> and a <code>height</code>, and it did not always keep to them. A long tick label, a strip band, or a legend on the side needs room outside the panel, and when one of them needed more than the plot had kept back, the plot drew it anyway and grew past the size you asked for.</p>
<p>Two things change. A plot now draws at any size that gives it room to draw, so a figure can be genuinely small. Only an empty canvas, or one asked for at a negative width or height, fails on size alone.</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="annotated-cell-6" data-filename="typst" style="background: #f1f3f5;"><pre class="sourceCode typst cw-auto code-annotation-code code-with-copy code-annotated"><code class="sourceCode typst"><span id="annotated-cell-6-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">teal</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> rgb<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#1F7A8C"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-6-2"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">gold</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> rgb<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#B5830A"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-6-3"></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-6" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-6-4" class="code-annotation-target"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">spark</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>column<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> colour<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> plot<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-6-5">  data<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> economics<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-6-6">  mapping<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> aes<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"date"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> column<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-6-7">  layers<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>geom-line<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>linewidth<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.8pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> colour<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> colour<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),),</span></span>
<span id="annotated-cell-6-8">  scales<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> scales<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> scale-date<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">()),</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-6" data-target-annotation="2" onclick="event.preventDefault();">2</a><span id="annotated-cell-6-9" class="code-annotation-target">  theme<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> theme-void<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(),</span></span>
<span id="annotated-cell-6-10">  width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2.2cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-6-11">  height<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.7cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-6-12"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-6-13"></span>
<span id="annotated-cell-6-14">Unemployment <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>box<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>baseline<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">30%</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> spark<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"unemploy"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> teal<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">))</span> rose for most of the</span>
<span id="annotated-cell-6-15">window, while the savings rate <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>box<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>baseline<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">30%</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> spark<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"psavert"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> gold<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">))</span></span>
<span id="annotated-cell-6-16">peaked and eased back.</span>
<span id="annotated-cell-6-17"></span>
<span id="annotated-cell-6-18"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>v<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.4cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-6-19"></span>
<span id="annotated-cell-6-20"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>plot<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-6-21">  data<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> economics<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-6-22">  mapping<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> aes<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"date"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"unemploy"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-6-23">  layers<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>geom-line<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>linewidth<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> colour<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> teal<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),),</span></span>
<span id="annotated-cell-6-24">  scales<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> scales<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> scale-date<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>date-format<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"[year]-[month repr:numerical]"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)),</span></span>
<span id="annotated-cell-6-25">  labels<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> labels<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Date"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Unemployed (thousands)"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-6-26">  theme<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> theme-minimal<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(),</span></span>
<span id="annotated-cell-6-27">  width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">12cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-6-28">  height<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">5cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-6-29"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
<div class="cell-annotation">
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-6" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-6" data-code-lines="4" data-code-annotation="1">A plot is ordinary Typst content, so wrapping one in a function gives you a sparkline generator.</span>
</dd>
<dt data-target-cell="annotated-cell-6" data-target-annotation="2">2</dt>
<dd>
<span data-code-cell="annotated-cell-6" data-code-lines="9" data-code-annotation="2"><code>theme-void()</code> strips the axes, and a stripped axis now reserves no room outside the panel, so the whole 2.2 cm by 0.7 cm canvas goes to the line.</span>
</dd>
</dl>
</div>
<div>
<div class="light-content">
<div style="text-align: center;">
<p><a href="../../posts/2026-08-25-gribouille-0-7/./assets/typst-render/fit-light.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-6" title="Two lines of prose, each carrying a small line chart no taller than the surrounding words: a rising unemployment sparkline in teal and a jagged savings-rate sparkline in gold that peaks and eases back. Below them, the same unemployment series drawn full width with labelled axes."><img src="https://mickael.canouil.fr/posts/2026-08-25-gribouille-0-7/assets/typst-render/fit-light.svg" class="img-fluid hero-art" alt="Two lines of prose, each carrying a small line chart no taller than the surrounding words: a rising unemployment sparkline in teal and a jagged savings-rate sparkline in gold that peaks and eases back. Below them, the same unemployment series drawn full width with labelled axes."></a></p>
</div>
</div>
<div class="dark-content">
<div style="text-align: center;">
<p><a href="../../posts/2026-08-25-gribouille-0-7/./assets/typst-render/fit-dark.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-7" title="Two lines of prose, each carrying a small line chart no taller than the surrounding words: a rising unemployment sparkline in teal and a jagged savings-rate sparkline in gold that peaks and eases back. Below them, the same unemployment series drawn full width with labelled axes."><img src="https://mickael.canouil.fr/posts/2026-08-25-gribouille-0-7/assets/typst-render/fit-dark.svg" class="img-fluid hero-art" alt="Two lines of prose, each carrying a small line chart no taller than the surrounding words: a rising unemployment sparkline in teal and a jagged savings-rate sparkline in gold that peaks and eases back. Below them, the same unemployment series drawn full width with labelled axes."></a></p>
</div>
</div>
</div>
</div>
<div class="highlight">
<p><strong>The size you ask for is the size you get.</strong> A plot fits its canvas, or it tells you how much room it needs.</p>
</div>
<p>The second change is what happens when the figure does not fit. Before it draws, the plot counts the room each part needs, from the reach of the tick labels to the width of a side legend, the shared one <code>compose()</code> lifts out of its panels included, and shrinks the panel to leave that room. When shrinking the panel is not enough, it fails instead of drawing past its edge:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb4" data-filename="plaintext" style="background: #f1f3f5;"><pre class="sourceCode default cw-auto code-with-copy"><code class="sourceCode default"><span id="cb4-1">error: panicked with: plot: the x-axis tick labels reach 3.05 cm past the panel</span>
<span id="cb4-2">on the right and the plot leaves them 1.6 cm. Increase `width`, rotate the</span>
<span id="cb4-3">labels with `guides(x: guide-axis(angle: 45))`, shorten the labels with a</span>
<span id="cb4-4">`labels:` function on the x scale, or shrink them with</span>
<span id="cb4-5">`theme(axis-text: element-text(size: ...))`.</span></code></pre></div></div>
<p>A strip label wider than its panel, and a legend that cannot fit, fail the same way, each saying what you can change.</p>
<p>Failing a compile is a blunt thing to do to a document that used to build. The alternative is worse: a figure that asks for <code>width: 12cm</code> and quietly takes fourteen is the kind of defect you find at the very end, in the PDF, once everything else is done.</p>
<p>A few smaller fixes come from the same work. An axis keeps room for its longest tick mark instead of for a fixed length, and a legend measures its title and its labels instead of guessing their size from the number of characters, so both give a little width back to the panel. A scale with no levels draws no legend at all, where it used to keep a box holding a title and no keys.</p>
</section>
<section id="log-axes-that-read-like-logs" class="level2" data-number="5">
<h2 data-number="5" class="anchored" data-anchor-id="log-axes-that-read-like-logs"><span class="header-section-number">5</span> Log axes that read like logs</h2>
<p>An automatic log axis breaks on one, two, and five times a power and writes each break as a plain number, which is a reasonable default but not how a log axis is usually read. Three pieces let you place and label the breaks as powers instead.</p>
<p><a href="https://m.canouil.dev/gribouille/reference/scales/breaks-log.html"><code>breaks-log(n: 5, base: 10)</code></a> puts the breaks on powers of the base, filling the gaps with steps in between when too few powers fall inside the data range, as <code>scales::breaks_log()</code> does. The base is separate from the scale transform, so <code>base: 2</code> places powers of two on a <code>log10</code> axis.</p>
<p><a href="https://m.canouil.dev/gribouille/reference/helpers/format-log.html"><code>format-log(base: 10, digits: 3)</code></a> writes a break as a power with a real superscript, so <code>1000</code> reads as <code>10^3</code>. A break that is not an exact power keeps its leading number, as the <code>3 × 10^4</code> label does below.</p>
<p><a href="https://m.canouil.dev/gribouille/reference/guides/guide-axis-logticks.html"><code>guide-axis-logticks()</code></a> now draws two rows of smaller ticks below the labelled decades, the tick at five times a power coming from the new <code>axis-ticks-mid</code> theme entry and drawn longer than its neighbours. Set <code>axis-ticks-mid: (length: 50%)</code> for the old look, where every small tick had the same length.</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="annotated-cell-8" data-filename="typst" style="background: #f1f3f5;"><pre class="sourceCode typst cw-auto code-annotation-code code-with-copy code-annotated"><code class="sourceCode typst"><span id="annotated-cell-8-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">growth</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> range<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">25</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">).</span>map<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>month <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=&gt;</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-8-2">  month: month<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-8-3">  users: calc<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>round<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">120</span> * calc<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>pow<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.32</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> month<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)),</span></span>
<span id="annotated-cell-8-4"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">))</span></span>
<span id="annotated-cell-8-5"></span>
<span id="annotated-cell-8-6"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>plot<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-8-7">  data<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> growth<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-8-8">  mapping<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> aes<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"month"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"users"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-8-9">  layers<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>geom-line<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>linewidth<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> colour<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> rgb<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#1F7A8C"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)),),</span></span>
<span id="annotated-cell-8-10">  scales<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> scales<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-8-11">    y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> scale-continuous<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-8-12">      transform<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"log10"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-8" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-8-13" class="code-annotation-target">      breaks<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> breaks-log<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(),</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-8" data-target-annotation="2" onclick="event.preventDefault();">2</a><span id="annotated-cell-8-14" class="code-annotation-target">      labels<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> format-log<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(),</span></span>
<span id="annotated-cell-8-15">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-8-16">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-8" data-target-annotation="3" onclick="event.preventDefault();">3</a><span id="annotated-cell-8-17" class="code-annotation-target">  guides<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> guides<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> guide-axis-logticks<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">()),</span></span>
<span id="annotated-cell-8-18">  labels<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> labels<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-8-19">    title<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"A Series That Grows by a Third Every Month"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-8-20">    x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Month"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-8-21">    y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Users"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-8-22">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-8-23">  theme<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> theme-minimal<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-8" data-target-annotation="4" onclick="event.preventDefault();">4</a><span id="annotated-cell-8-24" class="code-annotation-target">    axis-line<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> element-line<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>stroke<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.6pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-8-25">    axis-ticks<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> element-tick<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>length<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.25cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> stroke<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.8pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-8-26">    panel-grid-minor<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> element-blank<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(),</span></span>
<span id="annotated-cell-8-27">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-8-28">  width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">12cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-8-29">  height<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">7cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-8-30"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
<div class="cell-annotation">
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-8" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-8" data-code-lines="13" data-code-annotation="1">Breaks on powers of ten. Three decades leave too few powers to label on their own, so a step at three times each power fills the gaps.</span>
</dd>
<dt data-target-cell="annotated-cell-8" data-target-annotation="2">2</dt>
<dd>
<span data-code-cell="annotated-cell-8" data-code-lines="14" data-code-annotation="2">Each label is written as a power.</span>
</dd>
<dt data-target-cell="annotated-cell-8" data-target-annotation="3">3</dt>
<dd>
<span data-code-cell="annotated-cell-8" data-code-lines="17" data-code-annotation="3">The two rows of smaller ticks between the labels.</span>
</dd>
<dt data-target-cell="annotated-cell-8" data-target-annotation="4">4</dt>
<dd>
<span data-code-cell="annotated-cell-8" data-code-lines="24" data-code-annotation="4"><code>theme-minimal()</code> draws neither an axis line nor tick marks, so the figure turns both on to make the rows visible. <code>axis-ticks-mid</code> takes its colour and its stroke from <code>axis-ticks</code> and keeps its own length.</span>
</dd>
</dl>
</div>
<div>
<div class="light-content">
<div style="text-align: center;">
<p><a href="../../posts/2026-08-25-gribouille-0-7/./assets/typst-render/logticks-light.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-8" title="Line chart of a series growing by a third every month over twenty-four months, drawn on a log y-axis whose labels read 10 squared, 3 times 10 squared, 10 cubed, and so on up to 10 to the fifth, with long tick marks at each label, a medium tick between them, and shorter ticks either side."><img src="https://mickael.canouil.fr/posts/2026-08-25-gribouille-0-7/assets/typst-render/logticks-light.svg" class="img-fluid hero-art" alt="Line chart of a series growing by a third every month over twenty-four months, drawn on a log y-axis whose labels read 10 squared, 3 times 10 squared, 10 cubed, and so on up to 10 to the fifth, with long tick marks at each label, a medium tick between them, and shorter ticks either side."></a></p>
</div>
</div>
<div class="dark-content">
<div style="text-align: center;">
<p><a href="../../posts/2026-08-25-gribouille-0-7/./assets/typst-render/logticks-dark.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-9" title="Line chart of a series growing by a third every month over twenty-four months, drawn on a log y-axis whose labels read 10 squared, 3 times 10 squared, 10 cubed, and so on up to 10 to the fifth, with long tick marks at each label, a medium tick between them, and shorter ticks either side."><img src="https://mickael.canouil.fr/posts/2026-08-25-gribouille-0-7/assets/typst-render/logticks-dark.svg" class="img-fluid hero-art" alt="Line chart of a series growing by a third every month over twenty-four months, drawn on a log y-axis whose labels read 10 squared, 3 times 10 squared, 10 cubed, and so on up to 10 to the fifth, with long tick marks at each label, a medium tick between them, and shorter ticks either side."></a></p>
</div>
</div>
</div>
</div>
<p>A log axis also draws one minor gridline between two decades, where it used to draw a line at every step in between and left the panel banded grey. <code>n-minor</code> sets that count as it does on any other continuous axis, <code>n-minor: 0</code> removes the lines, and the steps in between stay where they belong, on the tick marks.</p>
</section>
<section id="polar-plots-that-fit-their-circle" class="level2" data-number="6">
<h2 data-number="6" class="anchored" data-anchor-id="polar-plots-that-fit-their-circle"><span class="header-section-number">6</span> Polar plots that fit their circle</h2>
<p>The radial coordinate system had several defects, all of them about space.</p>
<p>A <code>coord-radial()</code> plot now draws tick marks on its angular axis, from the <code>axis-ticks</code> theme entry, as a flat axis does. It also keeps room for the angular tick labels angle by angle, rather than taking the same band off every side of the circle.</p>
<div class="light-content">
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="assets/polar-light.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-10" title="The same rose chart, asking for the same 7 cm canvas, under each version."><img src="https://mickael.canouil.fr/posts/2026-08-25-gribouille-0-7/assets/polar-light.svg" class="hero-art img-fluid quarto-figure quarto-figure-center figure-img" alt="Two rose charts of counts per weekday, side by side, each inside a
dashed square drawn at the 7 cm by 7 cm canvas the plot asked for. On
the left, labelled &quot;0.6, 22 pt too wide&quot;, the circle and its weekday
labels cross the dashed frame on the right and at the top. On the right,
labelled &quot;0.7, exactly 7 cm&quot;, the same chart sits inside its frame with
the labels clear of the edge.
"></a></p>
</figure>
</div>
<figcaption>The same rose chart, asking for the same 7 cm canvas, under each version.</figcaption>
</figure>
</div>
</div>
<div class="dark-content">
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="assets/polar-dark.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-11" title="The same rose chart, asking for the same 7 cm canvas, under each version."><img src="https://mickael.canouil.fr/posts/2026-08-25-gribouille-0-7/assets/polar-dark.svg" class="hero-art img-fluid quarto-figure quarto-figure-center figure-img" alt="Two rose charts of counts per weekday, side by side, each inside a
dashed square drawn at the 7 cm by 7 cm canvas the plot asked for. On
the left, labelled &quot;0.6, 22 pt too wide&quot;, the circle and its weekday
labels cross the dashed frame on the right and at the top. On the right,
labelled &quot;0.7, exactly 7 cm&quot;, the same chart sits inside its frame with
the labels clear of the edge.
"></a></p>
</figure>
</div>
<figcaption>The same rose chart, asking for the same 7 cm canvas, under each version.</figcaption>
</figure>
</div>
</div>
<p>Both panels ask for a 7 cm by 7 cm canvas, and the dashed frame is drawn at exactly that size. The 0.6 plot comes out 22 pt wider, about 0.8 cm of chart hanging outside the box the document gave it, and the 0.7 plot measures 7 cm by 7 cm. The source of that figure is in <a href="https://github.com/mcanouil/mickael.canouil.fr/tree/main/posts/2026-08-25-gribouille-0-7/assets/_polar"><code>assets/_polar/</code></a>, since every cell in this post compiles against one version and this one needs two.</p>
<p>Every panel of a faceted radial plot keeps its own tick labels, on the angle and on the radius. A radial panel rings them inside its own circle rather than along an edge it shares with a neighbour, so a panel in the middle used to be left with no scale to read against.</p>
<p>A pie built with <code>coord-radial(theta: "y")</code> reads its tick marks and its arc from the scale that carries the sweep, which is <code>y</code>. That is the breaking half of the same fix, so check any pie chart you have.</p>
</section>
<section id="faster-on-large-data" class="level2" data-number="7">
<h2 data-number="7" class="anchored" data-anchor-id="faster-on-large-data"><span class="header-section-number">7</span> Faster on large data</h2>
<p>Two defects made the cost of a plot grow with the square of the number of rows, and both are gone. Here are the same two charts compiled under every release.</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="annotated-cell-9" data-filename="typst" style="background: #f1f3f5;"><pre class="sourceCode typst cw-auto code-annotation-code code-with-copy code-annotated"><code class="sourceCode typst"><a class="code-annotation-anchor" data-target-cell="annotated-cell-9" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-9-1" class="code-annotation-target"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">runs</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> csv<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-9-2">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"assets/_benchmark/results.csv"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-9-3">  row-type<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> dictionary<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-9-4"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-9" data-target-annotation="2" onclick="event.preventDefault();">2</a><span id="annotated-cell-9-5" class="code-annotation-target"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">runs</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> as-numeric<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>as-numeric<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>runs<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"rows"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"seconds"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-9-6"></span>
<span id="annotated-cell-9-7"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">median</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>values<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="annotated-cell-9-8">  <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">sorted</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> values<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>sorted<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">()</span></span>
<span id="annotated-cell-9-9">  sorted<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>at<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>int<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>sorted<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>len<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">()</span> / <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">))</span></span>
<span id="annotated-cell-9-10"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="annotated-cell-9-11"></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-9" data-target-annotation="3" onclick="event.preventDefault();">3</a><span id="annotated-cell-9-12" class="code-annotation-target"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">timings</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> summarise<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-9-13">  runs<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-9-14">  seconds<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> group <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=&gt;</span> median<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>group<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>map<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>row <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=&gt;</span> row<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>seconds<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)),</span></span>
<span id="annotated-cell-9-15">  by<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"chart"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"rows"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"version"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-9-16"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-9-17"></span>
<span id="annotated-cell-9-18"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>plot<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-9-19">  data<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> timings<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-9-20">  mapping<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> aes<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"rows"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"seconds"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> colour<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"version"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"version"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-9-21">  layers<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-9-22">    geom-line<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>linewidth<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.9pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-9-23">    geom-point<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.6pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> stroke<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">none</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-9-24">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-9-25">  facet<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> facet-wrap<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"chart"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> scales<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"free"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-9-26">  scales<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> scales<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-9-27">    x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> scale-continuous<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>labels<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> format-comma<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>digits<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)),</span></span>
<span id="annotated-cell-9-28">    y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> scale-continuous<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>transform<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"log10"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-9-29">    colour<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> scale-viridis-d<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(),</span></span>
<span id="annotated-cell-9-30">    fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> scale-viridis-d<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(),</span></span>
<span id="annotated-cell-9-31">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-9-32">  guides<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> guides<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">none</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-9-33">  labels<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> labels<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-9-34">    title<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Compile Time by Version"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-9-35">    subtitle<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Median of three runs, latest patch of each minor version"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-9-36">    x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Rows"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-9-37">    y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Seconds"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-9-38">    colour<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Version"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-9-39">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-9-40">  theme<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> theme-minimal<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(),</span></span>
<span id="annotated-cell-9-41">  width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">12cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-9-42">  height<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">6.5cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-9-43"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
<div class="cell-annotation">
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-9" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-9" data-code-lines="1" data-code-annotation="1">The figure reads the measurements rather than repeating them. The document, the timing script, and every run are in <a href="https://github.com/mcanouil/mickael.canouil.fr/tree/main/posts/2026-08-25-gribouille-0-7/assets/_benchmark"><code>assets/_benchmark/</code></a>, three runs per point, all on the same laptop.</span>
</dd>
<dt data-target-cell="annotated-cell-9" data-target-annotation="2">2</dt>
<dd>
<span data-code-cell="annotated-cell-9" data-code-lines="5" data-code-annotation="2">A CSV cell is a string, so the two numeric columns are converted before they reach a scale.</span>
</dd>
<dt data-target-cell="annotated-cell-9" data-target-annotation="3">3</dt>
<dd>
<span data-code-cell="annotated-cell-9" data-code-lines="12" data-code-annotation="3"><code>summarise()</code> collapses the three runs of each point to their median.</span>
</dd>
</dl>
</div>
<div>
<div class="light-content">
<div style="text-align: center;">
<p><a href="../../posts/2026-08-25-gribouille-0-7/./assets/typst-render/timings-light.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-12" title="Two panels of compile time against row count, on a log scale, one line per minor version. In the scatter panel, versions 0.1 to 0.6 sit together and reach about 15 seconds at 4,000 points, while 0.7 reaches 8. In the boxplot panel, 0.1 to 0.6 sit together and climb to about 28 seconds at 64,000 rows, while 0.7 climbs to 2.2."><img src="https://mickael.canouil.fr/posts/2026-08-25-gribouille-0-7/assets/typst-render/timings-light.svg" class="img-fluid hero-art" alt="Two panels of compile time against row count, on a log scale, one line per minor version. In the scatter panel, versions 0.1 to 0.6 sit together and reach about 15 seconds at 4,000 points, while 0.7 reaches 8. In the boxplot panel, 0.1 to 0.6 sit together and climb to about 28 seconds at 64,000 rows, while 0.7 climbs to 2.2."></a></p>
</div>
</div>
<div class="dark-content">
<div style="text-align: center;">
<p><a href="../../posts/2026-08-25-gribouille-0-7/./assets/typst-render/timings-dark.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-13" title="Two panels of compile time against row count, on a log scale, one line per minor version. In the scatter panel, versions 0.1 to 0.6 sit together and reach about 15 seconds at 4,000 points, while 0.7 reaches 8. In the boxplot panel, 0.1 to 0.6 sit together and climb to about 28 seconds at 64,000 rows, while 0.7 climbs to 2.2."><img src="https://mickael.canouil.fr/posts/2026-08-25-gribouille-0-7/assets/typst-render/timings-dark.svg" class="img-fluid hero-art" alt="Two panels of compile time against row count, on a log scale, one line per minor version. In the scatter panel, versions 0.1 to 0.6 sit together and reach about 15 seconds at 4,000 points, while 0.7 reaches 8. In the boxplot panel, 0.1 to 0.6 sit together and climb to about 28 seconds at 64,000 rows, while 0.7 climbs to 2.2."></a></p>
</div>
</div>
</div>
</div>
<p>A boxplot of sixty-four thousand rows takes about 28 seconds under every version from 0.1 to 0.6, and 2.2 seconds under 0.7. The scatter draws one mark per row, so it still grows, and what is left of that growth is the marks rather than the grouping.</p>
<p>Smaller wins come with them: a discrete scale with your own <code>limits</code>, the scale resolution of a faceted plot, and the pass that measures titles and labels are all cheaper. The <a href="https://m.canouil.dev/gribouille/guides/benchmarks.html">benchmarks guide</a> carries the current numbers, and what they say is to count the marks rather than the rows.</p>
</section>
<section id="documentation" class="level2" data-number="8">
<h2 data-number="8" class="anchored" data-anchor-id="documentation"><span class="header-section-number">8</span> Documentation</h2>
<p>The examples are now grouped by what you want to show, not by the part of the API they use. The <a href="https://m.canouil.dev/gribouille/gallery/">gallery</a> is a page of cards leading to six pages about a kind of story and six about a feature, each card says what shape of data it needs, and eighteen new plots on the bundled datasets show it in practice.</p>
</section>
<section id="wrap-up" class="level2" data-number="9">
<h2 data-number="9" class="anchored" data-anchor-id="wrap-up"><span class="header-section-number">9</span> Wrap-up</h2>
<div class="highlight">
<p><strong>Take the colours from the brand, and give the figure the size it was promised.</strong></p>
</div>
<p>Next on the list is more geoms and more worked examples. If you run into something unexpected, the issue tracker is the right place for it.</p>
<ul>
<li>Gribouille
<ul>
<li>Repository: <a href="https://github.com/mcanouil/gribouille" class="uri">https://github.com/mcanouil/gribouille</a>.</li>
<li>Documentation: <a href="https://m.canouil.dev/gribouille" class="uri">https://m.canouil.dev/gribouille</a>.</li>
<li>Typst Universe: <a href="https://typst.app/universe/package/gribouille" class="uri">https://typst.app/universe/package/gribouille</a>.</li>
</ul></li>
<li>Typst Render
<ul>
<li>Repository: <a href="https://github.com/mcanouil/quarto-typst-render" class="uri">https://github.com/mcanouil/quarto-typst-render</a>.</li>
<li>Documentation: <a href="https://m.canouil.dev/quarto-typst-render" class="uri">https://m.canouil.dev/quarto-typst-render</a>.</li>
</ul></li>
</ul>
<div class="callout callout-style-default callout-tip callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Tip</span>A note on contributions
</div>
</div>
<div class="callout-body-container callout-body">
<p>Gribouille is an unfunded spare-time project, and the API is still settling. Bug reports and ideas are very welcome on the issue tracker. Pull requests are not being accepted for now, for the reasons set out in the <a href="../../posts/2026-05-20-gribouille-grammar-of-graphics-for-typst/index.html">launch post</a>. Thanks in advance for your patience.</p>
</div>
</div>


</section>

<a onclick="window.scrollTo(0, 0); return false;" id="quarto-back-to-top"><i class="bi bi-arrow-up"></i> Back to top</a><div id="quarto-appendix" class="default"><section class="quarto-appendix-contents" id="quarto-reuse"><h2 class="anchored quarto-appendix-heading">Reuse</h2><div class="quarto-appendix-contents"><div><a rel="license" href="https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en_gb">CC BY-NC-SA 4.0</a></div></div></section><section class="quarto-appendix-contents" id="quarto-citation"><h2 class="anchored quarto-appendix-heading">Citation</h2><div><div class="quarto-appendix-secondary-label">BibTeX citation:</div><pre class="sourceCode code-with-copy quarto-appendix-bibtex"><code class="sourceCode bibtex">@misc{canouil2026,
  author = {CANOUIL, Mickaël},
  title = {Gribouille 0.7.0: {A} {Theme} from {Your} {Brand,} and
    {Plots} {That} {Fit}},
  date = {2026-08-25},
  url = {https://mickael.canouil.fr/posts/2026-08-25-gribouille-0-7/},
  langid = {en-GB}
}
</code></pre><div class="quarto-appendix-secondary-label">For attribution, please cite this work as:</div><div id="ref-canouil2026" class="csl-entry quarto-appendix-citeas">
CANOUIL, M. (2026-08-25). Gribouille 0.7.0: A Theme from Your Brand, and
Plots That Fit. <em>Mickael.canouil.fr</em>. <a href="https://mickael.canouil.fr/posts/2026-08-25-gribouille-0-7/">https://mickael.canouil.fr/posts/2026-08-25-gribouille-0-7/</a>
</div></div></section></div> ]]></description>
  <category>typst</category>
  <category>quarto</category>
  <category>grammar-of-graphics</category>
  <category>gribouille</category>
  <guid>https://mickael.canouil.fr/posts/2026-08-25-gribouille-0-7/</guid>
  <pubDate>Tue, 25 Aug 2026 00:00:00 GMT</pubDate>
  <media:content url="https://mickael.canouil.fr/posts/2026-08-25-gribouille-0-7/featured.png" medium="image" type="image/png" height="76" width="144"/>
</item>
<item>
  <title>A Clinical Study Report in Quarto, End to End</title>
  <link>https://mickael.canouil.fr/posts/2026-08-17-quarto-clinical-report/</link>
  <description><![CDATA[ 

<!--
@license MIT
@copyright 2026 Mickaël Canouil
@author Mickaël Canouil
-->
Skip to main content





<p>A clinical study report is a long document with numbered tables, a signed approval page, and firm rules about what goes where. I wanted to show how much of one Quarto can carry on its own. So I built one from public data, and put the whole project on GitHub.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="featured.png" class="lightbox" data-gallery="quarto-lightbox-gallery-1"><img src="https://mickael.canouil.fr/posts/2026-08-17-quarto-clinical-report/featured.png" class="img-featured img-fluid quarto-figure quarto-figure-center figure-img" alt="Card titled &quot;A Clinical Study Report in Quarto&quot; in dark teal on an
off-white background, under a teal band reading &quot;Quarto, Typst,
pharmaverse&quot;. An amber rule sits under the title, above the line &quot;Public
CDISC pilot data to a branded Typst PDF and an HTML site, from one
source.&quot; and four grey chips labelled \_brand.yml, Typst template, ICH
E3 numbering, and double programming. A faint diagonal DRAFT watermark
shows on the right.
" width="600"></a></p>
</figure>
</div>
<section id="what-the-report-is" class="level2">
<h2 class="anchored" data-anchor-id="what-the-report-is">What the Report Is</h2>
<p><code>demo-quarto-clinical-report</code> is an abbreviated clinical study report, written in the section order of ICH E3<sup>1</sup>. It has a synopsis, ethics, design, statistical methods, study patients, demographics, endpoints, safety, conclusions, and three appendices. The PDF is 50 pages, cover included, with 30 numbered tables, listings, and figures.</p>
<p>Two things before anything else. Nordvale Therapeutics is a fictional sponsor, invented for the demo. The data are the public CDISC pilot study <code>CDISCPILOT01</code> shipped with <code>pharmaverseadam</code>, and the report is not a regulatory submission.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="assets/cover.png" class="lightbox" data-gallery="quarto-lightbox-gallery-2" title="The cover page of the Typst PDF."><img src="https://mickael.canouil.fr/posts/2026-08-17-quarto-clinical-report/assets/cover.png" class="hero-art img-fluid figure-img" style="width:250px" alt="Cover page of the clinical study report. A dark teal band across the top holds the Nordvale Therapeutics logo. Below it, the protocol number, the title Clinical Study Report, an amber rule, the study subtitle, and a two-column grid of fields: compound, study phase, indication, sponsor, report version, and report date. An amber-edged panel reads Draft, this document is a draft and is not for regulatory submission. A confidentiality panel and a disclaimer sit at the bottom."></a></p>
<figcaption>The cover page of the Typst PDF.</figcaption>
</figure>
</div>
</section>
<section id="one-source-two-formats" class="level2">
<h2 class="anchored" data-anchor-id="one-source-two-formats">One Source, Two Formats</h2>
<p>The project carries one extension, <code>_extensions/nordvale/clinical</code>, and that extension contributes two formats.</p>
<div class="code-with-filename">
<div class="code-with-filename-file">
<pre><strong>_extensions/nordvale/clinical/_extension.yml</strong></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="annotated-cell-1" data-filename="_extensions/nordvale/clinical/_extension.yml" style="background: #f1f3f5;"><pre class="sourceCode yaml code-annotation-code code-with-copy code-annotated"><code class="sourceCode yaml"><span id="annotated-cell-1-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">contributes</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="annotated-cell-1-2"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">  </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">formats</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-1" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-1-3" class="code-annotation-target"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">common</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="annotated-cell-1-4"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">      </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">toc</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">true</span></span>
<span id="annotated-cell-1-5"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">      </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">toc-depth</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span></span>
<span id="annotated-cell-1-6"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">      </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">number-sections</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">true</span></span>
<span id="annotated-cell-1-7"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">      </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">lang</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> en-GB</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-1" data-target-annotation="2" onclick="event.preventDefault();">2</a><span id="annotated-cell-1-8" class="code-annotation-target"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">      </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">crossref</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="annotated-cell-1-9"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">        </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">custom</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="annotated-cell-1-10"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">          </span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">kind</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> float</span></span>
<span id="annotated-cell-1-11"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">            </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">key</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> listing</span></span>
<span id="annotated-cell-1-12"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">            </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">reference-prefix</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> Listing</span></span>
<span id="annotated-cell-1-13"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">            </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">caption-location</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> top</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-1" data-target-annotation="3" onclick="event.preventDefault();">3</a><span id="annotated-cell-1-14" class="code-annotation-target"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">typst</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="annotated-cell-1-15"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">      </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">template-partials</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="annotated-cell-1-16"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">        </span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> typst-template.typ</span></span>
<span id="annotated-cell-1-17"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">        </span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> typst-show.typ</span></span>
<span id="annotated-cell-1-18"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">      </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">filters</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="annotated-cell-1-19"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">        </span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">path</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> appendices.lua</span></span>
<span id="annotated-cell-1-20"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">          </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">at</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> post-quarto</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-1" data-target-annotation="4" onclick="event.preventDefault();">4</a><span id="annotated-cell-1-21" class="code-annotation-target"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">        </span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">path</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> tlf-numbers.lua</span></span>
<span id="annotated-cell-1-22"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">          </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">at</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> post-quarto</span></span>
<span id="annotated-cell-1-23"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">      </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">papersize</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> a4</span></span>
<span id="annotated-cell-1-24"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">      </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">section-numbering</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"1.1.1"</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-1" data-target-annotation="5" onclick="event.preventDefault();">5</a><span id="annotated-cell-1-25" class="code-annotation-target"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">      </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">font-paths</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="annotated-cell-1-26"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">        </span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> _extensions/nordvale/clinical/fonts</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-1" data-target-annotation="6" onclick="event.preventDefault();">6</a><span id="annotated-cell-1-27" class="code-annotation-target"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">html</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="annotated-cell-1-28"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">      </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">theme</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="annotated-cell-1-29"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">        </span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> cosmo</span></span>
<span id="annotated-cell-1-30"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">        </span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> brand</span></span>
<span id="annotated-cell-1-31"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">        </span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> clinical.scss</span></span>
<span id="annotated-cell-1-32"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">      </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">filters</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="annotated-cell-1-33"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">        </span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> inline-svglite.lua</span></span>
<span id="annotated-cell-1-34"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">        </span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">path</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> tlf-numbers.lua</span></span>
<span id="annotated-cell-1-35"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">          </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">at</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> post-quarto</span></span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
</div>
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-1" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-1" data-code-lines="3" data-code-annotation="1">What both formats agree on, merged into each one.</span>
</dd>
<dt data-target-cell="annotated-cell-1" data-target-annotation="2">2</dt>
<dd>
<span data-code-cell="annotated-cell-1" data-code-lines="8" data-code-annotation="2">A category of its own for the subject data listings. They are tabular data, and Quarto reserves <code>lst</code> for code, which it left-aligns and captions above.</span>
</dd>
<dt data-target-cell="annotated-cell-1" data-target-annotation="3">3</dt>
<dd>
<span data-code-cell="annotated-cell-1" data-code-lines="14" data-code-annotation="3">The two template partials that build the pages, and <code>appendices.lua</code>, which letters the appendix chapters and numbers the floats by chapter.</span>
</dd>
<dt data-target-cell="annotated-cell-1" data-target-annotation="4">4</dt>
<dd>
<span data-code-cell="annotated-cell-1" data-code-lines="21" data-code-annotation="4">The filter that gives every output its ICH E3 number, in both formats. See Every Output Has a Number.</span>
</dd>
<dt data-target-cell="annotated-cell-1" data-target-annotation="5">5</dt>
<dd>
<span data-code-cell="annotated-cell-1" data-code-lines="25" data-code-annotation="5">The fonts travel with the extension, so the PDF does not depend on what is installed.</span>
</dd>
<dt data-target-cell="annotated-cell-1" data-target-annotation="6">6</dt>
<dd>
<span data-code-cell="annotated-cell-1" data-code-lines="27" data-code-annotation="6">The theme, and <code>inline-svglite.lua</code>, which inlines the figures the <code>svglite</code> device draws so the page stylesheet resolves the brand fonts for them, as described in The Brand Reaches the Figures.</span>
</dd>
</dl>
<p>A document then asks for <code>clinical-typst</code> or <code>clinical-html</code>, and gets the sponsor identity with it.</p>
<div id="fig-flow" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-flow-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<div>
<div class="light-content">
<div style="text-align: center;">
<p><a href="../../posts/2026-08-17-quarto-clinical-report/./assets/typst-render/index/fig-flow-light.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-3" title="Figure&nbsp;1: One set of sections, one extension, two renderings."><img src="https://mickael.canouil.fr/posts/2026-08-17-quarto-clinical-report/assets/typst-render/index/fig-flow-light.svg" class="img-fluid hero-art figure-img" alt="Flow diagram. On the left, three stacked boxes: sections/*.qmd, R/tlf.R, and _brand.yml. An arrow points right to a taller box labelled Quarto with the nordvale/clinical extension. Another arrow points right to two stacked boxes: clinical-typst giving a PDF, and clinical-html giving a website."></a></p>
</div>
</div>
<div class="dark-content">
<div style="text-align: center;">
<p><a href="../../posts/2026-08-17-quarto-clinical-report/./assets/typst-render/index/fig-flow-dark.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-4" title="Figure&nbsp;1: One set of sections, one extension, two renderings."><img src="https://mickael.canouil.fr/posts/2026-08-17-quarto-clinical-report/assets/typst-render/index/fig-flow-dark.svg" class="img-fluid hero-art figure-img" alt="Flow diagram. On the left, three stacked boxes: sections/*.qmd, R/tlf.R, and _brand.yml. An arrow points right to a taller box labelled Quarto with the nordvale/clinical extension. Another arrow points right to two stacked boxes: clinical-typst giving a PDF, and clinical-html giving a website."></a></p>
</div>
</div>
</div>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-flow-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1: One set of sections, one extension, two renderings.
</figcaption>
</figure>
</div>
<p>The two renderings are not a screenshot of each other. They are the same chapter, computed once, laid out twice.</p>
<div class="quarto-layout-panel" data-layout-ncol="2">
<div class="quarto-layout-row">
<div class="quarto-layout-cell" style="flex-basis: 50.0%;justify-content: flex-start;">
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="assets/table.png" class="lightbox" data-gallery="quarto-lightbox-gallery-5" title="The Typst PDF"><img src="https://mickael.canouil.fr/posts/2026-08-17-quarto-clinical-report/assets/table.png" class="hero-art img-fluid figure-img" alt="Page 15 of the PDF. A numbered heading reads 7 Primary and secondary endpoints, followed by Table 14.2.1 with a dark teal header row, a source note under it, and a Kaplan-Meier figure with a risk table."></a></p>
<figcaption>The <strong>Typst</strong> PDF</figcaption>
</figure>
</div>
</div>
<div class="quarto-layout-cell" style="flex-basis: 50.0%;justify-content: flex-start;">
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="assets/html-site.png" class="lightbox" data-gallery="quarto-lightbox-gallery-6" title="The HTML site"><img src="https://mickael.canouil.fr/posts/2026-08-17-quarto-clinical-report/assets/html-site.png" class="hero-art img-fluid figure-img" alt="The same chapter on the HTML site. A left sidebar lists the chapters and appendices, and the main column shows Table 14.2.1 with the same teal header row and the same Kaplan-Meier figure."></a></p>
<figcaption>The <strong>HTML</strong> site</figcaption>
</figure>
</div>
</div>
</div>
</div>
</section>
<section id="the-typst-template" class="level2">
<h2 class="anchored" data-anchor-id="the-typst-template">The Typst Template</h2>
<p><code>typst-template.typ</code> defines one function, <code>clinical()</code>, with the document metadata as its arguments. <code>typst-show.typ</code> is the piece that maps Quarto metadata onto that function, including the brand colours and the logo Quarto resolves from <code>_brand.yml</code>.</p>
<p>The cover page is a page of its own, with the band reaching the paper edge.</p>
<div class="code-with-filename">
<div class="code-with-filename-file">
<pre><strong>_extensions/nordvale/clinical/typst-template.typ</strong></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="annotated-cell-2" data-filename="_extensions/nordvale/clinical/typst-template.typ" style="background: #f1f3f5;"><pre class="sourceCode typst code-annotation-code code-with-copy code-annotated"><code class="sourceCode typst"><span id="annotated-cell-2-1">page(</span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-2" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-2-2" class="code-annotation-target">  margin: (x: 0mm, y: 0mm),</span>
<span id="annotated-cell-2-3">  header: none,</span>
<span id="annotated-cell-2-4">  footer: none,</span>
<span id="annotated-cell-2-5">  numbering: none,</span>
<span id="annotated-cell-2-6">  background: none,</span>
<span id="annotated-cell-2-7">)[</span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-2" data-target-annotation="2" onclick="event.preventDefault();">2</a><span id="annotated-cell-2-8" class="code-annotation-target">  <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>block<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">100%</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> height<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">42mm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> primary<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> inset<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>x: <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">20mm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> y: <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">14mm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">))[</span></span>
<span id="annotated-cell-2-9">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> logo <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">!=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">none</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="annotated-cell-2-10">      logo</span>
<span id="annotated-cell-2-11">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span> else {</span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-2" data-target-annotation="3" onclick="event.preventDefault();">3</a><span id="annotated-cell-2-12" class="code-annotation-target">      text(fill: paper, size: 20pt, weight: "semibold")[<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>sponsor]</span>
<span id="annotated-cell-2-13">    }</span>
<span id="annotated-cell-2-14">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span></span>
<span id="annotated-cell-2-15">]</span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
</div>
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-2" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-2" data-code-lines="2" data-code-annotation="1">No margin on this page, so a filled block can reach the edge of the paper.</span>
</dd>
<dt data-target-cell="annotated-cell-2" data-target-annotation="2">2</dt>
<dd>
<span data-code-cell="annotated-cell-2" data-code-lines="8" data-code-annotation="2">The band itself, coloured with the brand primary, with its own inset for the logo.</span>
</dd>
<dt data-target-cell="annotated-cell-2" data-target-annotation="3">3</dt>
<dd>
<span data-code-cell="annotated-cell-2" data-code-lines="12" data-code-annotation="3">A sponsor with no logo still gets a cover, with the name set in the same place.</span>
</dd>
</dl>
<p>Then comes the document control page, which is the part a regulated document needs and a blog post rarely shows. It carries the revision history and the approval block, and both are written as metadata rather than as prose.</p>
<div class="code-with-filename">
<div class="code-with-filename-file">
<pre><strong>_metadata-csr.yml</strong></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="annotated-cell-3" data-filename="_metadata-csr.yml" style="background: #f1f3f5;"><pre class="sourceCode yaml code-annotation-code code-with-copy code-annotated"><code class="sourceCode yaml"><a class="code-annotation-anchor" data-target-cell="annotated-cell-3" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-3-1" class="code-annotation-target"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">status</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> draft</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-3" data-target-annotation="2" onclick="event.preventDefault();">2</a><span id="annotated-cell-3-2" class="code-annotation-target"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">approvals</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="annotated-cell-3-3"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">  </span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">name</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> Study Statistician</span></span>
<span id="annotated-cell-3-4"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">role</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> Biostatistics</span></span>
<span id="annotated-cell-3-5"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">  </span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">name</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> Clinical Study Lead</span></span>
<span id="annotated-cell-3-6"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">role</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> Clinical Development</span></span>
<span id="annotated-cell-3-7"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">  </span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">name</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> Medical Monitor</span></span>
<span id="annotated-cell-3-8"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">role</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> Clinical Safety and Pharmacovigilance</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-3" data-target-annotation="3" onclick="event.preventDefault();">3</a><span id="annotated-cell-3-9" class="code-annotation-target"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">revisions</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="annotated-cell-3-10"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">  </span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">version</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"0.1"</span></span>
<span id="annotated-cell-3-11"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">date</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> 2026-06-15</span></span>
<span id="annotated-cell-3-12"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">summary</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> First draft for internal review.</span></span>
<span id="annotated-cell-3-13"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">  </span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">version</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"1.0"</span></span>
<span id="annotated-cell-3-14"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">date</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> 2026-07-28</span></span>
<span id="annotated-cell-3-15"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">summary</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> Statistical results added after database lock.</span></span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
</div>
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-3" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-3" data-code-lines="1" data-code-annotation="1">The one key that decides whether the document is a draft.</span>
</dd>
<dt data-target-cell="annotated-cell-3" data-target-annotation="2">2</dt>
<dd>
<span data-code-cell="annotated-cell-3" data-code-lines="2" data-code-annotation="2">One entry per approver, in the order they appear on the page.</span>
</dd>
<dt data-target-cell="annotated-cell-3" data-target-annotation="3">3</dt>
<dd>
<span data-code-cell="annotated-cell-3" data-code-lines="9" data-code-annotation="3">The revision history, printed as a table above the signatures.</span>
</dd>
</dl>
<p>The template turns each approval into a signature block.</p>
<div class="code-with-filename">
<div class="code-with-filename-file">
<pre><strong>_extensions/nordvale/clinical/typst-template.typ</strong></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="annotated-cell-4" data-filename="_extensions/nordvale/clinical/typst-template.typ" style="background: #f1f3f5;"><pre class="sourceCode typst code-annotation-code code-with-copy code-annotated"><code class="sourceCode typst"><span id="annotated-cell-4-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">signature-block</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>name<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> role<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> rule<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> gray<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> block<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-4-2">  width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">100%</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-4-3">  inset<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>top: <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">10pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> bottom: <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">6pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-4-4"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)[</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-4" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-4-5" class="code-annotation-target">  <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>text<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">10pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> weight<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"semibold"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)[</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>name<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span></span>
<span id="annotated-cell-4-6">  <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>linebreak<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">()</span></span>
<span id="annotated-cell-4-7">  <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>text<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">9pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> rule<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)[</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>role<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-4" data-target-annotation="2" onclick="event.preventDefault();">2</a><span id="annotated-cell-4-8" class="code-annotation-target">  <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>v<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">26pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-4-9">  <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>grid<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-4-10">    columns<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1fr</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1fr</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-4-11">    column-gutter<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">12pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-4" data-target-annotation="3" onclick="event.preventDefault();">3</a><span id="annotated-cell-4-12" class="code-annotation-target">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>line<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>length<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">100%</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> stroke<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.6pt</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> rule<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>text<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">8pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> rule<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)[</span>Signature<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]],</span></span>
<span id="annotated-cell-4-13">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>line<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>length<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">100%</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> stroke<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.6pt</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> rule<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>text<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">8pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> rule<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)[</span>Date<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]],</span></span>
<span id="annotated-cell-4-14">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-4-15"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span></span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
</div>
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-4" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-4" data-code-lines="5" data-code-annotation="1">The name and the role, taken from one entry of <code>approvals</code>.</span>
</dd>
<dt data-target-cell="annotated-cell-4" data-target-annotation="2">2</dt>
<dd>
<span data-code-cell="annotated-cell-4" data-code-lines="8" data-code-annotation="2">The gap a pen needs, which is the only reason this block exists.</span>
</dd>
<dt data-target-cell="annotated-cell-4" data-target-annotation="3">3</dt>
<dd>
<span data-code-cell="annotated-cell-4" data-code-lines="12" data-code-annotation="3">Two ruled lines, one for the signature and one for the date.</span>
</dd>
</dl>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="assets/document-control.png" class="lightbox" data-gallery="quarto-lightbox-gallery-7" title="The document control page, with the revision history, the approval block, and the draft watermark."><img src="https://mickael.canouil.fr/posts/2026-08-17-quarto-clinical-report/assets/document-control.png" class="hero-art img-fluid figure-img" style="width:250px" alt="Page one of the report body. A heading reads Document control, followed by a table of two revisions with a teal header row, then an Approval section with three signature blocks, each showing a name, a role, and ruled lines for signature and date. A faint diagonal DRAFT watermark crosses the page. The footer reads Page 1 of 49."></a></p>
<figcaption>The document control page, with the revision history, the approval block, and the draft watermark.</figcaption>
</figure>
</div>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Note</span>The words the template writes itself
</div>
</div>
<div class="callout-body-container callout-body">
<p>The title of the table of contents comes from <code>quarto.language</code>, which is why the extension asks for Quarto 1.10.18 or later. Everything else the template writes is English in the source: “Signature”, “Date”, “Draft”, “Confidential”, and “Page N of M”. A key under <code>language</code> that Quarto does not ship passes validation and is merged over the defaults. It is then dropped before templates see it, so it reads back as an empty string, with no warning. I reported that as <a href="https://github.com/quarto-dev/quarto-cli/issues/14772"><span><span title="GitHub">quarto-dev/quarto-cli#14772</span><span class="gitlink-badge badge" aria-label="GitHub platform" title="GitHub" style="background-color: #c3c3c3; color: #000000;">GitHub</span></span></a>, and the fix proposed in <a href="https://github.com/quarto-dev/quarto-cli/pull/14773"><span><span title="GitHub">quarto-dev/quarto-cli#14773</span><span class="gitlink-badge badge" aria-label="GitHub platform" title="GitHub" style="background-color: #c3c3c3; color: #000000;">GitHub</span></span></a> which might land only in Quarto 1.11.</p>
</div>
</div>
<p>Two more pieces of furniture come from the same metadata. The running footer numbers the pages as X of Y, which Typst gives you with the final value of the page counter.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" data-filename="typst" style="background: #f1f3f5;"><pre class="sourceCode typst cw-auto code-with-copy"><code class="sourceCode typst"><span id="cb1-1">[Page <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>counter<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>page<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">).</span>display<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">()</span> of <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>counter<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>page<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">).</span>final<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">().</span>first<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">()</span>]</span></code></pre></div></div>
<p>The watermark is one comparison, read once at the top of the template.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb2" data-filename="typst" style="background: #f1f3f5;"><pre class="sourceCode typst cw-auto code-with-copy"><code class="sourceCode typst"><span id="cb2-1">let is-draft = lower(status) == "draft"</span></code></pre></div></div>
<div class="highlight">
<p><strong>One key decides the whole document:</strong> <code>status: draft</code> puts the watermark on every page and the draft notice on the cover. Setting it to <code>final</code> removes both, and there is nothing else to edit.</p>
</div>
<p>The last piece is orientation. Two listings are wider than a portrait page, so the appendix wraps those two in a <code>::: {.landscape}</code> div, and only those.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="assets/listing-landscape.png" class="lightbox" data-gallery="quarto-lightbox-gallery-8" title="The one landscape page, holding the two widest listings."><img src="https://mickael.canouil.fr/posts/2026-08-17-quarto-clinical-report/assets/listing-landscape.png" class="hero-art img-fluid figure-img" style="width:450px" alt="A landscape page from the appendix. Listing 16.2.7.1 lists serious treatment-emergent adverse events, with columns for subject, treatment, preferred term, system organ class, onset day, severity, relatedness, and outcome. Listing 16.2.8.1 below it lists abnormal laboratory values meeting a liver-injury criterion. The DRAFT watermark sits behind the tables."></a></p>
<figcaption>The one landscape page, holding the two widest listings.</figcaption>
</figure>
</div>
</section>
<section id="the-brand-reaches-the-figures" class="level2">
<h2 class="anchored" data-anchor-id="the-brand-reaches-the-figures">The Brand Reaches the Figures</h2>
<p><code>_brand.yml</code> holds nine named colours and three font families. The fonts are declared with <code>source: file</code>, and point at files bundled in the extension. The PDF then looks the same on my laptop and on a runner. Typst finds them through <code>font-paths</code>, and the HTML theme reads them from the brand.</p>
<p>The page furniture is the easy half. Figures and tables are the other half, because <code>ggplot2</code> and <code>gt</code> know nothing about a Quarto brand.</p>
<p>This project uses the approach I described in <a href="../../posts/2026-04-15-quarto-brand-figures-tables/">Branded Figures and Tables in R and Python with Quarto</a>, with its companion repository <a href="https://github.com/mcanouil/demo-quarto-brand-renderings">mcanouil/demo-quarto-brand-renderings</a> as the reference implementation. Read that post for the principle, and for the Python side. Only the names change here: <code>get_brand_info()</code> became <code>brand_raw()</code> and <code>brand()</code>, <code>theme_brand()</code> became <code>theme_nordvale()</code>, and <code>gt_brand()</code> became <code>gt_style_nordvale()</code>. <code>configure_brand_fonts()</code> kept its name.</p>
<p>Quarto publishes the resolved brand in an environment variable at render time, so the helpers read it from there.</p>
<div class="code-with-filename">
<div class="code-with-filename-file">
<pre><strong>R/brand.R</strong></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="annotated-cell-7" data-filename="R/brand.R" style="background: #f1f3f5;"><pre class="sourceCode r code-annotation-code code-with-copy code-annotated"><code class="sourceCode r"><span id="annotated-cell-7-1">brand_raw <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span>() {</span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-7" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-7-2" class="code-annotation-target">  execute_info <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">Sys.getenv</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"QUARTO_EXECUTE_INFO"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">unset =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">""</span>)</span>
<span id="annotated-cell-7-3">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> (<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">nzchar</span>(execute_info)) {</span>
<span id="annotated-cell-7-4">    info <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> jsonlite<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">fromJSON</span>(execute_info)</span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-7" data-target-annotation="2" onclick="event.preventDefault();">2</a><span id="annotated-cell-7-5" class="code-annotation-target">    brand <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> info[[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"format"</span>]][[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"render"</span>]][[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"brand"</span>]]</span>
<span id="annotated-cell-7-6">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> (<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">!</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">is.null</span>(brand) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;&amp;</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">length</span>(brand) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>L) {</span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-7" data-target-annotation="3" onclick="event.preventDefault();">3</a><span id="annotated-cell-7-7" class="code-annotation-target">      mode <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> (<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"light"</span> <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">%in%</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">names</span>(brand)) <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"light"</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">names</span>(brand)[[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>L]]</span>
<span id="annotated-cell-7-8">      <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">return</span>(brand[[mode]][[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"data"</span>]])</span>
<span id="annotated-cell-7-9">    }</span>
<span id="annotated-cell-7-10">  }</span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-7" data-target-annotation="4" onclick="event.preventDefault();">4</a><span id="annotated-cell-7-11" class="code-annotation-target">  yaml<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">read_yaml</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">brand_file</span>())</span>
<span id="annotated-cell-7-12">}</span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
</div>
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-7" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-7" data-code-lines="2" data-code-annotation="1">Quarto sets this variable for every chunk it executes, and it holds the render context as JSON.</span>
</dd>
<dt data-target-cell="annotated-cell-7" data-target-annotation="2">2</dt>
<dd>
<span data-code-cell="annotated-cell-7" data-code-lines="5" data-code-annotation="2">The brand Quarto resolved, after every <code>_brand.yml</code> in the project has been merged.</span>
</dd>
<dt data-target-cell="annotated-cell-7" data-target-annotation="3">3</dt>
<dd>
<span data-code-cell="annotated-cell-7" data-code-lines="7" data-code-annotation="3">The report is printed, so the light mode is the one to take.</span>
</dd>
<dt data-target-cell="annotated-cell-7" data-target-annotation="4">4</dt>
<dd>
<span data-code-cell="annotated-cell-7" data-code-lines="11" data-code-annotation="4">Outside a render there is no variable, so the helpers read the file and still work in a plain R session and in the tests.</span>
</dd>
</dl>
<p>From there, <code>theme_nordvale()</code> styles the plots, <code>treatment_colours()</code> fixes one colour per treatment group, and <code>gt_style_nordvale()</code> styles the tables. That last one stays inside the subset of <code>gt</code> options Quarto can translate to Typst: fonts, colours, borders, and alignment.</p>
<p>One HTML detail is worth the filter it took. The figures are drawn with <code>svglite</code>, and a standalone SVG file has to carry the fonts itself to display them. <code>inline-svglite.lua</code> puts the SVG content into the page instead of a link to the file. The page stylesheet then resolves the brand fonts, and the graphics device does not have to embed them.</p>
</section>
<section id="every-output-has-a-number" class="level2">
<h2 class="anchored" data-anchor-id="every-output-has-a-number">Every Output Has a Number</h2>
<p>Quarto numbers a table by where it falls in the document, so the first table of chapter 7 would be Table 7.1. A clinical study report is read by the ICH E3 output number instead, so that table is Table 14.2.1 wherever it sits. 14.2.1 reads as section 14, the displays that follow the text, then 2 for efficacy data, then the first table of that group. Demographic displays sit under 14.1 and safety displays under 14.3, while the subject data listings sit under 16.2, where 16.2.7 is the adverse event listing. The guideline stops at the group, and the digits after it are the sponsor’s own sequence. That is why the per-protocol version of 14.2.1 is 14.2.1.1. Tables and figures run as separate series, so a Table 14.2.1 and a Figure 14.2.1 both exist.</p>
<p>The report prints the E3 number, in the caption and in every reference to it. A reviewer asks for 14.3.1.2 and gets the same kind of output in any study from any sponsor.</p>
<p><code>R/tlf-index.R</code> is where every output number lives, and no other file in the project states one.</p>
<div class="code-with-filename">
<div class="code-with-filename-file">
<pre><strong>R/tlf-index.R</strong></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="annotated-cell-8" data-filename="R/tlf-index.R" style="background: #f1f3f5;"><pre class="sourceCode r code-annotation-code code-with-copy code-annotated"><code class="sourceCode r"><span id="annotated-cell-8-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">tlf_entry_spec</span>(</span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-8" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-8-2" class="code-annotation-target">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">key =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"tte"</span>,</span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-8" data-target-annotation="2" onclick="event.preventDefault();">2</a><span id="annotated-cell-8-3" class="code-annotation-target">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">ref =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"tbl-tte"</span>,</span>
<span id="annotated-cell-8-4">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">kind =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Table"</span>,</span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-8" data-target-annotation="3" onclick="event.preventDefault();">3</a><span id="annotated-cell-8-5" class="code-annotation-target">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">number =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"14.2.1"</span>,</span>
<span id="annotated-cell-8-6">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">section =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"14.2"</span>,</span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-8" data-target-annotation="4" onclick="event.preventDefault();">4</a><span id="annotated-cell-8-7" class="code-annotation-target">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">title =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Time to first dermatologic treatment-emergent adverse event"</span>,</span>
<span id="annotated-cell-8-8">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">subtitle =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Intention-to-treat population"</span>,</span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-8" data-target-annotation="5" onclick="event.preventDefault();">5</a><span id="annotated-cell-8-9" class="code-annotation-target">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">source_note =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Medians and confidence intervals are Kaplan-Meier estimates. ..."</span></span>
<span id="annotated-cell-8-10">)</span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
</div>
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-8" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-8" data-code-lines="2" data-code-annotation="1">The name the R code calls the output by. <code>tlf_entry()</code> stops with an error on a key it does not know, so a typo fails the render instead of printing an empty caption.</span>
</dd>
<dt data-target-cell="annotated-cell-8" data-target-annotation="2">2</dt>
<dd>
<span data-code-cell="annotated-cell-8" data-code-lines="3" data-code-annotation="2">The cross-reference identifier, which every reference in the report points at.</span>
</dd>
<dt data-target-cell="annotated-cell-8" data-target-annotation="3">3</dt>
<dd>
<span data-code-cell="annotated-cell-8" data-code-lines="5" data-code-annotation="3">The ICH E3 number, which the report prints, and the E3 section it belongs to, which the index of outputs prints.</span>
</dd>
<dt data-target-cell="annotated-cell-8" data-target-annotation="4">4</dt>
<dd>
<span data-code-cell="annotated-cell-8" data-code-lines="7" data-code-annotation="4">The title and the population, which <code>tlf_caption()</code> writes under the output.</span>
</dd>
<dt data-target-cell="annotated-cell-8" data-target-annotation="5">5</dt>
<dd>
<span data-code-cell="annotated-cell-8" data-code-lines="9" data-code-annotation="5">The source note, which <code>tlf_output()</code> attaches to the table itself.</span>
</dd>
</dl>
<p>A section then wraps the output in a cross-reference div and asks the index for the caption.</p>
<div class="code-with-filename">
<div class="code-with-filename-file">
<pre><strong>sections/07-efficacy.qmd</strong></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb3" data-filename="sections/07-efficacy.qmd" style="background: #f1f3f5;"><pre class="sourceCode markdown code-with-copy"><code class="sourceCode markdown"><span id="cb3-1">::: {#tbl-tte}</span>
<span id="cb3-2"></span>
<span id="cb3-3"><span class="in" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">```{r}</span></span>
<span id="cb3-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#| label: tte</span></span>
<span id="cb3-5"></span>
<span id="cb3-6"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">tlf_output</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">tlf_tte_summary</span>(adtte), <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"tte"</span>)</span>
<span id="cb3-7"><span class="in" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">```</span></span>
<span id="cb3-8"></span>
<span id="cb3-9"><span class="in" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">`{r} tlf_caption("tte")`</span></span>
<span id="cb3-10"></span>
<span id="cb3-11">:::</span></code></pre></div></div>
</div>
<p>Getting that number onto the page took more than metadata. Quarto builds a float number from the chapter and a running count, and nothing in the document can set it. A custom cross-reference type names the prefix, not the number. So the pre-render step writes the numbers to a file, and a filter applies them, differently in each format. In HTML the filter sets the number on the float, which Quarto then stringifies into the caption. In Typst it emits one show rule per label, because Typst numbers its own figures and ignores what the filter did to the float.</p>
<p>An HTML book needs one more step. Quarto writes its cross-reference index before any extension filter runs. A book then fills every reference from that index after Pandoc, so a filter never reaches those numbers. A post-render step renumbers them on the rendered pages instead.</p>
<p>The appendix then lists every output. <code>tlf_catalogue()</code> prints the index as a definition list, where each term is a cross-reference that resolves to the ICH E3 number, and each description carries the E3 section and the population.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="assets/index-of-outputs.png" class="lightbox" data-gallery="quarto-lightbox-gallery-9" title="The index of outputs, at the end of the listings appendix."><img src="https://mickael.canouil.fr/posts/2026-08-17-quarto-clinical-report/assets/index-of-outputs.png" class="hero-art img-fluid figure-img" style="width:250px" alt="A page headed A.2 Index of outputs. Each entry is a term such as Table 14.2.1 with a description under it reading, for example, Time to first dermatologic treatment-emergent adverse event. ICH E3 section 14.2. Intention-to-treat population."></a></p>
<figcaption>The index of outputs, at the end of the listings appendix.</figcaption>
</figure>
</div>
<p>Changing an output number is a one-line edit in one file, and every mention of it follows.</p>
</section>
<section id="book-or-one-long-document" class="level2">
<h2 class="anchored" data-anchor-id="book-or-one-long-document">Book or One Long Document</h2>
<p>A format is not tied to one kind of project, and this one shows it. The same extension renders the same sections as a Quarto book and as a single long document, and a profile picks between them. <code>_quarto-book.yml</code> builds the book. <code>_quarto-doc.yml</code> renders <code>csr.qmd</code>, which is nothing but includes, alongside the companion statistical analysis plan.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb4" data-filename="sh" style="background: #f1f3f5;"><pre class="sourceCode sh cw-auto code-with-copy"><code class="sourceCode bash"><span id="cb4-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">quarto</span> render                 <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># the book, in _book/</span></span>
<span id="cb4-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">quarto</span> render <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--profile</span> doc   <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># the single document, in _doc/</span></span></code></pre></div></div>
<p>Both project types produce a Typst PDF and an HTML output. The PDF comes out much the same either way, 50 pages in both, so the project type hardly shows there. The HTML is where it shows, and the book is the one I recommend to use. Each chapter becomes a page of its own, with a sidebar and a search box. A reviewer can then send a link straight to the section they ask about.</p>
<p>The study metadata that both share sits in <code>_metadata.yml</code>. The per-document keys do not, and that is deliberate. A shared <code>_metadata.yml</code> is always merged, and merging concatenates list values instead of replacing them. Put those keys in the shared file, and the approvals of the analysis plan join the approvals of the report. They live in <code>_metadata-csr.yml</code> instead, which the report loads, while <code>sap.qmd</code> sets its own.</p>
</section>
<section id="checking-the-numbers" class="level2">
<h2 class="anchored" data-anchor-id="checking-the-numbers">Checking the Numbers</h2>
<p>A report of computed numbers needs something to check those numbers. The project has three test files, and the workflow runs them before it renders anything.</p>
<p><code>test-invariants.R</code> checks the properties every table quietly relies on. The population flags nest as the methods chapter claims. A reason for discontinuation is present exactly for the subjects who did not complete. No subject is lost between the source data and the analysis datasets.</p>
<p><code>test-independent-test.R</code> recomputes every headline number the prose states, from the source packages rather than from the derived datasets. If <code>R/00-adam.R</code> gets a filter wrong, the test disagrees instead of restating the mistake.</p>
<p><code>test-double-programming.R</code> refits the models by a second route and compares the results with the strings the report prints. It catches a wrong estimate, a wrong reference group, or a transposed confidence interval, which no structural test can see.</p>
<p>The reproducibility appendix carries the rest of the traceability. It holds the analysis results datasets <code>gtsummary</code> keeps behind each table, a define.xml-style metadata excerpt, and the empty table shells next to the locked table. It also holds the session information, and an <code>renv</code> lockfile snapshotted in explicit mode from <code>DESCRIPTION</code>.</p>
</section>
<section id="what-i-would-watch-out-for" class="level2">
<h2 class="anchored" data-anchor-id="what-i-would-watch-out-for">What I Would Watch Out For</h2>
<p>Four things are worth knowing before you start. None of them are hard once you know where to look, and each one can cost an afternoon if you do not.</p>
<dl>
<dt>Floats numbered by chapter.</dt>
<dd>
Typst numbers figures from one counter, and Quarto zeroes that counter at each chapter. Typst is never told which chapter it is in, so every chapter opened with “Table 1”. <code>appendices.lua</code> reads the chapter from the heading counter and prefixes the float number with it. The same filter gives the appendices their letters.
</dd>
<dt>Tables that do not break.</dt>
<dd>
Quarto wraps every cross-referenced float in a <code>figure</code>, and a <code>figure</code> block is not breakable. A table taller than the space left overflowed the page instead of continuing on the next one. One show rule fixes it, and Typst then repeats the header rows on each page.
</dd>
</dl>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb5" data-filename="typst" style="background: #f1f3f5;"><pre class="sourceCode typst cw-auto code-with-copy"><code class="sourceCode typst"><span id="cb5-1">show figure.where(kind: "quarto-float-tbl"): set block(breakable: true)</span></code></pre></div></div>
<p>Plots stay unbreakable, so an image never parts from its caption.</p>
<dl>
<dt>Counting before filtering.</dt>
<dd>
A few subjects in the pilot data have a last date known to be alive that precedes their first dose. The report discloses how many, and that count is taken from the unfiltered derivation on purpose. Taken after the filter, the count can report zero one day, and turn a disclosure into a false statement.
</dd>
<dt>Defaults that are not neutral.</dt>
<dd>
<code>emmeans::contrast(method = "trt.vs.ctrl")</code> applies a Dunnett-style multiplicity adjustment by default. That adjustment widens the intervals beyond what the statistical methods chapter and the analysis plan describe. The secondary endpoint is descriptive here, so the call passes <code>adjust = "none"</code> and says so.
</dd>
</dl>
</section>
<section id="render-it-yourself" class="level2">
<h2 class="anchored" data-anchor-id="render-it-yourself">Render It Yourself</h2>
<p>You need Quarto 1.10.18 or later, for the <code>quarto.language</code> variables the Typst template reads, and R 4.4 or later.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb6" data-filename="sh" style="background: #f1f3f5;"><pre class="sourceCode sh cw-auto code-with-copy"><code class="sourceCode bash"><span id="cb6-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Rscript</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-e</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'renv::restore()'</span></span>
<span id="cb6-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">quarto</span> render</span></code></pre></div></div>
<p>The repository is at <a href="https://github.com/mcanouil/demo-quarto-clinical-report">mcanouil/demo-quarto-clinical-report</a>, and the rendered report is published at <a href="https://m.canouil.dev/demo-quarto-clinical-report/" class="uri">https://m.canouil.dev/demo-quarto-clinical-report/</a>. Here is the whole PDF, the one the workflow publishes on every push to <code>main</code>.</p>
<div style="text-align: center;">
<p><a href="https://m.canouil.dev/demo-quarto-clinical-report/Clinical-Study-Report.pdf" class="lightbox" data-gallery="quarto-lightbox-gallery-10"><embed src="https://m.canouil.dev/demo-quarto-clinical-report/Clinical-Study-Report.pdf" class="hero-art slide-deck img-fluid" loading="lazy" aria-label="The clinical study report, rendered from Quarto to a Typst PDF"></a></p>
</div>
<p>Open it full size: <a href="https://m.canouil.dev/demo-quarto-clinical-report/Clinical-Study-Report.pdf" target="_blank" rel="noopener noreferrer">Typst PDF</a>, <a href="https://m.canouil.dev/demo-quarto-clinical-report/" target="_blank" rel="noopener noreferrer">HTML book</a>.</p>
<p>Hope this shows that Quarto can be used in such context while providing profesional outputs.</p>
<p>Happy publishing!</p>


</section>


<a onclick="window.scrollTo(0, 0); return false;" id="quarto-back-to-top"><i class="bi bi-arrow-up"></i> Back to top</a><div id="quarto-appendix" class="default"><section id="footnotes" class="footnotes footnotes-end-of-document"><h2 class="anchored quarto-appendix-heading">Footnotes</h2>

<ol>
<li id="fn1"><p>ICH E3, Structure and Content of Clinical Study Reports, see <a href="https://database.ich.org/sites/default/files/E3_Guideline.pdf" class="uri">https://database.ich.org/sites/default/files/E3_Guideline.pdf</a>.↩︎</p></li>
</ol>
</section><section class="quarto-appendix-contents" id="quarto-reuse"><h2 class="anchored quarto-appendix-heading">Reuse</h2><div class="quarto-appendix-contents"><div><a rel="license" href="https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en_gb">CC BY-NC-SA 4.0</a></div></div></section><section class="quarto-appendix-contents" id="quarto-citation"><h2 class="anchored quarto-appendix-heading">Citation</h2><div><div class="quarto-appendix-secondary-label">BibTeX citation:</div><pre class="sourceCode code-with-copy quarto-appendix-bibtex"><code class="sourceCode bibtex">@misc{canouil2026,
  author = {CANOUIL, Mickaël},
  title = {A {Clinical} {Study} {Report} in {Quarto,} {End} to {End}},
  date = {2026-08-17},
  url = {https://mickael.canouil.fr/posts/2026-08-17-quarto-clinical-report/},
  langid = {en-GB}
}
</code></pre><div class="quarto-appendix-secondary-label">For attribution, please cite this work as:</div><div id="ref-canouil2026" class="csl-entry quarto-appendix-citeas">
CANOUIL, M. (2026-08-17). A Clinical Study Report in Quarto, End to End.
<em>Mickael.canouil.fr</em>. <a href="https://mickael.canouil.fr/posts/2026-08-17-quarto-clinical-report/">https://mickael.canouil.fr/posts/2026-08-17-quarto-clinical-report/</a>
</div></div></section></div> ]]></description>
  <category>quarto</category>
  <category>typst</category>
  <category>r</category>
  <category>clinical</category>
  <category>pharmaverse</category>
  <category>brand</category>
  <category>extensions</category>
  <guid>https://mickael.canouil.fr/posts/2026-08-17-quarto-clinical-report/</guid>
  <pubDate>Mon, 17 Aug 2026 00:00:00 GMT</pubDate>
  <media:content url="https://mickael.canouil.fr/posts/2026-08-17-quarto-clinical-report/featured.png" medium="image" type="image/png" height="76" width="144"/>
</item>
<item>
  <title>Gribouille 0.6.0: Wrangling Data and Breaks That Land on Round Numbers</title>
  <link>https://mickael.canouil.fr/posts/2026-07-28-gribouille-0-6/</link>
  <description><![CDATA[ 

<!--
@license MIT
@copyright 2026 Mickaël Canouil
@author Mickaël Canouil
-->
Skip to main content





<p><a href="https://github.com/mcanouil/gribouille">Gribouille</a> 0.6.0 is about the data before the plot, the numbers along the axis, and documentation. A set of wrangling verbs brings grouped aggregation, reshaping, and joining to the row-store data model, so the preparation stays in the document. Automatic breaks now come from the extended Wilkinson search, which puts ticks on rounder numbers closer to the data, and four <code>breaks-*</code> helpers let you place them yourself. The one thing to watch when you upgrade is the tick theming: <code>tick-length</code> and <code>tick-labels</code> are gone, replaced by a single <code>element-tick</code>.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="featured.png" class="lightbox" data-gallery="quarto-lightbox-gallery-1"><img src="https://mickael.canouil.fr/posts/2026-07-28-gribouille-0-6/featured.png" class="img-featured img-fluid quarto-figure quarto-figure-center figure-img" alt="Featured card for the Gribouille 0.6.0 release on a cream paper
background. On the left, the Gribouille wordmark sits above the tagline
&quot;Create elegant graphics with the Grammar of Graphics for Typst.&quot;, a
small orange &quot;v0.6.0 release&quot; pill, and the URL
m.canouil.dev/gribouille. On the right, a slightly rotated bordered card
frames three panels, one per change in the release. Across the top row,
a five-row table of class and highway values becomes a three-row table
of class, mean highway, and joined kind, with &quot;summarise()&quot; and
&quot;left-join()&quot; over an arrow between them. Bottom left, a line chart of
unemployment whose y-axis ticks sit at the quartiles of the series,
7,497 to 15,352, so the gridlines are unevenly spaced. Bottom right, a
penguin scatter with long tick marks on the x-axis, half-length ones on
the y-axis, and a duplicated y-axis on the right carrying marks but no
labels.
" width="600"></a></p>
</figure>
</div>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Note</span>At a glance
</div>
</div>
<div class="callout-body-container callout-body">
<ul>
<li><a href="https://github.com/mcanouil/gribouille">Gribouille</a> 0.6.0 on Typst Universe: <code>#import "@preview/gribouille:0.6.0": *</code>.</li>
<li><a href="https://m.canouil.dev/gribouille/reference/helpers/summarise.html"><code>summarise()</code></a> and <a href="https://m.canouil.dev/gribouille/reference/helpers/count.html"><code>count()</code></a> aggregate a dataset per group, one output row per group.</li>
<li><a href="https://m.canouil.dev/gribouille/reference/helpers/pivot-longer.html"><code>pivot-longer()</code></a> and <a href="https://m.canouil.dev/gribouille/reference/helpers/pivot-wider.html"><code>pivot-wider()</code></a> melt columns into a name/value pair and spread them back.</li>
<li><a href="https://m.canouil.dev/gribouille/reference/helpers/left-join.html"><code>left-join()</code></a> and the other four joins combine two datasets on their shared key columns; <code>bind-rows()</code> and <code>bind-cols()</code> stack or glue them.</li>
<li><code>select()</code>, <code>rename()</code>, <code>relocate()</code>, <code>drop-na()</code>, <code>distinct()</code>, and <code>slice-max()</code>/<code>slice-min()</code> handle the column and row work that a bare <code>.filter()</code> states awkwardly.</li>
<li>The verbs are <strong>experimental</strong>, and the <a href="https://m.canouil.dev/gribouille/guides/wrangle.html">Wrangling data</a> guide says when native Typst is enough on its own.</li>
<li>The documentation fills its gaps: the new guide above, a “Ticks and their labels” section in the <a href="https://m.canouil.dev/gribouille/guides/theming.html">theming guide</a>, and every <code>scale-*</code> page listing the keys its <code>..args</code> accepts, <code>oob</code> included.</li>
<li>Automatic continuous breaks come from the extended Wilkinson search, and <a href="https://m.canouil.dev/gribouille/reference/scales/breaks-width.html"><code>breaks-width()</code></a>, <a href="https://m.canouil.dev/gribouille/reference/scales/breaks-pretty.html"><code>breaks-pretty()</code></a>, <a href="https://m.canouil.dev/gribouille/reference/scales/breaks-extended.html"><code>breaks-extended()</code></a>, and <a href="https://m.canouil.dev/gribouille/reference/scales/breaks-quantile.html"><code>breaks-quantile()</code></a> build the <code>breaks:</code> closure for you.</li>
<li><iconify-icon inline="" icon="octicon:alert-16" aria-label="Breaking change" title="Breaking change"></iconify-icon> <strong>Breaking:</strong> tick marks are themed through the <code>axis-ticks</code> family with the new <a href="https://m.canouil.dev/gribouille/reference/themes/element-tick.html"><code>element-tick(colour:, stroke:, length:)</code></a>; <code>tick-length</code> and <code>tick-labels</code> are removed.</li>
<li><iconify-icon inline="" icon="octicon:alert-16" aria-label="Breaking change" title="Breaking change"></iconify-icon> <strong>Breaking:</strong> the internal summary-dispatch kernel gives up the <code>summarise</code> name to the new verb; <code>stat-summary(fun:)</code> is unchanged.</li>
</ul>
</div>
</div>
<p>Every figure in this post is a real, freshly compiled plot.</p>
<section id="breaking-changes" class="level2" data-number="1">
<h2 data-number="1" class="anchored" data-anchor-id="breaking-changes"><span class="header-section-number">1</span> <iconify-icon inline="" icon="octicon:alert-16" aria-label="Breaking change" title="Breaking change"></iconify-icon> Breaking changes</h2>
<p>Two names change, and only one of them is likely to be in your documents.</p>
<div class="callout callout-style-default callout-warning callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Warning</span>Migrate these names
</div>
</div>
<div class="callout-body-container callout-body">
<ul>
<li>Tick marks are one theme record now. <code>element-tick(colour:, stroke:, length:)</code> carries the mark length, so <code>tick-length</code> and its per-axis and per-side variants are removed: write <code>axis-ticks: element-tick(length: 0.2cm)</code> instead. <code>tick-labels</code> is removed too, because tick labels live in <code>axis-text</code>, so hide them per side with <code>axis-text-y-right: element-blank()</code>. To drop the marks and the space they reserve, <code>axis-ticks: element-blank()</code> is the one switch.</li>
<li><code>summarise</code> is the wrangling verb described below. The summary-dispatch kernel that used to carry that name is internal now. If you passed a summary by name or by closure to <code>stat-summary(fun:)</code> or <code>stat-summary-bin(fun:)</code>, nothing changes.</li>
</ul>
</div>
</div>
</section>
<section id="wrangling-data" class="level2" data-number="2">
<h2 data-number="2" class="anchored" data-anchor-id="wrangling-data"><span class="header-section-number">2</span> Wrangling data</h2>
<p>A Gribouille dataset is a row-store, an array of dictionaries with one dictionary per row. That is a plain Typst array, so a good part of the preparation is already a one-liner: <code>data.filter(...)</code> keeps rows, <code>data.map(...)</code> derives a column, <code>data.sorted(key: ...)</code> orders them. What Typst leaves open is grouped aggregation, reshaping, and joining, and that is what the new verbs do. They all take a row-store and return a row-store, so the result goes straight into <code>#plot</code> with nothing in between.</p>
<div class="callout callout-style-default callout-warning callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Warning</span>These verbs are experimental
</div>
</div>
<div class="callout-body-container callout-body">
<p>Their interfaces can change without notice while the design settles, and they may end up in a dedicated Typst data-wrangling package rather than in Gribouille. The plotting API and the native array methods are not affected.</p>
</div>
</div>
<p><a href="https://m.canouil.dev/gribouille/reference/helpers/summarise.html"><code>summarise()</code></a> collapses each group to one row. The grouping is per call, through <code>by:</code>, so there is no grouped state to remember to undo[^If you are a <code>dplyr</code> user, you know what I am talking about.]. Each aggregation is a closure that receives the group’s rows and returns one cell, so anything you can compute over the rows is fair game.</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="annotated-cell-1" data-filename="typst" style="background: #f1f3f5;"><pre class="sourceCode typst cw-auto code-annotation-code code-with-copy code-annotated"><code class="sourceCode typst"><span id="annotated-cell-1-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">by-class</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> summarise<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-1-2">  mpg<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-1" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-1-3" class="code-annotation-target">  mean-hwy<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> rows <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=&gt;</span> rows<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>map<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>row <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=&gt;</span> row<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>hwy<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">).</span>sum<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">()</span> / rows<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>len<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(),</span></span>
<span id="annotated-cell-1-4">  n<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> rows <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=&gt;</span> rows<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>len<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(),</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-1" data-target-annotation="2" onclick="event.preventDefault();">2</a><span id="annotated-cell-1-5" class="code-annotation-target">  by<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"class"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-1" data-target-annotation="3" onclick="event.preventDefault();">3</a><span id="annotated-cell-1-6" class="code-annotation-target"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">).</span>sorted<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>key<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> row <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=&gt;</span> row<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>mean-hwy<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-1-7"></span>
<span id="annotated-cell-1-8"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>plot<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-1-9">  data<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> by-class<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-1-10">  mapping<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> aes<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"class"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"mean-hwy"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-1-11">  layers<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-1-12">    geom-col<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> okabe-ito<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>at<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)),</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-1" data-target-annotation="4" onclick="event.preventDefault();">4</a><span id="annotated-cell-1-13" class="code-annotation-target">    geom-text<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>mapping<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> aes<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>label<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"n"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span> anchor<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"south"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">8pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-1-14">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-1-15">  scales<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> scales<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-1-16">    x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> scale-discrete<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>limits<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> by-class<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>map<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>row <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=&gt;</span> row<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>class<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)),</span></span>
<span id="annotated-cell-1-17">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-1-18">  labels<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> labels<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-1-19">    title<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Fuel Economy by Vehicle Class"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-1-20">    x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Vehicle Class"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-1-21">    y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Mean Highway mpg"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-1-22">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-1-23">  theme<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> theme-minimal<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(),</span></span>
<span id="annotated-cell-1-24">  width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">12cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-1-25">  height<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">7cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-1-26"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
<div class="cell-annotation">
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-1" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-1" data-code-lines="3" data-code-annotation="1">Every named argument is an aggregation: a closure over the group’s rows returning a single cell. A mean, a count, or a ratio of two columns all fit the same shape.</span>
</dd>
<dt data-target-cell="annotated-cell-1" data-target-annotation="2">2</dt>
<dd>
<span data-code-cell="annotated-cell-1" data-code-lines="5" data-code-annotation="2"><code>by:</code> takes one column name or an array of them; without it the whole dataset collapses to a single row.</span>
</dd>
<dt data-target-cell="annotated-cell-1" data-target-annotation="3">3</dt>
<dd>
<span data-code-cell="annotated-cell-1" data-code-lines="6,16" data-code-annotation="3">The output is an ordinary row-store, so <code>.sorted()</code> orders it, and feeding that order back as the discrete <code>limits</code> keeps the bars sorted.</span>
</dd>
<dt data-target-cell="annotated-cell-1" data-target-annotation="4">4</dt>
<dd>
<span data-code-cell="annotated-cell-1" data-code-lines="13" data-code-annotation="4"><code>n</code> is one of the aggregated columns, so it can be mapped like any other.</span>
</dd>
</dl>
</div>
<div>
<div class="light-content">
<div style="text-align: center;">
<p><a href="../../posts/2026-07-28-gribouille-0-6/./assets/typst-render/summarise-light.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-2" title="Bar chart of the mean highway fuel economy of each vehicle class, sorted from pickup at about 17 miles per gallon up to compact at about 30, with the number of vehicles in the class printed above each bar."><img src="https://mickael.canouil.fr/posts/2026-07-28-gribouille-0-6/assets/typst-render/summarise-light.svg" class="img-fluid hero-art" alt="Bar chart of the mean highway fuel economy of each vehicle class, sorted from pickup at about 17 miles per gallon up to compact at about 30, with the number of vehicles in the class printed above each bar."></a></p>
</div>
</div>
<div class="dark-content">
<div style="text-align: center;">
<p><a href="../../posts/2026-07-28-gribouille-0-6/./assets/typst-render/summarise-dark.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-3" title="Bar chart of the mean highway fuel economy of each vehicle class, sorted from pickup at about 17 miles per gallon up to compact at about 30, with the number of vehicles in the class printed above each bar."><img src="https://mickael.canouil.fr/posts/2026-07-28-gribouille-0-6/assets/typst-render/summarise-dark.svg" class="img-fluid hero-art" alt="Bar chart of the mean highway fuel economy of each vehicle class, sorted from pickup at about 17 miles per gallon up to compact at about 30, with the number of vehicles in the class printed above each bar."></a></p>
</div>
</div>
</div>
</div>
<p>When the aggregation is just a tally, <a href="https://m.canouil.dev/gribouille/reference/helpers/count.html"><code>count()</code></a> is the short form: it groups by the columns you name, adds an <code>n</code> column, and <code>sort: true</code> orders the groups from most to least frequent.</p>
<p>Two columns that measure the same thing are usually easier to plot once melted into one value column with a label column beside it. <a href="https://m.canouil.dev/gribouille/reference/helpers/pivot-longer.html"><code>pivot-longer()</code></a> does that, and <a href="https://m.canouil.dev/gribouille/reference/helpers/pivot-wider.html"><code>pivot-wider()</code></a> spreads them back.</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="annotated-cell-2" data-filename="typst" style="background: #f1f3f5;"><pre class="sourceCode typst cw-auto code-annotation-code code-with-copy code-annotated"><code class="sourceCode typst"><span id="annotated-cell-2-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">long</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> pivot-longer<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-2-2">  mpg<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-2" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-2-3" class="code-annotation-target">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"cty"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"hwy"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-2" data-target-annotation="2" onclick="event.preventDefault();">2</a><span id="annotated-cell-2-4" class="code-annotation-target">  names-to<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"metric"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-2-5">  values-to<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"mpg"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-2-6"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-2-7"></span>
<span id="annotated-cell-2-8"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>plot<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-2-9">  data<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> long<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-2-10">  mapping<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> aes<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"metric"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"mpg"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"metric"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-2-11">  layers<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>geom-boxplot<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(),),</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-2" data-target-annotation="3" onclick="event.preventDefault();">3</a><span id="annotated-cell-2-12" class="code-annotation-target">  facet<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> facet-wrap<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"cyl"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-2-13">  scales<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> scales<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> scale-okabe-ito<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">()),</span></span>
<span id="annotated-cell-2-14">  labels<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> labels<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-2-15">    title<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"City Against Highway, by Cylinder Count"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-2-16">    x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Metric"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-2-17">    y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Miles per Gallon"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-2-18">    fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Metric"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-2-19">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-2-20">  guides<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> guides<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">none</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-2-21">  theme<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> theme-minimal<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(),</span></span>
<span id="annotated-cell-2-22">  width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">12cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-2-23">  height<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">7cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-2-24"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
<div class="cell-annotation">
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-2" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-2" data-code-lines="3" data-code-annotation="1">The columns to melt. Each row of <code>mpg</code> becomes two rows here, one per measurement.</span>
</dd>
<dt data-target-cell="annotated-cell-2" data-target-annotation="2">2</dt>
<dd>
<span data-code-cell="annotated-cell-2" data-code-lines="4" data-code-annotation="2"><code>names-to</code> names the column holding the old column names, <code>values-to</code> the one holding their values.</span>
</dd>
<dt data-target-cell="annotated-cell-2" data-target-annotation="3">3</dt>
<dd>
<span data-code-cell="annotated-cell-2" data-code-lines="12" data-code-annotation="3">The columns that were not melted, <code>cyl</code> among them, are carried through, so they can still facet or colour the plot.</span>
</dd>
</dl>
</div>
<div>
<div class="light-content">
<div style="text-align: center;">
<p><a href="../../posts/2026-07-28-gribouille-0-6/./assets/typst-render/pivot-light.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-4" title="Box plots of miles per gallon for city and highway economy, faceted into one panel per cylinder count; the highway box sits above the city box in every panel, and both drop as the number of cylinders grows."><img src="https://mickael.canouil.fr/posts/2026-07-28-gribouille-0-6/assets/typst-render/pivot-light.svg" class="img-fluid hero-art" alt="Box plots of miles per gallon for city and highway economy, faceted into one panel per cylinder count; the highway box sits above the city box in every panel, and both drop as the number of cylinders grows."></a></p>
</div>
</div>
<div class="dark-content">
<div style="text-align: center;">
<p><a href="../../posts/2026-07-28-gribouille-0-6/./assets/typst-render/pivot-dark.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-5" title="Box plots of miles per gallon for city and highway economy, faceted into one panel per cylinder count; the highway box sits above the city box in every panel, and both drop as the number of cylinders grows."><img src="https://mickael.canouil.fr/posts/2026-07-28-gribouille-0-6/assets/typst-render/pivot-dark.svg" class="img-fluid hero-art" alt="Box plots of miles per gallon for city and highway economy, faceted into one panel per cylinder count; the highway box sits above the city box in every panel, and both drop as the number of cylinders grows."></a></p>
</div>
</div>
</div>
</div>
<p>Enriching a dataset with a lookup table is a join, not a dictionary read per row. Build the lookup as its own small row-store and <a href="https://m.canouil.dev/gribouille/reference/helpers/left-join.html"><code>left-join()</code></a> it on the shared key.</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="annotated-cell-3" data-filename="typst" style="background: #f1f3f5;"><pre class="sourceCode typst cw-auto code-annotation-code code-with-copy code-annotated"><code class="sourceCode typst"><span id="annotated-cell-3-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">by-class</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> summarise<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-3-2">  mpg<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-3-3">  mean-hwy<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> rows <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=&gt;</span> rows<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>map<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>row <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=&gt;</span> row<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>hwy<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">).</span>sum<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">()</span> / rows<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>len<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(),</span></span>
<span id="annotated-cell-3-4">  by<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"class"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-3-5"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">).</span>sorted<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>key<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> row <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=&gt;</span> row<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>mean-hwy<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-3-6"></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-3" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-3-7" class="code-annotation-target"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">kind</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-3-8">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>class: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"compact"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> kind: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Car"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-3-9">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>class: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"subcompact"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> kind: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Car"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-3-10">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>class: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"midsize"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> kind: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Car"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-3-11">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>class: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2seater"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> kind: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Car"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-3-12">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>class: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"minivan"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> kind: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Van"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-3-13">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>class: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"suv"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> kind: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"SUV"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-3-14">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>class: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"pickup"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> kind: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Truck"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-3-15"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-3-16"></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-3" data-target-annotation="2" onclick="event.preventDefault();">2</a><span id="annotated-cell-3-17" class="code-annotation-target"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">enriched</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> left-join<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>by-class<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> kind<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> by<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"class"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-3-18"></span>
<span id="annotated-cell-3-19"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>plot<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-3-20">  data<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> enriched<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-3-21">  mapping<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> aes<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"class"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"mean-hwy"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"kind"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-3-22">  layers<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>geom-col<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(),),</span></span>
<span id="annotated-cell-3-23">  scales<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> scales<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-3-24">    x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> scale-discrete<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>limits<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> enriched<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>map<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>row <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=&gt;</span> row<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>class<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)),</span></span>
<span id="annotated-cell-3-25">    fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> scale-okabe-ito<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(),</span></span>
<span id="annotated-cell-3-26">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-3-27">  labels<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> labels<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-3-28">    title<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Economy by Class and by Kind"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-3-29">    x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Vehicle Class"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-3-30">    y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Mean Highway mpg"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-3-31">    fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Kind"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-3-32">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-3-33">  theme<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> theme-minimal<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(),</span></span>
<span id="annotated-cell-3-34">  width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">12cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-3-35">  height<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">7cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-3-36"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
<div class="cell-annotation">
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-3" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-3" data-code-lines="7" data-code-annotation="1">The lookup is a row-store like any other, written inline here, read from a CSV just as well.</span>
</dd>
<dt data-target-cell="annotated-cell-3" data-target-annotation="2">2</dt>
<dd>
<span data-code-cell="annotated-cell-3" data-code-lines="17" data-code-annotation="2">Every row of the left dataset is kept and gains the lookup’s extra columns. <code>inner-join</code> keeps only the matched rows, <code>full-join</code> keeps both sides, and <code>semi-join</code>/<code>anti-join</code> filter the left dataset without adding columns.</span>
</dd>
</dl>
</div>
<div>
<div class="light-content">
<div style="text-align: center;">
<p><a href="../../posts/2026-07-28-gribouille-0-6/./assets/typst-render/join-light.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-6" title="The same bar chart of mean highway economy per vehicle class, sorted ascending, with the bars coloured by a joined vehicle kind: the pickup and SUV classes sit at the low end, the minivan in the middle, and the car classes at the high end."><img src="https://mickael.canouil.fr/posts/2026-07-28-gribouille-0-6/assets/typst-render/join-light.svg" class="img-fluid hero-art" alt="The same bar chart of mean highway economy per vehicle class, sorted ascending, with the bars coloured by a joined vehicle kind: the pickup and SUV classes sit at the low end, the minivan in the middle, and the car classes at the high end."></a></p>
</div>
</div>
<div class="dark-content">
<div style="text-align: center;">
<p><a href="../../posts/2026-07-28-gribouille-0-6/./assets/typst-render/join-dark.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-7" title="The same bar chart of mean highway economy per vehicle class, sorted ascending, with the bars coloured by a joined vehicle kind: the pickup and SUV classes sit at the low end, the minivan in the middle, and the car classes at the high end."><img src="https://mickael.canouil.fr/posts/2026-07-28-gribouille-0-6/assets/typst-render/join-dark.svg" class="img-fluid hero-art" alt="The same bar chart of mean highway economy per vehicle class, sorted ascending, with the bars coloured by a joined vehicle kind: the pickup and SUV classes sit at the low end, the minivan in the middle, and the car classes at the high end."></a></p>
</div>
</div>
</div>
</div>
<div class="highlight">
<p><strong>Every verb takes a row-store and returns a row-store:</strong> the result of a summarise, a pivot, or a join goes straight into <code>#plot</code> with no adapter in between.</p>
</div>
<p>One more thing before the plot, for real files. A CSV arrives as strings, and missing values are spelled in as many ways as there are people producing the files. <a href="https://m.canouil.dev/gribouille/reference/core/as-numeric.html"><code>as-numeric()</code></a> now takes an <code>na:</code> list of sentinel values, blanking them before it parses, so a placeholder does not survive as a number.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" data-filename="typst" style="background: #f1f3f5;"><pre class="sourceCode typst cw-auto code-with-copy"><code class="sourceCode typst"><span id="cb1-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">raw</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> csv<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"vehicles.csv"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> row-type<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> dictionary<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb1-2"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">clean</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> drop-na<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>as-numeric<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>raw<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"hwy"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> na<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"NA"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"-99"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)),</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"hwy"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span></code></pre></div></div>
<p>The <a href="https://m.canouil.dev/gribouille/guides/wrangle.html">Wrangling data</a> guide walks the whole path, from coercion to the plot, and it also says which tasks need no verb at all.</p>
</section>
<section id="breaks-that-land-on-round-numbers" class="level2" data-number="3">
<h2 data-number="3" class="anchored" data-anchor-id="breaks-that-land-on-round-numbers"><span class="header-section-number">3</span> Breaks that land on round numbers</h2>
<p>Until now, automatic breaks came from a fixed 1/2/5 ladder: pick the step from that list, cover the range. It is simple, and it is regularly a bit off, either too far from the data or with more ticks than anyone asked for. 0.6.0 replaces it with the extended Wilkinson search <span><a href="https://github.com/talbot" title="GitHub">[@talbot_extension_2010]</a><span class="gitlink-badge badge" title="GitHub" aria-label="GitHub platform" style="background-color: #c3c3c3; color: #000000;">GitHub</span></span>. It scores candidate tick sequences on three things at once: how simple the numbers are, how well they cover the data, and how close the count lands to the target. There is nothing to change in your code, plots come out with rounder ticks that sit closer to the data.</p>
<p>The same search is available as <a href="https://m.canouil.dev/gribouille/reference/scales/breaks-extended.html"><code>breaks-extended(n:)</code></a> when you want a different tick count. It is one of four helpers that build a closure for the <code>breaks:</code> and <code>minor-breaks:</code> arguments of a continuous scale. The closure is called with the values the scale trained on, once per panel, and returns the positions, so free-scale facets each get their own.</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="annotated-cell-5" data-filename="typst" style="background: #f1f3f5;"><pre class="sourceCode typst cw-auto code-annotation-code code-with-copy code-annotated"><code class="sourceCode typst"><span id="annotated-cell-5-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>plot<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-5-2">  data<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> economics<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-5-3">  mapping<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> aes<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"date"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"unemploy"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-5-4">  layers<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-5-5">    geom-line<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>colour<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> okabe-ito<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>at<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span> linewidth<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.5pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-5-6">    geom-point<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> okabe-ito<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>at<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span> stroke<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">none</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-5-7">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-5-8">  scales<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> scales<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-5-9">    x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> scale-date<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>date-format<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"[year]-[month repr:numerical]"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-5-10">    y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> scale-continuous<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-5" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-5-11" class="code-annotation-target">      breaks<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> breaks-quantile<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(),</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-5" data-target-annotation="2" onclick="event.preventDefault();">2</a><span id="annotated-cell-5-12" class="code-annotation-target">      minor-breaks<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> breaks-width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">500</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-5" data-target-annotation="3" onclick="event.preventDefault();">3</a><span id="annotated-cell-5-13" class="code-annotation-target">      labels<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> format-comma<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>digits<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-5-14">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-5-15">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-5-16">  labels<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> labels<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-5-17">    title<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Ticks Where the Data Sits"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-5-18">    x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Date"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-5-19">    y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Unemployed (thousands)"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-5-20">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-5-21">  theme<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> theme-minimal<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(),</span></span>
<span id="annotated-cell-5-22">  width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">12cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-5-23">  height<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">7cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-5-24"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
<div class="cell-annotation">
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-5" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-5" data-code-lines="11" data-code-annotation="1"><code>breaks-quantile(probs:)</code> puts the ticks at sample quantiles, the quartiles by default, so the axis reports where the mass of the data sits instead of drawing a regular grid.</span>
</dd>
<dt data-target-cell="annotated-cell-5" data-target-annotation="2">2</dt>
<dd>
<span data-code-cell="annotated-cell-5" data-code-lines="12" data-code-annotation="2"><code>minor-breaks:</code> takes the same closures. <code>breaks-width(width, offset:)</code> places a line every <code>width</code>, anchored on <code>offset</code>, so the positions stay on round multiples however the range moves.</span>
</dd>
<dt data-target-cell="annotated-cell-5" data-target-annotation="3">3</dt>
<dd>
<span data-code-cell="annotated-cell-5" data-code-lines="13" data-code-annotation="3">The tick labels are formatted by <code>format-comma()</code>, unrelated to where the ticks sit. <code>breaks-pretty(n:)</code> is the fourth helper, for round positions at a target count.</span>
</dd>
</dl>
</div>
<div>
<div class="light-content">
<div style="text-align: center;">
<p><a href="../../posts/2026-07-28-gribouille-0-6/./assets/typst-render/breaks-light.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-8" title="Line chart with a marker per month of the number of unemployed people in the United States from January 2008 to December 2009, rising from about 7,500 thousand to over 15,000 thousand, with y-axis ticks at the quartiles of the series and minor gridlines every 500."><img src="https://mickael.canouil.fr/posts/2026-07-28-gribouille-0-6/assets/typst-render/breaks-light.svg" class="img-fluid hero-art" alt="Line chart with a marker per month of the number of unemployed people in the United States from January 2008 to December 2009, rising from about 7,500 thousand to over 15,000 thousand, with y-axis ticks at the quartiles of the series and minor gridlines every 500."></a></p>
</div>
</div>
<div class="dark-content">
<div style="text-align: center;">
<p><a href="../../posts/2026-07-28-gribouille-0-6/./assets/typst-render/breaks-dark.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-9" title="Line chart with a marker per month of the number of unemployed people in the United States from January 2008 to December 2009, rising from about 7,500 thousand to over 15,000 thousand, with y-axis ticks at the quartiles of the series and minor gridlines every 500."><img src="https://mickael.canouil.fr/posts/2026-07-28-gribouille-0-6/assets/typst-render/breaks-dark.svg" class="img-fluid hero-art" alt="Line chart with a marker per month of the number of unemployed people in the United States from January 2008 to December 2009, rising from about 7,500 thousand to over 15,000 thousand, with y-axis ticks at the quartiles of the series and minor gridlines every 500."></a></p>
</div>
</div>
</div>
</div>
<p>A closure and an explicit array of breaks are not treated the same way, and the difference matters. An explicit array widens the axis so every requested tick stays visible. Closure breaks are clipped to the trained domain instead, since the closure has seen the data and cannot ask for anything outside it.</p>
<p>A related fix landed with them. A scale trained on whole numbers only, such as years or counts, now keeps whole automatic breaks, so a narrow range like 2020 to 2023 no longer labels half-steps.</p>
</section>
<section id="ticks-in-one-element" class="level2" data-number="4">
<h2 data-number="4" class="anchored" data-anchor-id="ticks-in-one-element"><span class="header-section-number">4</span> Ticks, in one element</h2>
<p>Tick marks used to be described in two places: their colour and stroke in <code>axis-ticks</code>, their length in a separate <code>tick-length</code> with its own per-axis and per-side variants. They are one record now. <a href="https://m.canouil.dev/gribouille/reference/themes/element-tick.html"><code>element-tick(colour:, stroke:, length:)</code></a> carries all three, on <code>axis-ticks</code> and on its per-axis, per-side, and minor variants.</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="annotated-cell-6" data-filename="typst" style="background: #f1f3f5;"><pre class="sourceCode typst cw-auto code-annotation-code code-with-copy code-annotated"><code class="sourceCode typst"><span id="annotated-cell-6-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>plot<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-6-2">  data<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> penguins<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-6-3">  mapping<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> aes<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"flipper-len"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"body-mass"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"species"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-6-4">  layers<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>geom-point<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> alpha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.8</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> stroke<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">none</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),),</span></span>
<span id="annotated-cell-6-5">  scales<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> scales<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-6" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-6-6" class="code-annotation-target">    y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> scale-continuous<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>secondary<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> dup-axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">()),</span></span>
<span id="annotated-cell-6-7">    fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> scale-okabe-ito<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(),</span></span>
<span id="annotated-cell-6-8">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-6-9">  labels<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> labels<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-6-10">    title<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"One Record per Tick Mark"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-6-11">    x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Flipper Length (mm)"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-6-12">    y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Body Mass (g)"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-6-13">    fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Species"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-6-14">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-6-15">  theme<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> theme-minimal<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-6-16">    axis-line<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> element-line<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>stroke<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-6" data-target-annotation="2" onclick="event.preventDefault();">2</a><span id="annotated-cell-6-17" class="code-annotation-target">    axis-ticks<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> element-tick<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>length<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.25cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> stroke<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.8pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-6" data-target-annotation="3" onclick="event.preventDefault();">3</a><span id="annotated-cell-6-18" class="code-annotation-target">    axis-ticks-y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> element-tick<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>length<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">50%</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-6" data-target-annotation="4" onclick="event.preventDefault();">4</a><span id="annotated-cell-6-19" class="code-annotation-target">    axis-text-y-right<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> element-blank<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(),</span></span>
<span id="annotated-cell-6-20">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-6-21">  width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">12cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-6-22">  height<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">7cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-6-23"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
<div class="cell-annotation">
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-6" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-6" data-code-lines="6" data-code-annotation="1"><code>dup-axis()</code> repeats the axis on the opposite side, and its <code>breaks:</code> and <code>labels:</code> are honoured now; the secondary axis used to draw the primary ones whatever you asked for.</span>
</dd>
<dt data-target-cell="annotated-cell-6" data-target-annotation="2">2</dt>
<dd>
<span data-code-cell="annotated-cell-6" data-code-lines="17" data-code-annotation="2">Length, colour, and stroke sit in the same record, set once on <code>axis-ticks</code> for every side.</span>
</dd>
<dt data-target-cell="annotated-cell-6" data-target-annotation="3">3</dt>
<dd>
<span data-code-cell="annotated-cell-6" data-code-lines="18" data-code-annotation="3">A ratio scales the length inherited from the parent surface, exactly as <code>size</code> and <code>stroke</code> do elsewhere, so the y marks come out half as long without naming an absolute value.</span>
</dd>
<dt data-target-cell="annotated-cell-6" data-target-annotation="4">4</dt>
<dd>
<span data-code-cell="annotated-cell-6" data-code-lines="19" data-code-annotation="4">Tick labels live in <code>axis-text</code>, so hiding them per side is the usual <code>element-blank()</code>, and the width they reserved goes back to the panel.</span>
</dd>
</dl>
</div>
<div>
<div class="light-content">
<div style="text-align: center;">
<p><a href="../../posts/2026-07-28-gribouille-0-6/./assets/typst-render/ticks-light.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-10" title="Scatter plot of penguin body mass against flipper length with long tick marks on the x-axis, half-length ticks on the y-axis, and a duplicated y-axis on the right carrying its marks but no labels."><img src="https://mickael.canouil.fr/posts/2026-07-28-gribouille-0-6/assets/typst-render/ticks-light.svg" class="img-fluid hero-art" alt="Scatter plot of penguin body mass against flipper length with long tick marks on the x-axis, half-length ticks on the y-axis, and a duplicated y-axis on the right carrying its marks but no labels."></a></p>
</div>
</div>
<div class="dark-content">
<div style="text-align: center;">
<p><a href="../../posts/2026-07-28-gribouille-0-6/./assets/typst-render/ticks-dark.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-11" title="Scatter plot of penguin body mass against flipper length with long tick marks on the x-axis, half-length ticks on the y-axis, and a duplicated y-axis on the right carrying its marks but no labels."><img src="https://mickael.canouil.fr/posts/2026-07-28-gribouille-0-6/assets/typst-render/ticks-dark.svg" class="img-fluid hero-art" alt="Scatter plot of penguin body mass against flipper length with long tick marks on the x-axis, half-length ticks on the y-axis, and a duplicated y-axis on the right carrying its marks but no labels."></a></p>
</div>
</div>
</div>
</div>
<p>Minor marks follow the same record through <code>axis-ticks-minor</code>. The sub-decade ticks that <a href="https://m.canouil.dev/gribouille/reference/guides/guide-axis-logticks.html"><code>guide-axis-logticks()</code></a> adds on a log axis used to be hardcoded at half length in the major stroke. They are themable now, and they inherit the major record, so the default look is unchanged.</p>
<div class="highlight">
<p><strong>One record, one switch:</strong> <code>element-tick</code> holds the colour, the stroke, and the length, and <code>axis-ticks: element-blank()</code> turns the marks off without reserving any depth.</p>
</div>
</section>
<section id="label-alignment-fix" class="level2" data-number="5">
<h2 data-number="5" class="anchored" data-anchor-id="label-alignment-fix"><span class="header-section-number">5</span> Label Alignment Fix</h2>
<p>The usual share of fixes rides along.</p>
<p>The one you are most likely to notice is vertical alignment of labels (<span><a href="https://github.com/mcanouil/gribouille/issues/208" title="GitHub">#208</a><span class="gitlink-badge badge" title="GitHub" aria-label="GitHub platform" style="background-color: #c3c3c3; color: #000000;">GitHub</span></span>). Legend entries, vertical colourbar tick labels, and y-axis tick labels now centre on the cap-height and baseline band instead of the glyph bounds. A label carrying a descender, <code>Chinstrap</code> next to <code>Adelie</code>, no longer sits a little higher than its neighbours.</p>
</section>
<section id="filling-the-documentation-gaps" class="level2" data-number="6">
<h2 data-number="6" class="anchored" data-anchor-id="filling-the-documentation-gaps"><span class="header-section-number">6</span> Filling the documentation gaps</h2>
<p>A good share of this release was writing down what was already there.</p>
<p>The reference pages had a real hole in them (<span><a href="https://github.com/mcanouil/gribouille/issues/211" title="GitHub">#211</a><span class="gitlink-badge badge" title="GitHub" aria-label="GitHub platform" style="background-color: #c3c3c3; color: #000000;">GitHub</span></span>). A <code>scale-*</code> constructor takes <code>..args</code>, and the page described a few of those keys in prose, which left the rest to be found by reading the source. Every <code>scale-*</code> page now lists each key it accepts, with its type, its accepted values, and its default. That includes <code>oob</code>, the out-of-range handling, which no page documented at all.</p>
<p>Cross-references are links again. A reference that closed a parenthetical printed as a bare <code>@geom-line</code> instead of a link, and a link sometimes ran into the word or the comma before it.</p>
<p>Two guides changed. The <a href="https://m.canouil.dev/gribouille/guides/wrangle.html">Wrangling data</a> guide is new, and it starts where the data does: coercion, then grouped aggregation, reshaping, joining, and the plot at the end. It also lists the tasks that need no verb at all, because <code>filter</code>, <code>map</code>, and <code>sorted</code> already do them. The <a href="https://m.canouil.dev/gribouille/guides/theming.html">theming guide</a> gains a “Ticks and their labels” section covering <code>element-tick</code>, ratio lengths, minor marks, and hiding labels per side.</p>
<p>The site itself got a pass for legibility. The GitHub icon in the navbar is a widget now. It shows the live star and fork counts and opens a project menu: repository, issues, pull requests, releases, star, fork, Typst Universe, and sponsoring. It replaces the separate links that used to sit in the navbar, and it shares one pill button style with the version switcher, the search button, and the scheme toggle.</p>
</section>
<section id="wrap-up" class="level2" data-number="7">
<h2 data-number="7" class="anchored" data-anchor-id="wrap-up"><span class="header-section-number">7</span> Wrap-up</h2>
<div class="highlight">
<p><strong>Prepare the data in the document, and let the axis pick better numbers.</strong></p>
</div>
<p>Next on the list is more geoms and more worked examples. If you run into something unexpected, the issue tracker is the right place for it.</p>
<ul>
<li>Gribouille
<ul>
<li>Repository: <a href="https://github.com/mcanouil/gribouille" class="uri">https://github.com/mcanouil/gribouille</a>.</li>
<li>Documentation: <a href="https://m.canouil.dev/gribouille" class="uri">https://m.canouil.dev/gribouille</a>.</li>
<li>Typst Universe: <a href="https://typst.app/universe/package/gribouille" class="uri">https://typst.app/universe/package/gribouille</a>.</li>
</ul></li>
<li>Typst Render
<ul>
<li>Repository: <a href="https://github.com/mcanouil/quarto-typst-render" class="uri">https://github.com/mcanouil/quarto-typst-render</a>.</li>
<li>Documentation: <a href="https://m.canouil.dev/quarto-typst-render" class="uri">https://m.canouil.dev/quarto-typst-render</a>.</li>
</ul></li>
</ul>
<div class="callout callout-style-default callout-tip callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Tip</span>A note on contributions
</div>
</div>
<div class="callout-body-container callout-body">
<p>Gribouille is an unfunded spare-time project, and the API is still settling. Bug reports and ideas are very welcome on the issue tracker. Pull requests are not being accepted for now, for the reasons set out in the <a href="../../posts/2026-05-20-gribouille-grammar-of-graphics-for-typst/index.html">launch post</a>. Thanks in advance for your patience.</p>
</div>
</div>


</section>

<a onclick="window.scrollTo(0, 0); return false;" id="quarto-back-to-top"><i class="bi bi-arrow-up"></i> Back to top</a><div id="quarto-appendix" class="default"><section class="quarto-appendix-contents" id="quarto-reuse"><h2 class="anchored quarto-appendix-heading">Reuse</h2><div class="quarto-appendix-contents"><div><a rel="license" href="https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en_gb">CC BY-NC-SA 4.0</a></div></div></section><section class="quarto-appendix-contents" id="quarto-citation"><h2 class="anchored quarto-appendix-heading">Citation</h2><div><div class="quarto-appendix-secondary-label">BibTeX citation:</div><pre class="sourceCode code-with-copy quarto-appendix-bibtex"><code class="sourceCode bibtex">@misc{canouil2026,
  author = {CANOUIL, Mickaël},
  title = {Gribouille 0.6.0: {Wrangling} {Data} and {Breaks} {That}
    {Land} on {Round} {Numbers}},
  date = {2026-07-28},
  url = {https://mickael.canouil.fr/posts/2026-07-28-gribouille-0-6/},
  langid = {en-GB}
}
</code></pre><div class="quarto-appendix-secondary-label">For attribution, please cite this work as:</div><div id="ref-canouil2026" class="csl-entry quarto-appendix-citeas">
CANOUIL, M. (2026-07-28). Gribouille 0.6.0: Wrangling Data and Breaks
That Land on Round Numbers. <em>Mickael.canouil.fr</em>. <a href="https://mickael.canouil.fr/posts/2026-07-28-gribouille-0-6/">https://mickael.canouil.fr/posts/2026-07-28-gribouille-0-6/</a>
</div></div></section></div> ]]></description>
  <category>typst</category>
  <category>quarto</category>
  <category>grammar-of-graphics</category>
  <category>gribouille</category>
  <guid>https://mickael.canouil.fr/posts/2026-07-28-gribouille-0-6/</guid>
  <pubDate>Tue, 28 Jul 2026 00:00:00 GMT</pubDate>
  <media:content url="https://mickael.canouil.fr/posts/2026-07-28-gribouille-0-6/featured.png" medium="image" type="image/png" height="76" width="144"/>
</item>
<item>
  <title>Stop Duplicating Your Quarto Source</title>
  <link>https://mickael.canouil.fr/posts/2026-07-24-quarto-stop-duplicating/</link>
  <description><![CDATA[ 

<!--
@license MIT
@copyright 2026 Mickaël Canouil
@author Mickaël Canouil
-->
Skip to main content





<p>One Quarto source can produce a web page, a PDF, and a slide deck. The trouble starts when the three outputs need slightly different things, and the usual fix is to write the same content twice.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="featured.png" class="lightbox" data-gallery="quarto-lightbox-gallery-1"><img src="https://mickael.canouil.fr/posts/2026-07-24-quarto-stop-duplicating/featured.png" class="img-featured img-fluid quarto-figure quarto-figure-center figure-img" alt="Card titled &quot;Stop Duplicating Your Quarto Source&quot; on a dark background.
A single file labelled index.qmd sits on the left, with three lines
fanning out to three output tiles: HTML in blue, Typst PDF in amber, and
reveal.js in teal. Below, three filter names are listed:
offset-headings, prism, and cascade.
" width="600"></a></p>
</figure>
</div>
<section id="the-problem" class="level2" data-number="1">
<h2 data-number="1" class="anchored" data-anchor-id="the-problem"><span class="header-section-number">1</span> The Problem</h2>
<p>I write training material as one <code>index.qmd</code> per module. Each module produces a handout, an exercise booklet, and a slide deck, in HTML, in PDF through Typst, and in reveal.js. The prose is the same in all of them, so the source has to be the same too.</p>
<p>Three things get in the way.</p>
<p>First, heading levels do not mean the same thing in each format. A <code>##</code> is a section in the handout and a slide in the deck, and Typst shifts the levels on its own when the document has no level-1 heading.</p>
<p>Then, some attributes only make sense in one format. A table that fits the page is too tall on a slide, so it needs a smaller font there and nowhere else.</p>
<p>Finally, a slide break has no meaning outside the deck. A <code>---</code> splits a slide in reveal.js, but the continuation slide loses its heading, and the rule itself has nothing to do in the HTML or the PDF.</p>
<p>The usual answer is <code>content-visible when-format="..."</code>, which means writing the block once per format. That is exactly the duplication I wanted to avoid.</p>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Note</span>Three filters
</div>
</div>
<div class="callout-body-container callout-body">
<ul>
<li><a href="https://github.com/mcanouil/quarto-offset-headings"><code>offset-headings</code></a> (0.4.0) shifts heading levels, per document or per heading.</li>
<li><a href="https://github.com/mcanouil/quarto-prism"><code>prism</code></a> (0.4.1) attaches an attribute to one format only.</li>
<li><a href="https://github.com/mcanouil/quarto-revealjs-cascade"><code>cascade</code></a> (1.0.0) repeats the heading chain after a <code>---</code> slide break.</li>
</ul>
</div>
</div>
</section>
<section id="installing-them" class="level2" data-number="2">
<h2 data-number="2" class="anchored" data-anchor-id="installing-them"><span class="header-section-number">2</span> Installing Them</h2>
<p>I manage my extensions with <span class="quarto-wizard" title="Quarto Wizard Logo" aria-label="Quarto Wizard Logo"></span> <a href="https://github.com/mcanouil/quarto-wizard">Quarto Wizard</a>, an extension for Visual Studio Code, Positron, and VSCodium. It browses the <a href="https://m.canouil.dev/quarto-extensions/">Quarto Extensions</a> listing, installs an extension into your project, and tells you when one you already have has a newer release. That last part is why I recommend it over the command line: <code>quarto add</code> installs an extension, but nothing then keeps track of what you have and what has moved on.</p>
<p>The buttons below open your editor and ask Quarto Wizard to install the extension into the current project.</p>
<div class="qw-install">
<div class="qw-item">
<p><code>offset-headings</code> <span class="btn-group btn-group-sm" aria-label="Install offset-headings with Quarto Wizard"><a href="vscode://mcanouil.quarto-wizard/install?repo=mcanouil/quarto-offset-headings" class="btn btn-outline-primary">Visual Studio Code</a> <a href="positron://mcanouil.quarto-wizard/install?repo=mcanouil/quarto-offset-headings" class="btn btn-outline-primary">Positron</a> <a href="vscodium://mcanouil.quarto-wizard/install?repo=mcanouil/quarto-offset-headings" class="btn btn-outline-primary">VSCodium</a></span></p>
</div>
<div class="qw-item">
<p><code>prism</code> <span class="btn-group btn-group-sm" aria-label="Install prism with Quarto Wizard"><a href="vscode://mcanouil.quarto-wizard/install?repo=mcanouil/quarto-prism" class="btn btn-outline-primary">Visual Studio Code</a> <a href="positron://mcanouil.quarto-wizard/install?repo=mcanouil/quarto-prism" class="btn btn-outline-primary">Positron</a> <a href="vscodium://mcanouil.quarto-wizard/install?repo=mcanouil/quarto-prism" class="btn btn-outline-primary">VSCodium</a></span></p>
</div>
<div class="qw-item">
<p><code>cascade</code> <span class="btn-group btn-group-sm" aria-label="Install cascade with Quarto Wizard"><a href="vscode://mcanouil.quarto-wizard/install?repo=mcanouil/quarto-revealjs-cascade" class="btn btn-outline-primary">Visual Studio Code</a> <a href="positron://mcanouil.quarto-wizard/install?repo=mcanouil/quarto-revealjs-cascade" class="btn btn-outline-primary">Positron</a> <a href="vscodium://mcanouil.quarto-wizard/install?repo=mcanouil/quarto-revealjs-cascade" class="btn btn-outline-primary">VSCodium</a></span></p>
</div>
</div>
<p>The same three extensions from the command line, if you prefer it:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" data-filename="bash" style="background: #f1f3f5;"><pre class="sourceCode bash cw-auto code-with-copy"><code class="sourceCode bash"><span id="cb1-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">quarto</span> add mcanouil/quarto-offset-headings</span>
<span id="cb1-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">quarto</span> add mcanouil/quarto-prism</span>
<span id="cb1-3"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">quarto</span> add mcanouil/quarto-revealjs-cascade</span></code></pre></div></div>
<p>Each section below then shows the <code>filters:</code> entry that turns the extension on.</p>
</section>
<section id="heading-levels" class="level2" data-number="3">
<h2 data-number="3" class="anchored" data-anchor-id="heading-levels"><span class="header-section-number">3</span> Heading Levels</h2>
<p>Pandoc already has <code>shift-heading-level-by</code>, and Quarto exposes it. But it runs as a final pass, after every Lua filter has finished. No filter can see the shifted levels, and the value applies to the whole document, so you cannot move a single heading.</p>
<p><code>offset-headings</code> does the same job as a filter instead.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb2" data-filename="yaml" style="background: #f1f3f5;"><pre class="sourceCode yaml cw-auto code-with-copy"><code class="sourceCode yaml"><span id="cb2-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">filters</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb2-2"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">  </span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> offset-headings</span></span></code></pre></div></div>
<p>You can shift every heading at once:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb3" data-filename="yaml" style="background: #f1f3f5;"><pre class="sourceCode yaml cw-auto code-with-copy"><code class="sourceCode yaml"><span id="cb3-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">extensions</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb3-2"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">  </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">offset-headings</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb3-3"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">by</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span></code></pre></div></div>
<p>Or one heading, with an attribute:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb4" data-filename="markdown" style="background: #f1f3f5;"><pre class="sourceCode markdown cw-auto code-with-copy"><code class="sourceCode markdown"><span id="cb4-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">## Section {offset-headings-by="-1"}</span></span></code></pre></div></div>
<p>The offset flows down to the nested headings by default, so a whole branch moves together. <code>offset-headings-depth</code> bounds how far it flows, <code>offset-headings-max-level</code> caps the level it can reach, and <code>offset-headings-recursive="false"</code> keeps the offset on the attributed heading alone. The document offset and the per-heading offset always add up, and the result is clamped to <code>[1, 6]</code>.</p>
<div class="callout callout-style-default callout-important callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
Important
</div>
</div>
<div class="callout-body-container callout-body">
<p>Quarto sets <code>shift-heading-level-by: -1</code> on its own when a document has no level-1 heading: always for Typst, and for PDF when <code>number-sections</code> is on and <code>top-level-division</code> is not set. That pass lands after the filter, so your Typst headings end up one level shallower than in HTML, and anything left at level 1 is destroyed. A Lua filter cannot read or cancel it, so set it explicitly instead:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb5" data-filename="yaml" style="background: #f1f3f5;"><pre class="sourceCode yaml cw-auto code-with-copy"><code class="sourceCode yaml"><span id="cb5-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">shift-heading-level-by</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span></span></code></pre></div></div>
<p>Quarto skips its automatic shift as soon as the key is set, whatever the value. The filter cannot tell that you have done it, so silence its warning with <code>quarto-shift-warning: false</code>.</p>
</div>
</div>
</section>
<section id="format-specific-attributes" class="level2" data-number="4">
<h2 data-number="4" class="anchored" data-anchor-id="format-specific-attributes"><span class="header-section-number">4</span> Format-Specific Attributes</h2>
<p><code>prism</code> reads attributes keyed by format, and keeps the ones that match the format being rendered.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb6" data-filename="yaml" style="background: #f1f3f5;"><pre class="sourceCode yaml cw-auto code-with-copy"><code class="sourceCode yaml"><span id="cb6-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">filters</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb6-2"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">  </span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> prism</span></span></code></pre></div></div>
<p>Each key is written <code>format:name="value"</code>. When the format matches, the attribute is re-emitted as <code>name="value"</code>; otherwise it is dropped. Classes and identifiers are left alone, and unprefixed attributes pass through.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb7" data-filename="markdown" style="background: #f1f3f5;"><pre class="sourceCode markdown cw-auto code-with-copy"><code class="sourceCode markdown"><span id="cb7-1">::: {revealjs:style="font-size: 2em;" html:style="font-size: 1.2rem;"}</span>
<span id="cb7-2">Same paragraph, two sizes.</span>
<span id="cb7-3">:::</span></code></pre></div></div>
<p>It works on divs, spans, code blocks, and headings. The format name is matched exactly against the Quarto target format, which includes custom formats. Under a custom <code>mcanouil-typst</code> format, only <code>mcanouil-typst:</code> matches, not <code>typst:</code>. The built-in PDF format is targeted with <code>pdf:</code>, since that is the format name Quarto requests, not the writer it happens to use underneath.</p>
<p>Two prefixes save repetition. <code>slide:</code> covers every HTML slide format, and <code>default:</code> provides a fallback used only when nothing more specific matched.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb8" data-filename="markdown" style="background: #f1f3f5;"><pre class="sourceCode markdown cw-auto code-with-copy"><code class="sourceCode markdown"><span id="cb8-1">::: {default:style="color: gray;" html:style="color: crimson;"}</span>
<span id="cb8-2">Crimson in HTML, gray everywhere else.</span>
<span id="cb8-3">:::</span></code></pre></div></div>
<p>The order is exact format, then group alias, then <code>default:</code>, then the unprefixed value.</p>
<p>A dropped attribute is silent by design, since a future format with that name would pick it up. That also hides a typo such as <code>revaeljs:style</code>, so turn the warning on while you write:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb9" data-filename="yaml" style="background: #f1f3f5;"><pre class="sourceCode yaml cw-auto code-with-copy"><code class="sourceCode yaml"><span id="cb9-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">extensions</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb9-2"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">  </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">prism</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb9-3"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">warn-on-drop</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">true</span></span></code></pre></div></div>
</section>
<section id="slide-breaks" class="level2" data-number="5">
<h2 data-number="5" class="anchored" data-anchor-id="slide-breaks"><span class="header-section-number">5</span> Slide Breaks</h2>
<p><code>cascade</code> repeats the current heading chain whenever a <code>---</code> starts a continuation slide.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb10" data-filename="yaml" style="background: #f1f3f5;"><pre class="sourceCode yaml cw-auto code-with-copy"><code class="sourceCode yaml"><span id="cb10-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">filters</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb10-2"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">  </span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> cascade</span></span></code></pre></div></div>
<p>You write the heading once and split as often as you need. The rule is removed from the non-reveal.js formats, so the same source still reads as continuous prose in the handout and the PDF. Set <code>keep-hrule: true</code> if you want the rule kept there.</p>
<p><code>.no-cascade</code> on a heading keeps it off the continuation slides, <code>depth</code> limits how many levels of the chain are repeated, and <code>cascade-depth</code> overrides that depth for one chain. I covered the slide side of it in more detail in <a href="../../posts/2026-04-21-quarto-revealjs-extensions/index.html">a previous post on reveal.js extensions</a>.</p>
</section>
<section id="putting-them-together" class="level2" data-number="6">
<h2 data-number="6" class="anchored" data-anchor-id="putting-them-together"><span class="header-section-number">6</span> Putting Them Together</h2>
<p>The demo below is one <code>demo.qmd</code> rendered to the three formats, with nothing format-specific in the body other than attributes.</p>
<p>The project file declares the three filters and the three outputs.</p>
<div class="code-with-filename">
<div class="code-with-filename-file">
<pre><strong>_quarto.yml</strong></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb11" data-filename="_quarto.yml" data-startfrom="5" style="background: #f1f3f5;"><pre class="sourceCode yaml code-with-copy"><code class="sourceCode yaml" style="counter-reset: source-line 4;"><span id="cb11-5"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">filters</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb11-6"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">  </span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> prism</span></span>
<span id="cb11-7"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">  </span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> offset-headings</span></span>
<span id="cb11-8"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">  </span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> cascade</span></span>
<span id="cb11-9"></span>
<span id="cb11-10"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">extensions</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb11-11"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">  </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">prism</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb11-12"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">warn-on-drop</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">true</span></span>
<span id="cb11-13"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">  </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">offset-headings</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb11-14"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">quarto-shift-warning</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">false</span></span></code></pre></div></div>
</div>
<p>The order matters, and it is the order of the <code>filters:</code> list, since all three register at <code>pre-ast</code>. <code>prism</code> runs first, so the attributes it promotes are plain attributes by the time the others look at them. <code>cascade</code> runs last, so it sees the final heading levels when it builds the chain to repeat.</p>
<div class="code-with-filename">
<div class="code-with-filename-file">
<pre><strong>_quarto.yml</strong></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb12" data-filename="_quarto.yml" data-startfrom="16" style="background: #f1f3f5;"><pre class="sourceCode yaml code-with-copy"><code class="sourceCode yaml" style="counter-reset: source-line 15;"><span id="cb12-16"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">format</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb12-17"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">  </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">html</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb12-18"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">output-file</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> demo.html</span></span>
<span id="cb12-19"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">embed-resources</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">true</span></span>
<span id="cb12-20"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">toc</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">true</span></span>
<span id="cb12-21"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">  </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">typst</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb12-22"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">output-file</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> demo.pdf</span></span>
<span id="cb12-23"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">shift-heading-level-by</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span></span>
<span id="cb12-24"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">  </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">revealjs</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb12-25"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">output-file</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> demo-slides.html</span></span>
<span id="cb12-26"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">embed-resources</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">true</span></span></code></pre></div></div>
</div>
<p>Both filters then meet on the same heading.</p>
<div class="code-with-filename">
<div class="code-with-filename-file">
<pre><strong>demo.qmd</strong></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb13" data-filename="demo.qmd" data-startfrom="7" style="background: #f1f3f5;"><pre class="sourceCode markdown code-with-copy"><code class="sourceCode markdown" style="counter-reset: source-line 6;"><span id="cb13-7"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">## Reading a File {revealjs:offset-headings-by="-1" revealjs:offset-headings-depth="1"}</span></span>
<span id="cb13-8"></span>
<span id="cb13-9"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">### From a CSV file</span></span></code></pre></div></div>
</div>
<p><code>prism</code> turns <code>revealjs:offset-headings-by</code> into <code>offset-headings-by</code> on the deck and drops it elsewhere. <code>offset-headings</code> then promotes the heading and its subsection by one level, so the section becomes a title slide and its subsections become slides. In the handout and the PDF, the same two headings stay at level 2 and level 3.</p>
<p>The code block shrinks on the deck only.</p>
<div class="code-with-filename">
<div class="code-with-filename-file">
<pre><strong>demo.qmd</strong></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb14" data-filename="demo.qmd" data-startfrom="13" style="background: #f1f3f5;"><pre class="sourceCode markdown code-with-copy"><code class="sourceCode markdown" style="counter-reset: source-line 12;"><span id="cb14-13"><span class="in" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">```{.r revealjs:style="font-size: 0.7em;"}</span></span>
<span id="cb14-14">penguins <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">read.csv</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"penguins.csv"</span>)</span>
<span id="cb14-15"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">str</span>(penguins)</span>
<span id="cb14-16"><span class="in" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">```</span></span></code></pre></div></div>
</div>
<p>The <code>---</code> splits the slide, and <code>cascade</code> puts <code>### From a CSV file</code> back on top of the continuation.</p>
<div class="code-with-filename">
<div class="code-with-filename-file">
<pre><strong>demo.qmd</strong></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb15" data-filename="demo.qmd" data-startfrom="18" style="background: #f1f3f5;"><pre class="sourceCode markdown code-with-copy"><code class="sourceCode markdown" style="counter-reset: source-line 17;"><span id="cb15-18">The <span class="in" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">`str()`</span> call prints the structure of the object, so you can check the column types before going further.</span>
<span id="cb15-19"></span>
<span id="cb15-20">---</span>
<span id="cb15-21"></span>
<span id="cb15-22">Two arguments matter more than the others.</span>
<span id="cb15-23"></span>
<span id="cb15-24"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">- </span><span class="in" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">`stringsAsFactors`</span> controls whether character columns become factors.</span>
<span id="cb15-25"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">- </span><span class="in" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">`na.strings`</span> lists the values to read as missing.</span></code></pre></div></div>
</div>
<p>The table gets a width in the HTML handout and a smaller font on the deck, from a single wrapper. Typst ignores <code>max-width</code>, so the PDF falls back to its own table sizing.</p>
<div class="code-with-filename">
<div class="code-with-filename-file">
<pre><strong>demo.qmd</strong></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb16" data-filename="demo.qmd" data-startfrom="39" style="background: #f1f3f5;"><pre class="sourceCode markdown code-with-copy"><code class="sourceCode markdown" style="counter-reset: source-line 38;"><span id="cb16-39">::: {revealjs:style="font-size: 0.6em;" default:style="max-width: 40em;"}</span>
<span id="cb16-40"></span>
<span id="cb16-41"><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">|</span> Function       <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">|</span> Separator <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">|</span> Decimal mark <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">|</span></span>
<span id="cb16-42"><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">| --------------</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">| ---------</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">| ------------</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">|</span></span>
<span id="cb16-43"><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">|</span> <span class="in" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">`read.csv()`</span>   <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">|</span> <span class="in" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">`,`</span>       <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">|</span> <span class="in" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">`.`</span>          <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">|</span></span>
<span id="cb16-44"><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">|</span> <span class="in" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">`read.csv2()`</span>  <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">|</span> <span class="in" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">`;`</span>       <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">|</span> <span class="in" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">`,`</span>          <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">|</span></span>
<span id="cb16-45"><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">|</span> <span class="in" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">`read.delim()`</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">|</span> tab       <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">|</span> <span class="in" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">`.`</span>          <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">|</span></span>
<span id="cb16-46"></span>
<span id="cb16-47">:::</span></code></pre></div></div>
</div>
<p>Here are the three outputs, rendered from that one file.</p>
<p>The HTML handout, with the headings at their written levels and no slide break in sight.</p>
<div style="text-align: center;">
<p><a href="assets/output/demo.html" class="lightbox" data-gallery="quarto-lightbox-gallery-2"><embed src="assets/output/demo.html" class="hero-art slide-deck img-fluid" loading="lazy" aria-label="HTML handout rendered from the demo source"></a></p>
</div>
<p>The PDF, through Typst, with the same heading levels as the HTML.</p>
<div style="text-align: center;">
<p><a href="assets/output/demo.pdf" class="lightbox" data-gallery="quarto-lightbox-gallery-3"><embed src="assets/output/demo.pdf" class="hero-art slide-deck img-fluid" loading="lazy" aria-label="Typst PDF rendered from the demo source"></a></p>
</div>
<p>The deck, with the promoted headings and the repeated heading on the continuation slide.</p>
<div style="text-align: center;">
<p><a href="assets/output/demo-slides.html" class="lightbox" data-gallery="quarto-lightbox-gallery-4"><embed src="assets/output/demo-slides.html" class="hero-art slide-deck img-fluid" loading="lazy" aria-label="Reveal.js slide deck rendered from the demo source"></a></p>
</div>
<p>Open them full size: <a href="assets/output/demo.html" target="_blank" rel="noopener noreferrer">HTML</a>, <a href="assets/output/demo.pdf" target="_blank" rel="noopener noreferrer">Typst PDF</a>, <a href="assets/output/demo-slides.html" target="_blank" rel="noopener noreferrer">Reveal.js</a>.</p>
<div class="highlight">
<p><strong>The differences between formats become attributes, not copies of the content.</strong></p>
</div>
</section>
<section id="gotchas" class="level2" data-number="7">
<h2 data-number="7" class="anchored" data-anchor-id="gotchas"><span class="header-section-number">7</span> Gotchas</h2>
<div class="callout callout-style-default callout-warning callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
Warning
</div>
</div>
<div class="callout-body-container callout-body">
<p>Three things caught me out while writing real modules with these filters.</p>
<ul>
<li>Quarto’s own conditional-content filter rewrites <code>.content-visible</code> and <code>.content-hidden</code> divs and strips custom attributes from them, so a <code>prism</code> attribute placed on that fence disappears without a word. Put it on an inner div instead.</li>
<li><code>cascade</code> does not hoist a <code>---</code> that sits inside a <code>.panel-tabset</code> div, so a slide break there is left alone rather than split into a continuation slide.</li>
<li>The first child of a section heading should be a heading, not prose. Otherwise <code>cascade</code> reads the slide level wrongly and repeats the wrong heading on the continuation slides.</li>
</ul>
</div>
</div>
</section>
<section id="wrapping-up" class="level2" data-number="8">
<h2 data-number="8" class="anchored" data-anchor-id="wrapping-up"><span class="header-section-number">8</span> Wrapping Up</h2>
<p>Each of these filters started as an annoyance in a real document, and each one removed a block of duplicated content. Together they let one source stay one source.</p>
<p>Happy writing!</p>


</section>

<a onclick="window.scrollTo(0, 0); return false;" id="quarto-back-to-top"><i class="bi bi-arrow-up"></i> Back to top</a><div id="quarto-appendix" class="default"><section class="quarto-appendix-contents" id="quarto-reuse"><h2 class="anchored quarto-appendix-heading">Reuse</h2><div class="quarto-appendix-contents"><div><a rel="license" href="https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en_gb">CC BY-NC-SA 4.0</a></div></div></section><section class="quarto-appendix-contents" id="quarto-citation"><h2 class="anchored quarto-appendix-heading">Citation</h2><div><div class="quarto-appendix-secondary-label">BibTeX citation:</div><pre class="sourceCode code-with-copy quarto-appendix-bibtex"><code class="sourceCode bibtex">@misc{canouil2026,
  author = {CANOUIL, Mickaël},
  title = {Stop {Duplicating} {Your} {Quarto} {Source}},
  date = {2026-07-24},
  url = {https://mickael.canouil.fr/posts/2026-07-24-quarto-stop-duplicating/},
  langid = {en-GB}
}
</code></pre><div class="quarto-appendix-secondary-label">For attribution, please cite this work as:</div><div id="ref-canouil2026" class="csl-entry quarto-appendix-citeas">
CANOUIL, M. (2026-07-24). Stop Duplicating Your Quarto Source.
<em>Mickael.canouil.fr</em>. <a href="https://mickael.canouil.fr/posts/2026-07-24-quarto-stop-duplicating/">https://mickael.canouil.fr/posts/2026-07-24-quarto-stop-duplicating/</a>
</div></div></section></div> ]]></description>
  <category>quarto</category>
  <category>extensions</category>
  <category>typst</category>
  <category>reveal.js</category>
  <category>multi-format</category>
  <category>quarto-wizard</category>
  <guid>https://mickael.canouil.fr/posts/2026-07-24-quarto-stop-duplicating/</guid>
  <pubDate>Fri, 24 Jul 2026 00:00:00 GMT</pubDate>
  <media:content url="https://mickael.canouil.fr/posts/2026-07-24-quarto-stop-duplicating/featured.png" medium="image" type="image/png" height="76" width="144"/>
</item>
<item>
  <title>Gribouille 0.5.0: Native Density, and Scales Keyed by Aesthetic</title>
  <link>https://mickael.canouil.fr/posts/2026-07-13-gribouille-0-5/</link>
  <description><![CDATA[ 

<!--
@license MIT
@copyright 2026 Mickaël Canouil
@author Mickaël Canouil
-->
Skip to main content





<p>Another <a href="https://github.com/mcanouil/gribouille">Gribouille</a> release. Gribouille 0.5.0 is all about density but not just that. Kernel density estimation runs natively now, in pure Typst, so densities, violins, two-dimensional densities, and ridgeline plots all draw without leaving the document. The LOESS smoother lands in the same pass. In addition to density, the one thing to watch when you upgrade is the scale system: the per-aesthetic <code>scale-*</code> constructors collapse into a single keyed <code>scales()</code>, so <code>scale-colour-viridis-d()</code> becomes <code>scales(colour: scale-viridis-d())</code>.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="featured.png" class="lightbox" data-gallery="quarto-lightbox-gallery-1"><img src="https://mickael.canouil.fr/posts/2026-07-13-gribouille-0-5/featured.png" class="img-featured img-fluid quarto-figure quarto-figure-center figure-img" alt="Featured card for the Gribouille 0.5.0 release on a cream paper
background. On the left, the Gribouille wordmark sits above the tagline
&quot;Create elegant graphics with the Grammar of Graphics for Typst.&quot;, a
small orange &quot;v0.5.0 release&quot; pill, and the URL
m.canouil.dev/gribouille. On the right, a slightly rotated bordered card
frames a two-by-two grid of four small charts: a pattern-filled
ridgeline, a violin with a beeswarm inside it, a streamgraph, and a bump
chart with filled points.
" width="600"></a></p>
</figure>
</div>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Note</span>At a glance
</div>
</div>
<div class="callout-body-container callout-body">
<ul>
<li><a href="https://github.com/mcanouil/gribouille">Gribouille</a> 0.5.0 on Typst Universe: <code>#import "@preview/gribouille:0.5.0": *</code>.</li>
<li><a href="https://m.canouil.dev/gribouille/reference/geoms/geom-density.html"><code>geom-density()</code></a> and <a href="https://m.canouil.dev/gribouille/reference/geoms/geom-violin.html"><code>geom-violin()</code></a> draw native Gaussian kernel densities, with <code>bw</code>, <code>adjust</code>, and <code>trim</code> control.</li>
<li><a href="https://m.canouil.dev/gribouille/reference/geoms/geom-density-2d.html"><code>geom-density-2d()</code></a> and <code>geom-density-2d-filled()</code> trace or shade a two-dimensional density; <a href="https://m.canouil.dev/gribouille/reference/geoms/geom-density-ridges.html"><code>geom-density-ridges()</code></a> stacks one ridge per group.</li>
<li><a href="https://m.canouil.dev/gribouille/reference/geoms/geom-smooth.html"><code>geom-smooth()</code></a> gains <code>method: "loess"</code> with <code>span</code> and <code>degree</code> control, next to the existing <code>"lm"</code>.</li>
<li><a href="https://m.canouil.dev/gribouille/reference/geoms/geom-beeswarm.html"><code>geom-beeswarm()</code></a> lays overplotted points into a deterministic swarm, and <code>stat-difference</code> shades the band between two series by which one is on top.</li>
<li>Streamgraphs (<code>position-stack(offset:)</code>), bump charts (<code>stat-connect(connection: "sigmoid")</code>), and waffle charts (<code>stat-waffle</code>) join the chart types.</li>
<li>Fills accept native Typst <code>tiling</code> patterns, both as a fixed <code>fill:</code> and through <code>scale-manual(values:)</code>.</li>
<li><iconify-icon inline="" icon="octicon:alert-16" aria-label="Breaking change" title="Breaking change"></iconify-icon> <strong>Breaking:</strong> the per-aesthetic <code>scale-*</code> constructors collapse into a keyed <code>scales()</code>; the aesthetic comes from the key, so <code>scale-colour-viridis-d()</code> becomes <code>scales(colour: scale-viridis-d())</code> and <code>scale-x-log10()</code> becomes <code>scales(x: scale-log10())</code>. <code>plot(scales:)</code> now takes only the <code>scales()</code> dictionary.</li>
<li><iconify-icon inline="" icon="octicon:alert-16" aria-label="Breaking change" title="Breaking change"></iconify-icon> <strong>Breaking:</strong> <code>linetype</code> defaults to <code>auto</code> on the line-drawing geoms, honouring a mapped linetype scale instead of pinning <code>"solid"</code>.</li>
</ul>
</div>
</div>
<p>Every figure in this post is a real, freshly compiled plot.</p>
<section id="breaking-changes" class="level2" data-number="1">
<h2 data-number="1" class="anchored" data-anchor-id="breaking-changes"><span class="header-section-number">1</span> <iconify-icon inline="" icon="octicon:alert-16" aria-label="Breaking change" title="Breaking change"></iconify-icon> Breaking changes</h2>
<p>Two changes need your attention before your old code compiles. Neither changes what a plot looks like, only how you spell the scales.</p>
<div class="callout callout-style-default callout-warning callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Warning</span>Migrate these names
</div>
</div>
<div class="callout-body-container callout-body">
<ul>
<li>The per-aesthetic <code>scale-*</code> constructors are gone. Wrap the aesthetic-agnostic constructor in <code>scales()</code> and let the key carry the aesthetic: <code>scale-colour-viridis-d()</code> becomes <code>scales(colour: scale-viridis-d())</code>, and <code>scale-x-log10()</code> becomes <code>scales(x: scale-log10())</code>. <code>plot(scales:)</code> takes only the <code>scales()</code> dictionary, no positional array, and <code>expand-limits()</code> returns an aesthetic-keyed dictionary.</li>
<li><code>linetype</code> now defaults to <code>auto</code> on <code>geom-segment</code>, <code>geom-curve</code>, <code>geom-spoke</code>, <code>geom-errorbar</code>, <code>geom-errorbarh</code>, <code>geom-linerange</code>, and <code>geom-pointrange</code>. A mapped linetype scale is honoured instead of being overridden by a pinned <code>"solid"</code>. Pass <code>linetype: "solid"</code> on the geom if you want the old fixed look.</li>
</ul>
</div>
</div>
<p>There is one more change, but it is internal. Every constructor dictionary now stores its concrete type under a single <code>name:</code> key. This only matters if you pattern-match the dictionaries a constructor returns; the way you call them in a plot is unchanged.</p>
</section>
<section id="a-keyed-scales" class="level2" data-number="2">
<h2 data-number="2" class="anchored" data-anchor-id="a-keyed-scales"><span class="header-section-number">2</span> A keyed <code>scales()</code></h2>
<p>The old scale names spelled the aesthetic into the function: <code>scale-colour-viridis-d</code>, <code>scale-x-log10</code>, <code>scale-fill-manual</code>. That meant one constructor per aesthetic, and a long list of near-duplicates. Now there is one aesthetic-agnostic set, and the aesthetic comes from the key you bind it to inside <code>scales()</code>, exactly like <a href="https://m.canouil.dev/gribouille/reference/guides/guides.html"><code>guides()</code></a>.</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="annotated-cell-1" data-filename="typst" style="background: #f1f3f5;"><pre class="sourceCode typst cw-auto code-annotation-code code-with-copy code-annotated"><code class="sourceCode typst"><span id="annotated-cell-1-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>plot<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-1-2">  data<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> penguins<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-1-3">  mapping<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> aes<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"flipper-len"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"body-mass"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"species"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-1-4">  layers<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>geom-point<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-1-5">    size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-1-6">    alpha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.8</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-1-7">    stroke<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-1-8">    colour<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> rgb<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#fafafa"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-1-9">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),),</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-1" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-1-10" class="code-annotation-target">  scales<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> scales<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-1" data-target-annotation="2" onclick="event.preventDefault();">2</a><span id="annotated-cell-1-11" class="code-annotation-target">    y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> scale-log10<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>breaks<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3000</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4000</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5000</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">6000</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)),</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-1" data-target-annotation="3" onclick="event.preventDefault();">3</a><span id="annotated-cell-1-12" class="code-annotation-target">    fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> scale-okabe-ito<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(),</span></span>
<span id="annotated-cell-1-13">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-1-14">  labels<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> labels<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-1-15">    title<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"One scales(), Keyed by Aesthetic"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-1-16">    x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Flipper Length (mm)"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-1-17">    y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Body Mass (g)"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-1-18">    fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Species"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-1-19">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-1-20">  theme<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> theme-minimal<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(),</span></span>
<span id="annotated-cell-1-21">  width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">12cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-1-22">  height<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">8cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-1-23"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
<div class="cell-annotation">
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-1" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-1" data-code-lines="10" data-code-annotation="1"><code>scales()</code> is keyed by aesthetic and named only. A stray positional argument is rejected.</span>
</dd>
<dt data-target-cell="annotated-cell-1" data-target-annotation="2">2</dt>
<dd>
<span data-code-cell="annotated-cell-1" data-code-lines="11" data-code-annotation="2"><code>scale-x-log10()</code> is now <code>scale-log10()</code> under the <code>y</code> key. The same constructor serves the <code>x</code> axis.</span>
</dd>
<dt data-target-cell="annotated-cell-1" data-target-annotation="3">3</dt>
<dd>
<span data-code-cell="annotated-cell-1" data-code-lines="12" data-code-annotation="3"><code>scale-colour-okabe-ito()</code> is now <code>scale-okabe-ito()</code> under the <code>colour</code> key. Use it under <code>fill</code> for a fill scale.</span>
</dd>
</dl>
</div>
<div>
<div class="light-content">
<div style="text-align: center;">
<p><a href="../../posts/2026-07-13-gribouille-0-5/./assets/typst-render/keyed-scales-light.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-2" title="Penguin scatter of body mass against flipper length, points coloured by species on a colour-blind-friendly palette, with the y-axis on a base-10 log scale."><img src="https://mickael.canouil.fr/posts/2026-07-13-gribouille-0-5/assets/typst-render/keyed-scales-light.svg" class="img-fluid hero-art" alt="Penguin scatter of body mass against flipper length, points coloured by species on a colour-blind-friendly palette, with the y-axis on a base-10 log scale."></a></p>
</div>
</div>
<div class="dark-content">
<div style="text-align: center;">
<p><a href="../../posts/2026-07-13-gribouille-0-5/./assets/typst-render/keyed-scales-dark.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-3" title="Penguin scatter of body mass against flipper length, points coloured by species on a colour-blind-friendly palette, with the y-axis on a base-10 log scale."><img src="https://mickael.canouil.fr/posts/2026-07-13-gribouille-0-5/assets/typst-render/keyed-scales-dark.svg" class="img-fluid hero-art" alt="Penguin scatter of body mass against flipper length, points coloured by species on a colour-blind-friendly palette, with the y-axis on a base-10 log scale."></a></p>
</div>
</div>
</div>
</div>
<p><code>scales()</code> is more than a tidy wrapper. You could hand <code>plot()</code> a plain named dictionary, <code>(colour: scale-okabe-ito())</code>, and it would look the same. The difference is that <code>scales()</code> checks every entry as it binds it, before the plot draws. A positional argument, an aesthetic key that does not exist, or a value that is not a scale spec each stops with a clear message that lists the valid names. A plain dictionary would keep a misspelled key like <code>colur:</code> and quietly drop the scale, so the plot would render with the wrong colours and no error to point at. An unknown argument to a <code>scale-*</code> constructor is caught at the same moment, reported with the keys that scale accepts for that aesthetic.</p>
<div class="highlight">
<p><strong>A dictionary swallows a typo; <code>scales()</code> rejects it:</strong> a wrong aesthetic key or a non-scale value fails loudly instead of drawing a misleading plot.</p>
</div>
</section>
<section id="native-density" class="level2" data-number="3">
<h2 data-number="3" class="anchored" data-anchor-id="native-density"><span class="header-section-number">3</span> Native density</h2>
<p>Density estimation is the heart of this release. <a href="https://m.canouil.dev/gribouille/reference/geoms/geom-density.html"><code>geom-density()</code></a> evaluates a Gaussian kernel on a grid and draws the smooth curve, all in Typst, with no external runtime. The bandwidth is chosen by Silverman’s rule and tuned with <code>adjust</code>; <code>trim</code> cuts the curve to the data range.</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="annotated-cell-2" data-filename="typst" style="background: #f1f3f5;"><pre class="sourceCode typst cw-auto code-annotation-code code-with-copy code-annotated"><code class="sourceCode typst"><span id="annotated-cell-2-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>plot<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-2-2">  data<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> penguins<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-2-3">  mapping<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> aes<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"flipper-len"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"species"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-2-4">  layers<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-2" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-2-5" class="code-annotation-target">    geom-density<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>alpha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> adjust<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-2-6">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-2-7">  scales<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> scales<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> scale-okabe-ito<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">()),</span></span>
<span id="annotated-cell-2-8">  labels<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> labels<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-2-9">    title<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Flipper Length, One Density per Species"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-2-10">    x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Flipper Length (mm)"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-2-11">    y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Density"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-2-12">    fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Species"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-2-13">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-2-14">  theme<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> theme-minimal<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(),</span></span>
<span id="annotated-cell-2-15">  width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">12cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-2-16">  height<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">7cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-2-17"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
<div class="cell-annotation">
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-2" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-2" data-code-lines="5" data-code-annotation="1"><code>adjust</code> scales the automatic bandwidth; <code>bw</code> pins it outright, and <code>trim: true</code> clips the curve to the observed range. Map <code>aes(weight: ...)</code> for a weighted density.</span>
</dd>
</dl>
</div>
<div>
<div class="light-content">
<div style="text-align: center;">
<p><a href="../../posts/2026-07-13-gribouille-0-5/./assets/typst-render/density-light.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-4" title="Three overlapping filled density curves of penguin flipper length, one per species, on a minimal theme with a species legend on the right."><img src="https://mickael.canouil.fr/posts/2026-07-13-gribouille-0-5/assets/typst-render/density-light.svg" class="img-fluid hero-art" alt="Three overlapping filled density curves of penguin flipper length, one per species, on a minimal theme with a species legend on the right."></a></p>
</div>
</div>
<div class="dark-content">
<div style="text-align: center;">
<p><a href="../../posts/2026-07-13-gribouille-0-5/./assets/typst-render/density-dark.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-5" title="Three overlapping filled density curves of penguin flipper length, one per species, on a minimal theme with a species legend on the right."><img src="https://mickael.canouil.fr/posts/2026-07-13-gribouille-0-5/assets/typst-render/density-dark.svg" class="img-fluid hero-art" alt="Three overlapping filled density curves of penguin flipper length, one per species, on a minimal theme with a species legend on the right."></a></p>
</div>
</div>
</div>
</div>
<p>The stat exposes the usual after-stat columns, <code>_density</code>, <code>_count</code>, <code>_scaled</code>, and <code>_n</code>, so you can map <code>y</code> to a count instead of a density when that reads better.</p>
</section>
<section id="violins-and-beeswarms" class="level2" data-number="4">
<h2 data-number="4" class="anchored" data-anchor-id="violins-and-beeswarms"><span class="header-section-number">4</span> Violins and beeswarms</h2>
<p>A violin is the same density, mirrored and turned on its side. <a href="https://m.canouil.dev/gribouille/reference/geoms/geom-violin.html"><code>geom-violin()</code></a> draws one silhouette per group, normalised by <code>scale</code>. <a href="https://m.canouil.dev/gribouille/reference/geoms/geom-beeswarm.html"><code>geom-beeswarm()</code></a> spreads the raw points into a deterministic, density-shaped swarm, a reproducible alternative to jitter. Layer the swarm over the violin and every penguin sits inside its own silhouette.</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="annotated-cell-3" data-filename="typst" style="background: #f1f3f5;"><pre class="sourceCode typst cw-auto code-annotation-code code-with-copy code-annotated"><code class="sourceCode typst"><span id="annotated-cell-3-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>plot<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-3-2">  data<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> penguins<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-3-3">  mapping<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> aes<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"species"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"body-mass"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"species"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-3-4">  layers<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-3" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-3-5" class="code-annotation-target">    geom-violin<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>scale<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"width"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> trim<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">false</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> alpha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.4</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-3" data-target-annotation="2" onclick="event.preventDefault();">2</a><span id="annotated-cell-3-6" class="code-annotation-target">    geom-beeswarm<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-3-7">      size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-3-8">      colour<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> rgb<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#fafafa"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-3-9">      stroke<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.4pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-3-10">      alpha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.95</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-3-11">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-3-12">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-3-13">  scales<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> scales<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> scale-okabe-ito<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">()),</span></span>
<span id="annotated-cell-3-14">  labels<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> labels<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-3-15">    title<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Every Penguin in Its Violin"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-3-16">    x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Species"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-3-17">    y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Body Mass (g)"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-3-18">    fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Species"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-3-19">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-3-20">  guides<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> guides<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">none</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-3-21">  theme<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> theme-minimal<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(),</span></span>
<span id="annotated-cell-3-22">  width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">12cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-3-23">  height<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">8cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-3-24"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
<div class="cell-annotation">
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-3" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-3" data-code-lines="5" data-code-annotation="1"><code>scale: "width"</code> gives every violin the same maximum width; <code>"area"</code> (the default) equalises area, and <code>"count"</code> scales width by the group size. Overlapping groups dodge with <code>position: "dodge"</code>.</span>
</dd>
<dt data-target-cell="annotated-cell-3" data-target-annotation="2">2</dt>
<dd>
<span data-code-cell="annotated-cell-3" data-code-lines="6" data-code-annotation="2">The swarm spreads the points on the discrete axis directly, no <code>as-factor</code> needed; <code>position-beeswarm(width:, adjust:)</code> tunes the spread.</span>
</dd>
</dl>
</div>
<div>
<div class="light-content">
<div style="text-align: center;">
<p><a href="../../posts/2026-07-13-gribouille-0-5/./assets/typst-render/violin-light.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-6" title="Three violin silhouettes of penguin body mass, one per species, each holding a beeswarm of the individual penguins as filled circles with a thin white-ish outline, spread sideways by local density, on a minimal theme."><img src="https://mickael.canouil.fr/posts/2026-07-13-gribouille-0-5/assets/typst-render/violin-light.svg" class="img-fluid hero-art" alt="Three violin silhouettes of penguin body mass, one per species, each holding a beeswarm of the individual penguins as filled circles with a thin white-ish outline, spread sideways by local density, on a minimal theme."></a></p>
</div>
</div>
<div class="dark-content">
<div style="text-align: center;">
<p><a href="../../posts/2026-07-13-gribouille-0-5/./assets/typst-render/violin-dark.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-7" title="Three violin silhouettes of penguin body mass, one per species, each holding a beeswarm of the individual penguins as filled circles with a thin white-ish outline, spread sideways by local density, on a minimal theme."><img src="https://mickael.canouil.fr/posts/2026-07-13-gribouille-0-5/assets/typst-render/violin-dark.svg" class="img-fluid hero-art" alt="Three violin silhouettes of penguin body mass, one per species, each holding a beeswarm of the individual penguins as filled circles with a thin white-ish outline, spread sideways by local density, on a minimal theme."></a></p>
</div>
</div>
</div>
</div>
</section>
<section id="two-dimensions-at-once" class="level2" data-number="5">
<h2 data-number="5" class="anchored" data-anchor-id="two-dimensions-at-once"><span class="header-section-number">5</span> Two dimensions at once</h2>
<p>For a scatter that is too dense to read, a two-dimensional density shows where the mass sits. <code>geom-density-2d-filled()</code> shades filled iso-bands of the joint density; <a href="https://m.canouil.dev/gribouille/reference/geoms/geom-density-2d.html"><code>geom-density-2d()</code></a> draws the iso-lines instead.</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="annotated-cell-4" data-filename="typst" style="background: #f1f3f5;"><pre class="sourceCode typst cw-auto code-annotation-code code-with-copy code-annotated"><code class="sourceCode typst"><span id="annotated-cell-4-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>plot<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-4-2">  data<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> penguins<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-4-3">  mapping<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> aes<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"flipper-len"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"body-mass"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-4-4">  layers<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-4" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-4-5" class="code-annotation-target">    geom-density-2d-filled<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(),</span></span>
<span id="annotated-cell-4-6">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-4-7">  scales<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> scales<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-4-8">    x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> scale-continuous<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>expand<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0%</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0%</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)),</span></span>
<span id="annotated-cell-4-9">    y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> scale-continuous<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>expand<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0%</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0%</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)),</span></span>
<span id="annotated-cell-4-10">    fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> scale-fermenter<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>palette<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"YlGnBu"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-4-11">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-4-12">  labels<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> labels<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-4-13">    title<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Where the Penguins Cluster"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-4-14">    x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Flipper Length (mm)"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-4-15">    y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Body Mass (g)"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-4-16">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-4-17">  guides<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> guides<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">none</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-4-18">  theme<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> theme-minimal<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(),</span></span>
<span id="annotated-cell-4-19">  width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">12cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-4-20">  height<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">8cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-4-21"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
<div class="cell-annotation">
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-4" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-4" data-code-lines="5" data-code-annotation="1"><code>bw</code> sets the per-axis bandwidth (a number or an <code>(x, y)</code> pair) and <code>n</code> the grid resolution. Map <code>fill: after-stat("_level")</code> on <code>geom-density-2d()</code> to shade the lines by level.</span>
</dd>
</dl>
</div>
<div>
<div class="light-content">
<div style="text-align: center;">
<p><a href="../../posts/2026-07-13-gribouille-0-5/./assets/typst-render/density-2d-light.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-8" title="A two-dimensional density of penguin body mass against flipper length drawn as filled contour bands, from pale low-density to deep blue-green high-density, with two clusters along the flipper-length to body-mass diagonal."><img src="https://mickael.canouil.fr/posts/2026-07-13-gribouille-0-5/assets/typst-render/density-2d-light.svg" class="img-fluid hero-art" alt="A two-dimensional density of penguin body mass against flipper length drawn as filled contour bands, from pale low-density to deep blue-green high-density, with two clusters along the flipper-length to body-mass diagonal."></a></p>
</div>
</div>
<div class="dark-content">
<div style="text-align: center;">
<p><a href="../../posts/2026-07-13-gribouille-0-5/./assets/typst-render/density-2d-dark.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-9" title="A two-dimensional density of penguin body mass against flipper length drawn as filled contour bands, from pale low-density to deep blue-green high-density, with two clusters along the flipper-length to body-mass diagonal."><img src="https://mickael.canouil.fr/posts/2026-07-13-gribouille-0-5/assets/typst-render/density-2d-dark.svg" class="img-fluid hero-art" alt="A two-dimensional density of penguin body mass against flipper length drawn as filled contour bands, from pale low-density to deep blue-green high-density, with two clusters along the flipper-length to body-mass diagonal."></a></p>
</div>
</div>
</div>
</div>
</section>
<section id="ridgelines" class="level2" data-number="6">
<h2 data-number="6" class="anchored" data-anchor-id="ridgelines"><span class="header-section-number">6</span> Ridgelines</h2>
<p>When a plain overlap gets crowded, a ridgeline offsets each density up the panel. <a href="https://m.canouil.dev/gribouille/reference/geoms/geom-density-ridges.html"><code>geom-density-ridges()</code></a> draws one ridge per <code>y</code> level, with heights normalised across levels so they compare fairly.</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="annotated-cell-5" data-filename="typst" style="background: #f1f3f5;"><pre class="sourceCode typst cw-auto code-annotation-code code-with-copy code-annotated"><code class="sourceCode typst"><span id="annotated-cell-5-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>plot<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-5-2">  data<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> penguins<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-5-3">  mapping<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> aes<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"body-mass"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"species"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"species"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-5-4">  layers<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-5" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-5-5" class="code-annotation-target">    geom-density-ridges<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>scale<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.5</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> alpha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.85</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-5-6">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-5-7">  scales<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> scales<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-5" data-target-annotation="2" onclick="event.preventDefault();">2</a><span id="annotated-cell-5-8" class="code-annotation-target">    y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> scale-discrete<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>expand<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">auto</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">55%</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)),</span></span>
<span id="annotated-cell-5-9">    fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> scale-okabe-ito<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(),</span></span>
<span id="annotated-cell-5-10">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-5-11">  labels<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> labels<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-5-12">    title<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Body Mass, Ridge by Ridge"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-5-13">    x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Body Mass (g)"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-5-14">    y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Species"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-5-15">    fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Species"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-5-16">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-5-17">  guides<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> guides<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">none</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-5-18">  theme<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> theme-minimal<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(),</span></span>
<span id="annotated-cell-5-19">  width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">12cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-5-20">  height<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">8cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-5-21"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
<div class="cell-annotation">
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-5" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-5" data-code-lines="5" data-code-annotation="1"><code>scale</code> is measured in y-level units: <code>1</code> lets a ridge just reach the next baseline, and a larger value lets them overlap. The stat supplies the <code>height</code> channel the geom draws from, so keep the default stat.</span>
</dd>
<dt data-target-cell="annotated-cell-5" data-target-annotation="2">2</dt>
<dd>
<span data-code-cell="annotated-cell-5" data-code-lines="8" data-code-annotation="2">The discrete y-scale needs a little headroom on top so the tallest ridge is not clipped.</span>
</dd>
</dl>
</div>
<div>
<div class="light-content">
<div style="text-align: center;">
<p><a href="../../posts/2026-07-13-gribouille-0-5/./assets/typst-render/ridges-light.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-10" title="Three stacked ridgeline densities of penguin body mass, one per species climbing up the panel, each filled by species on a minimal theme."><img src="https://mickael.canouil.fr/posts/2026-07-13-gribouille-0-5/assets/typst-render/ridges-light.svg" class="img-fluid hero-art" alt="Three stacked ridgeline densities of penguin body mass, one per species climbing up the panel, each filled by species on a minimal theme."></a></p>
</div>
</div>
<div class="dark-content">
<div style="text-align: center;">
<p><a href="../../posts/2026-07-13-gribouille-0-5/./assets/typst-render/ridges-dark.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-11" title="Three stacked ridgeline densities of penguin body mass, one per species climbing up the panel, each filled by species on a minimal theme."><img src="https://mickael.canouil.fr/posts/2026-07-13-gribouille-0-5/assets/typst-render/ridges-dark.svg" class="img-fluid hero-art" alt="Three stacked ridgeline densities of penguin body mass, one per species climbing up the panel, each filled by species on a minimal theme."></a></p>
</div>
</div>
</div>
</div>
</section>
<section id="a-loess-smoother" class="level2" data-number="7">
<h2 data-number="7" class="anchored" data-anchor-id="a-loess-smoother"><span class="header-section-number">7</span> A LOESS smoother</h2>
<p>Until now, fitted straight lines were the only smoothers <a href="https://m.canouil.dev/gribouille/reference/geoms/geom-smooth.html"><code>geom-smooth()</code></a> could draw (<code>method: "lm"</code>). Now <code>geom-smooth()</code> also takes <code>method: "loess"</code>, a local polynomial fit with a pointwise confidence band.</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="annotated-cell-6" data-filename="typst" style="background: #f1f3f5;"><pre class="sourceCode typst cw-auto code-annotation-code code-with-copy code-annotated"><code class="sourceCode typst"><span id="annotated-cell-6-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>plot<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-6-2">  data<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> penguins<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-6-3">  mapping<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> aes<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"flipper-len"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"body-mass"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-6-4">  layers<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-6-5">    geom-point<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> alpha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-6" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-6-6" class="code-annotation-target">    geom-smooth<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>method<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"loess"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> span<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.75</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> degree<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-6-7">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-6-8">  labels<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> labels<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-6-9">    title<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"A Local Fit, Not a Straight Line"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-6-10">    x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Flipper Length (mm)"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-6-11">    y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Body Mass (g)"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-6-12">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-6-13">  theme<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> theme-minimal<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(),</span></span>
<span id="annotated-cell-6-14">  width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">12cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-6-15">  height<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">8cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-6-16"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
<div class="cell-annotation">
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-6" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-6" data-code-lines="6" data-code-annotation="1"><code>span</code> sets the fraction of points in each local window, and <code>degree</code> the local polynomial order (<code>0</code>, <code>1</code>, or <code>2</code>). The default <code>method: "lm"</code> still draws the straight-line fit.</span>
</dd>
</dl>
</div>
<div>
<div class="light-content">
<div style="text-align: center;">
<p><a href="../../posts/2026-07-13-gribouille-0-5/./assets/typst-render/loess-light.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-12" title="Penguin scatter of body mass against flipper length with a curved LOESS trend line and a shaded confidence band following the bend in the data."><img src="https://mickael.canouil.fr/posts/2026-07-13-gribouille-0-5/assets/typst-render/loess-light.svg" class="img-fluid hero-art" alt="Penguin scatter of body mass against flipper length with a curved LOESS trend line and a shaded confidence band following the bend in the data."></a></p>
</div>
</div>
<div class="dark-content">
<div style="text-align: center;">
<p><a href="../../posts/2026-07-13-gribouille-0-5/./assets/typst-render/loess-dark.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-13" title="Penguin scatter of body mass against flipper length with a curved LOESS trend line and a shaded confidence band following the bend in the data."><img src="https://mickael.canouil.fr/posts/2026-07-13-gribouille-0-5/assets/typst-render/loess-dark.svg" class="img-fluid hero-art" alt="Penguin scatter of body mass against flipper length with a curved LOESS trend line and a shaded confidence band following the bend in the data."></a></p>
</div>
</div>
</div>
</div>
</section>
<section id="difference-bands" class="level2" data-number="8">
<h2 data-number="8" class="anchored" data-anchor-id="difference-bands"><span class="header-section-number">8</span> Difference bands</h2>
<p><code>stat-difference</code> shades the band between two series by which one is on top. Feed it <code>ymin</code> and <code>ymax</code>, map <code>fill: after-stat("_sign")</code>, and it splits the ribbon at every crossover, filling the gap in the colour of whichever line leads.</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="annotated-cell-7" data-filename="typst" style="background: #f1f3f5;"><pre class="sourceCode typst cw-auto code-annotation-code code-with-copy code-annotated"><code class="sourceCode typst"><span id="annotated-cell-7-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">d</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> range<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">25</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">).</span>map<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=&gt;</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="annotated-cell-7-2">  <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">x</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> i / <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span></span>
<span id="annotated-cell-7-3">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>x: x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> a: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span> * calc<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>sin<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>x * <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.7</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span> b: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">3.2</span> * calc<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>cos<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>x * <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.55</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">))</span></span>
<span id="annotated-cell-7-4"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">})</span></span>
<span id="annotated-cell-7-5"></span>
<span id="annotated-cell-7-6"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>plot<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-7-7">  data<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> d<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-7" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-7-8" class="code-annotation-target">  mapping<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> aes<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"x"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> ymin<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"a"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> ymax<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"b"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> after-stat<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"_sign"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)),</span></span>
<span id="annotated-cell-7-9">  layers<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-7-10">    geom-ribbon<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-7" data-target-annotation="2" onclick="event.preventDefault();">2</a><span id="annotated-cell-7-11" class="code-annotation-target">      stat<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> stat-difference<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>levels<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"A leads"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"B leads"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)),</span></span>
<span id="annotated-cell-7-12">      alpha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-7-13">      stroke<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">none</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-7-14">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-7-15">    geom-line<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>mapping<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> aes<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"a"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span> linewidth<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-7-16">    geom-line<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>mapping<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> aes<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"b"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span> linewidth<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> linetype<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dashed"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-7-17">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-7-18">  scales<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> scales<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> scale-manual<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>values<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>rgb<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#0072B2"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span> rgb<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#D55E00"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)))),</span></span>
<span id="annotated-cell-7-19">  labels<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> labels<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>title<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Which Series Is on Top"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Week"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Value"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">none</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-7-20">  theme<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> theme-minimal<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(),</span></span>
<span id="annotated-cell-7-21">  width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">12cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-7-22">  height<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">7cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-7-23"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
<div class="cell-annotation">
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-7" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-7" data-code-lines="8" data-code-annotation="1">The two series enter as <code>ymin</code> and <code>ymax</code>; <code>fill: after-stat("_sign")</code> colours each run by which one is higher.</span>
</dd>
<dt data-target-cell="annotated-cell-7" data-target-annotation="2">2</dt>
<dd>
<span data-code-cell="annotated-cell-7" data-code-lines="11" data-code-annotation="2"><code>levels:</code> names the two states, and exact crossovers are inserted as shared vertices so the bands meet cleanly.</span>
</dd>
</dl>
</div>
<div>
<div class="light-content">
<div style="text-align: center;">
<p><a href="../../posts/2026-07-13-gribouille-0-5/./assets/typst-render/difference-light.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-14" title="Two wavy series over twelve weeks, one a solid line and one dashed, with the band between them shaded blue where the first leads and orange where the second leads; the colour flips at each crossover."><img src="https://mickael.canouil.fr/posts/2026-07-13-gribouille-0-5/assets/typst-render/difference-light.svg" class="img-fluid hero-art" alt="Two wavy series over twelve weeks, one a solid line and one dashed, with the band between them shaded blue where the first leads and orange where the second leads; the colour flips at each crossover."></a></p>
</div>
</div>
<div class="dark-content">
<div style="text-align: center;">
<p><a href="../../posts/2026-07-13-gribouille-0-5/./assets/typst-render/difference-dark.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-15" title="Two wavy series over twelve weeks, one a solid line and one dashed, with the band between them shaded blue where the first leads and orange where the second leads; the colour flips at each crossover."><img src="https://mickael.canouil.fr/posts/2026-07-13-gribouille-0-5/assets/typst-render/difference-dark.svg" class="img-fluid hero-art" alt="Two wavy series over twelve weeks, one a solid line and one dashed, with the band between them shaded blue where the first leads and orange where the second leads; the colour flips at each crossover."></a></p>
</div>
</div>
</div>
</div>
</section>
<section id="pattern-fills" class="level2" data-number="9">
<h2 data-number="9" class="anchored" data-anchor-id="pattern-fills"><span class="header-section-number">9</span> Pattern fills</h2>
<p>A <code>fill</code> no longer has to be a flat colour. When colour/fill is not enough or is too much, a pattern can carry the information instead which helps improve accessibility and printability. Fills now accept native Typst <code>tiling</code> patterns, both as a fixed <code>fill:</code> and through <code>scale-manual(values:)</code>, and the legend swatches draw the patterns too. This is what keeps a bar chart readable once it is printed in black and white.</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="annotated-cell-8" data-filename="typst" style="background: #f1f3f5;"><pre class="sourceCode typst cw-auto code-annotation-code code-with-copy code-annotated"><code class="sourceCode typst"><span id="annotated-cell-8-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">cb</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>rgb<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#0072B2"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span> rgb<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#D55E00"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span> rgb<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#009E73"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">))</span></span>
<span id="annotated-cell-8-2"></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-8" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-8-3" class="code-annotation-target"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">stripes</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> tiling<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">6pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">6pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">))[</span></span>
<span id="annotated-cell-8-4">  <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>place<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>rect<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">100%</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> height<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">100%</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> cb<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>at<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">).</span>lighten<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">60%</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)))</span></span>
<span id="annotated-cell-8-5">  <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>place<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>line<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>start<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0%</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">100%</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span> end<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">100%</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0%</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span> stroke<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1pt</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> cb<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>at<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)))</span></span>
<span id="annotated-cell-8-6"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span></span>
<span id="annotated-cell-8-7"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">dots</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> tiling<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">7pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">7pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">))[</span></span>
<span id="annotated-cell-8-8">  <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>place<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>rect<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">100%</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> height<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">100%</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> cb<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>at<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">).</span>lighten<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">60%</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)))</span></span>
<span id="annotated-cell-8-9">  <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>place<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>dx<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> dy<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> circle<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>radius<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.5pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> cb<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>at<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)))</span></span>
<span id="annotated-cell-8-10"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span></span>
<span id="annotated-cell-8-11"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">cross</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> tiling<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">7pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">7pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">))[</span></span>
<span id="annotated-cell-8-12">  <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>place<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>rect<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">100%</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> height<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">100%</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> cb<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>at<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">).</span>lighten<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">60%</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)))</span></span>
<span id="annotated-cell-8-13">  <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>place<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>line<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>start<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0%</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">100%</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span> end<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">100%</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0%</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span> stroke<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.8pt</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> cb<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>at<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)))</span></span>
<span id="annotated-cell-8-14">  <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>place<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>line<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>start<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0%</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0%</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span> end<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">100%</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">100%</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span> stroke<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.8pt</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> cb<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>at<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)))</span></span>
<span id="annotated-cell-8-15"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span></span>
<span id="annotated-cell-8-16"></span>
<span id="annotated-cell-8-17"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>plot<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-8-18">  data<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> penguins<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-8-19">  mapping<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> aes<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"species"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"species"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-8-20">  layers<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>geom-bar<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(),),</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-8" data-target-annotation="2" onclick="event.preventDefault();">2</a><span id="annotated-cell-8-21" class="code-annotation-target">  scales<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> scales<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> scale-manual<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>values<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>stripes<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> dots<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> cross<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">))),</span></span>
<span id="annotated-cell-8-22">  labels<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> labels<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-8-23">    title<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Bars You Can Read in Greyscale"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-8-24">    x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Species"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-8-25">    y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Count"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-8-26">    fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Species"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-8-27">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-8-28">  theme<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> theme-minimal<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(),</span></span>
<span id="annotated-cell-8-29">  width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">12cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-8-30">  height<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">7cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-8-31"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
<div class="cell-annotation">
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-8" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-8" data-code-lines="3" data-code-annotation="1">Each pattern is a plain Typst <code>tiling</code>: a full-tile background box, then the marks on top. A tiling works anywhere a colour does.</span>
</dd>
<dt data-target-cell="annotated-cell-8" data-target-annotation="2">2</dt>
<dd>
<span data-code-cell="annotated-cell-8" data-code-lines="21" data-code-annotation="2"><code>scale-manual</code> binds one pattern per level. The legend swatches render the patterns, not flat colours.</span>
</dd>
</dl>
</div>
<div>
<div class="light-content">
<div style="text-align: center;">
<p><a href="../../posts/2026-07-13-gribouille-0-5/./assets/typst-render/patterns-light.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-16" title="A bar chart of penguin counts per species, each bar filled with a different Typst tiling pattern, diagonal stripes, dots, and cross-hatch, with a legend whose swatches show the same patterns."><img src="https://mickael.canouil.fr/posts/2026-07-13-gribouille-0-5/assets/typst-render/patterns-light.svg" class="img-fluid hero-art" alt="A bar chart of penguin counts per species, each bar filled with a different Typst tiling pattern, diagonal stripes, dots, and cross-hatch, with a legend whose swatches show the same patterns."></a></p>
</div>
</div>
<div class="dark-content">
<div style="text-align: center;">
<p><a href="../../posts/2026-07-13-gribouille-0-5/./assets/typst-render/patterns-dark.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-17" title="A bar chart of penguin counts per species, each bar filled with a different Typst tiling pattern, diagonal stripes, dots, and cross-hatch, with a legend whose swatches show the same patterns."><img src="https://mickael.canouil.fr/posts/2026-07-13-gribouille-0-5/assets/typst-render/patterns-dark.svg" class="img-fluid hero-art" alt="A bar chart of penguin counts per species, each bar filled with a different Typst tiling pattern, diagonal stripes, dots, and cross-hatch, with a legend whose swatches show the same patterns."></a></p>
</div>
</div>
</div>
</div>
</section>
<section id="new-chart-types" class="level2" data-number="10">
<h2 data-number="10" class="anchored" data-anchor-id="new-chart-types"><span class="header-section-number">10</span> New chart types</h2>
<p>A few new stats and positions turn familiar geoms into charts they could not draw before.</p>
<section id="streamgraphs" class="level3" data-number="10.1">
<h3 data-number="10.1" class="anchored" data-anchor-id="streamgraphs"><span class="header-section-number">10.1</span> Streamgraphs</h3>
<p><code>position-stack</code> gains a streamgraph baseline. Pass <code>offset: "silhouette"</code> to centre each stack on zero, or <code>offset: "wiggle"</code> for the Byron-Wattenberg baseline, and a stacked <a href="https://m.canouil.dev/gribouille/reference/geoms/geom-area.html"><code>geom-area()</code></a> becomes a streamgraph.</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="annotated-cell-9" data-filename="typst" style="background: #f1f3f5;"><pre class="sourceCode typst cw-auto code-annotation-code code-with-copy code-annotated"><code class="sourceCode typst"><span id="annotated-cell-9-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">cb</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>rgb<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#0072B2"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span> rgb<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#D55E00"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span> rgb<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#009E73"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span> rgb<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#E69F00"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">))</span></span>
<span id="annotated-cell-9-2"></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-9" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-9-3" class="code-annotation-target"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">pat</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>i<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> body<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> tiling<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">7pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">7pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">))[</span></span>
<span id="annotated-cell-9-4">  <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>place<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>rect<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">100%</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> height<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">100%</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> cb<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>at<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>i<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">).</span>lighten<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">55%</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)))</span></span>
<span id="annotated-cell-9-5">  <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>body</span>
<span id="annotated-cell-9-6"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span></span>
<span id="annotated-cell-9-7"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">stripes</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> pat<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)[</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>place<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>line<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>start<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0%</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">100%</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span> end<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">100%</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0%</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span> stroke<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1pt</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> cb<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>at<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)))]</span></span>
<span id="annotated-cell-9-8"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">dots</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> pat<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)[</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>place<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>dx<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> dy<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> circle<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>radius<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.4pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> cb<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>at<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)))]</span></span>
<span id="annotated-cell-9-9"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">cross</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> pat<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)[</span></span>
<span id="annotated-cell-9-10">  <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>place<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>line<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>start<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0%</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">100%</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span> end<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">100%</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0%</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span> stroke<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.8pt</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> cb<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>at<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)))</span></span>
<span id="annotated-cell-9-11">  <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>place<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>line<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>start<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0%</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0%</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span> end<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">100%</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">100%</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span> stroke<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.8pt</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> cb<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>at<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)))</span></span>
<span id="annotated-cell-9-12"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span></span>
<span id="annotated-cell-9-13"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">hlines</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> pat<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)[</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>place<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>line<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>start<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0%</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">50%</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span> end<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">100%</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">50%</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span> stroke<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1pt</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> cb<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>at<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)))]</span></span>
<span id="annotated-cell-9-14"></span>
<span id="annotated-cell-9-15"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">series</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">()</span></span>
<span id="annotated-cell-9-16"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> g <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"A"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"B"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"C"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"D"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="annotated-cell-9-17">  <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">peak</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>A: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> B: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">20</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> C: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">12</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> D: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">).</span>at<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>g<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-9-18">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> x <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> range<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">21</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="annotated-cell-9-19">    series<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>push<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">((</span></span>
<span id="annotated-cell-9-20">      x: x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-9-21">      y: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span> * calc<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>exp<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.02</span> * calc<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>pow<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> peak<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)),</span></span>
<span id="annotated-cell-9-22">      grp: g<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-9-23">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">))</span></span>
<span id="annotated-cell-9-24">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="annotated-cell-9-25"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="annotated-cell-9-26"></span>
<span id="annotated-cell-9-27"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>plot<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-9-28">  data<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> series<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-9-29">  mapping<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> aes<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"x"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"y"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"grp"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-9-30">  layers<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-9" data-target-annotation="2" onclick="event.preventDefault();">2</a><span id="annotated-cell-9-31" class="code-annotation-target">    geom-area<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>position<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> position-stack<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>offset<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"silhouette"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span> stroke<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-9-32">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-9-33">  scales<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> scales<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> scale-manual<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>values<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>stripes<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> dots<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> cross<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> hlines<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">))),</span></span>
<span id="annotated-cell-9-34">  labels<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> labels<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>title<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"A Streamgraph"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Time"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">none</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Series"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-9-35">  guides<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> guides<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">none</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-9-36">  theme<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> theme-minimal<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(),</span></span>
<span id="annotated-cell-9-37">  width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">12cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-9-38">  height<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">7cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-9-39"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
<div class="cell-annotation">
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-9" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-9" data-code-lines="3" data-code-annotation="1">Each band gets its own <code>tiling</code> pattern, so the streamgraph stays legible without relying on colour alone.</span>
</dd>
<dt data-target-cell="annotated-cell-9" data-target-annotation="2">2</dt>
<dd>
<span data-code-cell="annotated-cell-9" data-code-lines="31" data-code-annotation="2"><code>offset: "wiggle"</code> gives the classic streamgraph baseline instead; <code>"none"</code> (the default) stacks from zero as before.</span>
</dd>
</dl>
</div>
<div>
<div class="light-content">
<div style="text-align: center;">
<p><a href="../../posts/2026-07-13-gribouille-0-5/./assets/typst-render/stream-light.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-18" title="A four-series streamgraph, each band flowing left to right and centred on a common baseline so the stack swells and narrows over time, every band filled with its own Typst tiling pattern (stripes, dots, cross-hatch, and horizontal lines)."><img src="https://mickael.canouil.fr/posts/2026-07-13-gribouille-0-5/assets/typst-render/stream-light.svg" class="img-fluid hero-art" alt="A four-series streamgraph, each band flowing left to right and centred on a common baseline so the stack swells and narrows over time, every band filled with its own Typst tiling pattern (stripes, dots, cross-hatch, and horizontal lines)."></a></p>
</div>
</div>
<div class="dark-content">
<div style="text-align: center;">
<p><a href="../../posts/2026-07-13-gribouille-0-5/./assets/typst-render/stream-dark.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-19" title="A four-series streamgraph, each band flowing left to right and centred on a common baseline so the stack swells and narrows over time, every band filled with its own Typst tiling pattern (stripes, dots, cross-hatch, and horizontal lines)."><img src="https://mickael.canouil.fr/posts/2026-07-13-gribouille-0-5/assets/typst-render/stream-dark.svg" class="img-fluid hero-art" alt="A four-series streamgraph, each band flowing left to right and centred on a common baseline so the stack swells and narrows over time, every band filled with its own Typst tiling pattern (stripes, dots, cross-hatch, and horizontal lines)."></a></p>
</div>
</div>
</div>
</div>
<section id="bump-charts" class="level4" data-number="10.1.1">
<h4 data-number="10.1.1" class="anchored" data-anchor-id="bump-charts"><span class="header-section-number">10.1.1</span> Bump charts</h4>
<p><code>stat-connect</code> gains a <code>"sigmoid"</code> connector. Paired with <a href="https://m.canouil.dev/gribouille/reference/geoms/geom-line.html"><code>geom-line()</code></a> and a reversed y-scale, it draws a bump chart, one smooth S-curve between each pair of ranks.</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="annotated-cell-10" data-filename="typst" style="background: #f1f3f5;"><pre class="sourceCode typst cw-auto code-annotation-code code-with-copy code-annotated"><code class="sourceCode typst"><span id="annotated-cell-10-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">cb</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-10-2">  rgb<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#0072B2"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-10-3">  rgb<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#D55E00"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-10-4">  rgb<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#009E73"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-10-5">  rgb<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#E69F00"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-10-6"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-10-7"></span>
<span id="annotated-cell-10-8"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">standings</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-10-9">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>t: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> rank: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> team: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"A"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>t: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> rank: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> team: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"A"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-10-10">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>t: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> rank: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> team: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"A"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>t: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> rank: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> team: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"A"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-10-11">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>t: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> rank: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> team: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"B"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>t: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> rank: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> team: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"B"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-10-12">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>t: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> rank: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> team: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"B"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>t: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> rank: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> team: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"B"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-10-13">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>t: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> rank: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> team: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"C"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>t: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> rank: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> team: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"C"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-10-14">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>t: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> rank: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> team: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"C"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>t: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> rank: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> team: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"C"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-10-15">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>t: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> rank: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> team: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"D"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>t: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> rank: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> team: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"D"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-10-16">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>t: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> rank: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> team: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"D"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>t: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> rank: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> team: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"D"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-10-17"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-10-18"></span>
<span id="annotated-cell-10-19"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>plot<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-10-20">  data<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> standings<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-10-21">  mapping<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> aes<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"t"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"rank"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> colour<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"team"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"team"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-10-22">  layers<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-10" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-10-23" class="code-annotation-target">    geom-line<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>stat<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> stat-connect<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>connection<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"sigmoid"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span> linewidth<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.5pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-10" data-target-annotation="2" onclick="event.preventDefault();">2</a><span id="annotated-cell-10-24" class="code-annotation-target">    geom-point<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">5pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> stroke<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">none</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-10-25">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-10-26">  scales<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> scales<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-10-27">    x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> scale-continuous<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>breaks<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)),</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-10" data-target-annotation="3" onclick="event.preventDefault();">3</a><span id="annotated-cell-10-28" class="code-annotation-target">    y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> scale-reverse<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>breaks<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)),</span></span>
<span id="annotated-cell-10-29">    colour<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> scale-manual<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>values<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> cb<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-10-30">    fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> scale-manual<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>values<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> cb<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-10-31">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-10-32">  labels<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> labels<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>title<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"A Bump Chart"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Round"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Rank"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> colour<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Team"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Team"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-10-33">  guides<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> guides<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">none</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-10-34">  theme<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> theme-minimal<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(),</span></span>
<span id="annotated-cell-10-35">  width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">12cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-10-36">  height<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">7cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-10-37"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
<div class="cell-annotation">
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-10" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-10" data-code-lines="23" data-code-annotation="1"><code>stat-connect(connection: "sigmoid")</code> eases each line between its ranks; <code>smooth</code> sets how sharp the transition is.</span>
</dd>
<dt data-target-cell="annotated-cell-10" data-target-annotation="2">2</dt>
<dd>
<span data-code-cell="annotated-cell-10" data-code-lines="24" data-code-annotation="2">Mapping <code>fill</code> and setting <code>stroke: none</code> draws filled circles; <code>fill</code> paints the marker body, <code>colour</code> its outline.</span>
</dd>
<dt data-target-cell="annotated-cell-10" data-target-annotation="3">3</dt>
<dd>
<span data-code-cell="annotated-cell-10" data-code-lines="28" data-code-annotation="3">A reversed y-scale puts rank 1 at the top.</span>
</dd>
</dl>
</div>
<div>
<div class="light-content">
<div style="text-align: center;">
<p><a href="../../posts/2026-07-13-gribouille-0-5/./assets/typst-render/bump-light.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-20" title="A bump chart of four teams over four rounds, each team a coloured line that eases up and down between integer ranks with a filled circle at every round; rank 1 is at the top."><img src="https://mickael.canouil.fr/posts/2026-07-13-gribouille-0-5/assets/typst-render/bump-light.svg" class="img-fluid hero-art" alt="A bump chart of four teams over four rounds, each team a coloured line that eases up and down between integer ranks with a filled circle at every round; rank 1 is at the top."></a></p>
</div>
</div>
<div class="dark-content">
<div style="text-align: center;">
<p><a href="../../posts/2026-07-13-gribouille-0-5/./assets/typst-render/bump-dark.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-21" title="A bump chart of four teams over four rounds, each team a coloured line that eases up and down between integer ranks with a filled circle at every round; rank 1 is at the top."><img src="https://mickael.canouil.fr/posts/2026-07-13-gribouille-0-5/assets/typst-render/bump-dark.svg" class="img-fluid hero-art" alt="A bump chart of four teams over four rounds, each team a coloured line that eases up and down between integer ranks with a filled circle at every round; rank 1 is at the top."></a></p>
</div>
</div>
</div>
</div>
</section>
<section id="waffle-charts" class="level4" data-number="10.1.2">
<h4 data-number="10.1.2" class="anchored" data-anchor-id="waffle-charts"><span class="header-section-number">10.1.2</span> Waffle charts</h4>
<p><code>stat-waffle</code> turns per-group counts into unit cells on a grid. Map <code>fill</code> to the group and draw with <a href="https://m.canouil.dev/gribouille/reference/geoms/geom-tile.html"><code>geom-tile()</code></a>; each square is one observation.</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="annotated-cell-11" data-filename="typst" style="background: #f1f3f5;"><pre class="sourceCode typst cw-auto code-annotation-code code-with-copy code-annotated"><code class="sourceCode typst"><span id="annotated-cell-11-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">cb</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-11-2">  rgb<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#0072B2"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-11-3">  rgb<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#D55E00"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-11-4">  rgb<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#009E73"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-11-5"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-11-6"></span>
<span id="annotated-cell-11-7"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>plot<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-11-8">  data<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> penguins<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-11-9">  mapping<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> aes<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"species"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-11-10">  layers<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-11" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-11-11" class="code-annotation-target">    geom-tile<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-11-12">      stat<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> stat-waffle<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>rows<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">12</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-11-13">      width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.85</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-11-14">      height<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.85</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-11-15">      stroke<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1pt</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> rgb<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#fafafa"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-11-16">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-11-17">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-11-18">  scales<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> scales<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-11-19">    fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> scale-manual<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>values<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> cb<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-11-20">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-11-21">  labels<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> labels<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>title<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Penguins as a Waffle"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">none</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">none</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Species"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-11-22">  guides<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> guides<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">none</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">none</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-11-23">  theme<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> theme-void<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(),</span></span>
<span id="annotated-cell-11-24">  width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">12cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-11-25">  height<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">7cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-11-26"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
<div class="cell-annotation">
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-11" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-11" data-code-lines="11" data-code-annotation="1"><code>rows</code> sets the grid height in cells and columns grow to hold the total; the page-coloured stroke keeps the cells apart. Map <code>weight</code> instead when the data is already counted, one row per group.</span>
</dd>
</dl>
</div>
<div>
<div class="light-content">
<div style="text-align: center;">
<p><a href="../../posts/2026-07-13-gribouille-0-5/./assets/typst-render/waffle-light.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-22" title="A waffle chart of the penguins dataset, one square per bird laid on a grid and coloured by species, the three species filling consecutive runs of cells."><img src="https://mickael.canouil.fr/posts/2026-07-13-gribouille-0-5/assets/typst-render/waffle-light.svg" class="img-fluid hero-art" alt="A waffle chart of the penguins dataset, one square per bird laid on a grid and coloured by species, the three species filling consecutive runs of cells."></a></p>
</div>
</div>
<div class="dark-content">
<div style="text-align: center;">
<p><a href="../../posts/2026-07-13-gribouille-0-5/./assets/typst-render/waffle-dark.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-23" title="A waffle chart of the penguins dataset, one square per bird laid on a grid and coloured by species, the three species filling consecutive runs of cells."><img src="https://mickael.canouil.fr/posts/2026-07-13-gribouille-0-5/assets/typst-render/waffle-dark.svg" class="img-fluid hero-art" alt="A waffle chart of the penguins dataset, one square per bird laid on a grid and coloured by species, the three species filling consecutive runs of cells."></a></p>
</div>
</div>
</div>
</div>
</section>
</section>
</section>
<section id="agents-and-plugins" class="level2" data-number="11">
<h2 data-number="11" class="anchored" data-anchor-id="agents-and-plugins"><span class="header-section-number">11</span> Agents and plugins</h2>
<p>Gribouille ships an installable agent skill that teaches a coding assistant to write correct plots, checking every argument against the documentation. It installs with:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" data-filename="bash" style="background: #f1f3f5;"><pre class="sourceCode bash cw-auto code-with-copy"><code class="sourceCode bash"><span id="cb1-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">npx</span> skills add mcanouil/gribouille</span></code></pre></div></div>
<p>The repository doubles as a Claude Code plugin marketplace, so you can wire the same skill into Claude Code:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb2" data-filename="bash" style="background: #f1f3f5;"><pre class="sourceCode bash cw-auto code-with-copy"><code class="sourceCode bash"><span id="cb2-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">/plugin</span> marketplace add mcanouil/gribouille</span>
<span id="cb2-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">/plugin</span> install gribouille@gribouille</span></code></pre></div></div>
</section>
<section id="stricter-errors-and-rendering-fixes" class="level2" data-number="12">
<h2 data-number="12" class="anchored" data-anchor-id="stricter-errors-and-rendering-fixes"><span class="header-section-number">12</span> Stricter Errors and Rendering Fixes</h2>
<p>As always, a good share of the release is fixes rather than features.</p>
<div class="highlight">
<p><strong>Errors fail loudly now:</strong> an unknown aesthetic, theme key, stat, or column stops with a clear message instead of a misleading plot.</p>
</div>
<p>The biggest is stricter validation. An unknown <code>aes()</code> aesthetic, such as the US spelling <code>color</code>, now fails with the list of valid aesthetics instead of drawing an empty plot. <code>theme()</code> rejects a misspelled element key, an unknown <code>stat</code> or scale <code>transform</code> fails with a clear message rather than rendering as identity, and mapping an aesthetic to a missing column names the column and lists the ones that exist.</p>
<p>Two rendering fixes are worth calling out. Abutting opaque fills, such as tiles, hexes, and stacked bars, are now stroked with their own fill colour, which removes the hairline seams that antialiasing used to bleed through between them. And <code>stat-quantile</code> drops its cubic pair enumeration for an exact <code>O(n^2 log n)</code> search, so <code>geom-quantile</code> scales to thousands of rows.</p>
<p>Facets and compositions also gain spacing control. <code>facet-wrap</code> and <code>facet-grid</code> take a <code>gutter</code> argument, and a new <code>theme(panel-spacing:)</code> default sets the gap for both axes at once (a length, or a <code>(x:, y:)</code> dictionary); <code>compose</code>’s <code>gutter</code> now accepts the same dictionary form.</p>
</section>
<section id="wrap-up" class="level2" data-number="13">
<h2 data-number="13" class="anchored" data-anchor-id="wrap-up"><span class="header-section-number">13</span> Wrap-up</h2>
<div class="highlight">
<p><strong>Kernel density estimation, in pure Typst, plus one keyed <code>scales()</code> in place of a constructor per aesthetic.</strong></p>
</div>
<p>Next on the list is more geoms and more worked examples. If you run into something unexpected, the issue tracker is the right place for it.</p>
<ul>
<li>Gribouille
<ul>
<li>Repository: <a href="https://github.com/mcanouil/gribouille" class="uri">https://github.com/mcanouil/gribouille</a>.</li>
<li>Documentation: <a href="https://m.canouil.dev/gribouille" class="uri">https://m.canouil.dev/gribouille</a>.</li>
<li>Typst Universe: <a href="https://typst.app/universe/package/gribouille" class="uri">https://typst.app/universe/package/gribouille</a>.</li>
</ul></li>
<li>Typst Render
<ul>
<li>Repository: <a href="https://github.com/mcanouil/quarto-typst-render" class="uri">https://github.com/mcanouil/quarto-typst-render</a>.</li>
<li>Documentation: <a href="https://m.canouil.dev/quarto-typst-render" class="uri">https://m.canouil.dev/quarto-typst-render</a>.</li>
</ul></li>
</ul>
<div class="callout callout-style-default callout-tip callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Tip</span>A note on contributions
</div>
</div>
<div class="callout-body-container callout-body">
<p>Gribouille is an unfunded spare-time project, and the API is still settling. Bug reports and ideas are very welcome on the issue tracker. Pull requests are not being accepted for now, for the reasons set out in the <a href="../../posts/2026-05-20-gribouille-grammar-of-graphics-for-typst/index.html">launch post</a>. Thanks in advance for your patience.</p>
</div>
</div>


</section>

<a onclick="window.scrollTo(0, 0); return false;" id="quarto-back-to-top"><i class="bi bi-arrow-up"></i> Back to top</a><div id="quarto-appendix" class="default"><section class="quarto-appendix-contents" id="quarto-reuse"><h2 class="anchored quarto-appendix-heading">Reuse</h2><div class="quarto-appendix-contents"><div><a rel="license" href="https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en_gb">CC BY-NC-SA 4.0</a></div></div></section><section class="quarto-appendix-contents" id="quarto-citation"><h2 class="anchored quarto-appendix-heading">Citation</h2><div><div class="quarto-appendix-secondary-label">BibTeX citation:</div><pre class="sourceCode code-with-copy quarto-appendix-bibtex"><code class="sourceCode bibtex">@misc{canouil2026,
  author = {CANOUIL, Mickaël},
  title = {Gribouille 0.5.0: {Native} {Density,} and {Scales} {Keyed} by
    {Aesthetic}},
  date = {2026-07-13},
  url = {https://mickael.canouil.fr/posts/2026-07-13-gribouille-0-5/},
  langid = {en-GB}
}
</code></pre><div class="quarto-appendix-secondary-label">For attribution, please cite this work as:</div><div id="ref-canouil2026" class="csl-entry quarto-appendix-citeas">
CANOUIL, M. (2026-07-13). Gribouille 0.5.0: Native Density, and Scales
Keyed by Aesthetic. <em>Mickael.canouil.fr</em>. <a href="https://mickael.canouil.fr/posts/2026-07-13-gribouille-0-5/">https://mickael.canouil.fr/posts/2026-07-13-gribouille-0-5/</a>
</div></div></section></div> ]]></description>
  <category>typst</category>
  <category>quarto</category>
  <category>grammar-of-graphics</category>
  <category>gribouille</category>
  <guid>https://mickael.canouil.fr/posts/2026-07-13-gribouille-0-5/</guid>
  <pubDate>Mon, 13 Jul 2026 00:00:00 GMT</pubDate>
  <media:content url="https://mickael.canouil.fr/posts/2026-07-13-gribouille-0-5/featured.png" medium="image" type="image/png" height="76" width="144"/>
</item>
<item>
  <title>Serving a Shiny App and Its Docs from One Process</title>
  <link>https://mickael.canouil.fr/posts/2026-07-06-shiny-great-docs/</link>
  <description><![CDATA[ 

<!--
@license MIT
@copyright 2026 Mickaël Canouil
@author Mickaël Canouil
-->
Skip to main content





<p>A colleague asked how to embed <a href="https://posit-dev.github.io/great-docs/">great-docs</a>-generated documentation inside a Shiny for Python app and serve both from a single process. I built a minimal demo to find out, and shared it a few weeks ago on <a href="https://www.linkedin.com/posts/mickaelcanouil_github-mcanouildemo-shiny-great-docs-activity-7471258975955730432-0vI2"><iconify-icon inline="" icon="fa7-brands:linkedin" aria-label="LinkedIn" title="LinkedIn"></iconify-icon> LinkedIn</a> and <a href="https://bsky.app/profile/mickael.canouil.fr/post/3mo4cvjtxf22u"><iconify-icon inline="" icon="fa7-brands:bluesky" aria-label="Bluesky" title="Bluesky"></iconify-icon> Bluesky</a>, but I thought it deserved a proper write-up, so here it is.</p>
<p>When a Shiny app and its package documentation live as two separate services, you get two URLs to keep in sync and two deployment environments to handle. This post shows how to put both at the same origin: the app at <code>/</code> and the API reference at <code>/docs/</code>, so you serve everything from a single process. The short answer: not that hard, but there is one non-obvious gotcha in how URLs are resolved.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="featured.png" class="lightbox" data-gallery="quarto-lightbox-gallery-1"><img src="https://mickael.canouil.fr/posts/2026-07-06-shiny-great-docs/featured.png" class="img-featured img-fluid quarto-figure quarto-figure-center figure-img" alt="Dark navy background. Bold white text reads &quot;One URL. One Process.&quot;
Below in light blue: &quot;Shiny for Python + great-docs, served together&quot;.
Two cards side by side: the &quot;Shiny for Python&quot; wordmark labelled &quot;/&quot; and
the great-docs GD monogram labelled &quot;/docs/&quot; in green monospace text.
" width="600"></a></p>
</figure>
</div>
<p>The companion repository is <a href="https://github.com/mcanouil/demo-shiny-great-docs">mcanouil/demo-shiny-great-docs</a>.</p>
<section id="the-documentation-great-docs" class="level2" data-number="1">
<h2 data-number="1" class="anchored" data-anchor-id="the-documentation-great-docs"><span class="header-section-number">1</span> The documentation: <code>great-docs</code></h2>
<p><a href="https://posit-dev.github.io/great-docs/">great-docs</a> is Posit’s API documentation tool for Python packages. You point it at a module, give it a small YAML config, and it generates a Quarto-based site from your docstrings. The result is close to what <a href="https://pkgdown.r-lib.org/">pkgdown</a> gives you for R: a landing page, an API reference, and whatever extra pages you add.</p>
<p>The config for this project is short, see the full <a href="https://github.com/mcanouil/demo-shiny-great-docs/blob/main/great-docs.yml">great-docs.yml</a> in the repository.</p>
<div class="code-with-filename">
<div class="code-with-filename-file">
<pre><strong>great-docs.yml</strong></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="annotated-cell-1" data-filename="great-docs.yml" style="background: #f1f3f5;"><pre class="sourceCode yaml code-annotation-code code-with-copy code-annotated"><code class="sourceCode yaml"><a class="code-annotation-anchor" data-target-cell="annotated-cell-1" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-1-1" class="code-annotation-target"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">parser</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> numpy</span></span>
<span id="annotated-cell-1-2"></span>
<span id="annotated-cell-1-3"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">authors</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="annotated-cell-1-4"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">  </span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">name</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> Mickaël Canouil</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-1" data-target-annotation="2" onclick="event.preventDefault();">2</a><span id="annotated-cell-1-5" class="code-annotation-target"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">role</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> Author</span></span>
<span id="annotated-cell-1-6"></span>
<span id="annotated-cell-1-7"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">reference</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="annotated-cell-1-8"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">  </span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">title</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> Functions</span></span>
<span id="annotated-cell-1-9"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">desc</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> Utility functions</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-1" data-target-annotation="3" onclick="event.preventDefault();">3</a><span id="annotated-cell-1-10" class="code-annotation-target"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">contents</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="annotated-cell-1-11"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">      </span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> body_mass_distribution</span></span>
<span id="annotated-cell-1-12"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">      </span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> load_penguins</span></span>
<span id="annotated-cell-1-13"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">      </span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> mass_flipper_corr</span></span>
<span id="annotated-cell-1-14"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">      </span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> mass_vs_flipper</span></span>
<span id="annotated-cell-1-15"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">      </span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> species_counts</span></span>
<span id="annotated-cell-1-16"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">      </span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> summarise_by_species</span></span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
</div>
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-1" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-1" data-code-lines="1" data-code-annotation="1">Docstring format — <code>numpy</code> means NumPy-style sections (<code>Parameters</code>, <code>Returns</code>, <code>Examples</code>). Other options are <code>google</code> and <code>sphinx</code>.</span>
</dd>
<dt data-target-cell="annotated-cell-1" data-target-annotation="2">2</dt>
<dd>
<span data-code-cell="annotated-cell-1" data-code-lines="5" data-code-annotation="2">Author metadata appears in the generated site sidebar under Developers.</span>
</dd>
<dt data-target-cell="annotated-cell-1" data-target-annotation="3">3</dt>
<dd>
<span data-code-cell="annotated-cell-1" data-code-lines="10" data-code-annotation="3">Controls which names appear on the Reference page and in what order. The names not listed here are hidden from the public API.</span>
</dd>
</dl>
<p>Building the site copies the output into a <code>docs/</code> folder the app will serve.</p>
<div class="code-with-filename">
<div class="code-with-filename-file">
<pre><strong>scripts/build-docs.sh</strong></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="annotated-cell-2" data-filename="scripts/build-docs.sh" style="background: #f1f3f5;"><pre class="sourceCode bash code-annotation-code code-with-copy code-annotated"><code class="sourceCode bash"><a class="code-annotation-anchor" data-target-cell="annotated-cell-2" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-2-1" class="code-annotation-target"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">uv</span> run great-docs build</span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-2" data-target-annotation="2" onclick="event.preventDefault();">2</a><span id="annotated-cell-2-2" class="code-annotation-target"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">rm</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-rf</span> docs</span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-2" data-target-annotation="3" onclick="event.preventDefault();">3</a><span id="annotated-cell-2-3" class="code-annotation-target"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">cp</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-R</span> great-docs/_site docs</span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
</div>
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-2" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-2" data-code-lines="1" data-code-annotation="1">Runs <code>great-docs build</code> inside the project’s <a href="https://docs.astral.sh/uv/"><code>uv</code></a> virtual environment.</span>
</dd>
<dt data-target-cell="annotated-cell-2" data-target-annotation="2">2</dt>
<dd>
<span data-code-cell="annotated-cell-2" data-code-lines="2" data-code-annotation="2">Removes any stale output before copying fresh files.</span>
</dd>
<dt data-target-cell="annotated-cell-2" data-target-annotation="3">3</dt>
<dd>
<span data-code-cell="annotated-cell-2" data-code-lines="3" data-code-annotation="3"><code>great-docs</code> writes to <code>great-docs/_site</code> by default; this renames it to <code>docs</code>.</span>
</dd>
</dl>
</section>
<section id="the-app-shiny-for-python" class="level2" data-number="2">
<h2 data-number="2" class="anchored" data-anchor-id="the-app-shiny-for-python"><span class="header-section-number">2</span> The app: Shiny for Python</h2>
<p>The <code>penguin_analysis</code> module loads the dataset with <a href="https://pola.rs/"><code>polars</code></a>, computes summaries, and draws <a href="https://plotnine.org/"><code>plotnine</code></a> plots. The Shiny app wraps those functions in an interactive UI.</p>
<div class="code-with-filename">
<div class="code-with-filename-file">
<pre><strong>app.py</strong></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="annotated-cell-3" data-filename="app.py" style="background: #f1f3f5;"><pre class="sourceCode python code-annotation-code code-with-copy code-annotated"><code class="sourceCode python"><span id="annotated-cell-3-1">app_ui <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ui.page_navbar(</span>
<span id="annotated-cell-3-2">    ui.nav_panel(</span>
<span id="annotated-cell-3-3">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Explore"</span>,</span>
<span id="annotated-cell-3-4">        ui.layout_sidebar(</span>
<span id="annotated-cell-3-5">            ui.sidebar(</span>
<span id="annotated-cell-3-6">                ui.input_selectize(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"species"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Species"</span>,</span>
<span id="annotated-cell-3-7">                                   choices<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>_SPECIES,</span>
<span id="annotated-cell-3-8">                                   selected<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>_SPECIES,</span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-3" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-3-9" class="code-annotation-target">                                   multiple<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>),</span>
<span id="annotated-cell-3-10">            ),</span>
<span id="annotated-cell-3-11">            ui.output_text(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"correlation"</span>),</span>
<span id="annotated-cell-3-12">            ui.output_data_frame(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"table"</span>),</span>
<span id="annotated-cell-3-13">        ),</span>
<span id="annotated-cell-3-14">    ),</span>
<span id="annotated-cell-3-15">    ui.nav_panel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Plots"</span>,</span>
<span id="annotated-cell-3-16">        ui.output_plot(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"scatter"</span>),</span>
<span id="annotated-cell-3-17">        ui.output_plot(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"distribution"</span>),</span>
<span id="annotated-cell-3-18">    ),</span>
<span id="annotated-cell-3-19">    ui.nav_panel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Summary"</span>, ui.output_data_frame(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"summary"</span>)),</span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-3" data-target-annotation="2" onclick="event.preventDefault();">2</a><span id="annotated-cell-3-20" class="code-annotation-target">    ui.nav_spacer(),</span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-3" data-target-annotation="3" onclick="event.preventDefault();">3</a><span id="annotated-cell-3-21" class="code-annotation-target">    ui.nav_control(ui.a(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Documentation"</span>, href<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"/docs/"</span>, target<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"_blank"</span>)),</span>
<span id="annotated-cell-3-22">    title<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Palmer Penguins"</span>,</span>
<span id="annotated-cell-3-23">)</span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
</div>
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-3" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-3" data-code-lines="9" data-code-annotation="1"><code>multiple=True</code> lets the user select several species at once; all three are pre-selected so the app is useful immediately on load.</span>
</dd>
<dt data-target-cell="annotated-cell-3" data-target-annotation="2">2</dt>
<dd>
<span data-code-cell="annotated-cell-3" data-code-lines="20" data-code-annotation="2">Pushes items that follow it to the right end of the navbar.</span>
</dd>
<dt data-target-cell="annotated-cell-3" data-target-annotation="3">3</dt>
<dd>
<span data-code-cell="annotated-cell-3" data-code-lines="21" data-code-annotation="3">Injects a plain HTML link into the navbar. Here, <code>href="/docs/"</code> works because both routes share the same origin, so no absolute URL is needed.</span>
</dd>
</dl>
<p>The navbar links to <code>/docs/</code> so the user can jump from the app to the reference without leaving the same origin.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="assets/shiny-app.png" class="lightbox" data-gallery="quarto-lightbox-gallery-2" title="The Palmer Penguins Shiny app on the Explore tab. Three species are selected; the main panel shows a correlation value and the first rows of the filtered dataset. The Documentation link in the top-right corner routes to the same-origin API reference."><img src="https://mickael.canouil.fr/posts/2026-07-06-shiny-great-docs/assets/shiny-app.png" class="hero-art img-fluid figure-img" alt="Screenshot of the Palmer Penguins Shiny app. The navbar has a teal background showing the scatter-plot logo mark (glass-effect square with white and amber dots) followed by 'Palmer Penguins' in white and tabs Explore, Plots, Summary. The active Explore tab is underlined in amber. The left sidebar has a light teal background with a Species filter showing Adelie, Chinstrap, and Gentoo selected. The main area shows 'Body mass vs flipper length correlation: 0.873' and a data table."></a></p>
<figcaption>The Palmer Penguins Shiny app on the Explore tab. Three species are selected; the main panel shows a correlation value and the first rows of the filtered dataset. The Documentation link in the top-right corner routes to the same-origin API reference.</figcaption>
</figure>
</div>
</section>
<section id="serving-both-from-one-process" class="level2" data-number="3">
<h2 data-number="3" class="anchored" data-anchor-id="serving-both-from-one-process"><span class="header-section-number">3</span> Serving both from one process</h2>
<p>Shiny for Python serves static files through its own handler, which matches file paths literally. That means a URL like <code>/docs/reference/</code> returns a 404 because there is no file called <code>reference</code> at that path. What <code>great-docs</code> emits are clean directory links, and those 404 under Shiny’s handler.</p>
<p>The fix is to wrap the Shiny app in a <a href="https://www.starlette.io/">Starlette</a> application and mount the <code>docs/</code> folder with <code>StaticFiles(html=True)</code>.</p>
<div class="code-with-filename">
<div class="code-with-filename-file">
<pre><strong>app.py</strong></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="annotated-cell-4" data-filename="app.py" style="background: #f1f3f5;"><pre class="sourceCode python code-annotation-code code-with-copy code-annotated"><code class="sourceCode python"><span id="annotated-cell-4-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> starlette.applications <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> Starlette</span>
<span id="annotated-cell-4-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> starlette.routing <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> Mount</span>
<span id="annotated-cell-4-3"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> starlette.staticfiles <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> StaticFiles</span>
<span id="annotated-cell-4-4"></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-4" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-4-5" class="code-annotation-target">_DOCS_DIR <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Path(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">__file__</span>).parent <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"docs"</span></span>
<span id="annotated-cell-4-6"></span>
<span id="annotated-cell-4-7">app <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Starlette(</span>
<span id="annotated-cell-4-8">    routes<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>[</span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-4" data-target-annotation="2" onclick="event.preventDefault();">2</a><span id="annotated-cell-4-9" class="code-annotation-target">        Mount(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"/docs"</span>, app<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>StaticFiles(directory<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>_DOCS_DIR, html<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>), name<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"docs"</span>),</span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-4" data-target-annotation="3" onclick="event.preventDefault();">3</a><span id="annotated-cell-4-10" class="code-annotation-target">        Mount(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"/"</span>, app<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>shiny_app, name<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"shiny"</span>),</span>
<span id="annotated-cell-4-11">    ]</span>
<span id="annotated-cell-4-12">)</span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
</div>
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-4" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-4" data-code-lines="5" data-code-annotation="1">Resolves the <code>docs/</code> path relative to <code>app.py</code>, not the working directory, so the app starts correctly regardless of where <code>shiny run</code> is called from.</span>
</dd>
<dt data-target-cell="annotated-cell-4" data-target-annotation="2">2</dt>
<dd>
<span data-code-cell="annotated-cell-4" data-code-lines="9" data-code-annotation="2"><code>html=True</code> resolves <code>/docs/reference/</code> to <code>docs/reference/index.html</code> instead of returning a 404.</span>
</dd>
<dt data-target-cell="annotated-cell-4" data-target-annotation="3">3</dt>
<dd>
<span data-code-cell="annotated-cell-4" data-code-lines="10" data-code-annotation="3">The Shiny app is mounted last. Starlette tries routes in order, so the more-specific <code>/docs</code> prefix always wins before the catch-all <code>/</code>.</span>
</dd>
</dl>
<div class="highlight">
<p><strong><code>html=True</code> is the key detail.</strong> Without it, Starlette’s <code>StaticFiles</code> also returns 404 for directory URLs. With it, any URL that points to a directory resolves to that directory’s <code>index.html</code>, which is exactly what <code>great-docs</code> generates.</p>
</div>
<p>Because Starlette and Shiny both speak <a href="https://asgi.readthedocs.io/en/latest/">ASGI</a>, <code>shiny run app.py</code> still works after wrapping.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="assets/docs-site.png" class="lightbox" data-gallery="quarto-lightbox-gallery-3" title="The great-docs site at /docs/. It explains the integration, shows the Starlette routing code, and links to the API reference. The penguin-analysis logo appears in the navbar alongside a Reference link."><img src="https://mickael.canouil.fr/posts/2026-07-06-shiny-great-docs/assets/docs-site.png" class="hero-art img-fluid figure-img" alt="Screenshot of the great-docs site served at /docs/. The navbar shows the brand logo mark (teal square with scatter dots) and a Reference link. The page hero shows the same logo mark and the title 'penguin-analysis'. The main content shows a heading 'demo-shiny-great-docs', a description of the integration, a 'How the integration works' section explaining the StaticFiles html=True trick, and a code block showing the Starlette routing. The right sidebar shows Links, AI/Agents, Developers (Mickaël Canouil), and Community sections."></a></p>
<figcaption>The great-docs site at <code>/docs/</code>. It explains the integration, shows the Starlette routing code, and links to the API reference. The penguin-analysis logo appears in the navbar alongside a Reference link.</figcaption>
</figure>
</div>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
Note
</div>
</div>
<div class="callout-body-container callout-body">
<p>The <code>docs/</code> folder is generated output and is not committed to the repository. Run <code>scripts/build-docs.sh</code> whenever the module changes.</p>
</div>
</div>
</section>
<section id="quick-start" class="level2" data-number="4">
<h2 data-number="4" class="anchored" data-anchor-id="quick-start"><span class="header-section-number">4</span> Quick start</h2>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" data-filename="bash" style="background: #f1f3f5;"><pre class="sourceCode bash cw-auto code-with-copy"><code class="sourceCode bash"><span id="cb1-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">uv</span> sync <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--extra</span> app <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--extra</span> dev</span>
<span id="cb1-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">bash</span> scripts/build-docs.sh</span>
<span id="cb1-3"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">uv</span> run shiny run app.py <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--port</span> 8000</span></code></pre></div></div>
<p>Then <code>http://localhost:8000</code> is the app and <code>http://localhost:8000/docs/</code> is the documentation.</p>
<p>Happy coding!</p>


</section>

<a onclick="window.scrollTo(0, 0); return false;" id="quarto-back-to-top"><i class="bi bi-arrow-up"></i> Back to top</a><div id="quarto-appendix" class="default"><section class="quarto-appendix-contents" id="quarto-reuse"><h2 class="anchored quarto-appendix-heading">Reuse</h2><div class="quarto-appendix-contents"><div><a rel="license" href="https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en_gb">CC BY-NC-SA 4.0</a></div></div></section><section class="quarto-appendix-contents" id="quarto-citation"><h2 class="anchored quarto-appendix-heading">Citation</h2><div><div class="quarto-appendix-secondary-label">BibTeX citation:</div><pre class="sourceCode code-with-copy quarto-appendix-bibtex"><code class="sourceCode bibtex">@misc{canouil2026,
  author = {CANOUIL, Mickaël},
  title = {Serving a {Shiny} {App} and {Its} {Docs} from {One}
    {Process}},
  date = {2026-07-06},
  url = {https://mickael.canouil.fr/posts/2026-07-06-shiny-great-docs/},
  langid = {en-GB}
}
</code></pre><div class="quarto-appendix-secondary-label">For attribution, please cite this work as:</div><div id="ref-canouil2026" class="csl-entry quarto-appendix-citeas">
CANOUIL, M. (2026-07-06). Serving a Shiny App and Its Docs from One
Process. <em>Mickael.canouil.fr</em>. <a href="https://mickael.canouil.fr/posts/2026-07-06-shiny-great-docs/">https://mickael.canouil.fr/posts/2026-07-06-shiny-great-docs/</a>
</div></div></section></div> ]]></description>
  <category>python</category>
  <category>shiny</category>
  <category>quarto</category>
  <category>documentation</category>
  <guid>https://mickael.canouil.fr/posts/2026-07-06-shiny-great-docs/</guid>
  <pubDate>Mon, 06 Jul 2026 00:00:00 GMT</pubDate>
  <media:content url="https://mickael.canouil.fr/posts/2026-07-06-shiny-great-docs/featured.png" medium="image" type="image/png" height="76" width="144"/>
</item>
<item>
  <title>R Package Architecture: Shiny, Quarto, targets, and S7</title>
  <link>https://mickael.canouil.fr/posts/2026-07-01-r-package-shiny-quarto/</link>
  <description><![CDATA[ 

<!--
@license MIT
@copyright 2026 Mickaël Canouil
@author Mickaël Canouil
-->
Skip to main content





<p>My recent posts have all been about Quarto and Typst. This one is different: it is about R (<em>yet, still a bit about Quarto too</em>). My day job is to conduct statistical analysis of omics data, and most of it is spent writing R code through packages, Shiny apps, and scripts. These are used by everyone from data scientists to clinicians who never open a terminal. This post is about the package shape I settled on in my most recent projects, where the analysis logic, the interactive app, and the report all need to share the same code.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="featured.png" class="lightbox" data-gallery="quarto-lightbox-gallery-1"><img src="https://mickael.canouil.fr/posts/2026-07-01-r-package-shiny-quarto/featured.png" class="img-featured img-fluid quarto-figure quarto-figure-center figure-img" alt="Dark card on a navy background. Bold white text reads &quot;R Package
Architecture&quot;. Below in blue: &quot;S7 · targets · Shiny · Quarto&quot;. Four
cards in a row show the R, targets, Shiny, and Quarto hex logos,
labelled S7 object model, targets pipeline, Shiny app, and Quarto
extension.
" width="600"></a></p>
</figure>
</div>
<section id="when-does-this-pattern-fit" class="level2" data-number="1">
<h2 data-number="1" class="anchored" data-anchor-id="when-does-this-pattern-fit"><span class="header-section-number">1</span> When does this pattern fit?</h2>
<p>The pattern earns its complexity when several things are true at once. You have a domain object that several verbs operate on, such as load, summarise, and export. You want the same logic available interactively, in a batch pipeline, and in a report. You need branded, consistent output across formats: HTML, PDF, slides.</p>
<p>If you only need a handful of functions, this is too much structure. The cost is worth paying once the object model and the reporting start to repeat.</p>
</section>
<section id="the-four-pillars" class="level2" data-number="2">
<h2 data-number="2" class="anchored" data-anchor-id="the-four-pillars"><span class="header-section-number">2</span> The four pillars</h2>
<p>The package rests on four parts that compose cleanly.</p>
<div id="fig-architecture" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-architecture-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<div>
<div class="light-content">
<div style="text-align: center;">
<p><a href="../../posts/2026-07-01-r-package-shiny-quarto/./assets/typst-render/index/fig-architecture-light.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-2" title="Figure&nbsp;1: The four pillars of acme.toolkit. S7 objects are the shared currency: the pipeline, the report, and the Shiny app all pass them around."><img src="https://mickael.canouil.fr/posts/2026-07-01-r-package-shiny-quarto/assets/typst-render/index/fig-architecture-light.svg" class="img-fluid figure-img" alt="Architecture diagram with four boxes inside a border labelled acme.toolkit. Top row: S7 object model on the left connected by a right arrow to targets pipeline on the right. Bottom row: Shiny app on the left, Quarto extension on the right. Vertical down arrows connect the top row to the bottom row in each column."></a></p>
</div>
</div>
<div class="dark-content">
<div style="text-align: center;">
<p><a href="../../posts/2026-07-01-r-package-shiny-quarto/./assets/typst-render/index/fig-architecture-dark.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-3" title="Figure&nbsp;1: The four pillars of acme.toolkit. S7 objects are the shared currency: the pipeline, the report, and the Shiny app all pass them around."><img src="https://mickael.canouil.fr/posts/2026-07-01-r-package-shiny-quarto/assets/typst-render/index/fig-architecture-dark.svg" class="img-fluid figure-img" alt="Architecture diagram with four boxes inside a border labelled acme.toolkit. Top row: S7 object model on the left connected by a right arrow to targets pipeline on the right. Bottom row: Shiny app on the left, Quarto extension on the right. Vertical down arrows connect the top row to the bottom row in each column."></a></p>
</div>
</div>
</div>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-architecture-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1: The four pillars of acme.toolkit. S7 objects are the shared currency: the pipeline, the report, and the Shiny app all pass them around.
</figcaption>
</figure>
</div>
<p>Each pillar is a distinct tool with a different job. <a href="https://rconsortium.github.io/S7/">S7</a> defines the typed data structures the rest of the package operates on. <a href="https://docs.ropensci.org/targets/">targets</a> is a build system: it caches intermediate results and only re-runs steps whose inputs have changed. <a href="https://shiny.posit.co/r/">Shiny</a> turns R functions into an interactive web app, with no JavaScript needed. Quarto renders documents from a single source file: HTML pages, PDFs, slide decks, all from the same prose and code.</p>
<p>The S7 objects are the currency that flows between the four parts. The pipeline, the report, and the Shiny app all pass them around and call the same generics.</p>
</section>
<section id="an-s7-object-model" class="level2" data-number="3">
<h2 data-number="3" class="anchored" data-anchor-id="an-s7-object-model"><span class="header-section-number">3</span> An S7 object model</h2>
<p>R is a functional language, but it lets you define your own types. A class is a named structure with rules: it says what fields a value must have, what types those fields must be, and what makes a value valid. Once you have a class, you can write functions whose behaviour changes depending on what type of object you pass them. That is the whole idea.</p>
<p><a href="https://rconsortium.github.io/S7/">S7</a> is the R Consortium’s current answer to “how should R do this”. It replaces the older S3 and S4 systems with a cleaner API: you declare properties (the fields), a validator (the rules), and a constructor (how to build one). You call <code>@</code> to read a field, just like <code>$</code> for a list.</p>
<p>The package has three classes. <code>Dataset</code> wraps a <code>data.frame</code> and records when it was created. <code>AnalysisResult</code> holds the summary table and method name produced by one analysis run. <code>Project</code> owns a dataset and an ordered list of results.</p>
<div class="code-with-filename">
<div class="code-with-filename-file">
<pre><strong>R/aab-classes.R</strong></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="annotated-cell-1" data-filename="R/aab-classes.R" style="background: #f1f3f5;"><pre class="sourceCode r code-annotation-code code-with-copy code-annotated"><code class="sourceCode r"><span id="annotated-cell-1-1">Dataset <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> S7<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">new_class</span>(</span>
<span id="annotated-cell-1-2">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">name =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Dataset"</span>,</span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-1" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-1-3" class="code-annotation-target">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">properties =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">list</span>(</span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-1" data-target-annotation="2" onclick="event.preventDefault();">2</a><span id="annotated-cell-1-4" class="code-annotation-target">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">data =</span> S7<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span>class_any,</span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-1" data-target-annotation="3" onclick="event.preventDefault();">3</a><span id="annotated-cell-1-5" class="code-annotation-target">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">name =</span> S7<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">new_property</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">class =</span> S7<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span>class_character, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">default =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">""</span>),</span>
<span id="annotated-cell-1-6">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">created =</span> S7<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">new_property</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">class =</span> S7<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span>class_any, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">default =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">NULL</span>)</span>
<span id="annotated-cell-1-7">  ),</span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-1" data-target-annotation="4" onclick="event.preventDefault();">4</a><span id="annotated-cell-1-8" class="code-annotation-target">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">validator =</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span>(self) {</span>
<span id="annotated-cell-1-9">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> (<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">!</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">is.null</span>(self<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span>data) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;&amp;</span> <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">!</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">is.data.frame</span>(self<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span>data)) {</span>
<span id="annotated-cell-1-10">      <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">return</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"@data must be a data.frame or NULL"</span>)</span>
<span id="annotated-cell-1-11">    }</span>
<span id="annotated-cell-1-12">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> (<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">length</span>(self<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span>name) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">!=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>L) {</span>
<span id="annotated-cell-1-13">      <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">return</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"@name must be a single string"</span>)</span>
<span id="annotated-cell-1-14">    }</span>
<span id="annotated-cell-1-15">    <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">NULL</span></span>
<span id="annotated-cell-1-16">  },</span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-1" data-target-annotation="5" onclick="event.preventDefault();">5</a><span id="annotated-cell-1-17" class="code-annotation-target">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">constructor =</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">data =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">NULL</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">name =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">""</span>) {</span>
<span id="annotated-cell-1-18">    S7<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">new_object</span>(</span>
<span id="annotated-cell-1-19">      S7<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">S7_object</span>(),</span>
<span id="annotated-cell-1-20">      <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">data =</span> data,</span>
<span id="annotated-cell-1-21">      <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">name =</span> name,</span>
<span id="annotated-cell-1-22">      <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">created =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">Sys.time</span>()</span>
<span id="annotated-cell-1-23">    )</span>
<span id="annotated-cell-1-24">  }</span>
<span id="annotated-cell-1-25">)</span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
</div>
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-1" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-1" data-code-lines="3" data-code-annotation="1">Properties are typed slots; every instance must carry a value for each.</span>
</dd>
<dt data-target-cell="annotated-cell-1" data-target-annotation="2">2</dt>
<dd>
<span data-code-cell="annotated-cell-1" data-code-lines="4" data-code-annotation="2"><code>class_any</code> accepts any R object with no type check.</span>
</dd>
<dt data-target-cell="annotated-cell-1" data-target-annotation="3">3</dt>
<dd>
<span data-code-cell="annotated-cell-1" data-code-lines="5" data-code-annotation="3"><code>new_property()</code> adds a class constraint and a default value; omitting <code>name</code> gives <code>""</code>.</span>
</dd>
<dt data-target-cell="annotated-cell-1" data-target-annotation="4">4</dt>
<dd>
<span data-code-cell="annotated-cell-1" data-code-lines="8" data-code-annotation="4">All invariants go in <code>validator</code>; returning a string fails construction with that message.</span>
</dd>
<dt data-target-cell="annotated-cell-1" data-target-annotation="5">5</dt>
<dd>
<span data-code-cell="annotated-cell-1" data-code-lines="17" data-code-annotation="5">The constructor stamps <code>created</code> automatically; callers only supply <code>data</code> and <code>name</code>.</span>
</dd>
</dl>
<p>Verbs are S7 generics. A generic is a function that looks at the type of its first argument and calls the right implementation. You define the generic once, then attach a separate implementation for each class. Generics go in <code>aaa-generics.R</code>, before the class files, so the implementations can attach to them when R loads the package.</p>
<div class="code-with-filename">
<div class="code-with-filename-file">
<pre><strong>R/aaa-generics.R</strong></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="annotated-cell-2" data-filename="R/aaa-generics.R" style="background: #f1f3f5;"><pre class="sourceCode r code-annotation-code code-with-copy code-annotated"><code class="sourceCode r"><span id="annotated-cell-2-1">analyse <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> S7<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">new_generic</span>(</span>
<span id="annotated-cell-2-2">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"analyse"</span>,</span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-2" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-2-3" class="code-annotation-target">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"object"</span>,</span>
<span id="annotated-cell-2-4">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span>(object, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">method =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"describe"</span>, ...) {</span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-2" data-target-annotation="2" onclick="event.preventDefault();">2</a><span id="annotated-cell-2-5" class="code-annotation-target">    S7<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">S7_dispatch</span>()</span>
<span id="annotated-cell-2-6">  }</span>
<span id="annotated-cell-2-7">)</span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
</div>
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-2" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-2" data-code-lines="3" data-code-annotation="1">The dispatch argument: S7 picks the method based on the class of <code>object</code>.</span>
</dd>
<dt data-target-cell="annotated-cell-2" data-target-annotation="2">2</dt>
<dd>
<span data-code-cell="annotated-cell-2" data-code-lines="5" data-code-annotation="2">Must call <code>S7_dispatch()</code> in the body; this triggers method lookup and execution.</span>
</dd>
</dl>
<p>The <code>Project</code> method delegates to the <code>Dataset</code> method, stores the result, and updates the timestamp.</p>
<div class="code-with-filename">
<div class="code-with-filename-file">
<pre><strong>R/analyse.R</strong></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="annotated-cell-3" data-filename="R/analyse.R" style="background: #f1f3f5;"><pre class="sourceCode r code-annotation-code code-with-copy code-annotated"><code class="sourceCode r"><a class="code-annotation-anchor" data-target-cell="annotated-cell-3" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-3-1" class="code-annotation-target">S7<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">method</span>(analyse, Project) <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span>(object, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">method =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"describe"</span>, ...) {</span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-3" data-target-annotation="2" onclick="event.preventDefault();">2</a><span id="annotated-cell-3-2" class="code-annotation-target">  result <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">analyse</span>(object<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span>data, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">method =</span> method, ...)</span>
<span id="annotated-cell-3-3">  object<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span>results <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(object<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span>results, <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">list</span>(result))</span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-3" data-target-annotation="3" onclick="event.preventDefault();">3</a><span id="annotated-cell-3-4" class="code-annotation-target">  object<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span>modified <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">Sys.time</span>()</span>
<span id="annotated-cell-3-5">  object</span>
<span id="annotated-cell-3-6">}</span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
</div>
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-3" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-3" data-code-lines="1" data-code-annotation="1">Attaches a method to the <code>analyse</code> generic specifically for <code>Project</code> objects.</span>
</dd>
<dt data-target-cell="annotated-cell-3" data-target-annotation="2">2</dt>
<dd>
<span data-code-cell="annotated-cell-3" data-code-lines="2" data-code-annotation="2">Delegates to the <code>Dataset</code> method by passing <code>object@data</code>; no logic is duplicated.</span>
</dd>
<dt data-target-cell="annotated-cell-3" data-target-annotation="3">3</dt>
<dd>
<span data-code-cell="annotated-cell-3" data-code-lines="4" data-code-annotation="3">S7 slot assignment uses <code>@&lt;-</code>, the same accessor as reading (<code>@</code>).</span>
</dd>
</dl>
<p>One more thing worth keeping: the <code>filter()</code> generic uses a search-path fallback so the package’s own <code>filter()</code> method works on <code>Dataset</code> objects while <a href="https://dplyr.tidyverse.org/">dplyr</a>’s <code>filter()</code> still works on data frames. The default method walks the search path and delegates to the next plain function of the same name.</p>
<div class="highlight">
<p><strong>One generic, many callers:</strong> <code>analyse(dataset)</code> and <code>analyse(project)</code> look identical at the call site. S7 routes each to the right implementation. No type-checking <code>if</code> branches anywhere in the calling code.</p>
</div>
</section>
<section id="a-config-driven-pipeline" class="level2" data-number="4">
<h2 data-number="4" class="anchored" data-anchor-id="a-config-driven-pipeline"><span class="header-section-number">4</span> A config-driven pipeline</h2>
<p>Most people who run an analysis are not R developers. They know what data they have and what question they want to answer, but they should not need to open a <code>.R</code> file to say so.</p>
<p>A YAML config makes this possible. It reads like plain English: here is the data file, here are the analyses to run, here is what the report should be called. No R syntax, no package knowledge, no risk of accidentally breaking the pipeline logic. The package owns the code; the config owns the decisions.</p>
<p>R developers who want more control are not forced through the config. They can call <code>Dataset()</code>, <code>analyse()</code>, and the rest of the verbs directly, compose them however they like, and build their own pipeline. The config-driven workflow is a convenience layer on top, not a gate.</p>
<p><code>generate_pipeline()</code> validates the config, fills a <a href="https://cran.r-project.org/package=whisker">whisker</a>/Mustache template, and writes a <code>_targets.R</code> file. <code>run_pipeline()</code> calls <a href="https://docs.ropensci.org/targets/">targets</a>’ <code>tar_make()</code> to execute it. <code>report_pipeline()</code> renders the Quarto report from the store.</p>
<div class="code-with-filename">
<div class="code-with-filename-file">
<pre><strong>pipeline-config.yml</strong></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" data-filename="pipeline-config.yml" style="background: #f1f3f5;"><pre class="sourceCode yaml code-with-copy"><code class="sourceCode yaml"><span id="cb1-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">data</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb1-2"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">  </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">file</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> demo.csv</span></span>
<span id="cb1-3"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">  </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">name</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> demo</span></span>
<span id="cb1-4"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">analyses</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb1-5"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">  </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">summary</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb1-6"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">method</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> describe</span></span>
<span id="cb1-7"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">  </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">pairs</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb1-8"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">method</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> correlate</span></span>
<span id="cb1-9"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">report</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb1-10"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">  </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">title</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Acme Analysis Report"</span></span>
<span id="cb1-11"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">  </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">formats</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb1-12"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> acme-html</span></span></code></pre></div></div>
</div>
<p>The template is the interesting part. Each analysis in the config becomes one <code>targets</code> target. The whisker <code>{#analyses} ... {{/analyses}}</code> section expands once per entry.</p>
<div class="code-with-filename">
<div class="code-with-filename-file">
<pre><strong>inst/templates/_targets.R</strong></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb2" data-filename="inst/templates/_targets.R" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb2-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">list</span>(</span>
<span id="cb2-2">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">tar_target</span>(input_file, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"{{{data_file}}}"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">format =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"file"</span>),</span>
<span id="cb2-3">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">tar_target</span>(</span>
<span id="cb2-4">    dataset,</span>
<span id="cb2-5">    acme.toolkit<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">Dataset</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">data =</span> utils<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">read.csv</span>(input_file), <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">name =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"{{{data_name}}}"</span>)</span>
<span id="cb2-6">  ){{<span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#analyses}},</span></span>
<span id="cb2-7">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">tar_target</span>(</span>
<span id="cb2-8">    {{name}},</span>
<span id="cb2-9">    acme.toolkit<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">analyse</span>(dataset, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">method =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"{{method}}"</span>)</span>
<span id="cb2-10">  ){{<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span>analyses}}</span>
<span id="cb2-11"><span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">)</span></span></code></pre></div></div>
</div>
<p>The leading comma belongs at the start of each repeated block, after the always-present <code>dataset</code> target. This avoids a trailing comma in the generated list, which would be a parse error.</p>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
Note
</div>
</div>
<div class="callout-body-container callout-body">
<p>The config hash is written into the file header at generation time. If the config drifts from what was used to generate the pipeline, a later run can warn about it.</p>
</div>
</div>
</section>
<section id="a-bundled-quarto-extension" class="level2" data-number="5">
<h2 data-number="5" class="anchored" data-anchor-id="a-bundled-quarto-extension"><span class="header-section-number">5</span> A bundled Quarto extension</h2>
<p>Branding lives in exactly one place: a Quarto extension under <code>inst/_extensions/acme/</code>. One <code>_extension.yml</code> defines three formats.</p>
<div class="code-with-filename">
<div class="code-with-filename-file">
<pre><strong>inst/_extensions/acme/_extension.yml</strong></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb3" data-filename="inst/_extensions/acme/_extension.yml" style="background: #f1f3f5;"><pre class="sourceCode yaml code-with-copy"><code class="sourceCode yaml"><span id="cb3-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">title</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> Acme</span></span>
<span id="cb3-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">version</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.1.0</span></span>
<span id="cb3-3"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">quarto-required</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"&gt;=1.5.0"</span></span>
<span id="cb3-4"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">contributes</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb3-5"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">  </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">metadata</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb3-6"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">project</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb3-7"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">      </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">brand</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> _brand.yml</span></span>
<span id="cb3-8"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">  </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">formats</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb3-9"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">common</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb3-10"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">      </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">filters</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb3-11"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">        </span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> filters/prose-divs.lua</span></span>
<span id="cb3-12"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">        </span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> filters/copyright-year.lua</span></span>
<span id="cb3-13"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">      </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">toc</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">true</span></span>
<span id="cb3-14"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">      </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">number-sections</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">true</span></span>
<span id="cb3-15"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">html</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb3-16"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">      </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">theme</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">[</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">cosmo</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">,</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> brand</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">,</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> acme.scss</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">]</span></span>
<span id="cb3-17"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">      </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">embed-resources</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">true</span></span>
<span id="cb3-18"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">      </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">title-block-banner</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">true</span></span>
<span id="cb3-19"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">typst</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb3-20"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">      </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">papersize</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> a4</span></span>
<span id="cb3-21"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">      </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">margin</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">{</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">top</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> 2.5cm</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">,</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">bottom</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> 2.5cm</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">,</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">left</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> 2cm</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">,</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">right</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> 2cm</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">}</span></span>
<span id="cb3-22"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">revealjs</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb3-23"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">      </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">theme</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">[</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">default</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">,</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> brand</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">,</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> revealjs/theme.scss</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">]</span></span>
<span id="cb3-24"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">      </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">embed-resources</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">true</span></span></code></pre></div></div>
</div>
<p>A document opts in with one line.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb4" data-filename="yaml" style="background: #f1f3f5;"><pre class="sourceCode yaml cw-auto code-with-copy"><code class="sourceCode yaml"><span id="cb4-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">format</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> acme-html</span></span></code></pre></div></div>
<p>Colours, fonts, and logos live in <code>_brand.yml</code>. Quarto’s native brand support feeds them into every format, so the SCSS files only add layout rules.</p>
<p>The extension ships inside the package at <code>inst/_extensions/acme/</code>. When the pipeline is generated, <code>.install_acme_extension()</code> copies it into the project directory. Quarto resolves <code>_extensions/</code> from the document’s directory, so the copy must be local. Re-skinning the whole system means editing three files: <code>_brand.yml</code>, <code>acme.scss</code>, and <code>revealjs/theme.scss</code>.</p>
<div class="highlight">
<p><strong>One extension, three formats:</strong> <code>acme-html</code>, <code>acme-typst</code>, and <code>acme-revealjs</code> all draw from the same <code>_brand.yml</code>. Swap the palette once and every output follows.</p>
</div>
</section>
<section id="a-modular-shiny-app" class="level2" data-number="6">
<h2 data-number="6" class="anchored" data-anchor-id="a-modular-shiny-app"><span class="header-section-number">6</span> A modular Shiny app</h2>
<p>The <a href="https://shiny.posit.co/r/">Shiny</a> app reuses the package’s analysis code. It does not re-implement it. The modules call <code>analyse()</code>, the same generic the batch pipeline calls.</p>
<p>Shiny modules exist to solve a scoping problem. In a plain Shiny app, every input and output shares a single namespace, so names clash as the app grows. A module wraps a piece of UI and its server logic into an isolated unit with its own namespace. You can drop it into any app by calling two functions: one for the UI, one for the server.</p>
<p>That last point matters here. The three modules are not just internal implementation details of this package’s bundled app. They are the public interface for other <a href="https://shiny.posit.co/r/">Shiny</a> apps. If you already have a Shiny app and you want to add an acme.toolkit analysis panel to it, you call <code>mod_analysis_ui()</code> and <code>mod_analysis_server()</code> and you are done. No copy-pasting, no re-implementing the analysis logic.</p>
<div class="highlight">
<p><strong>Modules as a public API:</strong> the three modules ship as the embeddable interface for any Shiny app. Drop them into an existing app the same way you drop them into the bundled one.</p>
</div>
<p><code>mod_input</code> loads a CSV and writes a <code>Dataset</code> into shared state. <code>mod_analysis</code> reads the dataset, runs <code>analyse()</code>, and writes the result. <code>mod_report</code> reads the result and offers a Quarto report as a download.</p>
<p>The top-level server owns the shared state and passes it into each module.</p>
<div class="code-with-filename">
<div class="code-with-filename-file">
<pre><strong>R/app_server.R</strong></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="annotated-cell-8" data-filename="R/app_server.R" style="background: #f1f3f5;"><pre class="sourceCode r code-annotation-code code-with-copy code-annotated"><code class="sourceCode r"><span id="annotated-cell-8-1">app_server <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span>(input, output, session) {</span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-8" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-8-2" class="code-annotation-target">  dataset <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> shiny<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">reactiveVal</span>(<span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">NULL</span>)</span>
<span id="annotated-cell-8-3">  result <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> shiny<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">reactiveVal</span>(<span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">NULL</span>)</span>
<span id="annotated-cell-8-4"></span>
<span id="annotated-cell-8-5">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">mod_input_server</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"input"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">dataset =</span> dataset)</span>
<span id="annotated-cell-8-6">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">mod_analysis_server</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"analysis"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">dataset =</span> dataset, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">result =</span> result)</span>
<span id="annotated-cell-8-7">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">mod_report_server</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"report"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">result =</span> result)</span>
<span id="annotated-cell-8-8"></span>
<span id="annotated-cell-8-9">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">invisible</span>(<span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">NULL</span>)</span>
<span id="annotated-cell-8-10">}</span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
</div>
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-8" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-8" data-code-lines="2,3" data-code-annotation="1">Shared state lives here. Modules never reach into each other.</span>
</dd>
</dl>
<p>The data flows in a straight line.</p>
<div id="fig-data-flow" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-data-flow-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<div>
<div class="light-content">
<div style="text-align: center;">
<p><a href="../../posts/2026-07-01-r-package-shiny-quarto/./assets/typst-render/index/fig-data-flow-light.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-4" title="Figure&nbsp;2: Data flows in one direction through shared reactive state. Modules never reach into each other."><img src="https://mickael.canouil.fr/posts/2026-07-01-r-package-shiny-quarto/assets/typst-render/index/fig-data-flow-light.svg" class="img-fluid figure-img" alt="Horizontal flow diagram. Five boxes left to right: mod_input, dataset, mod_analysis, result, mod_report. Each consecutive pair is connected by an arrow labelled writes or reads."></a></p>
</div>
</div>
<div class="dark-content">
<div style="text-align: center;">
<p><a href="../../posts/2026-07-01-r-package-shiny-quarto/./assets/typst-render/index/fig-data-flow-dark.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-5" title="Figure&nbsp;2: Data flows in one direction through shared reactive state. Modules never reach into each other."><img src="https://mickael.canouil.fr/posts/2026-07-01-r-package-shiny-quarto/assets/typst-render/index/fig-data-flow-dark.svg" class="img-fluid figure-img" alt="Horizontal flow diagram. Five boxes left to right: mod_input, dataset, mod_analysis, result, mod_report. Each consecutive pair is connected by an arrow labelled writes or reads."></a></p>
</div>
</div>
</div>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-data-flow-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;2: Data flows in one direction through shared reactive state. Modules never reach into each other.
</figcaption>
</figure>
</div>
<p>Inside <code>mod_analysis</code>, the call is:</p>
<div class="code-with-filename">
<div class="code-with-filename-file">
<pre><strong>R/mod_analysis.R</strong></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="annotated-cell-9" data-filename="R/mod_analysis.R" style="background: #f1f3f5;"><pre class="sourceCode r code-annotation-code code-with-copy code-annotated"><code class="sourceCode r"><span id="annotated-cell-9-1">shiny<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">observeEvent</span>(input[[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"run"</span>]], {</span>
<span id="annotated-cell-9-2">  ds <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">dataset</span>()</span>
<span id="annotated-cell-9-3">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> (<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">is.null</span>(ds)) {</span>
<span id="annotated-cell-9-4">    shiny<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">showNotification</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Load a dataset first."</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">type =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"warning"</span>)</span>
<span id="annotated-cell-9-5">    <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">return</span>()</span>
<span id="annotated-cell-9-6">  }</span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-9" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-9-7" class="code-annotation-target">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">result</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">analyse</span>(ds, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">method =</span> input[[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"method"</span>]]))</span>
<span id="annotated-cell-9-8">})</span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
</div>
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-9" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-9" data-code-lines="7" data-code-annotation="1">Same <code>analyse()</code> generic as the pipeline. No logic is duplicated.</span>
</dd>
</dl>
<p>The module does not know how <code>analyse()</code> works. If you update the analysis logic inside the package, both the pipeline and the Shiny app pick it up.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="assets/shiny-app.png" class="lightbox" data-gallery="quarto-lightbox-gallery-6" title="The Input tab of the acme.toolkit Shiny app. The sidebar lets the user choose between a demo dataset and a CSV upload; the main panel shows the loaded data immediately."><img src="https://mickael.canouil.fr/posts/2026-07-01-r-package-shiny-quarto/assets/shiny-app.png" class="hero-art img-fluid figure-img" alt="Screenshot of the acme.toolkit Shiny app. The navbar has a navy blue background showing the Acme logo mark (a white square with an orange inner square) followed by 'acme.toolkit' in white and tabs Input, Analysis, Report. The active Input tab is underlined in orange. The left sidebar has a light blue background and shows a Data source section with Demo dataset selected, an Upload CSV option, and a Dataset name field. The main area shows a table with columns id, group, height, weight, and score."></a></p>
<figcaption>The Input tab of the acme.toolkit Shiny app. The sidebar lets the user choose between a demo dataset and a CSV upload; the main panel shows the loaded data immediately.</figcaption>
</figure>
</div>
</section>
<section id="putting-it-all-together" class="level2" data-number="7">
<h2 data-number="7" class="anchored" data-anchor-id="putting-it-all-together"><span class="header-section-number">7</span> Putting it all together</h2>
<p>Depending on who you are, you interact with the package differently.</p>
<p>If you are a clinician or analyst who does not write R, you fill in a YAML config file. You say which data to use, which analyses to run, and what to call the report. Then you call <code>run_pipeline()</code>. A branded HTML or PDF report appears in your project folder. You never open a <code>.R</code> file.</p>
<p>If you want to explore the data interactively, you open the Shiny app with <code>run_app()</code>. You upload a CSV, pick an analysis method, and download the result. The app calls the same <code>analyse()</code> function the batch pipeline calls.</p>
<p>If you are an R developer who wants to extend or customise the analysis, you work directly with the S7 classes and generics. You write a new method, add a property, or compose the verbs in a custom script. The change flows into the pipeline, the Shiny app, and the report without touching any of those.</p>
<p>That is what the architecture is for. The same analysis logic runs in a terminal, in a browser, and in a PDF. Keeping it in one place is the only way to keep it consistent.</p>
<p>The full working package is at <a href="https://github.com/mcanouil/demo-r-package-shiny-quarto">mcanouil/demo-r-package-shiny-quarto</a>. It builds clean with zero errors, warnings, and notes.</p>
<p>Happy coding!</p>


</section>

<a onclick="window.scrollTo(0, 0); return false;" id="quarto-back-to-top"><i class="bi bi-arrow-up"></i> Back to top</a><div id="quarto-appendix" class="default"><section class="quarto-appendix-contents" id="quarto-reuse"><h2 class="anchored quarto-appendix-heading">Reuse</h2><div class="quarto-appendix-contents"><div><a rel="license" href="https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en_gb">CC BY-NC-SA 4.0</a></div></div></section><section class="quarto-appendix-contents" id="quarto-citation"><h2 class="anchored quarto-appendix-heading">Citation</h2><div><div class="quarto-appendix-secondary-label">BibTeX citation:</div><pre class="sourceCode code-with-copy quarto-appendix-bibtex"><code class="sourceCode bibtex">@misc{canouil2026,
  author = {CANOUIL, Mickaël},
  title = {R {Package} {Architecture:} {Shiny,} {Quarto,} Targets, and
    {S7}},
  date = {2026-07-01},
  url = {https://mickael.canouil.fr/posts/2026-07-01-r-package-shiny-quarto/},
  langid = {en-GB}
}
</code></pre><div class="quarto-appendix-secondary-label">For attribution, please cite this work as:</div><div id="ref-canouil2026" class="csl-entry quarto-appendix-citeas">
CANOUIL, M. (2026-07-01). R Package Architecture: Shiny, Quarto,
targets, and S7. <em>Mickael.canouil.fr</em>. <a href="https://mickael.canouil.fr/posts/2026-07-01-r-package-shiny-quarto/">https://mickael.canouil.fr/posts/2026-07-01-r-package-shiny-quarto/</a>
</div></div></section></div> ]]></description>
  <category>r</category>
  <category>shiny</category>
  <category>quarto</category>
  <category>s7</category>
  <category>targets</category>
  <guid>https://mickael.canouil.fr/posts/2026-07-01-r-package-shiny-quarto/</guid>
  <pubDate>Wed, 01 Jul 2026 00:00:00 GMT</pubDate>
  <media:content url="https://mickael.canouil.fr/posts/2026-07-01-r-package-shiny-quarto/featured.png" medium="image" type="image/png" height="76" width="144"/>
</item>
<item>
  <title>Gribouille 0.4.0: Temporal Scales, Any-Glyph Markers, and a Finer Grid</title>
  <link>https://mickael.canouil.fr/posts/2026-06-22-gribouille-0-4/</link>
  <description><![CDATA[ 

<!--
@license MIT
@copyright 2026 Mickaël Canouil
@author Mickaël Canouil
-->
Skip to main content





<p>Another <a href="https://github.com/mcanouil/gribouille">Gribouille</a> release. Gribouille 0.4.0 is mostly about time and detail. Scales now read dates and times from plain ISO strings, so you no longer convert to epoch days by hand. The <code>shape</code> aesthetic accepts any character, so a letter or an emoji can be the point marker. The panel grid splits into a major and minor cascade, with minor gridlines drawn by default. And <code>labs()</code> is renamed to <code>labels()</code>, the largest of a handful of breaking renames.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="featured.png" class="lightbox" data-gallery="quarto-lightbox-gallery-1"><img src="https://mickael.canouil.fr/posts/2026-06-22-gribouille-0-4/featured.png" class="img-featured img-fluid quarto-figure quarto-figure-center figure-img" alt="Featured card for the Gribouille 0.4.0 release on a cream paper
background. On the left, the Gribouille wordmark sits above the tagline
&quot;Create elegant graphics with the Grammar of Graphics for Typst.&quot;, a
small orange &quot;v0.4.0 release&quot; pill, and the URL
m.canouil.dev/gribouille. On the right, a slightly rotated bordered card
frames a midnight-sky line chart of Gribouille's cumulative GitHub stars
over time, a luminous gold step trail that rises from a long flat run to
a final amber spike.
" width="600"></a></p>
</figure>
</div>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Note</span>At a glance
</div>
</div>
<div class="callout-body-container callout-body">
<ul>
<li><a href="https://github.com/mcanouil/gribouille">Gribouille</a> 0.4.0 on Typst Universe: <code>#import "@preview/gribouille:0.4.0": *</code>.</li>
<li>Continuous scale <code>limits</code>, <code>breaks</code>, and the <code>geom-vline</code>/<code>geom-hline</code>/<code>geom-abline</code> intercepts accept ISO-8601 date, datetime, and time strings under a temporal scale.</li>
<li>The <code>shape</code> aesthetic accepts any character; a value outside the eight built-in keywords renders as a literal glyph, so letters or emoji work as markers.</li>
<li><a href="https://m.canouil.dev/gribouille/reference/geoms/geom-area.html"><code>geom-area()</code></a> gains a <code>direction</code> parameter (<code>"hv"</code> or <code>"vh"</code>) that steps the filled top edge like <a href="https://m.canouil.dev/gribouille/reference/geoms/geom-step.html"><code>geom-step()</code></a>.</li>
<li>Panel gridlines cascade <code>panel-grid</code> to <code>panel-grid-major</code> / <code>panel-grid-minor</code> to <code>-x</code> / <code>-y</code>; continuous axes draw minor gridlines by default.</li>
<li>Scale <code>limits</code> and <code>expand</code> accept <code>auto</code> on one side, keeping the trained bound or per-scale default for the other.</li>
<li>Bundled datasets (<code>penguins</code>, <code>economics</code>, <code>mpg</code>) ship as row-store arrays, so <code>penguins.filter(...)</code> and <code>.map(...)</code> work directly.</li>
<li><strong>Breaking renames</strong>: <code>labs()</code> to <code>labels()</code>, <code>col-mix()</code> to <code>colour-mix()</code>, <code>xlim</code>/<code>ylim</code> to <code>x-limits</code>/<code>y-limits</code>, the <code>dx</code>/<code>dy</code> offsets to <code>nudge-x</code>/<code>nudge-y</code> aesthetics, and <code>clip</code> to a boolean.</li>
</ul>
</div>
</div>
<p>Every figure in this post is a real, freshly compiled plot.</p>
<section id="breaking-changes" class="level2" data-number="1">
<h2 data-number="1" class="anchored" data-anchor-id="breaking-changes"><span class="header-section-number">1</span> Breaking changes</h2>
<p>This release tidies a few names before they settle for good. None of them change what a plot does, only how you spell it.</p>
<div class="callout callout-style-default callout-warning callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Warning</span>Migrate these names
</div>
</div>
<div class="callout-body-container callout-body">
<ul>
<li>Rename <code>labs(...)</code> to <code>labels(...)</code>, and the <code>plot()</code> / <code>compose()</code> <code>labs:</code> argument to <code>labels:</code>. There is no <code>labs</code> alias.</li>
<li>Rename <code>col-mix(...)</code> to <code>colour-mix(...)</code>; its arguments are now <code>colour1</code> and <code>colour2</code>.</li>
<li>In <code>coord-cartesian()</code>, <code>stat-function()</code>, and <code>geom-function()</code>, rename <code>xlim</code>/<code>ylim</code> to <code>x-limits</code>/<code>y-limits</code>.</li>
<li>In <code>coord-cartesian()</code> and <code>coord-radial()</code>, pass <code>clip</code> as a boolean (<code>true</code>/<code>false</code>) instead of the strings <code>"on"</code>/<code>"off"</code>.</li>
<li>In <code>geom-text</code>, <code>geom-label</code>, and <code>geom-typst</code>, drop <code>dx</code>/<code>dy</code>; use the <code>nudge-x</code>/<code>nudge-y</code> aesthetics instead, where a number shifts in data units and a Typst length shifts in canvas units.</li>
</ul>
</div>
</div>
</section>
<section id="scales-that-read-dates-and-times" class="level2" data-number="2">
<h2 data-number="2" class="anchored" data-anchor-id="scales-that-read-dates-and-times"><span class="header-section-number">2</span> Scales that read dates and times</h2>
<p>Before 0.4.0, putting a date on an axis meant converting it to a number first, usually days since some epoch. Now a temporal scale reads ISO-8601 strings directly. The scale <code>limits</code> and <code>breaks</code> take them, and so do the <code>geom-vline</code>, <code>geom-hline</code>, and <code>geom-abline</code> intercepts.</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="annotated-cell-1" data-filename="typst" style="background: #f1f3f5;"><pre class="sourceCode typst cw-auto code-annotation-code code-with-copy code-annotated"><code class="sourceCode typst"><span id="annotated-cell-1-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">visits</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-1-2">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>day: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-01-05"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> n: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">12</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-1-3">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>day: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-01-20"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> n: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">19</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-1-4">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>day: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-02-08"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> n: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">27</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-1-5">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>day: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-02-25"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> n: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">41</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-1-6">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>day: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-03-10"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> n: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">58</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-1-7">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>day: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-03-28"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> n: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">64</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-1-8"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-1-9"></span>
<span id="annotated-cell-1-10"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>plot<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-1-11">  data<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> visits<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-1-12">  mapping<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> aes<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"day"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"n"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-1-13">  layers<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-1-14">    geom-line<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(),</span></span>
<span id="annotated-cell-1-15">    geom-point<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-1" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-1-16" class="code-annotation-target">    geom-vline<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-1-17">      xintercept<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-02-14"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-1-18">      linetype<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dashed"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-1-19">      colour<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> rgb<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#D55E00"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-1-20">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-1-21">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-1-22">  scales<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-1-23">    scale-x-date<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-1" data-target-annotation="2" onclick="event.preventDefault();">2</a><span id="annotated-cell-1-24" class="code-annotation-target">      limits<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-01-01"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-01"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-1-25">      breaks<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-01-01"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-02-01"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-03-01"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-01"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-1-26">      date-format<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"[month repr:short]"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-1-27">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-1-28">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-1" data-target-annotation="3" onclick="event.preventDefault();">3</a><span id="annotated-cell-1-29" class="code-annotation-target">  labels<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> labels<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-1-30">    title<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Dates Straight From Strings"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-1-31">    x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Month"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-1-32">    y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Visits"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-1-33">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-1-34">  theme<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> theme-minimal<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(),</span></span>
<span id="annotated-cell-1-35">  width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">12cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-1-36">  height<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">7cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-1-37"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
<div class="cell-annotation">
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-1" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-1" data-code-lines="16" data-code-annotation="1"><code>geom-vline</code> reads a plain ISO date string as its intercept under a date scale; no epoch maths.</span>
</dd>
<dt data-target-cell="annotated-cell-1" data-target-annotation="2">2</dt>
<dd>
<span data-code-cell="annotated-cell-1" data-code-lines="24" data-code-annotation="2"><code>limits</code> and <code>breaks</code> take ISO-8601 strings too. The same works for datetimes and times.</span>
</dd>
<dt data-target-cell="annotated-cell-1" data-target-annotation="3">3</dt>
<dd>
<span data-code-cell="annotated-cell-1" data-code-lines="29" data-code-annotation="3"><code>labs()</code> is now <code>labels()</code>. This is the headline breaking rename.</span>
</dd>
</dl>
</div>
<div>
<div class="light-content">
<div style="text-align: center;">
<p><a href="../../posts/2026-06-22-gribouille-0-4/./assets/typst-render/date-strings-light.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-2" title="A line chart of visits over the first three months of 2026, with a dashed orange vertical line marking the 14th of February. The x-axis ticks fall on the first of each month."><img src="https://mickael.canouil.fr/posts/2026-06-22-gribouille-0-4/assets/typst-render/date-strings-light.svg" class="img-fluid hero-art" alt="A line chart of visits over the first three months of 2026, with a dashed orange vertical line marking the 14th of February. The x-axis ticks fall on the first of each month."></a></p>
</div>
</div>
<div class="dark-content">
<div style="text-align: center;">
<p><a href="../../posts/2026-06-22-gribouille-0-4/./assets/typst-render/date-strings-dark.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-3" title="A line chart of visits over the first three months of 2026, with a dashed orange vertical line marking the 14th of February. The x-axis ticks fall on the first of each month."><img src="https://mickael.canouil.fr/posts/2026-06-22-gribouille-0-4/assets/typst-render/date-strings-dark.svg" class="img-fluid hero-art" alt="A line chart of visits over the first three months of 2026, with a dashed orange vertical line marking the 14th of February. The x-axis ticks fall on the first of each month."></a></p>
</div>
</div>
</div>
</div>
</section>
<section id="a-marker-from-any-character" class="level2" data-number="3">
<h2 data-number="3" class="anchored" data-anchor-id="a-marker-from-any-character"><span class="header-section-number">3</span> A marker from any character</h2>
<p>The <code>shape</code> aesthetic used to take one of eight built-in keywords. Now any other value renders as a literal glyph, so a letter, a symbol, or an emoji becomes the marker. Here the species initials stand in for the usual point shapes.</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="annotated-cell-2" data-filename="typst" style="background: #f1f3f5;"><pre class="sourceCode typst cw-auto code-annotation-code code-with-copy code-annotated"><code class="sourceCode typst"><span id="annotated-cell-2-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>plot<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-2-2">  data<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> penguins<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-2-3">  mapping<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> aes<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"flipper-len"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"body-mass"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> shape<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"species"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-2-4">  layers<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>geom-point<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">9pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),),</span></span>
<span id="annotated-cell-2-5">  scales<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-2" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-2-6" class="code-annotation-target">    scale-shape-manual<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>values<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"A"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"🐧"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"star"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)),</span></span>
<span id="annotated-cell-2-7">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-2-8">  labels<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> labels<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-2-9">    title<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Markers From Any Character"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-2-10">    x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Flipper Length (mm)"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-2-11">    y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Body Mass (g)"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-2-12">    shape<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Species"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-2-13">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-2-14">  theme<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> theme-minimal<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(),</span></span>
<span id="annotated-cell-2-15">  width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">12cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-2-16">  height<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">8cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-2-17"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
<div class="cell-annotation">
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-2" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-2" data-code-lines="6" data-code-annotation="1">Each value outside the eight keywords renders as a literal glyph. A font set in <code>theme(text: ...)</code> reaches these markers too.</span>
</dd>
</dl>
</div>
<div>
<div class="light-content">
<div style="text-align: center;">
<p><a href="../../posts/2026-06-22-gribouille-0-4/./assets/typst-render/any-glyph-light.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-4" title="Penguin scatter of body mass against flipper length, with each species drawn as its initial letter, A, C, or G, instead of a point glyph, and a shape legend on the right."><img src="https://mickael.canouil.fr/posts/2026-06-22-gribouille-0-4/assets/typst-render/any-glyph-light.svg" class="img-fluid hero-art" alt="Penguin scatter of body mass against flipper length, with each species drawn as its initial letter, A, C, or G, instead of a point glyph, and a shape legend on the right."></a></p>
</div>
</div>
<div class="dark-content">
<div style="text-align: center;">
<p><a href="../../posts/2026-06-22-gribouille-0-4/./assets/typst-render/any-glyph-dark.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-5" title="Penguin scatter of body mass against flipper length, with each species drawn as its initial letter, A, C, or G, instead of a point glyph, and a shape legend on the right."><img src="https://mickael.canouil.fr/posts/2026-06-22-gribouille-0-4/assets/typst-render/any-glyph-dark.svg" class="img-fluid hero-art" alt="Penguin scatter of body mass against flipper length, with each species drawn as its initial letter, A, C, or G, instead of a point glyph, and a shape legend on the right."></a></p>
</div>
</div>
</div>
</div>
</section>
<section id="stepped-areas" class="level2" data-number="4">
<h2 data-number="4" class="anchored" data-anchor-id="stepped-areas"><span class="header-section-number">4</span> Stepped areas</h2>
<p><code>geom-area()</code> gains a <code>direction</code> parameter. The default keeps the smooth filled area. Pass <code>"hv"</code> or <code>"vh"</code> and the filled top edge steps like <code>geom-step()</code>, which suits counts and other quantities that hold flat between observations.</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="annotated-cell-3" data-filename="typst" style="background: #f1f3f5;"><pre class="sourceCode typst cw-auto code-annotation-code code-with-copy code-annotated"><code class="sourceCode typst"><span id="annotated-cell-3-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">series</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-3-2">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>x: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> y: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>x: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> y: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>x: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> y: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>x: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> y: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">6</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>x: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> y: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-3-3"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-3-4"></span>
<span id="annotated-cell-3-5"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>plot<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-3-6">  data<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> series<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-3-7">  mapping<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> aes<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"x"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"y"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-3" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-3-8" class="code-annotation-target">  layers<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>geom-area<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>direction<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"hv"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> alpha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),),</span></span>
<span id="annotated-cell-3-9">  labels<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> labels<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>title<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"A Stepped Area"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"x"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"y"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-3-10">  theme<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> theme-minimal<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(),</span></span>
<span id="annotated-cell-3-11">  width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">12cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-3-12">  height<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">7cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-3-13"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
<div class="cell-annotation">
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-3" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-3" data-code-lines="8" data-code-annotation="1"><code>direction: "hv"</code> steps the top edge; <code>"vh"</code> steps it the other way round, and the default <code>none</code> keeps the smooth area.</span>
</dd>
</dl>
</div>
<div>
<div class="light-content">
<div style="text-align: center;">
<p><a href="../../posts/2026-06-22-gribouille-0-4/./assets/typst-render/stepped-area-light.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-6" title="A filled area chart whose top edge rises and falls in right-angled steps rather than straight diagonals, over x values 0 to 4."><img src="https://mickael.canouil.fr/posts/2026-06-22-gribouille-0-4/assets/typst-render/stepped-area-light.svg" class="img-fluid hero-art" alt="A filled area chart whose top edge rises and falls in right-angled steps rather than straight diagonals, over x values 0 to 4."></a></p>
</div>
</div>
<div class="dark-content">
<div style="text-align: center;">
<p><a href="../../posts/2026-06-22-gribouille-0-4/./assets/typst-render/stepped-area-dark.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-7" title="A filled area chart whose top edge rises and falls in right-angled steps rather than straight diagonals, over x values 0 to 4."><img src="https://mickael.canouil.fr/posts/2026-06-22-gribouille-0-4/assets/typst-render/stepped-area-dark.svg" class="img-fluid hero-art" alt="A filled area chart whose top edge rises and falls in right-angled steps rather than straight diagonals, over x values 0 to 4."></a></p>
</div>
</div>
</div>
</div>
</section>
<section id="a-grid-cascade-and-minor-gridlines" class="level2" data-number="5">
<h2 data-number="5" class="anchored" data-anchor-id="a-grid-cascade-and-minor-gridlines"><span class="header-section-number">5</span> A grid cascade and minor gridlines</h2>
<p>Continuous axes now draw minor gridlines by default, halfway between the major lines. The grid also splits into a cascade you can style one piece at a time: <code>panel-grid</code> feeds <code>panel-grid-major</code> and <code>panel-grid-minor</code>, and each of those feeds an <code>-x</code> and a <code>-y</code> variant. So you can thicken the major Y lines and blank only the minor X lines without touching anything else.</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="annotated-cell-4" data-filename="typst" style="background: #f1f3f5;"><pre class="sourceCode typst cw-auto code-annotation-code code-with-copy code-annotated"><code class="sourceCode typst"><span id="annotated-cell-4-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>plot<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-4-2">  data<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> penguins<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-4-3">  mapping<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> aes<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"flipper-len"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"body-mass"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"species"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-4-4">  layers<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>geom-point<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> alpha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.7</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),),</span></span>
<span id="annotated-cell-4-5">  labels<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> labels<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-4-6">    title<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Major and Minor, Styled Apart"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-4-7">    x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Flipper Length (mm)"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-4-8">    y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Body Mass (g)"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-4-9">    fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Species"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-4-10">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-4-11">  theme<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> theme-minimal<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-4" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-4-12" class="code-annotation-target">    panel-grid-major-y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> element-line<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>stroke<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-4" data-target-annotation="2" onclick="event.preventDefault();">2</a><span id="annotated-cell-4-13" class="code-annotation-target">    panel-grid-minor-x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> element-blank<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(),</span></span>
<span id="annotated-cell-4-14">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-4-15">  width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">12cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-4-16">  height<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">8cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-4-17"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
<div class="cell-annotation">
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-4" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-4" data-code-lines="12" data-code-annotation="1">The major horizontal lines are thickened on their own. <code>scale-*-continuous</code> also accepts <code>minor-breaks</code> and <code>n-minor</code> to place the minor lines.</span>
</dd>
<dt data-target-cell="annotated-cell-4" data-target-annotation="2">2</dt>
<dd>
<span data-code-cell="annotated-cell-4" data-code-lines="13" data-code-annotation="2">Only the minor vertical lines are blanked; the minor horizontal lines stay on by default.</span>
</dd>
</dl>
</div>
<div>
<div class="light-content">
<div style="text-align: center;">
<p><a href="../../posts/2026-06-22-gribouille-0-4/./assets/typst-render/grid-cascade-light.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-8" title="Penguin scatter of body mass against flipper length coloured by species. The horizontal major gridlines are noticeably thicker, the minor vertical gridlines are absent, and faint minor horizontal gridlines sit between the major ones."><img src="https://mickael.canouil.fr/posts/2026-06-22-gribouille-0-4/assets/typst-render/grid-cascade-light.svg" class="img-fluid hero-art" alt="Penguin scatter of body mass against flipper length coloured by species. The horizontal major gridlines are noticeably thicker, the minor vertical gridlines are absent, and faint minor horizontal gridlines sit between the major ones."></a></p>
</div>
</div>
<div class="dark-content">
<div style="text-align: center;">
<p><a href="../../posts/2026-06-22-gribouille-0-4/./assets/typst-render/grid-cascade-dark.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-9" title="Penguin scatter of body mass against flipper length coloured by species. The horizontal major gridlines are noticeably thicker, the minor vertical gridlines are absent, and faint minor horizontal gridlines sit between the major ones."><img src="https://mickael.canouil.fr/posts/2026-06-22-gribouille-0-4/assets/typst-render/grid-cascade-dark.svg" class="img-fluid hero-art" alt="Penguin scatter of body mass against flipper length coloured by species. The horizontal major gridlines are noticeably thicker, the minor vertical gridlines are absent, and faint minor horizontal gridlines sit between the major ones."></a></p>
</div>
</div>
</div>
</div>
</section>
<section id="filter-pin-and-auto-bounds" class="level2" data-number="6">
<h2 data-number="6" class="anchored" data-anchor-id="filter-pin-and-auto-bounds"><span class="header-section-number">6</span> Filter, pin, and auto bounds</h2>
<p>A few smaller changes make everyday plotting shorter. The bundled datasets are plain row-store arrays now, so you can <code>filter</code> and <code>map</code> them in place before they reach <code>plot()</code>. Any aesthetic can be pinned as a constant straight on a geom. And a scale <code>limits</code> or <code>expand</code> accepts <code>auto</code> on one side, keeping the trained value for the other.</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="annotated-cell-5" data-filename="typst" style="background: #f1f3f5;"><pre class="sourceCode typst cw-auto code-annotation-code code-with-copy code-annotated"><code class="sourceCode typst"><span id="annotated-cell-5-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>plot<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-5" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-5-2" class="code-annotation-target">  data<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> penguins<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>filter<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>row <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=&gt;</span> row<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>species <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Gentoo"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-5-3">  mapping<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> aes<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"flipper-len"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"body-mass"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-5" data-target-annotation="2" onclick="event.preventDefault();">2</a><span id="annotated-cell-5-4" class="code-annotation-target">  layers<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>geom-point<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> rgb<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#0072B2"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span> alpha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.6</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),),</span></span>
<span id="annotated-cell-5-5">  scales<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-5" data-target-annotation="3" onclick="event.preventDefault();">3</a><span id="annotated-cell-5-6" class="code-annotation-target">    scale-y-continuous<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>limits<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">auto</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">6500</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)),</span></span>
<span id="annotated-cell-5-7">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-5-8">  labels<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> labels<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-5-9">    title<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Filter, Pin, and Auto Bounds"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-5-10">    x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Flipper Length (mm)"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-5-11">    y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Body Mass (g)"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-5-12">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-5-13">  theme<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> theme-minimal<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(),</span></span>
<span id="annotated-cell-5-14">  width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">12cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-5-15">  height<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">8cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-5-16"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
<div class="cell-annotation">
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-5" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-5" data-code-lines="2" data-code-annotation="1"><code>penguins</code> is a row-store array, so <code>.filter(...)</code> and <code>.map(...)</code> work before <code>plot()</code> ever sees it.</span>
</dd>
<dt data-target-cell="annotated-cell-5" data-target-annotation="2">2</dt>
<dd>
<span data-code-cell="annotated-cell-5" data-code-lines="4" data-code-annotation="2"><code>colour</code> is pinned as a constant on the geom; any aesthetic key can be set this way.</span>
</dd>
<dt data-target-cell="annotated-cell-5" data-target-annotation="3">3</dt>
<dd>
<span data-code-cell="annotated-cell-5" data-code-lines="6" data-code-annotation="3"><code>limits: (auto, 6500)</code> fixes the upper bound and leaves the lower one trained on the data.</span>
</dd>
</dl>
</div>
<div>
<div class="light-content">
<div style="text-align: center;">
<p><a href="../../posts/2026-06-22-gribouille-0-4/./assets/typst-render/filter-pin-auto-light.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-10" title="Scatter of body mass against flipper length for Gentoo penguins only, every point a fixed blue, with the y-axis capped at 6500 grams while its lower bound is left to the data."><img src="https://mickael.canouil.fr/posts/2026-06-22-gribouille-0-4/assets/typst-render/filter-pin-auto-light.svg" class="img-fluid hero-art" alt="Scatter of body mass against flipper length for Gentoo penguins only, every point a fixed blue, with the y-axis capped at 6500 grams while its lower bound is left to the data."></a></p>
</div>
</div>
<div class="dark-content">
<div style="text-align: center;">
<p><a href="../../posts/2026-06-22-gribouille-0-4/./assets/typst-render/filter-pin-auto-dark.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-11" title="Scatter of body mass against flipper length for Gentoo penguins only, every point a fixed blue, with the y-axis capped at 6500 grams while its lower bound is left to the data."><img src="https://mickael.canouil.fr/posts/2026-06-22-gribouille-0-4/assets/typst-render/filter-pin-auto-dark.svg" class="img-fluid hero-art" alt="Scatter of body mass against flipper length for Gentoo penguins only, every point a fixed blue, with the y-axis capped at 6500 grams while its lower bound is left to the data."></a></p>
</div>
</div>
</div>
</div>
<p>There is more in the same vein. <code>element-text()</code> and <code>element-typst()</code> honour <code>angle</code> on axis titles, strip text, and legend labels now, not only tick labels and the plot title. A single <code>legend-position</code> theme entry sets a global side for every legend. And <code>guide-legend(key-size:)</code> resizes the discrete legend key glyphs, with the row spacing growing to match.</p>
</section>
<section id="an-advanced-example-a-star-history-chart" class="level2" data-number="7">
<h2 data-number="7" class="anchored" data-anchor-id="an-advanced-example-a-star-history-chart"><span class="header-section-number">7</span> An advanced example: a star-history chart</h2>
<p>To pull these together, here is a real chart: Gribouille’s own GitHub star history, drawn with Gribouille. The data comes from two small scripts shipped alongside this post under <code>assets/star-history/</code>. <a href="assets/star-history/star-history.sh"><code>star-history.sh</code></a> walks the stargazers API into a daily-cumulative <code>date,stars</code> CSV, and <a href="assets/star-history/release-history.sh"><code>release-history.sh</code></a> lists the tagged releases. The plot then reads both CSVs straight from Typst.</p>
<p>It leans on a good share of this release:</p>
<ul>
<li>Temporal date strings everywhere: <code>scale-x-date</code> breaks on the first of each month, a gold <code>geom-vline</code> at the public-release date, and dashed <code>geom-vline</code> markers at each tagged release, all from plain ISO strings.</li>
<li>Markers from a character: every daily count is a <code>sym.star</code> glyph, and the spike is a hotter amber star.</li>
<li>A stepped fill: <code>geom-area(direction: "hv")</code> lays a faint glow under the cumulative <code>geom-step</code> trail.</li>
<li>A tuned grid: the <code>panel-grid</code> cascade blanks the major X lines and the minor lines, keeping only faint major Y lines.</li>
<li>A boolean <code>coord-cartesian(clip: false)</code> so the count annotation can float above the panel.</li>
<li><code>labels()</code> for the title, subtitle, and caption, and the bundled-array <code>.filter</code>/<code>.map</code> to lift and halo the star glyphs.</li>
</ul>
<div class="cell">
<details class="code-fold">
<summary>Show the full Typst source</summary>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb1" data-filename="typst" style="background: #f1f3f5;"><pre class="sourceCode typst cw-auto code-with-copy"><code class="sourceCode typst"><span id="cb1-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// Loads a daily-cumulative star count and release markers from CSV and plots</span></span>
<span id="cb1-2"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// them as a midnight-sky step trail: a luminous staircase whose daily counts</span></span>
<span id="cb1-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// read like a constellation, with the final spike glowing brightest.</span></span>
<span id="cb1-4"></span>
<span id="cb1-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// Named palette: colours reused across several layers. One-off shades (the panel</span></span>
<span id="cb1-6"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// gradient, point rim, release tint, transparent bloom ink) stay inline at use.</span></span>
<span id="cb1-7"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">palette</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb1-8">  sky-deep: rgb<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#0a132e"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span> <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// plot margin: a shade darker, to gather the figure</span></span>
<span id="cb1-9">  trail: rgb<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#f4d58d"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span> <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// cumulative curve: luminous starlight gold</span></span>
<span id="cb1-10">  star: rgb<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#ffe7a3"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span> <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// daily-count points: bright warm star</span></span>
<span id="cb1-11">  peak: rgb<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#ff8c42"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span> <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// the spike: hotter amber, separates from the gold</span></span>
<span id="cb1-12">  ink: rgb<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#e8ecf5"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span> <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// foreground text: soft starlight white</span></span>
<span id="cb1-13">  muted: rgb<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#b3bdd6"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span> <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// secondary text and ticks</span></span>
<span id="cb1-14">  cloud: rgb<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#28406f"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span> <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// annotation boxes: moonlit cloud, lighter than the sky</span></span>
<span id="cb1-15">  cloud-edge: rgb<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#6b7cb0"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span> <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// faint rim catching the moonlight</span></span>
<span id="cb1-16"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb1-17"></span>
<span id="cb1-18"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">raw-stars</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> csv<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"/assets/star-history/star-history.csv"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> row-type<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> dictionary<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">).</span>map<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>row <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=&gt;</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb1-19">  date: row<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>date<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-20">  stars: float<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>row<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>stars<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb1-21"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">))</span></span>
<span id="cb1-22"></span>
<span id="cb1-23"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// Snap the leading 0-star baseline to the first of the creation month so the</span></span>
<span id="cb1-24"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// flat segment starts on the first month tick rather than mid-month.</span></span>
<span id="cb1-25"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">stars</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb1-26">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>..raw-stars<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>first<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(),</span> date: raw-stars<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>first<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">().</span>date<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>slice<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">7</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"-01"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb1-27">  ..raw-stars<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>slice<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb1-28"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb1-29"></span>
<span id="cb1-30"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// The fan geometry interpolates between numeric x positions, so the head and tail</span></span>
<span id="cb1-31"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// dates are converted to days since the 2000-01-01 epoch scale-x-date trains against;</span></span>
<span id="cb1-32"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// the count annotations pinned to the fan apex reuse that numeric head-x as well.</span></span>
<span id="cb1-33"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// Vline intercepts, scale breaks, and narrative annotation x values take ISO strings</span></span>
<span id="cb1-34"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// directly.</span></span>
<span id="cb1-35"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">epoch</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> datetime<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>year<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2000</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> month<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> day<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb1-36"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">to-days</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>iso<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb1-37">  datetime<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb1-38">    year<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> int<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>iso<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>slice<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)),</span></span>
<span id="cb1-39">    month<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> int<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>iso<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>slice<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">7</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)),</span></span>
<span id="cb1-40">    day<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> int<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>iso<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>slice<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)),</span></span>
<span id="cb1-41">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb1-42">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> epoch</span>
<span id="cb1-43"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">).</span>days<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">()</span></span>
<span id="cb1-44"></span>
<span id="cb1-45"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">star-max</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> stars<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>map<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>row <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=&gt;</span> row<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>stars<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">).</span>fold<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> calc<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>max<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb1-46"></span>
<span id="cb1-47"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// One releases dataset (minor/major only, patch dropped): x in epoch days, y just</span></span>
<span id="cb1-48"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// above the x-axis baseline so the tiny tags sit clear of the trail and the peak.</span></span>
<span id="cb1-49"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">releases</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb1-50">  csv<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"/assets/star-history/release-history.csv"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> row-type<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> dictionary<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb1-51">    .filter<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>row <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=&gt;</span> row<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>tag<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>split<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"."</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">).</span>last<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">()</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"0"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb1-52">    .map<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>row <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=&gt;</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb1-53">      x: row<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>date<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-54">      y: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">6</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-55">      tag: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"v"</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> row<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>tag<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-56">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">))</span></span>
<span id="cb1-57"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb1-58"></span>
<span id="cb1-59"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// The 2026-06-19 row carries the spike; it drives the peak marker and its labels.</span></span>
<span id="cb1-60"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">peak-idx</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> stars<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>position<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>row <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=&gt;</span> row<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>date <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-06-19"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb1-61"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">peak</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> stars<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>at<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>peak-idx<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb1-62"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">peak-jump</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> int<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>peak<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>stars <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> stars<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>at<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>peak-idx <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">).</span>stars<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb1-63"></span>
<span id="cb1-64"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// Shooting-star fan: one solid gold band tapering from a point at the first date</span></span>
<span id="cb1-65"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// (the tail tip) up to the star at the head, where it spans the star's height.</span></span>
<span id="cb1-66"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// Both edges are arcs sharing the tip; the top edge meets the star's top point and</span></span>
<span id="cb1-67"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// the lower edge its lower-left peak.</span></span>
<span id="cb1-68"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">head-x</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> to-days<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>peak<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>date<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb1-69"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">head-y</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> peak<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>stars</span>
<span id="cb1-70"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">tail-x</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> to-days<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>stars<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>first<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">().</span>date<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// first date: where the tail tip lands</span></span>
<span id="cb1-71"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">fan-sag</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">30</span> <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// stars the edges drop from the head down to the tip</span></span>
<span id="cb1-72"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">fan-arc</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.7</span> <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// &gt;1 keeps the edges flat at the head, steep toward the tip</span></span>
<span id="cb1-73"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">head-top</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> head-y + 2.5 <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// top edge meets the star's top point</span></span>
<span id="cb1-74"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">head-bot</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> head-y - 3.5 <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// lower edge meets the star's lower-left peak</span></span>
<span id="cb1-75"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">tip-y</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span> <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// y of the tail tip at the first date</span></span>
<span id="cb1-76"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// An arc through (tail-x, tip-y) at the tip and (head-x, hy) at the head.</span></span>
<span id="cb1-77"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">arc-y</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> hy<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb1-78">  <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">t</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> tail-x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> / <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>head-x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> tail-x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb1-79">  hy <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> fan-sag * calc<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>pow<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> t<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> fan-arc<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>tip-y <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> hy <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> fan-sag<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> * <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> t<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb1-80"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb1-81"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">fan-band</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>n<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> range<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>n <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">).</span>map<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=&gt;</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb1-82">  <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">x</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> tail-x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>head-x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> tail-x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> * i / n</span>
<span id="cb1-83">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>x: x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> ymax: arc-y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> head-top<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span> ymin: arc-y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> head-bot<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">))</span></span>
<span id="cb1-84"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">})</span></span>
<span id="cb1-85"></span>
<span id="cb1-86"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// A cross-thickness gradient cannot ride a single ribbon (a Typst gradient maps to</span></span>
<span id="cb1-87"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// the whole bounding box, not the band's local thickness), so the fan is sliced</span></span>
<span id="cb1-88"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// into thin sub-bands stacked from the lower to the upper edge, each filled by</span></span>
<span id="cb1-89"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// sampling a light -&gt; peak -&gt; light gradient: the amber peak colour runs as a</span></span>
<span id="cb1-90"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// bright line down the middle and lightens toward both edges.</span></span>
<span id="cb1-91"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">fan-slices</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">32</span></span>
<span id="cb1-92"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">fan-grad</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> gradient<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>linear<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb1-93">  palette<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>star<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>transparentize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">50%</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb1-94">  palette<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>peak<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>transparentize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">50%</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb1-95">  palette<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>star<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>transparentize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">50%</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb1-96"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb1-97"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">fan-layers</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb1-98">  <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">rows</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> fan-band<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">40</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb1-99">  range<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>fan-slices<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">).</span>map<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>k <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=&gt;</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb1-100">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">flo</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> k / fan-slices</span>
<span id="cb1-101">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">fhi</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>k <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> / fan-slices</span>
<span id="cb1-102">    geom-ribbon<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb1-103">      data<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> rows<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>map<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>r <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=&gt;</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb1-104">        x: r<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-105">        ymin: r<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>ymin <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>r<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>ymax <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> r<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>ymin<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> * flo<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-106">        ymax: r<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>ymin <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>r<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>ymax <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> r<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>ymin<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> * fhi<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-107">      <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)),</span></span>
<span id="cb1-108">      mapping<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> aes<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"x"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> ymin<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"ymin"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> ymax<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"ymax"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb1-109">      inherit-aes<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">false</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-110">      fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> fan-grad<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>sample<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">((</span>flo <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> fhi<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> / <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span> * <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">100%</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb1-111">      stroke<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">none</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-112">      alpha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.95</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-113">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb1-114">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">})</span></span>
<span id="cb1-115"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb1-116"></span>
<span id="cb1-117"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">y-step</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">25</span></span>
<span id="cb1-118"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">y-breaks</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> range<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> calc<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>floor<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>star-max / y-step<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">).</span>map<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=&gt;</span> i * y-step<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb1-119"></span>
<span id="cb1-120"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// One x break per month (first of the month) so the short-month label never</span></span>
<span id="cb1-121"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// repeats, unlike the auto breaks that fall mid-month within a single month.</span></span>
<span id="cb1-122"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">month-firsts</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> stars<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>map<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>row <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=&gt;</span> row<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>date<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>slice<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">7</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"-01"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">).</span>dedup<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">()</span></span>
<span id="cb1-123"></span>
<span id="cb1-124"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>plot<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb1-125">  data<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> stars<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-126">  mapping<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> aes<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"date"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"stars"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb1-127">  layers<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb1-128">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// Faint luminous glow beneath the trail.</span></span>
<span id="cb1-129">    geom-area<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb1-130">      stat<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"identity"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-131">      direction<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"hv"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-132">      fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> palette<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>trail<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-133">      alpha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.1</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-134">      stroke<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">none</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-135">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb1-136">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// Releases recede into the sky: thin dashed verticals plus tiny tags set in</span></span>
<span id="cb1-137">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// the empty upper-left, so the trail stays the hero.</span></span>
<span id="cb1-138">    geom-vline<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb1-139">      data<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> releases<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-140">      mapping<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> aes<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>xintercept<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"x"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb1-141">      colour<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> rgb<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#6677aa"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb1-142">      stroke<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.4pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-143">      linetype<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dashed"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-144">      alpha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.45</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-145">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb1-146">    geom-label<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb1-147">      data<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> releases<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-148">      mapping<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> aes<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"x"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"y"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> label<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"tag"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb1-149">      inherit-aes<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">false</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-150">      colour<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> palette<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>muted<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-151">      fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> palette<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>cloud<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>transparentize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">35%</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb1-152">      stroke<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.25pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-153">      size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">8pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-154">      inset<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">3pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-155">      radius<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">5pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-156">      anchor<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"west"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-157">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb1-158">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// The day the repository went public: a warm gold marker, distinct from the</span></span>
<span id="cb1-159">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// cool release lines.</span></span>
<span id="cb1-160">    annotate<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb1-161">      <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"vline"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-162">      xintercept<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-05-17"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-163">      colour<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> palette<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>star<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-164">      stroke<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-165">      linetype<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dashed"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-166">      alpha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.4</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-167">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb1-168">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// The cumulative trail.</span></span>
<span id="cb1-169">    geom-step<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>stroke<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.4pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> colour<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> palette<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>trail<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb1-170">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// Each daily count is a star glyph: a soft halo under a bright sparkle. The</span></span>
<span id="cb1-171">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// glyph's optical centre sits low, so the larger glow is lifted to stay</span></span>
<span id="cb1-172">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// concentric with the bright star (geom-point has no nudge).</span></span>
<span id="cb1-173">    geom-point<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb1-174">      data<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> d <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=&gt;</span> d</span>
<span id="cb1-175">        .filter<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>row <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=&gt;</span> row<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>stars <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">!=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb1-176">        .map<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>row <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=&gt;</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb1-177">          ..row<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-178">          stars: row<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>stars <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.25</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-179">        <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)),</span></span>
<span id="cb1-180">      shape<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> sym<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>star<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-181">      size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">24pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-182">      fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> palette<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>star<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-183">      alpha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.16</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-184">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb1-185">    geom-point<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb1-186">      data<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> d <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=&gt;</span> d<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>filter<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>row <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=&gt;</span> row<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>stars <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">!=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb1-187">      shape<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> sym<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>star<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-188">      size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">14pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-189">      fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> palette<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>star<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-190">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb1-191">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// Shooting-star fan sweeping down-left from behind the head: sliced sub-bands</span></span>
<span id="cb1-192">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// give it an amber centre-line lightening to the edges; drawn before the peak</span></span>
<span id="cb1-193">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// star so the amber head sits over its apex.</span></span>
<span id="cb1-194">    ..fan-layers<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-195">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// The spike glows brightest: its own (lifted) halo, then a hot amber star.</span></span>
<span id="cb1-196">    geom-point<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb1-197">      data<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">((</span>..peak<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> stars: peak<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>stars <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.25</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),),</span></span>
<span id="cb1-198">      shape<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> sym<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>star<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-199">      size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">46pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-200">      fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> palette<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>peak<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-201">      alpha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.22</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-202">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb1-203">    geom-point<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb1-204">      data<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">((</span>..peak<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> stars: peak<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>stars <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.25</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),),</span></span>
<span id="cb1-205">      shape<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> sym<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>star<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-206">      size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">26pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-207">      fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> palette<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>peak<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-208">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb1-209">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// The count floats in clear sky just above the head, clear of the gold fan.</span></span>
<span id="cb1-210">    annotate<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb1-211">      <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"typst"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-212">      x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> head-x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-213">      y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> head-y <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-214">      label<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>str<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>int<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>peak<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>stars<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">))],</span></span>
<span id="cb1-215">      colour<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> palette<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>peak<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-216">      size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">13pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-217">      anchor<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"south"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-218">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb1-219">    annotate<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb1-220">      <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"typst"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-221">      x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> head-x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">3.5</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-222">      y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">115</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-223">      label<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>align<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>right<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)[</span></span>
<span id="cb1-224">        +<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>str<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>peak-jump<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> in a day <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb1-225">        <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>text<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.75em</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)[</span>Hacker News Front Page<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span></span>
<span id="cb1-226">      <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]],</span></span>
<span id="cb1-227">      colour<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> palette<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>peak<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-228">      size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">13pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-229">      anchor<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"east"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-230">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb1-231">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// Narrative beats: the private build over the flat run, and the public day.</span></span>
<span id="cb1-232">    annotate<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb1-233">      <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"label"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-234">      x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-23"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-235">      y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">11.5</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-236">      label<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Quietly built in private"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-237">      colour<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> palette<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>ink<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-238">      fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> palette<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>cloud<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>transparentize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">20%</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb1-239">      stroke<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.6pt</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> palette<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>cloud-edge<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>transparentize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">30%</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb1-240">      size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">12pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-241">      inset<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">6pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-242">      radius<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">10pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-243">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb1-244">    annotate<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb1-245">      <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"label"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-246">      x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-05-17"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-247">      y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">37.5</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-248">      label<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>align<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>center<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)[</span>Made public <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">\ </span>17<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>super<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span>th<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span> of May<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]],</span></span>
<span id="cb1-249">      colour<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> palette<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>star<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-250">      fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> palette<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>cloud<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>transparentize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">20%</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb1-251">      stroke<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.6pt</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> palette<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>cloud-edge<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>transparentize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">30%</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb1-252">      size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">10pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-253">      inset<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">5pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-254">      radius<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">10pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-255">      anchor<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"east"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-256">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb1-257">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb1-258">  scales<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb1-259">    scale-x-date<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb1-260">      breaks<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> month-firsts<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-261">      date-format<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"[month repr:long] [year]"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-262">      expand<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0%</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0%</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb1-263">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb1-264">    scale-y-continuous<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb1-265">      breaks<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> y-breaks<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-266">      labels<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> y <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=&gt;</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>box<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>baseline<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.4em</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)[</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>str<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>int<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">))]</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>text<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb1-267">          size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2em</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-268">        <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)[</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>sym<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>star<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]],</span></span>
<span id="cb1-269">      expand<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0%</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">6%</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb1-270">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb1-271">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb1-272">  coord<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> coord-cartesian<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>clip<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">false</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb1-273">  labels<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> labels<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb1-274">    title<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span></span>
<span id="cb1-275">      <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>text<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> palette<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>trail<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> weight<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"bold"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)[</span>Gribouille<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span>, a <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>text<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> palette<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>peak<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> weight<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"bold"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)[</span>Rising Star<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span></span>
<span id="cb1-276">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">],</span></span>
<span id="cb1-277">    subtitle<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span></span>
<span id="cb1-278">      <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#set</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">par</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>justify<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">true</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb1-279">      Gribouille brings the <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>text<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> palette<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>ink<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)[</span>grammar of graphics<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span>, the idea behind <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">`ggplot2`</span> and <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">`plotnine`</span>, to Typst: layered geoms, scales, and themes for publication-quality charts written in pure markup. Built quietly in private through April, it went <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>text<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> palette<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>star<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)[</span>public on the 17<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>super<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span>th<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span> of May 2026<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span> and shipped <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>box<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb1-280">        fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> palette<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>cloud<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>transparentize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">35%</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb1-281">        stroke<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.25pt</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> palette<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>cloud-edge<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>transparentize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">30%</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb1-282">        inset<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>x: <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">3pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> y: <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb1-283">        outset<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>y: <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb1-284">        radius<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">4pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-285">      <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)[</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>text<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.82em</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> palette<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>muted<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)[</span>v0.1.0<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]]</span> three days later. Each release drew a bigger crowd, and a single day in June carried it past <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>text<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> palette<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>peak<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)[</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>str<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>int<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>peak<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>stars<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">))</span> stars<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">].</span></span>
<span id="cb1-286">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">],</span></span>
<span id="cb1-287">    x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">none</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-288">    y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">none</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-289">    caption<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span></span>
<span id="cb1-290">      This very chart was drawn with Gribouille (<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>link<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"https://m.canouil.dev/gribouille"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)[</span>m.canouil.dev/gribouille<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span>). <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb1-291">      Author: <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>link<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"https://mickael.canouil.fr"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)[</span>mickael.canouil.fr<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span> | Data source: GitHub API</span>
<span id="cb1-292">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">],</span></span>
<span id="cb1-293">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb1-294">  theme<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> theme-minimal<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb1-295">    ink<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> palette<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>ink<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-296">    paper<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> palette<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>sky-deep<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-297">    text<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> element-text<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>font<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Libertinus Serif"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"DejaVu Sans Mono"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)),</span></span>
<span id="cb1-298">    tick-length<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.12cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-299">    panel-background<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> element-rect<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> gradient<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>linear<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb1-300">      <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// Hold the dark top longer so it blends into the sky-deep frame, then</span></span>
<span id="cb1-301">      <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// lighten only toward the lower half of the panel.</span></span>
<span id="cb1-302">      <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>palette<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>sky-deep<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0%</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb1-303">      <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>rgb<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#0a1330"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">35%</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb1-304">      <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>rgb<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#1c2f5e"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">100%</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb1-305">      dir<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> ttb<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-306">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)),</span></span>
<span id="cb1-307">    panel-grid-major-x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> element-blank<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(),</span></span>
<span id="cb1-308">    panel-grid-minor<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> element-blank<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(),</span></span>
<span id="cb1-309">    panel-grid-major-y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> element-line<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>colour<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> palette<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>ink<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>transparentize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">88%</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)),</span></span>
<span id="cb1-310">    axis-ticks<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> element-line<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>colour<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> palette<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>muted<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb1-311">    axis-text<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> element-text<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>colour<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> palette<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>muted<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">10pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb1-312">    axis-title<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> element-text<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>colour<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> palette<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>ink<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">12pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb1-313">    axis-title-y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> element-text<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>margin<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> margin<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>right<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">14pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)),</span></span>
<span id="cb1-314">    plot-title<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> element-text<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb1-315">      font<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Didot"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-316">      colour<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> palette<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>ink<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-317">      size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">25.5pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-318">      weight<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"regular"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-319">      margin<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> margin<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>top<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">6pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> bottom<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">20pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb1-320">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb1-321">    plot-subtitle<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> element-text<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb1-322">      colour<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> palette<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>muted<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-323">      size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">13.3pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-324">      margin<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> margin<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>bottom<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">24pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb1-325">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb1-326">    plot-caption<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> element-text<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb1-327">      colour<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> palette<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>muted<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-328">      size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">9.5pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-329">      margin<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> margin<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>top<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">16pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb1-330">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb1-331">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// Outer frame: pad the whole figure so it breathes inside the page.</span></span>
<span id="cb1-332">    plot-background<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> element-rect<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb1-333">      fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> palette<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>sky-deep<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-334">      inset<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> margin<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>top<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">22pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> right<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">22pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> bottom<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">22pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> left<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">22pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb1-335">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb1-336">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb1-337">  width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">auto</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-338">  height<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">auto</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-339">  alt<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Midnight-sky line chart of Gribouille's cumulative GitHub stars per day from April to June 2026, titled </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">\"</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">Gribouille, a Rising Star</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">\"</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">. A luminous gold step trail rises from zero across a long flat run labelled </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">\"</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">Quietly built in private</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">\"</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">, lifts at a gold dashed marker labelled </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">\"</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">Made public 17th of May</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">\"</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">, then climbs through dashed release markers (v0.1.0 onward) to a final amber spike of "</span></span>
<span id="cb1-340">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> str<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>int<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>peak<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>stars<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">))</span></span>
<span id="cb1-341">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">" stars, annotated </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">\"</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">+"</span></span>
<span id="cb1-342">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> str<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>peak-jump<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb1-343">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">" in a day</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">\"</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">. The y axis counts stars in steps of "</span></span>
<span id="cb1-344">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> str<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>y-step<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb1-345">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"; the x axis spans months."</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-346"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span></code></pre></div></div>
</details>
<div>
<div class="light-content">
<div style="text-align: center;">
<p><a href="../../posts/2026-06-22-gribouille-0-4/./assets/typst-render/star-history-light.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-12" title="Midnight-sky line chart of Gribouille's cumulative GitHub stars per day from April to June 2026, titled 'Gribouille, a Rising Star'. A luminous gold step trail rises from zero across a long flat run labelled 'Quietly built in private', lifts at a gold dashed marker labelled 'Made public 17th of May', then climbs through dashed release markers to a final amber spike annotated with a one-day jump."><img src="https://mickael.canouil.fr/posts/2026-06-22-gribouille-0-4/assets/typst-render/star-history-light.svg" class="img-fluid hero-art" alt="Midnight-sky line chart of Gribouille's cumulative GitHub stars per day from April to June 2026, titled 'Gribouille, a Rising Star'. A luminous gold step trail rises from zero across a long flat run labelled 'Quietly built in private', lifts at a gold dashed marker labelled 'Made public 17th of May', then climbs through dashed release markers to a final amber spike annotated with a one-day jump."></a></p>
</div>
</div>
<div class="dark-content">
<div style="text-align: center;">
<p><a href="../../posts/2026-06-22-gribouille-0-4/./assets/typst-render/star-history-dark.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-13" title="Midnight-sky line chart of Gribouille's cumulative GitHub stars per day from April to June 2026, titled 'Gribouille, a Rising Star'. A luminous gold step trail rises from zero across a long flat run labelled 'Quietly built in private', lifts at a gold dashed marker labelled 'Made public 17th of May', then climbs through dashed release markers to a final amber spike annotated with a one-day jump."><img src="https://mickael.canouil.fr/posts/2026-06-22-gribouille-0-4/assets/typst-render/star-history-dark.svg" class="img-fluid hero-art" alt="Midnight-sky line chart of Gribouille's cumulative GitHub stars per day from April to June 2026, titled 'Gribouille, a Rising Star'. A luminous gold step trail rises from zero across a long flat run labelled 'Quietly built in private', lifts at a gold dashed marker labelled 'Made public 17th of May', then climbs through dashed release markers to a final amber spike annotated with a one-day jump."></a></p>
</div>
</div>
</div>
</div>
<p>The full, unedited source is in the repository under <a href="https://github.com/mcanouil/gribouille/tree/main/.github/star-history"><code>.github/star-history/</code></a>.</p>
</section>
<section id="theme-position-and-scale-fixes" class="level2" data-number="8">
<h2 data-number="8" class="anchored" data-anchor-id="theme-position-and-scale-fixes"><span class="header-section-number">8</span> Theme, Position, and Scale Fixes</h2>
<p>As with every release, a good share of the work is fixes rather than features.</p>
<p>The theme cascade is the largest of them. Setting the base <code>line</code> or <code>rect</code> stroke in <code>theme()</code> now reaches every line and rect surface: the panel grid, axis lines, ticks, and legend marks. Setting the base <code>text</code> size cascades to every text surface in the same way. On top of that, <code>element-line</code>, <code>element-rect</code>, and <code>element-text</code> accept a ratio such as <code>80%</code> to scale a single surface against its parent, while an absolute length still pins one outright.</p>
<p>A second group lines up marks and positions.</p>
<ul>
<li><code>position: "dodge"</code> now shifts text, labels, points, lines, paths, steps, point ranges, and line ranges side by side, matching the dodged bars instead of staying on the category centre.</li>
<li>The scalar and Typst-length <code>nudge-x</code>/<code>nudge-y</code> values apply to <code>geom-text</code>, <code>geom-label</code>, and <code>geom-typst</code>, which used to be a silent no-op.</li>
</ul>
<p>A third group cleans up scales and presets.</p>
<ul>
<li>Explicit continuous <code>breaks</code> expand the axis range so the requested ticks stay visible, unless a side is pinned by explicit <code>limits</code>.</li>
<li><code>theme-grey()</code> now matches ggplot2’s <code>theme_grey()</code>, with white gridlines and no axis lines.</li>
</ul>
<div class="highlight">
<p><strong>The theme cascade got more honest:</strong> set the base line, rect, or text once and every surface inherits it, with ratios to scale a single surface against its parent.</p>
</div>
</section>
<section id="wrap-up" class="level2" data-number="9">
<h2 data-number="9" class="anchored" data-anchor-id="wrap-up"><span class="header-section-number">9</span> Wrap-up</h2>
<div class="highlight">
<p><strong>Dates as strings, any glyph as a marker, and a grid you can tune major and minor apart.</strong></p>
</div>
<p>Next on the list is more geoms and more worked examples. If you run into something unexpected, the issue tracker is the right place for it.</p>
<ul>
<li>Gribouille
<ul>
<li>Repository: <a href="https://github.com/mcanouil/gribouille" class="uri">https://github.com/mcanouil/gribouille</a>.</li>
<li>Documentation: <a href="https://m.canouil.dev/gribouille" class="uri">https://m.canouil.dev/gribouille</a>.</li>
<li>Typst Universe: <a href="https://typst.app/universe/package/gribouille" class="uri">https://typst.app/universe/package/gribouille</a>.</li>
</ul></li>
<li>Typst Render
<ul>
<li>Repository: <a href="https://github.com/mcanouil/quarto-typst-render" class="uri">https://github.com/mcanouil/quarto-typst-render</a>.</li>
<li>Documentation: <a href="https://m.canouil.dev/quarto-typst-render" class="uri">https://m.canouil.dev/quarto-typst-render</a>.</li>
</ul></li>
</ul>
<div class="callout callout-style-default callout-tip callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Tip</span>A note on contributions
</div>
</div>
<div class="callout-body-container callout-body">
<p>Gribouille is an unfunded spare-time project, and the API is still settling. Bug reports and ideas are very welcome on the issue tracker. Pull requests are not being accepted for now, for the reasons set out in the <a href="../../posts/2026-05-20-gribouille-grammar-of-graphics-for-typst/index.html">launch post</a>. Thanks in advance for your patience.</p>
</div>
</div>


</section>

<a onclick="window.scrollTo(0, 0); return false;" id="quarto-back-to-top"><i class="bi bi-arrow-up"></i> Back to top</a><div id="quarto-appendix" class="default"><section class="quarto-appendix-contents" id="quarto-reuse"><h2 class="anchored quarto-appendix-heading">Reuse</h2><div class="quarto-appendix-contents"><div><a rel="license" href="https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en_gb">CC BY-NC-SA 4.0</a></div></div></section><section class="quarto-appendix-contents" id="quarto-citation"><h2 class="anchored quarto-appendix-heading">Citation</h2><div><div class="quarto-appendix-secondary-label">BibTeX citation:</div><pre class="sourceCode code-with-copy quarto-appendix-bibtex"><code class="sourceCode bibtex">@misc{canouil2026,
  author = {CANOUIL, Mickaël},
  title = {Gribouille 0.4.0: {Temporal} {Scales,} {Any-Glyph} {Markers,}
    and a {Finer} {Grid}},
  date = {2026-06-22},
  url = {https://mickael.canouil.fr/posts/2026-06-22-gribouille-0-4/},
  langid = {en-GB}
}
</code></pre><div class="quarto-appendix-secondary-label">For attribution, please cite this work as:</div><div id="ref-canouil2026" class="csl-entry quarto-appendix-citeas">
CANOUIL, M. (2026-06-22). Gribouille 0.4.0: Temporal Scales, Any-Glyph
Markers, and a Finer Grid. <em>Mickael.canouil.fr</em>. <a href="https://mickael.canouil.fr/posts/2026-06-22-gribouille-0-4/">https://mickael.canouil.fr/posts/2026-06-22-gribouille-0-4/</a>
</div></div></section></div> ]]></description>
  <category>typst</category>
  <category>quarto</category>
  <category>grammar-of-graphics</category>
  <category>gribouille</category>
  <guid>https://mickael.canouil.fr/posts/2026-06-22-gribouille-0-4/</guid>
  <pubDate>Mon, 22 Jun 2026 00:00:00 GMT</pubDate>
  <media:content url="https://mickael.canouil.fr/posts/2026-06-22-gribouille-0-4/featured.png" medium="image" type="image/png" height="76" width="144"/>
</item>
<item>
  <title>Gribouille 0.3.0: Finer Guide Control and a Themed Compose</title>
  <link>https://mickael.canouil.fr/posts/2026-06-15-gribouille-0-3/</link>
  <description><![CDATA[ 

<!--
@license MIT
@copyright 2026 Mickaël Canouil
@author Mickaël Canouil
-->
Skip to main content





<p>Another week, another <a href="https://github.com/mcanouil/gribouille">Gribouille</a> release. Gribouille 0.3.0 is narrower in scope than <a href="../../posts/2026-06-03-gribouille-0-2/index.html">0.2</a>, but it brings some wanted controls. The headline is guide control: a single argument now hides axis ticks and legends without touching the theme. Alongside that, <code>compose()</code> gains a <code>theme:</code> parameter, <code>defer()</code> replaces <code>plot(..., defer: true)</code>, <code>geom-area()</code> stacks by default, and <code>annotate()</code> can let marks overflow the panel.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="featured.png" class="lightbox" data-gallery="quarto-lightbox-gallery-1"><img src="https://mickael.canouil.fr/posts/2026-06-15-gribouille-0-3/featured.png" class="img-featured img-fluid quarto-figure quarto-figure-center figure-img" alt="Featured card for the Gribouille 0.3.0 release on a cream paper
background. On the left, the Gribouille wordmark sits above the tagline
&quot;Create elegant graphics with the Grammar of Graphics for Typst.&quot;, a
small orange &quot;v0.3.0 release&quot; pill, and the URL
m.canouil.dev/gribouille. On the right, a slightly rotated bordered card
frames a scatter plot of Palmer Penguin body mass against flipper
length, with the three species shown as coloured point clouds.
" width="600"></a></p>
</figure>
</div>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Note</span>At a glance
</div>
</div>
<div class="callout-body-container callout-body">
<ul>
<li><a href="https://github.com/mcanouil/gribouille">Gribouille</a> 0.3.0 on Typst Universe: <code>#import "@preview/gribouille:0.3.0": *</code>.</li>
<li><a href="https://m.canouil.dev/gribouille/reference/guides/guides.html"><code>guides(x: none)</code></a> / <code>guides(y: none)</code> hide tick marks and tick labels; the axis line, grid, and title stay. Remove the title separately with <code>labs(x: none)</code>.</li>
<li><a href="https://m.canouil.dev/gribouille/reference/guides/guides.html"><code>guides(default: none)</code></a> hides every legend without its own override.</li>
<li>Under <a href="https://m.canouil.dev/gribouille/reference/coord/coord-radial.html"><code>coord-radial</code></a>, <code>guides(theta: none)</code> hides the full angular axis and <code>guides(r: none)</code> hides radial tick labels.</li>
<li><code>guides(none)</code> replaces the removed <code>guide-none()</code>; <code>guides(auto)</code> restores the default.</li>
<li><a href="https://m.canouil.dev/gribouille/reference/core/compose.html"><code>compose()</code></a> gains <code>theme:</code>, which styles the composition chrome and propagates into panels with no theme of their own.</li>
<li><strong>Breaking change</strong>: <code>plot(..., defer: true)</code> is replaced by <code>defer(plot, ...)</code>. Panels inside a <code>compose()</code> also no longer accept their own <code>width</code>/<code>height</code>.</li>
<li><a href="https://m.canouil.dev/gribouille/reference/geoms/geom-area.html"><code>geom-area()</code></a> defaults to <code>stat: "align"</code> and <code>position: "stack"</code>; groups with mismatched x values are resampled automatically.</li>
<li><a href="https://m.canouil.dev/gribouille/reference/layers/annotate.html"><code>annotate()</code></a> gains <code>clip:</code> (default <code>true</code>); <code>clip: false</code> lets a mark overflow the panel.</li>
</ul>
</div>
</div>
<p>Every figure in this post is a real, freshly compiled plot.</p>
<section id="guide-controls" class="level2" data-number="1">
<h2 data-number="1" class="anchored" data-anchor-id="guide-controls"><span class="header-section-number">1</span> Guide controls</h2>
<p>The biggest visible change is a new layer of control over what guides draw.</p>
<p>Before 0.3.0, hiding an axis’s tick marks and labels meant reaching into <code>theme()</code> for the right element surface. Now, <code>guides(x: none)</code> does it directly. The axis line, the vertical grid lines, and the axis title all stay. To drop the title too, add <code>labs(x: none)</code> alongside it.</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="annotated-cell-1" data-filename="typst" style="background: #f1f3f5;"><pre class="sourceCode typst cw-auto code-annotation-code code-with-copy code-annotated"><code class="sourceCode typst"><span id="annotated-cell-1-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>plot<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-1-2">  data<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> penguins<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-1-3">  mapping<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> aes<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"flipper-len"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"body-mass"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> colour<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"species"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-1-4">  layers<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>geom-point<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> alpha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.7</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),),</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-1" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-1-5" class="code-annotation-target">  guides<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> guides<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">none</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-1-6">  labs<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> labs<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-1-7">    title<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Ticks Off, Grid Stays"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-1-8">    x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Flipper Length (mm)"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-1-9">    y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Body Mass (g)"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-1-10">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-1-11">  theme<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> theme-minimal<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(),</span></span>
<span id="annotated-cell-1-12">  width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">12cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-1-13">  height<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">8cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-1-14"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
<div class="cell-annotation">
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-1" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-1" data-code-lines="5" data-code-annotation="1"><code>guides(x: none)</code> removes tick marks and labels. The line, grid, and title are untouched. <code>guides(y: none)</code> does the same for the y-axis.</span>
</dd>
</dl>
</div>
<div>
<div class="light-content">
<div style="text-align: center;">
<p><a href="../../posts/2026-06-15-gribouille-0-3/./assets/typst-render/guides-x-none-light.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-2" title="Penguin scatter of body mass against flipper length with no x-axis tick marks or labels. The vertical grid lines and the axis title 'Flipper Length (mm)' remain."><img src="https://mickael.canouil.fr/posts/2026-06-15-gribouille-0-3/assets/typst-render/guides-x-none-light.svg" class="img-fluid hero-art" alt="Penguin scatter of body mass against flipper length with no x-axis tick marks or labels. The vertical grid lines and the axis title 'Flipper Length (mm)' remain."></a></p>
</div>
</div>
<div class="dark-content">
<div style="text-align: center;">
<p><a href="../../posts/2026-06-15-gribouille-0-3/./assets/typst-render/guides-x-none-dark.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-3" title="Penguin scatter of body mass against flipper length with no x-axis tick marks or labels. The vertical grid lines and the axis title 'Flipper Length (mm)' remain."><img src="https://mickael.canouil.fr/posts/2026-06-15-gribouille-0-3/assets/typst-render/guides-x-none-dark.svg" class="img-fluid hero-art" alt="Penguin scatter of body mass against flipper length with no x-axis tick marks or labels. The vertical grid lines and the axis title 'Flipper Length (mm)' remain."></a></p>
</div>
</div>
</div>
</div>
<p>The same syntax now controls legends too. <code>guides(none)</code> hides a legend; <code>guides(auto)</code> restores the default. Both replace the removed <code>guide-none()</code>.</p>
<p>When you want all legends gone at once, <code>guides(default: none)</code> sets the fallback for every aesthetic without its own override.</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="annotated-cell-2" data-filename="typst" style="background: #f1f3f5;"><pre class="sourceCode typst cw-auto code-annotation-code code-with-copy code-annotated"><code class="sourceCode typst"><span id="annotated-cell-2-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>plot<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-2-2">  data<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> penguins<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-2-3">  mapping<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> aes<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"flipper-len"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"body-mass"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> colour<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"species"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-2-4">  layers<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>geom-point<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> alpha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.7</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),),</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-2" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-2-5" class="code-annotation-target">  guides<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> guides<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>default<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">none</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-2-6">  labs<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> labs<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-2-7">    title<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"No Legends"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-2-8">    x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Flipper Length (mm)"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-2-9">    y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Body Mass (g)"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-2-10">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-2-11">  theme<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> theme-minimal<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(),</span></span>
<span id="annotated-cell-2-12">  width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">12cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-2-13">  height<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">8cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-2-14"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
<div class="cell-annotation">
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-2" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-2" data-code-lines="5" data-code-annotation="1">Every aesthetic without an explicit guide inherits <code>none</code>; the colour legend disappears and the plot area fills the freed space.</span>
</dd>
</dl>
</div>
<div>
<div class="light-content">
<div style="text-align: center;">
<p><a href="../../posts/2026-06-15-gribouille-0-3/./assets/typst-render/guides-default-none-light.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-4" title="Penguin scatter of body mass against flipper length, coloured by species, with no legend shown."><img src="https://mickael.canouil.fr/posts/2026-06-15-gribouille-0-3/assets/typst-render/guides-default-none-light.svg" class="img-fluid hero-art" alt="Penguin scatter of body mass against flipper length, coloured by species, with no legend shown."></a></p>
</div>
</div>
<div class="dark-content">
<div style="text-align: center;">
<p><a href="../../posts/2026-06-15-gribouille-0-3/./assets/typst-render/guides-default-none-dark.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-5" title="Penguin scatter of body mass against flipper length, coloured by species, with no legend shown."><img src="https://mickael.canouil.fr/posts/2026-06-15-gribouille-0-3/assets/typst-render/guides-default-none-dark.svg" class="img-fluid hero-art" alt="Penguin scatter of body mass against flipper length, coloured by species, with no legend shown."></a></p>
</div>
</div>
</div>
</div>
</section>
<section id="radial-guide-controls" class="level2" data-number="2">
<h2 data-number="2" class="anchored" data-anchor-id="radial-guide-controls"><span class="header-section-number">2</span> Radial guide controls</h2>
<p>The same guide syntax extends to <code>coord-radial</code>. <code>guides(theta: none)</code> hides the full angular axis, the arc, minor ticks, and tick labels together. <code>guides(r: none)</code> hides only the radial tick labels, leaving the spokes and circles in place.</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="annotated-cell-3" data-filename="typst" style="background: #f1f3f5;"><pre class="sourceCode typst cw-auto code-annotation-code code-with-copy code-annotated"><code class="sourceCode typst"><span id="annotated-cell-3-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>plot<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-3-2">  data<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> penguins<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-3-3">  mapping<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> aes<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"species"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"species"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-3-4">  layers<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>geom-bar<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(),),</span></span>
<span id="annotated-cell-3-5">  coord<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> coord-radial<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(),</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-3" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-3-6" class="code-annotation-target">  guides<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> guides<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>theta<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">none</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> default<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">none</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-3-7">  labs<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> labs<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-3-8">    title<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Angular Axis Hidden"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-3-9">    x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">none</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-3-10">    y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Count"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-3-11">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-3-12">  theme<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> theme-minimal<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(),</span></span>
<span id="annotated-cell-3-13">  width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">10cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-3-14">  height<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">10cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-3-15"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
<div class="cell-annotation">
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-3" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-3" data-code-lines="6" data-code-annotation="1"><code>theta: none</code> removes the angular ring and labels. <code>default: none</code> also drops the fill legend, leaving the radial grid as the only scale reference.</span>
</dd>
</dl>
</div>
<div>
<div class="light-content">
<div style="text-align: center;">
<p><a href="../../posts/2026-06-15-gribouille-0-3/./assets/typst-render/guides-radial-light.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-6" title="A coxcomb chart of penguin counts by species using coord-radial, with the angular tick labels hidden. Only the radial grid is visible."><img src="https://mickael.canouil.fr/posts/2026-06-15-gribouille-0-3/assets/typst-render/guides-radial-light.svg" class="img-fluid hero-art" alt="A coxcomb chart of penguin counts by species using coord-radial, with the angular tick labels hidden. Only the radial grid is visible."></a></p>
</div>
</div>
<div class="dark-content">
<div style="text-align: center;">
<p><a href="../../posts/2026-06-15-gribouille-0-3/./assets/typst-render/guides-radial-dark.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-7" title="A coxcomb chart of penguin counts by species using coord-radial, with the angular tick labels hidden. Only the radial grid is visible."><img src="https://mickael.canouil.fr/posts/2026-06-15-gribouille-0-3/assets/typst-render/guides-radial-dark.svg" class="img-fluid hero-art" alt="A coxcomb chart of penguin counts by species using coord-radial, with the angular tick labels hidden. Only the radial grid is visible."></a></p>
</div>
</div>
</div>
</div>
</section>
<section id="compose-gets-a-theme" class="level2" data-number="3">
<h2 data-number="3" class="anchored" data-anchor-id="compose-gets-a-theme"><span class="header-section-number">3</span> Compose gets a theme</h2>
<p>Two changes landed in <code>compose()</code> together.</p>
<p>The first is a <code>theme:</code> parameter. Pass a theme and it styles the composition chrome: the shared title, the hoisted legend, and the panel tags. It also propagates into any panel that has no theme of its own, so you can set one theme once and let it cascade instead of repeating it across every panel.</p>
<p>The second is the <code>defer()</code> helper.</p>
<div class="callout callout-style-default callout-warning callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Warning</span>Breaking change
</div>
</div>
<div class="callout-body-container callout-body">
<p><code>plot(..., defer: true)</code> is removed. Replace <code>plot(data: ..., ..., defer: true)</code> with <code>defer(plot, data: ..., ...)</code>. Panels inside a <code>compose()</code> also no longer accept their own <code>width</code>/<code>height</code>; the composition sizes each cell.</p>
</div>
</div>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="annotated-cell-4" data-filename="typst" style="background: #f1f3f5;"><pre class="sourceCode typst cw-auto code-annotation-code code-with-copy code-annotated"><code class="sourceCode typst"><a class="code-annotation-anchor" data-target-cell="annotated-cell-4" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-4-1" class="code-annotation-target"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">panel</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> title<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> defer<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>plot<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-4-2">  data<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> penguins<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-4-3">  mapping<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> aes<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"flipper-len"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> colour<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"species"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-4-4">  layers<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>geom-point<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> alpha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.85</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),),</span></span>
<span id="annotated-cell-4-5">  labs<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> labs<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>title<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> title<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">none</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">none</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-4-6"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-4-7"></span>
<span id="annotated-cell-4-8"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>compose<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-4-9">  panel<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"body-mass"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Body Mass"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-4-10">  panel<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"bill-len"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Bill Length"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-4-11">  columns<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-4-12">  tag-levels<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"1"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-4-13">  tag-prefix<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"("</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-4-14">  tag-suffix<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">")"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-4-15">  guides<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> guides<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>default<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> guide-legend<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>position<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"bottom"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)),</span></span>
<span id="annotated-cell-4-16">  labs<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> labs<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>title<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"One Theme, Two Panels"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-4" data-target-annotation="2" onclick="event.preventDefault();">2</a><span id="annotated-cell-4-17" class="code-annotation-target">  theme<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> theme-minimal<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(),</span></span>
<span id="annotated-cell-4-18">  width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">18cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-4-19">  height<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">8cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-4-20"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
<div class="cell-annotation">
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-4" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-4" data-code-lines="1" data-code-annotation="1"><code>defer(plot, ...)</code> replaces <code>plot(..., defer: true)</code>. The panel no longer sets <code>width</code>/<code>height</code>.</span>
</dd>
<dt data-target-cell="annotated-cell-4" data-target-annotation="2">2</dt>
<dd>
<span data-code-cell="annotated-cell-4" data-code-lines="17" data-code-annotation="2"><code>theme: theme-minimal()</code> styles the title, legend, and tags, and propagates into both panels because neither sets its own theme.</span>
</dd>
</dl>
</div>
<div>
<div class="light-content">
<div style="text-align: center;">
<p><a href="../../posts/2026-06-15-gribouille-0-3/./assets/typst-render/compose-theme-light.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-8" title="Two scatter panels side by side, tagged (1) Body Mass and (2) Bill Length, sharing a species legend at the bottom and a title across the top. The minimal theme is set once on compose()."><img src="https://mickael.canouil.fr/posts/2026-06-15-gribouille-0-3/assets/typst-render/compose-theme-light.svg" class="img-fluid hero-art" alt="Two scatter panels side by side, tagged (1) Body Mass and (2) Bill Length, sharing a species legend at the bottom and a title across the top. The minimal theme is set once on compose()."></a></p>
</div>
</div>
<div class="dark-content">
<div style="text-align: center;">
<p><a href="../../posts/2026-06-15-gribouille-0-3/./assets/typst-render/compose-theme-dark.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-9" title="Two scatter panels side by side, tagged (1) Body Mass and (2) Bill Length, sharing a species legend at the bottom and a title across the top. The minimal theme is set once on compose()."><img src="https://mickael.canouil.fr/posts/2026-06-15-gribouille-0-3/assets/typst-render/compose-theme-dark.svg" class="img-fluid hero-art" alt="Two scatter panels side by side, tagged (1) Body Mass and (2) Bill Length, sharing a species legend at the bottom and a title across the top. The minimal theme is set once on compose()."></a></p>
</div>
</div>
</div>
</div>
</section>
<section id="area-stacks-by-default" class="level2" data-number="4">
<h2 data-number="4" class="anchored" data-anchor-id="area-stacks-by-default"><span class="header-section-number">4</span> Area stacks by default</h2>
<p><code>geom-area()</code> now defaults to <code>stat: "align"</code> and <code>position: "stack"</code>.</p>
<p>Before 0.3.0, stacking a multi-group area chart needed both arguments spelled out. Now they are the defaults. <code>stat: "align"</code> also handles groups that have different x values: it resamples each group onto a shared grid before stacking, so the x values do not need to match.</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="annotated-cell-5" data-filename="typst" style="background: #f1f3f5;"><pre class="sourceCode typst cw-auto code-annotation-code code-with-copy code-annotated"><code class="sourceCode typst"><span id="annotated-cell-5-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">series</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-5-2">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>x: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> y: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> g: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"A"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>x: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> y: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> g: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"A"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>x: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> y: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">6</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> g: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"A"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>x: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> y: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">9</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> g: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"A"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-5-3">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>x: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> y: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> g: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"B"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>x: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> y: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> g: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"B"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>x: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> y: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> g: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"B"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>x: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> y: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">6</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> g: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"B"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-5-4">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>x: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> y: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> g: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"C"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>x: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> y: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> g: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"C"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>x: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> y: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> g: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"C"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>x: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> y: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> g: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"C"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-5-5"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-5-6"></span>
<span id="annotated-cell-5-7"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>plot<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-5-8">  data<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> series<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-5-9">  mapping<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> aes<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"x"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"y"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"g"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-5" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-5-10" class="code-annotation-target">  layers<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>geom-area<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(),),</span></span>
<span id="annotated-cell-5-11">  labs<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> labs<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-5-12">    title<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Stacked by Default"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-5-13">    x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"x"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-5-14">    y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"y"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-5-15">    fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Group"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-5-16">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-5-17">  theme<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> theme-minimal<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(),</span></span>
<span id="annotated-cell-5-18">  width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">12cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-5-19">  height<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">8cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-5-20"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
<div class="cell-annotation">
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-5" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-5" data-code-lines="10" data-code-annotation="1">No <code>stat:</code> or <code>position:</code> needed. Groups A, B, and C have different x breakpoints; <code>stat: "align"</code> resamples them onto a shared grid before stacking.</span>
</dd>
</dl>
</div>
<div>
<div class="light-content">
<div style="text-align: center;">
<p><a href="../../posts/2026-06-15-gribouille-0-3/./assets/typst-render/geom-area-stack-light.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-10" title="Stacked area chart with three groups A, B, and C over x values 0 to 4. Groups A and B have different x breakpoints; stat-align resamples them onto a shared grid before stacking."><img src="https://mickael.canouil.fr/posts/2026-06-15-gribouille-0-3/assets/typst-render/geom-area-stack-light.svg" class="img-fluid hero-art" alt="Stacked area chart with three groups A, B, and C over x values 0 to 4. Groups A and B have different x breakpoints; stat-align resamples them onto a shared grid before stacking."></a></p>
</div>
</div>
<div class="dark-content">
<div style="text-align: center;">
<p><a href="../../posts/2026-06-15-gribouille-0-3/./assets/typst-render/geom-area-stack-dark.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-11" title="Stacked area chart with three groups A, B, and C over x values 0 to 4. Groups A and B have different x breakpoints; stat-align resamples them onto a shared grid before stacking."><img src="https://mickael.canouil.fr/posts/2026-06-15-gribouille-0-3/assets/typst-render/geom-area-stack-dark.svg" class="img-fluid hero-art" alt="Stacked area chart with three groups A, B, and C over x values 0 to 4. Groups A and B have different x breakpoints; stat-align resamples them onto a shared grid before stacking."></a></p>
</div>
</div>
</div>
</div>
</section>
<section id="annotations-can-overflow" class="level2" data-number="5">
<h2 data-number="5" class="anchored" data-anchor-id="annotations-can-overflow"><span class="header-section-number">5</span> Annotations can overflow</h2>
<p><code>annotate()</code> gains a <code>clip</code> argument. The default is <code>true</code>, which preserves the existing behaviour: marks outside the panel are clipped. Set <code>clip: false</code> and the mark is drawn even if it sits past the axis limits.</p>
<p>This is useful for corner insets, labels anchored outside the data range, or decorations that belong in the margin. A paired fix means an <code>annotate(clip: false)</code> placed outside the scale <code>limits</code> now draws correctly. Before 0.3.0, the out-of-range pre-pass silently dropped it.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="annotated-cell-6" data-filename="typst" style="background: #f1f3f5;"><pre class="sourceCode typst cw-auto code-annotation-code code-with-copy code-annotated"><code class="sourceCode typst"><span id="annotated-cell-6-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>plot<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-6-2">  data<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> penguins<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-6-3">  mapping<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> aes<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"flipper-len"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"body-mass"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-6-4">  layers<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-6-5">    geom-point<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> alpha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.6</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-6-6">    annotate<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-6-7">      geom-text<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>label<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"← past the edge"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-6-8">      x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">235</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-6-9">      y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5000</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-6" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-6-10" class="code-annotation-target">      clip<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">false</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-6-11">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-6-12">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-6-13">  labs<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> labs<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-6-14">    title<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Annotation Outside the Panel"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-6-15">    x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Flipper Length (mm)"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-6-16">    y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Body Mass (g)"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-6-17">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-6-18">  theme<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> theme-minimal<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(),</span></span>
<span id="annotated-cell-6-19">  width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">12cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-6-20">  height<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">8cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-6-21"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-6" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-6" data-code-lines="10" data-code-annotation="1"><code>clip: false</code> keeps the mark visible even though <code>x = 235</code> sits past the panel boundary.</span>
</dd>
</dl>
</section>
<section id="legend-and-scale-fixes" class="level2" data-number="6">
<h2 data-number="6" class="anchored" data-anchor-id="legend-and-scale-fixes"><span class="header-section-number">6</span> Legend and Scale Fixes</h2>
<p>As with 0.2, a large share of the release is fixes rather than features. Legend layout was the main focus.</p>
<ul>
<li>Horizontal legends with centred or right <code>align</code> now centre or right-justify the key graphic under the title, instead of leaving it pinned to the left while the title moved.</li>
<li><code>guide-legend(nrow:)</code> and <code>guide-legend(ncolumn:)</code> now work for continuous size legends, laying keys out in a grid as they do for discrete swatch legends.</li>
<li><code>guide-legend(align:)</code> now aligns the legend title as well as the entry labels. Passing a plain string such as <code>"left"</code> also reports a clear error instead of being silently ignored.</li>
<li><code>legend-background</code> now inherits the base <code>rect</code> element, so <code>theme(rect: ...)</code> cascades to it as it does for every other <code>*-background</code> surface.</li>
<li>A hoisted <code>compose()</code> legend that leaves no room for the panels, or a left/right legend that would overrun the caption, now reports a clear layout error instead of silently overprinting.</li>
</ul>
<p>A second group cleans up statistics and scale behaviour.</p>
<ul>
<li><code>stat-bin-2d</code> and <code>stat-bin-hex</code> <code>_density</code> is now the cell’s fraction of the total count, matching ggplot2, instead of an undocumented count-per-cell-area value.</li>
<li><code>sqrt</code> and <code>log10</code> transforms validate their domain and report clear errors for out-of-range data. The <code>sqrt</code> inverse also clamps padded view bounds at zero so the axis stays monotone and shows the <code>0</code> break.</li>
<li>The out-of-range filter now respects scale <code>expand</code>, so points and annotations inside the expansion headroom are kept. Reversed <code>limits</code> (a flipped axis) no longer drop every row.</li>
<li><code>geom-linerange()</code> honours its <code>alpha</code> parameter, which was always documented but never applied.</li>
</ul>
<div class="highlight">
<p><strong>Most of the legend fixes are invisible at a glance:</strong> existing plots look the same, but the cases that used to produce overlapping keys, misaligned titles, or silent layout glitches now behave correctly.</p>
</div>
</section>
<section id="editor-support-tinymist-docstrings" class="level2" data-number="7">
<h2 data-number="7" class="anchored" data-anchor-id="editor-support-tinymist-docstrings"><span class="header-section-number">7</span> Editor support: Tinymist docstrings</h2>
<p>The published package now ships Tinymist-friendly docstrings. Hovering over a gribouille function in a compatible editor shows formatted parameters, return values, and examples, instead of the raw <code>@</code>-tag comments. Variadic sinks such as <code>guides()</code> and <code>theme()</code> list their accepted keys explicitly, so you can see which aesthetics or element surfaces each function takes without opening the reference.</p>
</section>
<section id="wrap-up" class="level2" data-number="8">
<h2 data-number="8" class="anchored" data-anchor-id="wrap-up"><span class="header-section-number">8</span> Wrap-up</h2>
<div class="highlight">
<p><strong>Guide control moved one level up: hide ticks and legends with one argument, no theme surgery needed.</strong></p>
</div>
<p>Next on the list is more geoms and worked examples. If you run into something unexpected, the issue tracker is the right place for it.</p>
<ul>
<li>Gribouille
<ul>
<li>Repository: <a href="https://github.com/mcanouil/gribouille" class="uri">https://github.com/mcanouil/gribouille</a>.</li>
<li>Documentation: <a href="https://m.canouil.dev/gribouille" class="uri">https://m.canouil.dev/gribouille</a>.</li>
<li>Typst Universe: <a href="https://typst.app/universe/package/gribouille" class="uri">https://typst.app/universe/package/gribouille</a>.</li>
</ul></li>
<li>Typst Render
<ul>
<li>Repository: <a href="https://github.com/mcanouil/quarto-typst-render" class="uri">https://github.com/mcanouil/quarto-typst-render</a>.</li>
<li>Documentation: <a href="https://m.canouil.dev/quarto-typst-render" class="uri">https://m.canouil.dev/quarto-typst-render</a>.</li>
</ul></li>
</ul>
<div class="callout callout-style-default callout-tip callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Tip</span>A note on contributions
</div>
</div>
<div class="callout-body-container callout-body">
<p>Gribouille is an unfunded spare-time project, and the API is still settling. Bug reports and ideas are very welcome on the issue tracker. Pull requests are not being accepted for now, for the reasons set out in the <a href="../../posts/2026-05-20-gribouille-grammar-of-graphics-for-typst/index.html">launch post</a>. Thanks in advance for your patience.</p>
</div>
</div>


</section>

<a onclick="window.scrollTo(0, 0); return false;" id="quarto-back-to-top"><i class="bi bi-arrow-up"></i> Back to top</a><div id="quarto-appendix" class="default"><section class="quarto-appendix-contents" id="quarto-reuse"><h2 class="anchored quarto-appendix-heading">Reuse</h2><div class="quarto-appendix-contents"><div><a rel="license" href="https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en_gb">CC BY-NC-SA 4.0</a></div></div></section><section class="quarto-appendix-contents" id="quarto-citation"><h2 class="anchored quarto-appendix-heading">Citation</h2><div><div class="quarto-appendix-secondary-label">BibTeX citation:</div><pre class="sourceCode code-with-copy quarto-appendix-bibtex"><code class="sourceCode bibtex">@misc{canouil2026,
  author = {CANOUIL, Mickaël},
  title = {Gribouille 0.3.0: {Finer} {Guide} {Control} and a {Themed}
    {Compose}},
  date = {2026-06-15},
  url = {https://mickael.canouil.fr/posts/2026-06-15-gribouille-0-3/},
  langid = {en-GB}
}
</code></pre><div class="quarto-appendix-secondary-label">For attribution, please cite this work as:</div><div id="ref-canouil2026" class="csl-entry quarto-appendix-citeas">
CANOUIL, M. (2026-06-15). Gribouille 0.3.0: Finer Guide Control and a
Themed Compose. <em>Mickael.canouil.fr</em>. <a href="https://mickael.canouil.fr/posts/2026-06-15-gribouille-0-3/">https://mickael.canouil.fr/posts/2026-06-15-gribouille-0-3/</a>
</div></div></section></div> ]]></description>
  <category>typst</category>
  <category>quarto</category>
  <category>grammar-of-graphics</category>
  <category>gribouille</category>
  <guid>https://mickael.canouil.fr/posts/2026-06-15-gribouille-0-3/</guid>
  <pubDate>Mon, 15 Jun 2026 00:00:00 GMT</pubDate>
  <media:content url="https://mickael.canouil.fr/posts/2026-06-15-gribouille-0-3/featured.png" medium="image" type="image/png" height="76" width="144"/>
</item>
<item>
  <title>Why Quarto Stuck: Four Years from a Biostatistician’s Desk</title>
  <link>https://mickael.canouil.fr/posts/2026-06-10-why-quarto-stuck/</link>
  <description><![CDATA[ 

<!--
@license MIT
@copyright 2026 Mickaël Canouil
@author Mickaël Canouil
-->
Skip to main content





<p>After four years of using Quarto every day, I want to explain why it stuck as my go-to for reports, slides, websites, and books. Before Quarto, that work was spread across R Markdown, <code>bookdown</code>, <code>xaringan</code>, and <code>distill</code>. Quarto folded all of that into one command line tool, and I have not looked back since the early days before v1.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="featured.png" class="lightbox" data-gallery="quarto-lightbox-gallery-1"><img src="https://mickael.canouil.fr/posts/2026-06-10-why-quarto-stuck/featured.png" class="img-featured img-fluid quarto-figure quarto-figure-center figure-img" alt="A dark poster with the title &quot;Why Quarto Stuck&quot; above the line &quot;from R
Markdown to one CLI&quot;, set over a softly glowing horizontal timeline with
nodes for the years 2022 to 2026, the final 2026 node picked out in
coral.
" width="600"></a></p>
</figure>
</div>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Note</span>At a Glance
</div>
</div>
<div class="callout-body-container callout-body">
<ul>
<li>Four years of daily Quarto use.</li>
<li>Close to 14,000 contributions on <code>quarto-dev/quarto-cli</code> across discussions, issues, and pull requests.</li>
<li>One CLI replaced a fragmented R Markdown toolchain for reports, slides, websites, and books.</li>
<li>The extension system turned Quarto into a platform: more than 40 extensions built and maintained.</li>
<li>Typst for PDF: fast compilation, readable programming model, no more LaTeX friction.</li>
<li><a href="https://github.com/mcanouil/gribouille">Gribouille</a>: a grammar of graphics in pure Typst, so plots compile where the document does.</li>
</ul>
</div>
</div>
<section id="it-started-by-helping-out" class="level2">
<h2 class="anchored" data-anchor-id="it-started-by-helping-out">It Started by Helping Out</h2>
<p>My time with Quarto began before v1: back-and-forth between the issue/discussion tracker and real document use in my research on the genetics of type 2 diabetes and obesity, when the CLI was still finding its shape. I still spend time in the <a href="https://github.com/quarto-dev/quarto-cli"><code>quarto-dev/quarto-cli</code></a> repository, answering questions, helping people troubleshoot, surfacing gaps in the documentation, and guiding the same R Markdown and <code>bookdown</code> migration I once made myself.</p>
<p><em>The figure below (Figure&nbsp;1) traces that involvement month by month, split across discussions, issues, and pull requests, and it is itself drawn with <a href="https://github.com/mcanouil/gribouille">Gribouille</a> through the <a href="https://github.com/mcanouil/quarto-typst-render"><code>quarto-typst-render</code></a> extension.</em><sup>1</sup></p>
<div id="fig-activity" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-activity-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<div>
<div class="light-content">
<div style="text-align: center;">
<p><a href="../../posts/2026-06-10-why-quarto-stuck/./assets/typst-render/quarto-cli-activity-light.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-2" title="Figure&nbsp;1: Monthly contributions to quarto-dev/quarto-cli from mid-2022 to mid-2026, split across discussions, issues, and pull requests."><img src="https://mickael.canouil.fr/posts/2026-06-10-why-quarto-stuck/assets/typst-render/quarto-cli-activity-light.svg" class="img-fluid figure-img" alt="Stacked area chart of my monthly activity on quarto-dev/quarto-cli from mid-2022 to mid-2026, split by thread type into discussions, issues, and pull requests. Discussions are the largest band throughout, issues second, pull requests a thin top band; activity climbs through 2023, peaks across 2023 and 2024, then eases, with the final months of 2026 partial."></a></p>
</div>
</div>
<div class="dark-content">
<div style="text-align: center;">
<p><a href="../../posts/2026-06-10-why-quarto-stuck/./assets/typst-render/quarto-cli-activity-dark.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-3" title="Figure&nbsp;1: Monthly contributions to quarto-dev/quarto-cli from mid-2022 to mid-2026, split across discussions, issues, and pull requests."><img src="https://mickael.canouil.fr/posts/2026-06-10-why-quarto-stuck/assets/typst-render/quarto-cli-activity-dark.svg" class="img-fluid figure-img" alt="Stacked area chart of my monthly activity on quarto-dev/quarto-cli from mid-2022 to mid-2026, split by thread type into discussions, issues, and pull requests. Discussions are the largest band throughout, issues second, pull requests a thin top band; activity climbs through 2023, peaks across 2023 and 2024, then eases, with the final months of 2026 partial."></a></p>
</div>
</div>
</div>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-activity-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1: Monthly contributions to <code>quarto-dev/quarto-cli</code> from mid-2022 to mid-2026, split across discussions, issues, and pull requests.
</figcaption>
</figure>
</div>
<p>The totals still catch me off guard. Close to 14,000 contributions in all: comments, replies, pull requests, issues, and discussions across four years.</p>
<div class="highlight">
<p><strong>Close to 14,000 contributions</strong> across discussions, issues, and pull requests: that is a lot of people who were stuck and then were not.</p>
</div>
<p>Working through those threads deepened my understanding of what Quarto does well and where it falls short, which shaped how I use it for my own work today.</p>
</section>
<section id="from-r-markdown-to-one-cli" class="level2">
<h2 class="anchored" data-anchor-id="from-r-markdown-to-one-cli">From R Markdown to One CLI</h2>
<p>The first thing that won me over was the scope of a single tool. With R Markdown I kept a small mental map of which package produced which output. With Quarto I write <code>quarto render</code>, and the same project gives me an HTML report, a PDF through Typst, a Reveal.js deck, a website, or a book. The fragmented toolchain became one CLI, and that alone changed how I work.</p>
<div>
<div class="light-content">
<div style="text-align: center;">
<p><a href="../../posts/2026-06-10-why-quarto-stuck/./assets/typst-render/one-source-light.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-4" title="A diagram: a single source document, a .qmd file, with an arrow pointing to five output labels, HTML, PDF, Reveal.js, Website, and Book."><img src="https://mickael.canouil.fr/posts/2026-06-10-why-quarto-stuck/assets/typst-render/one-source-light.svg" class="img-fluid" alt="A diagram: a single source document, a .qmd file, with an arrow pointing to five output labels, HTML, PDF, Reveal.js, Website, and Book."></a></p>
</div>
</div>
<div class="dark-content">
<div style="text-align: center;">
<p><a href="../../posts/2026-06-10-why-quarto-stuck/./assets/typst-render/one-source-dark.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-5" title="A diagram: a single source document, a .qmd file, with an arrow pointing to five output labels, HTML, PDF, Reveal.js, Website, and Book."><img src="https://mickael.canouil.fr/posts/2026-06-10-why-quarto-stuck/assets/typst-render/one-source-dark.svg" class="img-fluid" alt="A diagram: a single source document, a .qmd file, with an arrow pointing to five output labels, HTML, PDF, Reveal.js, Website, and Book."></a></p>
</div>
</div>
</div>
<p>The defaults are also less surprising. Format options live under a single <code>format:</code> key, so an HTML and a PDF variant of the same document sit side by side instead of fighting each other. Even the format names are more intuitive: <code>html</code>, <code>pdf</code>, <code>revealjs</code>, <code>website</code>, and <code>book</code> are easier to remember than the R Markdown equivalents (<em>also way less typing</em>). They are consistent across formats too, so I do not have to remember that <code>html_document</code> is for HTML but <code>pdf_document</code> is for PDF. A project/directory-level <code>_metadata.yml</code> sets shared options once, and conditional content with <code>{.content-visible when-format="html"}</code> reads far more clearly than the output checks I used to scatter through R Markdown code cells. None of this is magic, it is less surprising, and after years of small surprises that matters.</p>
<p>Multi-language support is also native in Quarto. I move between R and Python in the same workflow, even if not yet in the same document (<em>I have strong hopes for <strong>Quarto 2</strong> ongoing developments</em>).</p>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
Note
</div>
</div>
<div class="callout-body-container callout-body">
<p>R and Python still live in separate documents within a project, not in one shared session. For my work that boundary has never been a real problem. When both languages were needed, I relied on <code>reticulate</code> to call Python from R.</p>
</div>
</div>
<p>The look and feel by default is, in my opinion, better, especially for HTML. And because R is no longer mandatory, writing notes or plain prose is a first-class use case, not an afterthought. That is also what makes Quarto easy to recommend: I have helped colleagues and clients move from R and <code>bookdown</code>, without asking them to change how they think.</p>
</section>
<section id="building-my-own" class="level2">
<h2 class="anchored" data-anchor-id="building-my-own">Building My Own</h2>
<p>If one feature made Quarto really stick, it is the extension system. An extension adds shortcodes, filters, formats, or whole project types without forking or hacking the core. That single idea turned Quarto from a better R Markdown<sup>2</sup> into a platform I build on.</p>
<p>I know this because I keep building on it with tools that began as solutions to my own needs but that I share in the spirit of open source.</p>
<ol class="qx-timeline">
  <li class="qx-more"><span class="qx-more-inner"><span class="qx-when">2022 Q3</span><a href="https://github.com/mcanouil/quarto-revealjs-storybook">Storybook</a>, <a href="https://github.com/mcanouil/quarto-revealjs-coeos">Coeos</a>, <a href="https://github.com/mcanouil/quarto-iconify">Iconify</a>, <a href="https://github.com/mcanouil/quarto-letter">Letter</a>, <a href="https://github.com/mcanouil/quarto-animate">Animate</a>, and <a href="https://github.com/mcanouil/quarto-elevator">Elevator</a>, my first handful while learning the format.</span></li>
  <li class="qx-more"><span class="qx-more-inner"><span class="qx-when">2022 Q4</span><a href="https://github.com/mcanouil/quarto-masonry">Masonry</a>.</span></li>
  <li class="qx-more"><span class="qx-more-inner"><span class="qx-when">2023 Q1</span><a href="https://github.com/mcanouil/quarto-lua-env">Lua Env</a> and <a href="https://github.com/mcanouil/quarto-revealjs-spotlight">Spotlight</a>.</span></li>
  <li class="qx-card qx-left"><div class="qx-box"><a href="https://github.com/mcanouil/quarto-preview-colour">Preview Colour</a> shows an inline swatch next to a hex, RGB, or HSL code. <span class="qx-extra">Also <a href="https://github.com/mcanouil/quarto-badge">Version Badge</a>.</span></div><div class="qx-tick">2023 Q2</div></li>
  <li class="qx-card qx-right"><div class="qx-box"><a href="https://github.com/mcanouil/quarto-invoice">Invoice</a> is a Typst template I actually send to clients.</div><div class="qx-tick">2023 Q4</div></li>
  <li class="qx-card qx-left"><div class="qx-box"><a href="https://github.com/mcanouil/quarto-highlight-text">Highlight Text</a> highlights text across HTML, Typst, Reveal.js, and Docx.</div><div class="qx-tick">2024 Q2</div></li>
  <li class="qx-more"><span class="qx-more-inner"><span class="qx-when">2024 Q3</span><a href="https://github.com/mcanouil/quarto-github">GitHub</a>, later replaced by Git Link.</span></li>
  <li class="qx-card qx-right"><div class="qx-box"><a href="https://github.com/mcanouil/quarto-wizard">Quarto Wizard</a> manages extensions from VS Code and Positron.</div><div class="qx-tick">2024 Q4</div></li>
  <li class="qx-more"><span class="qx-more-inner"><span class="qx-when">2025 Q1</span><a href="https://github.com/mcanouil/quarto-div-reuse">Div Reuse</a> and <a href="https://github.com/mcanouil/quarto-language-cell-decorator">Language Cell Decorator</a>.</span></li>
  <li class="qx-card qx-left"><div class="qx-box"><a href="https://github.com/mcanouil/quarto-mcanouil">quarto-mcanouil</a> brings <code>brand.yml</code> theming to HTML, Typst, and Reveal.js, and <a href="https://github.com/mcanouil/quarto-gitlink">Git Link</a> turns issue, pull request, and commit references into links. <span class="qx-extra">Also <a href="https://github.com/mcanouil/quarto-external">External</a>, <a href="https://github.com/mcanouil/quarto-modal">Modal</a>, and <a href="https://github.com/mcanouil/quarto-toc-depth">TOC Depth</a>.</span></div><div class="qx-tick">2025 Q3</div></li>
  <li class="qx-card qx-right"><div class="qx-box"><a href="https://github.com/mcanouil/quarto-extensions-updater">Extensions Updater</a> updates a project's extensions automatically, like Dependabot. <span class="qx-extra">Also <a href="https://github.com/mcanouil/quarto-collapse-output">Collapse Output</a>, <a href="https://github.com/mcanouil/quarto-remember">Remember</a>, <a href="https://github.com/mcanouil/quarto-revealjs-tabset">Tabset</a>, and <a href="https://github.com/mcanouil/quarto-offcanvas">Offcanvas</a>.</span></div><div class="qx-tick">2025 Q4</div></li>
  <li class="qx-card qx-left"><div class="qx-box"><a href="https://github.com/mcanouil/quarto-code-window">Code Window</a> dresses code blocks as macOS or Windows windows, and <a href="https://github.com/mcanouil/quarto-typst-render">Typst Render</a> compiles Typst blocks to images for any format. <span class="qx-extra">Also <a href="https://github.com/mcanouil/quarto-revealjs-a11y">A11y</a>.</span></div><div class="qx-tick">2026 Q1</div></li>
  <li class="qx-card qx-right"><div class="qx-box"><a href="https://github.com/mcanouil/quarto-prism">Prism</a> attaches format-specific values to a single Div, Span, or CodeBlock. <span class="qx-extra">Also <a href="https://github.com/mcanouil/quarto-revealjs-fragmention">Fragmention</a>, <a href="https://github.com/mcanouil/quarto-revealjs-cascade">Cascade</a>, and <a href="https://github.com/mcanouil/quarto-revealjs-codefrag">Code Annotation Fragments</a>.</span></div><div class="qx-tick">2026 Q2</div></li>
</ol>
<div class="highlight">
<p><strong>The extension system is the real unlock:</strong> it let me shape the authoring experience around my work instead of waiting for the core to grow a feature.</p>
</div>
<p>The same mechanism scales up. Embedding a Quarto format extension inside an R package gives a large organisation branded, client-ready HTML, PDF, and Reveal.js with its corporate identity baked in. My needs as a biostatistician and consultant drove most of this, and some of it the community adopted. <a href="https://github.com/mcanouil/quarto-wizard">Quarto Wizard</a> manages extensions from VS Code and Positron, and the <a href="https://github.com/mcanouil/quarto-extensions">Quarto Extensions</a> directory lists more than 300 of them through the GitHub API. I also maintain that directory as a community resource that stands apart from the core team, so anyone can find an extension without knowing it exists first. I have written before about the Wizard (<a href="../../posts/2025-10-20-quarto-wizard-1-0-0/index.html">1.0.0</a> and <a href="../../posts/2026-01-12-quarto-wizard-2-0-0/index.html">2.0.0</a>), about <a href="../../posts/2025-12-12-quarto-extensions-updater/index.html">keeping extensions updated</a>, and about <a href="../../posts/2025-11-06-quarto-extensions-lua/index.html">the Lua behind Quarto extensions</a>.</p>
</section>
<section id="then-came-typst" class="level2">
<h2 class="anchored" data-anchor-id="then-came-typst">Then Came Typst</h2>
<p>Building all of this kept pulling me toward one format in particular, and that format is Typst. It has become my most recent rabbit hole, both standalone with <code>typst compile</code> and through Quarto’s native <code>typst</code> output. For PDF it is a genuine step up from LaTeX: compilation is fast, the programming model is readable, and layout control is far more direct. I no longer dread a page that needs precise placement.</p>
<p>Out of that work came <a href="../../posts/2026-05-20-gribouille-grammar-of-graphics-for-typst/index.html">Gribouille</a>, a grammar of graphics written in pure Typst, so I can draw a plot where the document is compiled rather than importing a rendered image. The chart above (Figure&nbsp;1), the one tracing my time on <code>quarto-cli</code>, is one of its outputs. The non-obvious lesson, for me, was how little I missed LaTeX once the small frictions were gone. The other was that Typst’s documentation is remarkably easy to search: the model is consistent enough that the right page turns up before I have finished typing the query. My tutorials on building Typst templates for Quarto (<a href="../../posts/2026-02-27-typst-template-tutorial-part1/index.html">Part 1</a> and <a href="../../posts/2026-03-05-typst-template-tutorial-part2/index.html">Part 2</a>) document those experiments, from <a href="../../posts/2026-01-19-typst-document-dispatcher/index.html">document dispatching</a> to <a href="../../posts/2026-06-03-gribouille-0-2/index.html">the Gribouille releases</a>. Most recently, <a href="../../posts/2026-05-28-typst-linkedin-carousels/index.html">building LinkedIn carousels with Typst</a> showed that the same source-to-output loop works for social content as well as technical documents.</p>
</section>
<section id="four-years-on" class="level2">
<h2 class="anchored" data-anchor-id="four-years-on">Four Years On</h2>
<p>Quarto stuck for a plain reason: it stopped getting in my way. What began as answering other people’s questions turned into building my own tools, and then into a new way to make PDFs altogether. Four years on, that is still why I open it every day.</p>
<p>There is also something coming I’m excited about, namely Quarto 2, a ground-up rewrite in Rust: faster compilation, a tree-sitter parser with real error messages, and a collaborative editor built on Automerge. I cannot wait. Backwards compatibility is one of the main goals, but some breaking changes are likely to happen in favour of a better experience. I am sure it will be worth it.</p>
<p>Happy publishing!</p>


</section>


<a onclick="window.scrollTo(0, 0); return false;" id="quarto-back-to-top"><i class="bi bi-arrow-up"></i> Back to top</a><div id="quarto-appendix" class="default"><section id="footnotes" class="footnotes footnotes-end-of-document"><h2 class="anchored quarto-appendix-heading">Footnotes</h2>

<ol>
<li id="fn1"><p>The counts come from my own <a href="https://github.com/mcanouil/quarto-cli-activity"><code>quarto-cli-activity</code></a> data for <code>quarto-dev/quarto-cli</code>.↩︎</p></li>
<li id="fn2"><p>To be fair, R Markdown supports templates and filters, but to me they always felt like afterthoughts, not first-class features.↩︎</p></li>
</ol>
</section><section class="quarto-appendix-contents" id="quarto-reuse"><h2 class="anchored quarto-appendix-heading">Reuse</h2><div class="quarto-appendix-contents"><div><a rel="license" href="https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en_gb">CC BY-NC-SA 4.0</a></div></div></section><section class="quarto-appendix-contents" id="quarto-citation"><h2 class="anchored quarto-appendix-heading">Citation</h2><div><div class="quarto-appendix-secondary-label">BibTeX citation:</div><pre class="sourceCode code-with-copy quarto-appendix-bibtex"><code class="sourceCode bibtex">@misc{canouil2026,
  author = {CANOUIL, Mickaël},
  title = {Why {Quarto} {Stuck:} {Four} {Years} from a
    {Biostatistician’s} {Desk}},
  date = {2026-06-10},
  url = {https://mickael.canouil.fr/posts/2026-06-10-why-quarto-stuck/},
  langid = {en-GB}
}
</code></pre><div class="quarto-appendix-secondary-label">For attribution, please cite this work as:</div><div id="ref-canouil2026" class="csl-entry quarto-appendix-citeas">
CANOUIL, M. (2026-06-10). Why Quarto Stuck: Four Years from a
Biostatistician’s Desk. <em>Mickael.canouil.fr</em>. <a href="https://mickael.canouil.fr/posts/2026-06-10-why-quarto-stuck/">https://mickael.canouil.fr/posts/2026-06-10-why-quarto-stuck/</a>
</div></div></section></div> ]]></description>
  <category>quarto</category>
  <category>typst</category>
  <category>extensions</category>
  <category>rmarkdown</category>
  <guid>https://mickael.canouil.fr/posts/2026-06-10-why-quarto-stuck/</guid>
  <pubDate>Wed, 10 Jun 2026 00:00:00 GMT</pubDate>
  <media:content url="https://mickael.canouil.fr/posts/2026-06-10-why-quarto-stuck/featured.png" medium="image" type="image/png" height="76" width="144"/>
</item>
<item>
  <title>Gribouille 0.2.0 and 0.2.1: Composing Plots and a Sturdier Core</title>
  <link>https://mickael.canouil.fr/posts/2026-06-03-gribouille-0-2/</link>
  <description><![CDATA[ 

<!--
@license MIT
@copyright 2026 Mickaël Canouil
@author Mickaël Canouil
-->
Skip to main content





<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="featured.png" class="lightbox" data-gallery="quarto-lightbox-gallery-1"><img src="https://mickael.canouil.fr/posts/2026-06-03-gribouille-0-2/featured.png" class="img-featured img-fluid quarto-figure quarto-figure-center figure-img" alt="Featured card for the Gribouille 0.2.1 release on a cream paper
background. On the left, the Gribouille wordmark sits above the tagline
&quot;Create elegant graphics with the Grammar of Graphics for Typst.&quot;, a
small orange &quot;v0.2.1 release&quot; pill, and the URL
m.canouil.dev/gribouille. On the right, a slightly rotated bordered card
frames a scatter plot titled &quot;Penguins Dataset&quot; of Palmer Penguin body
mass against flipper length, the three species shown as shaded point
clouds with short callout labels.
" width="600"></a></p>
</figure>
</div>
<section id="introduction" class="level2" data-number="1">
<h2 data-number="1" class="anchored" data-anchor-id="introduction"><span class="header-section-number">1</span> Introduction</h2>
<p><a href="https://github.com/mcanouil/gribouille">Gribouille</a> shipped its <a href="../../posts/2026-05-20-gribouille-grammar-of-graphics-for-typst/index.html">first version</a> a fortnight ago. This is the first feature update: 0.2.0 landed first, and 0.2.1 followed shortly after with <code>align-panels</code> and two more fixes, so this post covers the 0.2 update as a whole. It has two faces. On the surface, <a href="https://m.canouil.dev/gribouille/reference/core/compose.html"><code>compose()</code></a> learned to number, nest, title, share a legend across panels, and line those panels up across the grid. <code>facet-grid()</code> got free scales, and labels and guides became easier to control. Underneath, most of the time went into the internals, turning a long list of panics into clear errors and correct output.</p>
<p>Every figure in this post is a real, freshly compiled plot.</p>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Note</span>At a glance
</div>
</div>
<div class="callout-body-container callout-body">
<ul>
<li><a href="https://github.com/mcanouil/gribouille">Gribouille</a> 0.2.1 on Typst Universe: <code>#import "@preview/gribouille:0.2.1": *</code>.</li>
<li><a href="https://m.canouil.dev/gribouille/reference/core/compose.html"><code>compose()</code></a> can tag panels (<code>A</code>, <code>1</code>, <code>i</code>, …), nest into another <code>compose()</code>, carry its own <code>labs</code>, control the shared legend through <code>guides</code>, and size itself with <code>width</code>/<code>height</code> and relative <code>widths</code>/<code>heights</code>.</li>
<li><a href="https://m.canouil.dev/gribouille/reference/core/compose.html"><code>compose(align-panels: true)</code></a> shares panel margins grid-wise, so the plot areas line up across rows and columns even when their axis labels differ in width (0.2.1).</li>
<li><a href="https://m.canouil.dev/gribouille/reference/facets/facet-grid.html"><code>facet-grid(scales: ...)</code></a> supports <code>"free_x"</code>, <code>"free_y"</code>, and <code>"free"</code>, matching <code>ggplot2</code>.</li>
<li>A layer’s <code>data</code> accepts a function applied to the plot data, for per-layer subsets or transforms without a second dataset.</li>
<li><a href="https://m.canouil.dev/gribouille/reference/guides/guides.html"><code>guides()</code></a> gains a <code>default</code> entry, a fallback inherited by every aesthetic without its own override.</li>
<li><a href="https://m.canouil.dev/gribouille/reference/labs/labs.html"><code>labs()</code></a> fields default to <code>auto</code>; pass <code>none</code> to drop an axis or legend title and reclaim the space it reserved.</li>
<li>A long internals pass: many operations that used to panic now report a clear error or handle the edge, <code>after-scale</code>/<code>stage</code> channels resolve correctly, and <code>geom-segment()</code>/<code>geom-curve()</code>/<code>geom-ribbon()</code>/<code>geom-area()</code> draw on discrete scales instead of nothing (0.2.1).</li>
</ul>
</div>
</div>
<p>Every <code>{typst}</code> block below pulls the library in through a one-line preamble, <code>#import "@preview/gribouille:0.2.1": *</code>, exactly as the <a href="../../posts/2026-05-20-gribouille-grammar-of-graphics-for-typst/index.html">launch post</a> explained.</p>
</section>
<section id="compose-grew-up" class="level2" data-number="2">
<h2 data-number="2" class="anchored" data-anchor-id="compose-grew-up"><span class="header-section-number">2</span> Compose grew up</h2>
<p>The launch already had <a href="https://m.canouil.dev/gribouille/reference/core/compose.html"><code>compose()</code></a>: pass a few deferred plots, get them laid out on a grid with their shared legend hoisted to the edge. This release turns it into a small layout language of its own, close in spirit to <code>patchwork</code>.</p>
<p>Panels can be numbered with <code>tag-levels</code>, the composition can carry its own title through <code>labs</code>, and the shared legend’s side is set once with a <code>default</code> guide.</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="annotated-cell-1" data-filename="typst" style="background: #f1f3f5;"><pre class="sourceCode typst cw-auto code-annotation-code code-with-copy code-annotated"><code class="sourceCode typst"><span id="annotated-cell-1-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">panel</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> title<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> plot<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-1-2">  data<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> penguins<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-1-3">  mapping<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> aes<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"flipper-len"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> colour<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"species"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-1-4">  layers<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>geom-point<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> alpha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.85</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),),</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-1" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-1-5" class="code-annotation-target">  labs<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> labs<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>title<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> title<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">none</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">none</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-1-6">  theme<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> theme-minimal<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(),</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-1" data-target-annotation="2" onclick="event.preventDefault();">2</a><span id="annotated-cell-1-7" class="code-annotation-target">  defer<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">true</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-1-8"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-1-9"></span>
<span id="annotated-cell-1-10"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>compose<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-1-11">  panel<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"body-mass"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Body Mass"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-1-12">  panel<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"bill-len"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Bill Length"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-1-13">  columns<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-1" data-target-annotation="3" onclick="event.preventDefault();">3</a><span id="annotated-cell-1-14" class="code-annotation-target">  tag-levels<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"1"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-1-15">  tag-prefix<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"("</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-1-16">  tag-suffix<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">")"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-1-17">  tag-corner<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"top-right"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-1" data-target-annotation="4" onclick="event.preventDefault();">4</a><span id="annotated-cell-1-18" class="code-annotation-target">  guides<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> guides<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>default<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> guide-legend<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>position<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"bottom"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)),</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-1" data-target-annotation="5" onclick="event.preventDefault();">5</a><span id="annotated-cell-1-19" class="code-annotation-target">  labs<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> labs<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>title<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Two Views, One Shared Legend"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-1-20">  width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">18cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-1-21">  height<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">8cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-1-22"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
<div class="cell-annotation">
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-1" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-1" data-code-lines="5" data-code-annotation="1"><code>x: none</code> and <code>y: none</code> drop the per-panel axis titles and give the space back to the data.</span>
</dd>
<dt data-target-cell="annotated-cell-1" data-target-annotation="2">2</dt>
<dd>
<span data-code-cell="annotated-cell-1" data-code-lines="7" data-code-annotation="2"><code>defer: true</code> returns a spec instead of drawing, so <code>compose()</code> can place it.</span>
</dd>
<dt data-target-cell="annotated-cell-1" data-target-annotation="3">3</dt>
<dd>
<span data-code-cell="annotated-cell-1" data-code-lines="14" data-code-annotation="3"><code>tag-levels: "1"</code> numbers the panels in order; <code>"A"</code>, <code>"a"</code>, <code>"I"</code>, and <code>"i"</code> give the other styles.</span>
</dd>
<dt data-target-cell="annotated-cell-1" data-target-annotation="4">4</dt>
<dd>
<span data-code-cell="annotated-cell-1" data-code-lines="18" data-code-annotation="4">The <code>default</code> guide sets the legend side once, and every aesthetic without its own guide inherits it.</span>
</dd>
<dt data-target-cell="annotated-cell-1" data-target-annotation="5">5</dt>
<dd>
<span data-code-cell="annotated-cell-1" data-code-lines="19" data-code-annotation="5">A composition-level <code>labs()</code> writes one title above the whole figure.</span>
</dd>
</dl>
</div>
<div>
<div class="light-content">
<div style="text-align: center;">
<p><a href="../../posts/2026-06-03-gribouille-0-2/./assets/typst-render/compose-light.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-2" title="Two side-by-side scatter plots tagged (1) Body Mass and (2) Bill Length against flipper length, points coloured by species, with a single shared species legend below both panels and a title across the top reading 'Two Views, One Shared Legend'."><img src="https://mickael.canouil.fr/posts/2026-06-03-gribouille-0-2/assets/typst-render/compose-light.svg" class="img-fluid hero-art" alt="Two side-by-side scatter plots tagged (1) Body Mass and (2) Bill Length against flipper length, points coloured by species, with a single shared species legend below both panels and a title across the top reading 'Two Views, One Shared Legend'."></a></p>
</div>
</div>
<div class="dark-content">
<div style="text-align: center;">
<p><a href="../../posts/2026-06-03-gribouille-0-2/./assets/typst-render/compose-dark.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-3" title="Two side-by-side scatter plots tagged (1) Body Mass and (2) Bill Length against flipper length, points coloured by species, with a single shared species legend below both panels and a title across the top reading 'Two Views, One Shared Legend'."><img src="https://mickael.canouil.fr/posts/2026-06-03-gribouille-0-2/assets/typst-render/compose-dark.svg" class="img-fluid hero-art" alt="Two side-by-side scatter plots tagged (1) Body Mass and (2) Bill Length against flipper length, points coloured by species, with a single shared species legend below both panels and a title across the top reading 'Two Views, One Shared Legend'."></a></p>
</div>
</div>
</div>
</div>
<p>Three things here are new. <code>tag-levels</code>, with <code>tag-prefix</code>, <code>tag-suffix</code>, and <code>tag-corner</code>, labels each panel and styles the tag through a new <code>plot-tag</code> theme element. A composition now accepts its own <code>labs</code> (title, subtitle, caption) and <code>alt</code> text, so the figure reads as one unit. The shared legend is steered through <code>guides</code> instead of the old <code>guides-placement</code> argument, which is removed.</p>
<div class="callout callout-style-default callout-tip callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Tip</span>Nesting
</div>
</div>
<div class="callout-body-container callout-body">
<p>A <code>compose()</code> call also accepts <code>defer: true</code>, so it can become a panel inside another <code>compose()</code>. A per-depth <code>tag-levels</code> array then continues the numbering down the tree, giving tags such as <code>B.1</code> and <code>B.2</code>.</p>
</div>
</div>
<p>The 0.2.1 release adds one more control and tidies the tags. By default each panel keeps its own margins. When one panel carries wide axis labels and another panel carries narrow ones, their plot areas start at different offsets and never quite line up. <code>align-panels: true</code> shares the margins grid-wise, left and right per column, top and bottom per row, so the data areas align across the grid the way <code>patchwork</code> and <code>cowplot</code> do. The panel tags also reserve their own band now, sitting above each panel instead of overlapping the data.</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="annotated-cell-2" data-filename="typst" style="background: #f1f3f5;"><pre class="sourceCode typst cw-auto code-annotation-code code-with-copy code-annotated"><code class="sourceCode typst"><span id="annotated-cell-2-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">panel</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> title<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> plot<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-2-2">  data<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> penguins<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-2-3">  mapping<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> aes<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"flipper-len"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> colour<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"species"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-2-4">  layers<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>geom-point<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> alpha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.85</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),),</span></span>
<span id="annotated-cell-2-5">  labs<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> labs<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>title<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> title<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">none</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-2-6">  theme<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> theme-minimal<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(),</span></span>
<span id="annotated-cell-2-7">  defer<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">true</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-2-8"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-2-9"></span>
<span id="annotated-cell-2-10"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>compose<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-2-11">  panel<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"body-mass"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Body Mass (g)"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-2-12">  panel<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"bill-len"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Bill Length (mm)"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-2-13">  columns<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-2" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-2-14" class="code-annotation-target">  align-panels<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">true</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-2" data-target-annotation="2" onclick="event.preventDefault();">2</a><span id="annotated-cell-2-15" class="code-annotation-target">  tag-levels<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"a"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-2-16">  guides<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> guides<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>default<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> guide-legend<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>position<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"bottom"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)),</span></span>
<span id="annotated-cell-2-17">  width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">12cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-2-18">  height<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">14cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-2-19"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
<div class="cell-annotation">
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-2" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-2" data-code-lines="14" data-code-annotation="1"><code>align-panels: true</code> makes the two plot areas share a left edge, even though the four-digit body-mass labels are wider than the two-digit bill-length labels.</span>
</dd>
<dt data-target-cell="annotated-cell-2" data-target-annotation="2">2</dt>
<dd>
<span data-code-cell="annotated-cell-2" data-code-lines="15" data-code-annotation="2">The tags now reserve their own band above each panel instead of overlapping the data.</span>
</dd>
</dl>
</div>
<div>
<div class="light-content">
<div style="text-align: center;">
<p><a href="../../posts/2026-06-03-gribouille-0-2/./assets/typst-render/align-panels-light.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-4" title="Two stacked scatter panels tagged (a) and (b): body mass with four-digit y labels above bill length with two-digit y labels, both against flipper length and coloured by species. With align-panels on, the two plot areas share a left edge despite the different label widths, and the tags sit in a band above each panel rather than over the data. A shared species legend runs along the bottom."><img src="https://mickael.canouil.fr/posts/2026-06-03-gribouille-0-2/assets/typst-render/align-panels-light.svg" class="img-fluid hero-art" alt="Two stacked scatter panels tagged (a) and (b): body mass with four-digit y labels above bill length with two-digit y labels, both against flipper length and coloured by species. With align-panels on, the two plot areas share a left edge despite the different label widths, and the tags sit in a band above each panel rather than over the data. A shared species legend runs along the bottom."></a></p>
</div>
</div>
<div class="dark-content">
<div style="text-align: center;">
<p><a href="../../posts/2026-06-03-gribouille-0-2/./assets/typst-render/align-panels-dark.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-5" title="Two stacked scatter panels tagged (a) and (b): body mass with four-digit y labels above bill length with two-digit y labels, both against flipper length and coloured by species. With align-panels on, the two plot areas share a left edge despite the different label widths, and the tags sit in a band above each panel rather than over the data. A shared species legend runs along the bottom."><img src="https://mickael.canouil.fr/posts/2026-06-03-gribouille-0-2/assets/typst-render/align-panels-dark.svg" class="img-fluid hero-art" alt="Two stacked scatter panels tagged (a) and (b): body mass with four-digit y labels above bill length with two-digit y labels, both against flipper length and coloured by species. With align-panels on, the two plot areas share a left edge despite the different label widths, and the tags sit in a band above each panel rather than over the data. A shared species legend runs along the bottom."></a></p>
</div>
</div>
</div>
</div>
</section>
<section id="free-the-facet-scales" class="level2" data-number="3">
<h2 data-number="3" class="anchored" data-anchor-id="free-the-facet-scales"><span class="header-section-number">3</span> Free the facet scales</h2>
<p><a href="https://m.canouil.dev/gribouille/reference/facets/facet-grid.html"><code>facet-grid()</code></a> used to share one pair of axes across every panel. It now takes a <code>scales</code> argument, matching <code>ggplot2</code>: <code>"free_x"</code> frees the x-axis per column, <code>"free_y"</code> frees the y-axis per row, and <code>"free"</code> frees both. Non-positional scales such as colour stay shared, and the panels keep an equal size.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" data-filename="typst" style="background: #f1f3f5;"><pre class="sourceCode typst cw-auto code-with-copy"><code class="sourceCode typst"><span id="cb1-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>plot<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb1-2">  data<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> penguins<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-3">  mapping<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> aes<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"flipper-len"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"body-mass"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> colour<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"species"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb1-4">  layers<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>geom-point<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> alpha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.7</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),),</span></span>
<span id="cb1-5">  facet<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> facet-grid<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>rows<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"species"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> scales<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"free_y"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb1-6">  theme<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> theme-minimal<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(),</span></span>
<span id="cb1-7">  width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">12cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-8">  height<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">14cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-9"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span></code></pre></div></div>
<div>
<div class="light-content">
<div style="text-align: center;">
<p><a href="../../posts/2026-06-03-gribouille-0-2/./assets/typst-render/facet-free-light.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-6" title="Three stacked panels, one per species (Adelie, Chinstrap, Gentoo), each a scatter of body mass against flipper length with its own y-axis range, sharing the colour scale."><img src="https://mickael.canouil.fr/posts/2026-06-03-gribouille-0-2/assets/typst-render/facet-free-light.svg" class="img-fluid hero-art" alt="Three stacked panels, one per species (Adelie, Chinstrap, Gentoo), each a scatter of body mass against flipper length with its own y-axis range, sharing the colour scale."></a></p>
</div>
</div>
<div class="dark-content">
<div style="text-align: center;">
<p><a href="../../posts/2026-06-03-gribouille-0-2/./assets/typst-render/facet-free-dark.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-7" title="Three stacked panels, one per species (Adelie, Chinstrap, Gentoo), each a scatter of body mass against flipper length with its own y-axis range, sharing the colour scale."><img src="https://mickael.canouil.fr/posts/2026-06-03-gribouille-0-2/assets/typst-render/facet-free-dark.svg" class="img-fluid hero-art" alt="Three stacked panels, one per species (Adelie, Chinstrap, Gentoo), each a scatter of body mass against flipper length with its own y-axis range, sharing the colour scale."></a></p>
</div>
</div>
</div>
<p>Each row now picks its own y-range, so a group sitting in a narrow band no longer wastes most of its panel on empty space.</p>
</section>
<section id="highlight-a-subset-without-a-second-dataset" class="level2" data-number="4">
<h2 data-number="4" class="anchored" data-anchor-id="highlight-a-subset-without-a-second-dataset"><span class="header-section-number">4</span> Highlight a subset without a second dataset</h2>
<p>A layer’s <code>data</code> now takes a function as well as a frame. The function receives the plot data and returns the rows that layer should draw. So one dataset can feed a faint base layer and a sharp highlight on top, with no second table to keep in step.</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="annotated-cell-4" data-filename="typst" style="background: #f1f3f5;"><pre class="sourceCode typst cw-auto code-annotation-code code-with-copy code-annotated"><code class="sourceCode typst"><span id="annotated-cell-4-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>plot<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-4-2">  data<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> penguins<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-4-3">  mapping<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> aes<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"flipper-len"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"body-mass"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-4-4">  layers<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-4-5">    geom-point<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> alpha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.35</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-4-6">    geom-point<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-4" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-4-7" class="code-annotation-target">      data<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> rows <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=&gt;</span> rows<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>filter<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>r <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=&gt;</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="annotated-cell-4-8">        <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">mass</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> r<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>at<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"body-mass"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> default<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">none</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-4-9">        mass <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">!=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">none</span> and mass <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5500</span></span>
<span id="annotated-cell-4-10">      <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}),</span></span>
<span id="annotated-cell-4-11">      size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">3pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-4-12">      colour<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> rgb<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#D55E00"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-4-13">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-4-14">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-4-15">  labs<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> labs<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-4-16">    title<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"One Dataset, One Layer Highlighted"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-4-17">    x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Flipper Length (mm)"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-4-18">    y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Body Mass (g)"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-4-19">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-4-20">  theme<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> theme-minimal<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(),</span></span>
<span id="annotated-cell-4-21">  width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">12cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-4-22">  height<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">8cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-4-23"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
<div class="cell-annotation">
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-4" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-4" data-code-lines="7" data-code-annotation="1">The function gets the plot data as an array of rows and returns the subset to draw, here the penguins at 5,500 grams and above.</span>
</dd>
</dl>
</div>
<div>
<div class="light-content">
<div style="text-align: center;">
<p><a href="../../posts/2026-06-03-gribouille-0-2/./assets/typst-render/layer-data-light.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-8" title="Penguin scatter of body mass against flipper length, every point faint, with the heaviest individuals at 5,500 grams and above re-drawn as larger orange markers."><img src="https://mickael.canouil.fr/posts/2026-06-03-gribouille-0-2/assets/typst-render/layer-data-light.svg" class="img-fluid hero-art" alt="Penguin scatter of body mass against flipper length, every point faint, with the heaviest individuals at 5,500 grams and above re-drawn as larger orange markers."></a></p>
</div>
</div>
<div class="dark-content">
<div style="text-align: center;">
<p><a href="../../posts/2026-06-03-gribouille-0-2/./assets/typst-render/layer-data-dark.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-9" title="Penguin scatter of body mass against flipper length, every point faint, with the heaviest individuals at 5,500 grams and above re-drawn as larger orange markers."><img src="https://mickael.canouil.fr/posts/2026-06-03-gribouille-0-2/assets/typst-render/layer-data-dark.svg" class="img-fluid hero-art" alt="Penguin scatter of body mass against flipper length, every point faint, with the heaviest individuals at 5,500 grams and above re-drawn as larger orange markers."></a></p>
</div>
</div>
</div>
</div>
<p>The same trick covers per-layer transforms, not just filtering. Return a reshaped frame and that layer draws it, while the rest of the plot keeps the original data.</p>
</section>
<section id="tidier-labels-and-guides" class="level2" data-number="5">
<h2 data-number="5" class="anchored" data-anchor-id="tidier-labels-and-guides"><span class="header-section-number">5</span> Tidier labels and guides</h2>
<p>Two smaller changes remove a recurring annoyance.</p>
<p>First, every <a href="https://m.canouil.dev/gribouille/reference/labs/labs.html"><code>labs()</code></a> field defaults to <code>auto</code>. Passing <code>none</code> now drops an axis or legend title and reclaims the margin it reserved, instead of leaving an empty gap. <a href="https://m.canouil.dev/gribouille/reference/themes/element-blank.html"><code>element-blank()</code></a> on a text surface does the same from the theme side.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb2" data-filename="typst" style="background: #f1f3f5;"><pre class="sourceCode typst cw-auto code-with-copy"><code class="sourceCode typst"><span id="cb2-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>plot<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb2-2">  data<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> penguins<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb2-3">  mapping<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> aes<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"flipper-len"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"body-mass"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb2-4">  layers<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>geom-point<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> alpha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.7</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),),</span></span>
<span id="cb2-5">  labs<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> labs<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb2-6">    title<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Axis Titles Off, Space Reclaimed"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb2-7">    x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">none</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb2-8">    y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">none</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb2-9">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb2-10">  theme<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> theme-minimal<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(),</span></span>
<span id="cb2-11">  width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">12cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb2-12">  height<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">8cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb2-13"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span></code></pre></div></div>
<div>
<div class="light-content">
<div style="text-align: center;">
<p><a href="../../posts/2026-06-03-gribouille-0-2/./assets/typst-render/labs-none-light.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-10" title="A scatter of body mass against flipper length with no axis titles, the plot area reaching closer to the edges, under a single bold title."><img src="https://mickael.canouil.fr/posts/2026-06-03-gribouille-0-2/assets/typst-render/labs-none-light.svg" class="img-fluid hero-art" alt="A scatter of body mass against flipper length with no axis titles, the plot area reaching closer to the edges, under a single bold title."></a></p>
</div>
</div>
<div class="dark-content">
<div style="text-align: center;">
<p><a href="../../posts/2026-06-03-gribouille-0-2/./assets/typst-render/labs-none-dark.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-11" title="A scatter of body mass against flipper length with no axis titles, the plot area reaching closer to the edges, under a single bold title."><img src="https://mickael.canouil.fr/posts/2026-06-03-gribouille-0-2/assets/typst-render/labs-none-dark.svg" class="img-fluid hero-art" alt="A scatter of body mass against flipper length with no axis titles, the plot area reaching closer to the edges, under a single bold title."></a></p>
</div>
</div>
</div>
<p>Second, <a href="https://m.canouil.dev/gribouille/reference/guides/guides.html"><code>guides()</code></a> takes a <code>default</code> entry. It holds the fallback guide options, the legend side most often, and any aesthetic without its own guide inherits from it. You saw it above feeding the shared legend in <code>compose()</code>; it works the same on a single <code>plot()</code>. Legend labels also honour the <code>legend-text</code> alignment now, and <a href="https://m.canouil.dev/gribouille/reference/themes/element-text.html"><code>element-text()</code></a>/<a href="https://m.canouil.dev/gribouille/reference/themes/element-typst.html"><code>element-typst()</code></a> gained an <code>align</code> argument and a working <code>angle</code>, so tick labels and titles rotate as asked.</p>
</section>
<section id="the-heavy-lifting-was-underneath" class="level2" data-number="6">
<h2 data-number="6" class="anchored" data-anchor-id="the-heavy-lifting-was-underneath"><span class="header-section-number">6</span> The heavy lifting was underneath</h2>
<p>The list above is the visible part. The larger share of this release is a pass over the internals, and it is the kind of work that does not show up in a screenshot.</p>
<p>A whole class of inputs that used to crash the compile now behaves. A <code>geom-col()</code> on a continuous axis with a single distinct value, an empty <code>values</code> array in a <code>scale-*-manual()</code>, a non-positive <code>bins</code>, a sparse contour grid, or <code>width: auto</code> on an unbounded page: each of these used to panic. Each now either draws or reports a clear, actionable error.</p>
<div class="highlight">
<p><strong>Most of the 0.2 work is invisible:</strong> the figures look the same, but far fewer inputs make the compiler give up.</p>
</div>
<p>A second group is about getting the right answer rather than not crashing.</p>
<ul>
<li><code>after-scale</code> and <code>stage</code> channels now train on the marker’s source column, so grouped geoms such as <a href="https://m.canouil.dev/gribouille/reference/geoms/geom-smooth.html"><code>geom-smooth()</code></a>, and <a href="https://m.canouil.dev/gribouille/reference/geoms/geom-errorbar.html"><code>geom-errorbar()</code></a> or <a href="https://m.canouil.dev/gribouille/reference/geoms/geom-rug.html"><code>geom-rug()</code></a>, resolve a mapped colour instead of panicking.</li>
<li><a href="https://m.canouil.dev/gribouille/reference/coord/coord-cartesian.html"><code>coord-cartesian(xlim:, ylim:)</code></a> zooms the axes instead of being ignored, and <a href="https://m.canouil.dev/gribouille/reference/coord/coord-flip.html"><code>coord-flip(reverse: true)</code></a> keeps a <code>log10</code> or <code>sqrt</code> transform.</li>
<li>A column mapped to both a positional and a grouping aesthetic, for example <code>aes(x: "species", fill: "species")</code>, now resolves the grouping across aggregating stats. Previously it drew every mark in the ink colour with an empty guide.</li>
<li>Number formatters round correctly, so <code>0.9999995</code> no longer renders as <code>0.1</code>, and continuous scales honour an explicit <code>breaks</code>.</li>
<li><a href="https://m.canouil.dev/gribouille/reference/geoms/geom-segment.html"><code>geom-segment()</code></a>, <a href="https://m.canouil.dev/gribouille/reference/geoms/geom-curve.html"><code>geom-curve()</code></a>, <a href="https://m.canouil.dev/gribouille/reference/geoms/geom-ribbon.html"><code>geom-ribbon()</code></a>, and <a href="https://m.canouil.dev/gribouille/reference/geoms/geom-area.html"><code>geom-area()</code></a> draw on a discrete scale instead of silently rendering nothing (0.2.1).</li>
</ul>
<p>A third group, and a big one, is about layout space: what a plot reserves, and what it reclaims when it is not needed. The <code>labs(none)</code> and <code>element-blank()</code> controls shown above are the visible handles, but most of the work was below them, deciding how much room each part of a figure should take.</p>
<ul>
<li>A standalone plot no longer keeps a fixed empty margin on its top and right edges; the panel grows into that space.</li>
<li>A missing axis title reclaims the band it would have reserved, on the x-axis side as well as the y-axis side.</li>
<li><code>width</code> and <code>height</code> now bound the whole image, title, subtitle, caption, and background padding included, so the data panel shrinks to fit and long titles wrap instead of overflowing.</li>
<li>Legends reserve the room a label actually needs, measuring multi-line and wider-than-usual labels across swatches, size ladders, and colourbars, so nothing clips or overlaps.</li>
</ul>
<p>None of this changes the API. Documents that compiled before still compile, and the plots that were already correct look the same. What changed is the long tail of inputs that did not.</p>
</section>
<section id="typst-render-moved-too" class="level2" data-number="7">
<h2 data-number="7" class="anchored" data-anchor-id="typst-render-moved-too"><span class="header-section-number">7</span> Typst Render moved too</h2>
<p>The figures here are compiled by the <a href="https://github.com/mcanouil/quarto-typst-render"><code>quarto-typst-render</code></a> extension, which had its own run of releases since the launch post. Three are worth knowing about for a documentation-heavy project.</p>
<ul>
<li><code>code-fold</code> and <code>code-summary</code> collapse the echoed Typst source into a <code>&lt;details&gt;</code> block, so a long example does not push the figure off the screen.</li>
<li>Quarto code annotations now work on echoed Typst source: the <code>// &lt;1&gt;</code> markers and the numbered list under the <code>compose()</code> block above are exactly that feature.</li>
<li><code>output-source</code> writes the compiled Typst source, preamble and colour bindings included, next to each saved image, which is handy when you want to reproduce a figure outside Quarto.</li>
</ul>
</section>
<section id="wrap-up" class="level2" data-number="8">
<h2 data-number="8" class="anchored" data-anchor-id="wrap-up"><span class="header-section-number">8</span> Wrap-up</h2>
<div class="highlight">
<p><strong>A composition language on top, a sturdier core underneath.</strong></p>
</div>
<p>Next on the list is more geoms, a wider set of worked examples, and feedback from the people writing Typst documents day to day.</p>
<ul>
<li>Gribouille
<ul>
<li>Repository: <a href="https://github.com/mcanouil/gribouille" class="uri">https://github.com/mcanouil/gribouille</a>.</li>
<li>Documentation: <a href="https://m.canouil.dev/gribouille" class="uri">https://m.canouil.dev/gribouille</a>.</li>
<li>Typst Universe: <a href="https://typst.app/universe/package/gribouille" class="uri">https://typst.app/universe/package/gribouille</a>.</li>
</ul></li>
<li>Typst Render
<ul>
<li>Repository: <a href="https://github.com/mcanouil/quarto-typst-render" class="uri">https://github.com/mcanouil/quarto-typst-render</a>.</li>
<li>Documentation: <a href="https://m.canouil.dev/quarto-typst-render" class="uri">https://m.canouil.dev/quarto-typst-render</a>.</li>
</ul></li>
</ul>
<div class="callout callout-style-default callout-tip callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Tip</span>A note on contributions
</div>
</div>
<div class="callout-body-container callout-body">
<p>Gribouille is an unfunded spare-time project, and the API is still settling. Bug reports and ideas are very welcome on the issue tracker. Pull requests are not being accepted for now, for the reasons set out in the launch post. Thanks in advance for your patience.</p>
</div>
</div>


</section>

<a onclick="window.scrollTo(0, 0); return false;" id="quarto-back-to-top"><i class="bi bi-arrow-up"></i> Back to top</a><div id="quarto-appendix" class="default"><section class="quarto-appendix-contents" id="quarto-reuse"><h2 class="anchored quarto-appendix-heading">Reuse</h2><div class="quarto-appendix-contents"><div><a rel="license" href="https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en_gb">CC BY-NC-SA 4.0</a></div></div></section><section class="quarto-appendix-contents" id="quarto-citation"><h2 class="anchored quarto-appendix-heading">Citation</h2><div><div class="quarto-appendix-secondary-label">BibTeX citation:</div><pre class="sourceCode code-with-copy quarto-appendix-bibtex"><code class="sourceCode bibtex">@misc{canouil2026,
  author = {CANOUIL, Mickaël},
  title = {Gribouille 0.2.0 and 0.2.1: {Composing} {Plots} and a
    {Sturdier} {Core}},
  date = {2026-06-03},
  url = {https://mickael.canouil.fr/posts/2026-06-03-gribouille-0-2/},
  langid = {en-GB}
}
</code></pre><div class="quarto-appendix-secondary-label">For attribution, please cite this work as:</div><div id="ref-canouil2026" class="csl-entry quarto-appendix-citeas">
CANOUIL, M. (2026-06-03). Gribouille 0.2.0 and 0.2.1: Composing Plots
and a Sturdier Core. <em>Mickael.canouil.fr</em>. <a href="https://mickael.canouil.fr/posts/2026-06-03-gribouille-0-2/">https://mickael.canouil.fr/posts/2026-06-03-gribouille-0-2/</a>
</div></div></section></div> ]]></description>
  <category>typst</category>
  <category>quarto</category>
  <category>grammar-of-graphics</category>
  <category>gribouille</category>
  <guid>https://mickael.canouil.fr/posts/2026-06-03-gribouille-0-2/</guid>
  <pubDate>Wed, 03 Jun 2026 00:00:00 GMT</pubDate>
  <media:content url="https://mickael.canouil.fr/posts/2026-06-03-gribouille-0-2/featured.png" medium="image" type="image/png" height="76" width="144"/>
</item>
<item>
  <title>Build LinkedIn Carousels with Typst: the slide Layout</title>
  <link>https://mickael.canouil.fr/posts/2026-05-28-typst-linkedin-carousels/</link>
  <description><![CDATA[ 

<!--
@license MIT
@copyright 2026 Mickaël Canouil
@author Mickaël Canouil
-->
Skip to main content





<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="featured.png" class="lightbox" data-gallery="quarto-lightbox-gallery-1"><img src="https://mickael.canouil.fr/posts/2026-05-28-typst-linkedin-carousels/featured.png" class="img-featured img-fluid quarto-figure quarto-figure-center figure-img" alt="Wide social card on a pale grey-green background with a slim emerald
rail down the left edge. A ruled monospace kicker reads &quot;TYPST ·
TUTORIAL&quot; above large display type that spells &quot;Build LinkedIn Carousels
with Typst&quot;, the word &quot;Typst&quot; in emerald, then a smaller line &quot;The Slide
Layout&quot;. Stacked rounded cards form a motif on the right.
" width="600"></a></p>
</figure>
</div>
<section id="introduction" class="level2">
<h2 class="anchored" data-anchor-id="introduction">Introduction</h2>
<p>Since January 2026 I have made the <a href="https://www.linkedin.com/in/mickaelcanouil/recent-activity/all/">carousels for my LinkedIn posts</a> with Typst. It has worked well, so here is how I do it.</p>
<p>A LinkedIn carousel is not a special thing. It is a multi-page document that the feed turns into swipeable cards, and you upload it as a single PDF. So if you can make a multi-page PDF, you can make a carousel.</p>
<p><a href="https://typst.app">Typst</a> is a fine tool for exactly that. You describe each page in plain text, compile to PDF in a fraction of a second, and keep the source under version control like any other code. No design tool, no manual export, no moving boxes around by hand.</p>
<p>This post shows the one helper I use to make this easy: a small <code>slide</code> helper that gives every page a full-bleed background with consistent padding. Every slide shown below is a real Typst compilation, rendered to SVG by the <a href="https://github.com/mcanouil/quarto-typst-render"><code>Typst Render</code> Quarto extension</a>, so you see the code and its result side by side.</p>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Note</span>At a glance
</div>
</div>
<div class="callout-body-container callout-body">
<ul>
<li>This deck uses a square 1:1 page, the format LinkedIn shows carousels in.</li>
<li>Set the page margin to <code>0cm</code> so backgrounds reach every edge.</li>
<li>A <code>slide</code> layout adds a rail, a page number, and the padding to every card.</li>
<li>Compile with <code>typst compile</code>, then upload the PDF or export PNG frames.</li>
</ul>
</div>
</div>
</section>
<section id="setting-up-the-page" class="level2">
<h2 class="anchored" data-anchor-id="setting-up-the-page">Setting up the page</h2>
<p>Start by setting the page geometry once, at the top of the file.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" data-filename="typst" style="background: #f1f3f5;"><pre class="sourceCode typst cw-auto code-with-copy"><code class="sourceCode typst"><span id="cb1-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#set</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">document</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>title<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"My Carousel"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> author<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Your Name"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb1-2"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#set</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">text</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>lang<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"en"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb1-3"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#set</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">page</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">21cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> height<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">21cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> margin<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span></code></pre></div></div>
<p>The 21cm by 21cm size is a square 1:1. The <a href="https://www.linkedin.com/pulse/linkedin-image-size-guide-2026-jan-van-musscher-wslwe/">LinkedIn image size guide</a> recommends carousels as a square 1080 by 1080, or a 4:5 portrait. The exact dimensions do not matter, only the ratio does, so any square pair works.</p>
<p>The important line is <code>margin: 0cm</code>.</p>
<div class="highlight">
<p><strong>Zero page margin is the trick:</strong> the background reaches every edge, and the <code>slide</code> helper adds the padding back inside.</p>
</div>
<p>Carousel slides want colour and imagery that reach every edge, and a page margin would fence the content into a smaller box with white gutters. Removing the margin gives you the full page, and you add the padding back yourself. That is what the <code>slide</code> helper does.</p>
<p>For convenience, I keep the page geometry, a small palette, the fonts, and the <code>slide</code> layout in a shared preamble file.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb2" data-filename="typst" style="background: #f1f3f5;"><pre class="sourceCode typst cw-auto code-with-copy"><code class="sourceCode typst"><span id="cb2-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#set</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">document</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>author<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Mickaël Canouil"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb2-2"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#set</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">text</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>lang<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"en"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb2-3"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#set</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">page</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">21cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> height<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">21cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> margin<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb2-4"></span>
<span id="cb2-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// ── palette ──────────────────────────────────────────────────────────────</span></span>
<span id="cb2-6"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">paper</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> rgb<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#f4f6f5"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb2-7"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">ink</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> rgb<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#0f1f1c"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb2-8"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">ink-soft</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> rgb<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#5a6b66"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb2-9"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">accent</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> rgb<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#1f9d72"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb2-10"></span>
<span id="cb2-11"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// ── fonts (Typst built-ins, identical on every machine and in CI) ──────────</span></span>
<span id="cb2-12"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">display</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Libertinus Serif"</span></span>
<span id="cb2-13"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">body</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Libertinus Serif"</span></span>
<span id="cb2-14"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">mono</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"DejaVu Sans Mono"</span></span>
<span id="cb2-15"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#set</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">text</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>font<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> body<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> ink<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">19pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb2-16"></span>
<span id="cb2-17"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// ── the slide layout taught in the post ────────────────────────────────────</span></span>
<span id="cb2-18"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// A full-height accent rail on the left edge and an optional page index,</span></span>
<span id="cb2-19"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// stamped on every page; content sits in an inset cleared past the rail.</span></span>
<span id="cb2-20"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">slide</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>index<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">none</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> paper<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> body<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> page<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb2-21">  place<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>left <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> top<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> rect<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.55cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> height<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">100%</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> accent<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">))</span></span>
<span id="cb2-22">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> index <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">!=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">none</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb2-23">    place<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>top <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> right<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> dx<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.4cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> dy<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.4cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> text<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>font<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> mono<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">14pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> ink-soft<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> index<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">))</span></span>
<span id="cb2-24">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb2-25">  block<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">100%</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> height<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">100%</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> inset<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>left: <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2.6cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> right: <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.7cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> y: <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.7cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span> body<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb2-26"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">})</span></span>
<span id="cb2-27"></span>
<span id="cb2-28"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// ── ruled kicker label used by the examples ────────────────────────────────</span></span>
<span id="cb2-29"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">kicker</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>t<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> grid<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb2-30">  columns<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.8cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">auto</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb2-31">  column-gutter<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.35cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb2-32">  align<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> horizon<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb2-33">  line<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>length<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.8cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> stroke<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2pt</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> accent<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb2-34">  text<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>font<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> mono<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">13pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> weight<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"medium"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> ink-soft<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> tracking<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.5pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> upper<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>t<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)),</span></span>
<span id="cb2-35"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span></code></pre></div></div>
</section>
<section id="the-slide-layout" class="level2">
<h2 class="anchored" data-anchor-id="the-slide-layout">The <code>slide</code> layout</h2>
<p>A <code>slide</code> helper can do more than add padding. It can also draw the parts that repeat on every card. The layout below draws a full-height accent rail down the left edge, adds a page number in the top-right corner, then lays out your content in the space that is left. Here it is, defined and then called with an empty body so you can see the rail and the padded area where <code>body</code> goes.</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="annotated-cell-3" data-filename="typst" style="background: #f1f3f5;"><pre class="sourceCode typst cw-auto code-annotation-code code-with-copy code-annotated"><code class="sourceCode typst"><a class="code-annotation-anchor" data-target-cell="annotated-cell-3" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-3-1" class="code-annotation-target"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">slide</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>index<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">none</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> paper<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> body<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> page<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-3" data-target-annotation="2" onclick="event.preventDefault();">2</a><span id="annotated-cell-3-2" class="code-annotation-target">  place<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>left <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> top<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> rect<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.55cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> height<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">100%</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> accent<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">))</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-3" data-target-annotation="3" onclick="event.preventDefault();">3</a><span id="annotated-cell-3-3" class="code-annotation-target">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> index <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">!=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">none</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="annotated-cell-3-4">    place<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-3-5">      top <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> right<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> dx<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.4cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> dy<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.4cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-3-6">      text<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>font<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> mono<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">14pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> ink-soft<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> index<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-3-7">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-3-8">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-3" data-target-annotation="4" onclick="event.preventDefault();">4</a><span id="annotated-cell-3-9" class="code-annotation-target">  block<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-3-10">    width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">100%</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> height<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">100%</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> inset<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>left: <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2.6cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> right: <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.7cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> y: <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.7cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-3-11">    body</span>
<span id="annotated-cell-3-12">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-3" data-target-annotation="5" onclick="event.preventDefault();">5</a><span id="annotated-cell-3-13" class="code-annotation-target"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">})</span></span>
<span id="annotated-cell-3-14"></span>
<span id="annotated-cell-3-15"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>slide<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">({</span></span>
<span id="annotated-cell-3-16">  block<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-3-17">    width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">100%</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> height<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">100%</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-3-18">    stroke<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>paint: ink-soft<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> thickness: <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> dash: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dashed"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-3-19">    radius<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">8pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-3-20">    align<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>center <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> horizon<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> text<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>font<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> mono<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> ink-soft<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"body goes here"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)),</span></span>
<span id="annotated-cell-3-21">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-3-22"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">})</span></span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
<div class="cell-annotation">
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-3" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-3" data-code-lines="1" data-code-annotation="1">Start a fresh page and paint its background with <code>fill</code>, so each call is one card in the carousel.</span>
</dd>
<dt data-target-cell="annotated-cell-3" data-target-annotation="2">2</dt>
<dd>
<span data-code-cell="annotated-cell-3" data-code-lines="2" data-code-annotation="2">Draw the rail: a thin rectangle pinned to the left edge, the full height of the page. With <code>margin: 0cm</code> it reaches the top, bottom, and side.</span>
</dd>
<dt data-target-cell="annotated-cell-3" data-target-annotation="3">3</dt>
<dd>
<span data-code-cell="annotated-cell-3" data-code-lines="3" data-code-annotation="3">Add a small page number like <code>2 / 3</code> in the top-right, but only when an <code>index</code> is passed.</span>
</dd>
<dt data-target-cell="annotated-cell-3" data-target-annotation="4">4</dt>
<dd>
<span data-code-cell="annotated-cell-3" data-code-lines="9,10,11,12" data-code-annotation="4">Add back the padding that <code>margin: 0cm</code> removed, with a wider left inset so the content clears the rail.</span>
</dd>
<dt data-target-cell="annotated-cell-3" data-target-annotation="5">5</dt>
<dd>
<span data-code-cell="annotated-cell-3" data-code-lines="13" data-code-annotation="5"><code>body</code> is a trailing content argument, so the helper is called as <code>#slide({ ... })</code> with the card’s content in the braces.</span>
</dd>
</dl>
</div>
<p><a href="../../posts/2026-05-28-typst-linkedin-carousels/./assets/typst-render/layout.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-2" title="An otherwise empty slide showing what the layout draws on its own: the emerald rail down the left edge and a dashed outline marking the padded content area where the body is placed."><img src="https://mickael.canouil.fr/posts/2026-05-28-typst-linkedin-carousels/assets/typst-render/layout.svg" class="img-fluid hero-art w-50 mx-auto d-block" alt="An otherwise empty slide showing what the layout draws on its own: the emerald rail down the left edge and a dashed outline marking the padded content area where the body is placed."></a></p>
</div>
<p>The helper is optional: a bare <code>#page(...)</code> would work too. Defining <code>slide</code> once is what keeps the rail, the page number, and the padding identical on every card, and every call site short.</p>
<div class="highlight">
<p><strong>Define the layout once, and every card matches</strong> without repeating the setup.</p>
</div>
<p>The page geometry, a small palette, the fonts, this <code>slide</code> layout, and a ruled <code>kicker</code> label are the file’s setup. In this post they live in a shared preamble, so each live example below shows only the card it draws.</p>
</section>
<section id="slides-live" class="level2">
<h2 class="anchored" data-anchor-id="slides-live">Slides, live</h2>
<p>The cover card passes an <code>index</code>, adds a <code>kicker</code> at the top, and centres the headline. The rail and the page number come from the layout.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb3" data-filename="typst" style="background: #f1f3f5;"><pre class="sourceCode typst cw-auto code-with-copy"><code class="sourceCode typst"><span id="cb3-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>slide<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>index<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"1 / 3"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb3-2">  kicker<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Typst · Carousel"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb3-3">  v<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1fr</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb3-4">  block<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">16cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb3-5">    text<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>font<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> display<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">54pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> weight<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"bold"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> ink<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Build decks"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb3-6">    linebreak<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">()</span></span>
<span id="cb3-7">    text<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>font<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> display<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">54pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> weight<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"bold"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> accent<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"from text"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb3-8">    v<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.4cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb3-9">    text<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">20pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> ink-soft<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"One layout, used on every card."</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb3-10">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">})</span></span>
<span id="cb3-11">  v<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1fr</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb3-12"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">})</span></span></code></pre></div></div>
<p><a href="../../posts/2026-05-28-typst-linkedin-carousels/./assets/typst-render/cover.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-3" title="Cover card with an emerald rail down the left edge, a '1 / 3' index top-right, a ruled 'TYPST · CAROUSEL' kicker, and a large two-line headline 'Build decks from text'."><img src="https://mickael.canouil.fr/posts/2026-05-28-typst-linkedin-carousels/assets/typst-render/cover.svg" class="img-fluid hero-art w-50 mx-auto d-block" alt="Cover card with an emerald rail down the left edge, a '1 / 3' index top-right, a ruled 'TYPST · CAROUSEL' kicker, and a large two-line headline 'Build decks from text'."></a></p>
<p>A content card stacks a few numbered rows with a <code>grid</code> to keep the numbers and text aligned.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb4" data-filename="typst" style="background: #f1f3f5;"><pre class="sourceCode typst cw-auto code-with-copy"><code class="sourceCode typst"><span id="cb4-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>slide<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>index<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2 / 3"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb4-2">  kicker<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Why Typst"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb4-3">  v<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb4-4">  block<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb4-5">    width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">16cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb4-6">    text<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb4-7">      font<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> display<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">32pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> weight<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"bold"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> ink<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb4-8">      <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Three reasons it fits carousels."</span></span>
<span id="cb4-9">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb4-10">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb4-11">  v<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.7cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb4-12"></span>
<span id="cb4-13">  <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">row</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>n<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> t<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> grid<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb4-14">    columns<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.3cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1fr</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb4-15">    align<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>left <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> horizon<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> left <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> horizon<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb4-16">    text<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>font<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> mono<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">22pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> weight<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"bold"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> accent<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> str<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>n<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)),</span></span>
<span id="cb4-17">    block<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">15cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> text<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">18pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> ink<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> t<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)),</span></span>
<span id="cb4-18">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb4-19"></span>
<span id="cb4-20">  stack<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb4-21">    dir<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> ttb<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb4-22">    spacing<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.7cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb4-23">    row<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Plain text in, PDF out. Diff it, version it, regenerate it."</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb4-24">    row<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"One layout sets the look of every page at once."</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb4-25">    row<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"No design tool, no manual export, no pixel pushing."</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb4-26">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb4-27"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">})</span></span></code></pre></div></div>
<p><a href="../../posts/2026-05-28-typst-linkedin-carousels/./assets/typst-render/content.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-4" title="Content card with the rail, a '2 / 3' index, the heading 'Three reasons it fits carousels.', and a numbered list of three short benefits."><img src="https://mickael.canouil.fr/posts/2026-05-28-typst-linkedin-carousels/assets/typst-render/content.svg" class="img-fluid hero-art w-50 mx-auto d-block" alt="Content card with the rail, a '2 / 3' index, the heading 'Three reasons it fits carousels.', and a numbered list of three short benefits."></a></p>
<p>The closing card changes the background with the <code>fill</code> parameter. The rail and the page number stay the same.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb5" data-filename="typst" style="background: #f1f3f5;"><pre class="sourceCode typst cw-auto code-with-copy"><code class="sourceCode typst"><span id="cb5-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>slide<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>index<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"3 / 3"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> ink<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb5-2">  v<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1fr</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb5-3">  kicker<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Ship it"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb5-4">  v<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb5-5">  text<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb5-6">    font<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> display<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">46pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> weight<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"bold"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> paper<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb5-7">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Compile and post."</span></span>
<span id="cb5-8">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb5-9">  v<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.6cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb5-10">  text<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb5-11">    font<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> mono<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">17pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> rgb<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#8fa49d"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb5-12">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"typst compile carousel.typ carousel.pdf"</span></span>
<span id="cb5-13">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb5-14">  v<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1fr</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb5-15"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">})</span></span></code></pre></div></div>
<p><a href="../../posts/2026-05-28-typst-linkedin-carousels/./assets/typst-render/cta.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-5" title="Closing card on a dark green-black background with the rail and a '3 / 3' index, reading 'Compile and post.' above the command to compile the carousel to PDF."><img src="https://mickael.canouil.fr/posts/2026-05-28-typst-linkedin-carousels/assets/typst-render/cta.svg" class="img-fluid hero-art w-50 mx-auto d-block" alt="Closing card on a dark green-black background with the rail and a '3 / 3' index, reading 'Compile and post.' above the command to compile the carousel to PDF."></a></p>
<p>Each card renders as a true square, so what you see is what the feed will show.</p>
</section>
<section id="compile-the-whole-deck" class="level2">
<h2 class="anchored" data-anchor-id="compile-the-whole-deck">Compile the whole deck</h2>
<p>Stack your slides in one <code>carousel.typ</code> file, then compile it. Each <code>#slide(...)</code> becomes one page, and the PDF is the carousel.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb6" data-filename="bash" style="background: #f1f3f5;"><pre class="sourceCode bash cw-auto code-with-copy"><code class="sourceCode bash"><span id="cb6-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Each #slide is one page; the PDF is the carousel</span></span>
<span id="cb6-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">typst</span> compile carousel.typ carousel.pdf</span>
<span id="cb6-3"></span>
<span id="cb6-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Typst can also export one PNG per page, no extra tool needed</span></span>
<span id="cb6-5"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">typst</span> compile carousel.typ <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"carousel-{p}.png"</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--ppi</span> 144</span></code></pre></div></div>
<p>Upload <code>carousel.pdf</code> to LinkedIn as a document post, and keep the PNG frames for a preview.</p>
</section>
<section id="want-a-gif-for-another-platform" class="level2">
<h2 class="anchored" data-anchor-id="want-a-gif-for-another-platform">Want a GIF for another platform?</h2>
<p>LinkedIn takes the PDF, but some places want a GIF. Build one from the PNG frames with <a href="https://imagemagick.org">ImageMagick</a>.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb7" data-filename="bash" style="background: #f1f3f5;"><pre class="sourceCode bash cw-auto code-with-copy"><code class="sourceCode bash"><span id="cb7-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Assemble the exported PNG frames into a looping GIF</span></span>
<span id="cb7-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">magick</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-delay</span> 250 <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-loop</span> 0 carousel-<span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">*</span>.png carousel.gif</span></code></pre></div></div>
<p><code>-delay 250</code> holds each frame for 2.5 seconds, and <code>-loop 0</code> loops forever.</p>
<p>Here is this post’s deck as a looping GIF.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="assets/carousel/carousel.gif" class="lightbox" data-gallery="quarto-lightbox-gallery-6"><img src="https://mickael.canouil.fr/posts/2026-05-28-typst-linkedin-carousels/assets/carousel/carousel.gif" class="hero-art img-fluid w-50 mx-auto d-block quarto-figure quarto-figure-center figure-img" alt="A looping GIF cycling through three square carousel cards: the cover 'Build decks from text', a 'Three reasons it fits carousels' list, and a dark closing 'Compile and post.' card, each with the emerald rail and a page number."></a></p>
</figure>
</div>
<div class="callout callout-style-default callout-tip callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Tip</span>A few design tips
</div>
</div>
<div class="callout-body-container callout-body">
<ul>
<li>Keep body text at 14pt or larger so it stays legible at thumbnail size.</li>
<li>Aim for five to seven slides: a cover, the content, and a closing call to action.</li>
<li>Use one visual idea per deck: a palette, a font pairing, a single motif.</li>
<li>Avoid copy-pasting the previous carousel, so each one looks different.</li>
</ul>
</div>
</div>
</section>
<section id="bonus-an-llm-prompt" class="level2">
<h2 class="anchored" data-anchor-id="bonus-an-llm-prompt">Bonus: an LLM prompt</h2>
<p>If you would rather start from a draft, paste the prompt below into your assistant of choice. It encodes the geometry, the <code>slide</code> helper, and the design constraints, so the output compiles with the <code>typst</code> CLI.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb8" data-filename="text" style="background: #f1f3f5;"><pre class="sourceCode default cw-auto code-with-copy"><code class="sourceCode default"><span id="cb8-1">You are designing a LinkedIn carousel as a Typst document.</span>
<span id="cb8-2">Produce a single self-contained carousel.typ that compiles with `typst compile`.</span>
<span id="cb8-3"></span>
<span id="cb8-4">Hard requirements:</span>
<span id="cb8-5"></span>
<span id="cb8-6">- Page geometry: #set page(width: 21cm, height: 21cm, margin: 0cm).</span>
<span id="cb8-7">  This is a square 1:1 (LinkedIn shows carousels at 1080 by 1080); keep the ratio.</span>
<span id="cb8-8">- Define one `slide` layout and use it for every card.</span>
<span id="cb8-9">  Build it from a full-page block with zero page margin and an inset, so the background reaches every edge and the content keeps consistent padding:</span>
<span id="cb8-10">    #let slide(fill: &lt;default-bg&gt;, body) = page(fill: fill, {</span>
<span id="cb8-11">      block(width: 100%, height: 100%, inset: 1.7cm, body)</span>
<span id="cb8-12">    })</span>
<span id="cb8-13">  Add any repeated parts you want on top of that (a header, a page number, ...).</span>
<span id="cb8-14">- Invent a small palette (background, ink, soft ink, one accent) and a font trio (display, body, mono).</span>
<span id="cb8-15">  Prefer legible fonts.</span>
<span id="cb8-16">- Five to seven cards following the arc: cover, then content, then a closing call to action.</span>
<span id="cb8-17">  Use one card with `fill: &lt;accent-or-dark&gt;` to invert the look.</span>
<span id="cb8-18">- Body text at least 14pt; headlines 30 to 56pt, so everything reads at thumbnail size.</span>
<span id="cb8-19">  Use place, grid, stack, and v()/h() for layout.</span>
<span id="cb8-20"></span>
<span id="cb8-21">Then tell me the two `typst compile` commands to export it to PDF and to one PNG per page.</span>
<span id="cb8-22"></span>
<span id="cb8-23">The topic of the carousel is: &lt;describe your announcement here&gt;.</span>
<span id="cb8-24">Layout and style (optional): &lt;describe any layout or look you want, for example a side rail, page numbers, a colour band, a serif or a sans display; leave blank to let you pick a clean design&gt;.</span></code></pre></div></div>
<div class="highlight">
<p><strong>It comes down to one idea:</strong> a single <code>slide</code> helper turns a blank page into a carousel page, and the rest is the content you put on it.</p>
</div>
<p>Now build a deck for your next post. If you make one, tag me on <a href="https://www.linkedin.com/in/mickaelcanouil/">LinkedIn</a> so I can see it.</p>


</section>

<a onclick="window.scrollTo(0, 0); return false;" id="quarto-back-to-top"><i class="bi bi-arrow-up"></i> Back to top</a><div id="quarto-appendix" class="default"><section class="quarto-appendix-contents" id="quarto-reuse"><h2 class="anchored quarto-appendix-heading">Reuse</h2><div class="quarto-appendix-contents"><div><a rel="license" href="https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en_gb">CC BY-NC-SA 4.0</a></div></div></section><section class="quarto-appendix-contents" id="quarto-citation"><h2 class="anchored quarto-appendix-heading">Citation</h2><div><div class="quarto-appendix-secondary-label">BibTeX citation:</div><pre class="sourceCode code-with-copy quarto-appendix-bibtex"><code class="sourceCode bibtex">@misc{canouil2026,
  author = {CANOUIL, Mickaël},
  title = {Build {LinkedIn} {Carousels} with {Typst:} The `Slide`
    {Layout}},
  date = {2026-05-28},
  url = {https://mickael.canouil.fr/posts/2026-05-28-typst-linkedin-carousels/},
  langid = {en-GB}
}
</code></pre><div class="quarto-appendix-secondary-label">For attribution, please cite this work as:</div><div id="ref-canouil2026" class="csl-entry quarto-appendix-citeas">
CANOUIL, M. (2026-05-28). Build LinkedIn Carousels with Typst: the
`slide` Layout. <em>Mickael.canouil.fr</em>. <a href="https://mickael.canouil.fr/posts/2026-05-28-typst-linkedin-carousels/">https://mickael.canouil.fr/posts/2026-05-28-typst-linkedin-carousels/</a>
</div></div></section></div> ]]></description>
  <category>typst</category>
  <category>design</category>
  <category>linkedin</category>
  <category>carousel</category>
  <category>productivity</category>
  <guid>https://mickael.canouil.fr/posts/2026-05-28-typst-linkedin-carousels/</guid>
  <pubDate>Thu, 28 May 2026 00:00:00 GMT</pubDate>
  <media:content url="https://mickael.canouil.fr/posts/2026-05-28-typst-linkedin-carousels/featured.png" medium="image" type="image/png" height="76" width="144"/>
</item>
<item>
  <title>Gribouille: a Grammar of Graphics for Typst</title>
  <link>https://mickael.canouil.fr/posts/2026-05-20-gribouille-grammar-of-graphics-for-typst/</link>
  <description><![CDATA[ 

<!--
@license MIT
@copyright 2026 Mickaël Canouil
@author Mickaël Canouil
-->
Skip to main content





<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="featured.png" class="lightbox" data-gallery="quarto-lightbox-gallery-1"><img src="https://mickael.canouil.fr/posts/2026-05-20-gribouille-grammar-of-graphics-for-typst/featured.png" class="img-featured img-fluid quarto-figure quarto-figure-center figure-img" alt="Featured card for the Gribouille release on a cream paper background. On
the left, the Gribouille wordmark sits above the tagline &quot;Create elegant
graphics with the Grammar of Graphics for Typst.&quot;, a small orange
&quot;v0.1.0 release&quot; pill, and the URL m.canouil.dev/gribouille. On the
right, a slightly rotated bordered card frames a scatter plot of Palmer
Penguin body mass against flipper length, with three coloured point
clouds for the species and per-species summary labels.
" width="600"></a></p>
</figure>
</div>
<section id="introduction" class="level2" data-number="1">
<h2 data-number="1" class="anchored" data-anchor-id="introduction"><span class="header-section-number">1</span> Introduction</h2>
<p>Typst has grown from a curious newcomer to a credible alternative for scientific writing, reports, and slide decks. What it still lacked, until today, was a real grammar of graphics: a way to declare a figure with the same vocabulary you would reach for in <a href="https://ggplot2.tidyverse.org"><code>ggplot2</code></a>, then have it draw, lay out, and align with the rest of the document.</p>
<p><a href="https://github.com/mcanouil/gribouille">Gribouille</a> (French for <em>scribble</em>) is the first cut of that library. Version 0.1.0 lands on <a href="https://typst.app/universe/package/gribouille">Typst Universe</a> with this announcement, modelled closely on <code>ggplot2</code>, drawing on top of <a href="https://cetz-package.github.io/"><code>cetz</code></a>, and shipped with a documentation site that is itself a Quarto project rendered by the <a href="https://github.com/mcanouil/quarto-typst-render"><code>quarto-typst-render</code></a> extension. Every figure in this post is a real, freshly compiled plot.</p>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Note</span>At a glance
</div>
</div>
<div class="callout-body-container callout-body">
<ul>
<li><a href="https://github.com/mcanouil/gribouille">Gribouille</a> v0.1.0 on Typst Universe: <code>#import "@preview/gribouille:0.1.0": *</code>.</li>
<li>A broad library of geoms and stats, scales spanning every aesthetic, a range of built-in themes, and full facet support.</li>
<li><a href="https://m.canouil.dev/gribouille/reference/core/compose.html"><code>compose()</code></a> orchestrates several plots into a single figure with a shared, hoisted legend, fed by a new <code>defer: true</code> flag on <a href="https://m.canouil.dev/gribouille/reference/core/plot.html"><code>plot()</code></a>.</li>
<li><a href="https://github.com/mcanouil/quarto-typst-render"><code>quarto-typst-render</code></a> v0.13.1 ships <code>typst_define()</code>, the Python and R helper that streams arbitrary values, scalars, lists, dictionaries, and full data frames alike, straight into Typst code blocks.</li>
</ul>
</div>
</div>
</section>
<section id="why-a-grammar-of-graphics-for-typst" class="level2" data-number="2">
<h2 data-number="2" class="anchored" data-anchor-id="why-a-grammar-of-graphics-for-typst"><span class="header-section-number">2</span> Why a grammar of graphics for Typst</h2>
<p>Until now, producing a figure inside a Typst document meant taking a detour through another language. You would leave Typst to call out to <code>ggplot2</code>, <a href="https://matplotlib.org"><code>matplotlib</code></a>, or <a href="https://plotnine.org"><code>plotnine</code></a>, save a PNG or SVG, and import it back. That works, but it splits the toolchain in two: one font system on the page, another inside the figure; one colour palette in the report, another in the chart; one render pass that knows about the layout, another that does not.</p>
<p><code>cetz</code> already gives Typst a robust low-level drawing API, but the declarative layer that turns a dataset, a mapping, and a few layers into a publication-quality figure was missing. That is exactly the gap Gribouille fills.</p>
<div class="highlight">
<p><strong>One toolchain.</strong> Same fonts, same palette, same render pass, from data to PDF.</p>
</div>
<p>The pay-off is a single toolchain. Give a plot an <code>alt:</code> description and Gribouille wraps the result in a Typst <code>figure</code> element with <code>kind: "gribouille-plot"</code>, so you can attach show rules, captions, or cross-references in one place; with or without it, every plot shares the document’s fonts, palette, and geometry. The YAML front-matter of this very post wires its <code>background:</code> and <code>foreground:</code> to the site’s light and dark colours, and every figure below reacts automatically when you toggle the theme. Compilation is deterministic, offline-friendly, and produces a PDF straight out of Typst with no Python or R runtime required.</p>
</section>
<section id="a-respectful-nod-to-ggplot2" class="level2" data-number="3">
<h2 data-number="3" class="anchored" data-anchor-id="a-respectful-nod-to-ggplot2"><span class="header-section-number">3</span> A respectful nod to ggplot2</h2>
<p>A short personal aside. I have been using <code>ggplot2</code> since almost its first release, and a large part of how I think about data visualisation comes from that work. This release would not exist without Hadley Wickham’s original vision, nor without the people carrying it forward today, in particular Thomas Lin Pedersen and Teun van den Brand, and the wider community of contributors who keep shaping the package through to the recent v4 release.</p>
<p>Gribouille is, in many ways, a port of <code>ggplot2</code>’s vocabulary into Typst. The names line up: <a href="https://m.canouil.dev/gribouille/reference/core/plot.html"><code>plot()</code></a> instead of <code>ggplot()</code>, <code>geom-point()</code> for <code>geom_point()</code>, <code>facet-wrap()</code>, <code>scale-colour-*()</code>, <a href="https://m.canouil.dev/gribouille/reference/themes/theme-minimal.html"><code>theme-minimal()</code></a>, and so on. Where the two part ways, it is because Typst is a different language, with no lazy evaluation, no plot object you can mutate after the fact, and no <code>ggsave()</code> output pipeline. Column names are plain strings. Per-aesthetic type coercion is done inline with <a href="https://m.canouil.dev/gribouille/reference/helpers/as-factor.html"><code>as-factor()</code></a> and <a href="https://m.canouil.dev/gribouille/reference/helpers/as-numeric.html"><code>as-numeric()</code></a>. These are opinionated departures, but the mental model is the same one you already have.</p>
</section>
<section id="build-a-penguins-plot-one-layer-at-a-time" class="level2" data-number="4">
<h2 data-number="4" class="anchored" data-anchor-id="build-a-penguins-plot-one-layer-at-a-time"><span class="header-section-number">4</span> Build a penguins plot, one layer at a time</h2>
<p>The library ships the <a href="https://allisonhorst.github.io/palmerpenguins/">Palmer Penguins</a> dataset under the <a href="https://m.canouil.dev/gribouille/reference/datasets/penguins.html"><code>penguins</code></a> symbol. The walk-through below builds a single figure end to end, one grammar concept at a time, with brief notes on alternatives at each step. It is a tighter version of the <a href="https://m.canouil.dev/gribouille/get-started/">Get Started</a> page on the documentation site.</p>
<p>Every plot you build composes the same pieces. <a href="https://m.canouil.dev/gribouille/reference/core/plot.html"><code>plot()</code></a> wraps a stack of layers, each sheet sitting on the one below, from data and mapping at the foundation up through theme and labels.</p>
<div>
<div class="light-content">
<p><a href="../../posts/2026-05-20-gribouille-grammar-of-graphics-for-typst/./assets/typst-render/grammar-light.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-2" title="A plot() container surrounding six stacked parallax sheets representing the grammar layers, from bottom to top: data plus aes(), layer (geom plus stat plus position), scale-*, coord-*, facet-*, and theme plus labs."><img src="https://mickael.canouil.fr/posts/2026-05-20-gribouille-grammar-of-graphics-for-typst/assets/typst-render/grammar-light.svg" class="img-fluid hero-art" style="display: block; margin-inline: auto;" alt="A plot() container surrounding six stacked parallax sheets representing the grammar layers, from bottom to top: data plus aes(), layer (geom plus stat plus position), scale-*, coord-*, facet-*, and theme plus labs."></a></p>
</div>
<div class="dark-content">
<p><a href="../../posts/2026-05-20-gribouille-grammar-of-graphics-for-typst/./assets/typst-render/grammar-dark.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-3" title="A plot() container surrounding six stacked parallax sheets representing the grammar layers, from bottom to top: data plus aes(), layer (geom plus stat plus position), scale-*, coord-*, facet-*, and theme plus labs."><img src="https://mickael.canouil.fr/posts/2026-05-20-gribouille-grammar-of-graphics-for-typst/assets/typst-render/grammar-dark.svg" class="img-fluid hero-art" style="display: block; margin-inline: auto;" alt="A plot() container surrounding six stacked parallax sheets representing the grammar layers, from bottom to top: data plus aes(), layer (geom plus stat plus position), scale-*, coord-*, facet-*, and theme plus labs."></a></p>
</div>
</div>
<p>The post’s YAML points the <code>typst-render</code> extension at a one-line preamble file so every <code>{typst}</code> block has the library in scope:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" data-filename="yaml" style="background: #f1f3f5;"><pre class="sourceCode yaml cw-auto code-with-copy"><code class="sourceCode yaml"><span id="cb1-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">extensions</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb1-2"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">  </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">typst-render</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb1-3"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">preamble</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> _preamble.typ</span></span></code></pre></div></div>
<p>And <code>_preamble.typ</code> contains:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb2" data-filename="typst" style="background: #f1f3f5;"><pre class="sourceCode typst cw-auto code-with-copy"><code class="sourceCode typst"><span id="cb2-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"@preview/gribouille:0.1.0"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span></span></code></pre></div></div>
<p>You would write the same <code>#import</code> once at the top of your own Typst document.</p>
<section id="data-and-aesthetics" class="level3" data-number="4.1">
<h3 data-number="4.1" class="anchored" data-anchor-id="data-and-aesthetics"><span class="header-section-number">4.1</span> Data and aesthetics</h3>
<p>Every plot starts with a dataset and an <a href="https://m.canouil.dev/gribouille/reference/core/aes.html"><code>aes()</code></a> mapping that binds column names to visual channels. At a minimum, <a href="https://m.canouil.dev/gribouille/reference/geoms/geom-point.html"><code>geom-point()</code></a> needs <code>x</code> and <code>y</code>.</p>
<div class="quarto-layout-panel" data-layout-ncol="2">
<div class="quarto-layout-row quarto-layout-valign-center">
<div class="quarto-layout-cell" style="flex-basis: 50.0%;justify-content: flex-start;">
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb3" data-filename="typst" style="background: #f1f3f5;"><pre class="sourceCode typst cw-auto code-with-copy"><code class="sourceCode typst"><span id="cb3-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>plot<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb3-2">  data<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> penguins<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb3-3">  mapping<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> aes<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"flipper-len"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"body-mass"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb3-4">  layers<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>geom-point<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),),</span></span>
<span id="cb3-5">  width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">12cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb3-6">  height<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">9cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb3-7"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span></code></pre></div></div>
</div>
<div class="quarto-layout-cell" style="flex-basis: 50.0%;justify-content: flex-start;">
<div class="light-content">
<p><a href="../../posts/2026-05-20-gribouille-grammar-of-graphics-for-typst/./assets/typst-render/step-1-light.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-4" title="Scatter of penguin body mass against flipper length, every point in the default theme ink colour, no legend."><img src="https://mickael.canouil.fr/posts/2026-05-20-gribouille-grammar-of-graphics-for-typst/assets/typst-render/step-1-light.svg" class="img-fluid hero-art" alt="Scatter of penguin body mass against flipper length, every point in the default theme ink colour, no legend."></a></p>
</div>
<div class="dark-content">
<p><a href="../../posts/2026-05-20-gribouille-grammar-of-graphics-for-typst/./assets/typst-render/step-1-dark.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-5" title="Scatter of penguin body mass against flipper length, every point in the default theme ink colour, no legend."><img src="https://mickael.canouil.fr/posts/2026-05-20-gribouille-grammar-of-graphics-for-typst/assets/typst-render/step-1-dark.svg" class="img-fluid hero-art" alt="Scatter of penguin body mass against flipper length, every point in the default theme ink colour, no legend."></a></p>
</div>
</div>
</div>
</div>
<p>The plot-level <code>mapping</code> is inherited by every layer. If only one layer needs a given mapping, you can also pass it inside that layer’s <code>mapping:</code> argument and keep the plot-level mapping minimal.</p>
</section>
<section id="encode-a-third-variable-with-colour" class="level3" data-number="4.2">
<h3 data-number="4.2" class="anchored" data-anchor-id="encode-a-third-variable-with-colour"><span class="header-section-number">4.2</span> Encode a third variable with colour</h3>
<p>Add <code>colour: "species"</code> and the points split visually by group.</p>
<div class="quarto-layout-panel" data-layout-ncol="2">
<div class="quarto-layout-row quarto-layout-valign-center">
<div class="quarto-layout-cell" style="flex-basis: 50.0%;justify-content: flex-start;">
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb4" data-filename="typst" style="background: #f1f3f5;"><pre class="sourceCode typst cw-auto code-with-copy"><code class="sourceCode typst"><span id="cb4-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>plot<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb4-2">  data<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> penguins<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb4-3">  mapping<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> aes<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb4-4">    x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"flipper-len"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb4-5">    y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"body-mass"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb4-6">    colour<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"species"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb4-7">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb4-8">  layers<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>geom-point<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> alpha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.85</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),),</span></span>
<span id="cb4-9">  width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">12cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb4-10">  height<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">9cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb4-11"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span></code></pre></div></div>
</div>
<div class="quarto-layout-cell" style="flex-basis: 50.0%;justify-content: flex-start;">
<div class="light-content">
<p><a href="../../posts/2026-05-20-gribouille-grammar-of-graphics-for-typst/./assets/typst-render/step-2a-light.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-6" title="Scatter of body mass against flipper length, points coloured by species (Adelie, Chinstrap, Gentoo) with a species legend on the right."><img src="https://mickael.canouil.fr/posts/2026-05-20-gribouille-grammar-of-graphics-for-typst/assets/typst-render/step-2a-light.svg" class="img-fluid hero-art" alt="Scatter of body mass against flipper length, points coloured by species (Adelie, Chinstrap, Gentoo) with a species legend on the right."></a></p>
</div>
<div class="dark-content">
<p><a href="../../posts/2026-05-20-gribouille-grammar-of-graphics-for-typst/./assets/typst-render/step-2a-dark.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-7" title="Scatter of body mass against flipper length, points coloured by species (Adelie, Chinstrap, Gentoo) with a species legend on the right."><img src="https://mickael.canouil.fr/posts/2026-05-20-gribouille-grammar-of-graphics-for-typst/assets/typst-render/step-2a-dark.svg" class="img-fluid hero-art" alt="Scatter of body mass against flipper length, points coloured by species (Adelie, Chinstrap, Gentoo) with a species legend on the right."></a></p>
</div>
</div>
</div>
</div>
<p>If your figure must remain legible in greyscale, swap <code>colour: "species"</code> for <code>shape: "species"</code> and the same grouping rides the point shape instead. Both aesthetics can be combined when accessibility matters.</p>
<div class="quarto-layout-panel" data-layout-ncol="2">
<div class="quarto-layout-row quarto-layout-valign-center">
<div class="quarto-layout-cell" style="flex-basis: 50.0%;justify-content: flex-start;">
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb5" data-filename="typst" style="background: #f1f3f5;"><pre class="sourceCode typst cw-auto code-with-copy"><code class="sourceCode typst"><span id="cb5-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>plot<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb5-2">  data<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> penguins<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb5-3">  mapping<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> aes<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb5-4">    x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"flipper-len"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb5-5">    y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"body-mass"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb5-6">    shape<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"species"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb5-7">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb5-8">  layers<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>geom-point<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> alpha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.85</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),),</span></span>
<span id="cb5-9">  width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">12cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb5-10">  height<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">9cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb5-11"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span></code></pre></div></div>
</div>
<div class="quarto-layout-cell" style="flex-basis: 50.0%;justify-content: flex-start;">
<div class="light-content">
<p><a href="../../posts/2026-05-20-gribouille-grammar-of-graphics-for-typst/./assets/typst-render/step-2b-light.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-8" title="Scatter of body mass against flipper length, monochrome with point glyphs distinguishing species: open circles for Adelie, triangles for Chinstrap, squares for Gentoo, with a species shape legend on the right."><img src="https://mickael.canouil.fr/posts/2026-05-20-gribouille-grammar-of-graphics-for-typst/assets/typst-render/step-2b-light.svg" class="img-fluid hero-art" alt="Scatter of body mass against flipper length, monochrome with point glyphs distinguishing species: open circles for Adelie, triangles for Chinstrap, squares for Gentoo, with a species shape legend on the right."></a></p>
</div>
<div class="dark-content">
<p><a href="../../posts/2026-05-20-gribouille-grammar-of-graphics-for-typst/./assets/typst-render/step-2b-dark.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-9" title="Scatter of body mass against flipper length, monochrome with point glyphs distinguishing species: open circles for Adelie, triangles for Chinstrap, squares for Gentoo, with a species shape legend on the right."><img src="https://mickael.canouil.fr/posts/2026-05-20-gribouille-grammar-of-graphics-for-typst/assets/typst-render/step-2b-dark.svg" class="img-fluid hero-art" alt="Scatter of body mass against flipper length, monochrome with point glyphs distinguishing species: open circles for Adelie, triangles for Chinstrap, squares for Gentoo, with a species shape legend on the right."></a></p>
</div>
</div>
</div>
</div>
</section>
<section id="stack-a-second-layer" class="level3" data-number="4.3">
<h3 data-number="4.3" class="anchored" data-anchor-id="stack-a-second-layer"><span class="header-section-number">4.3</span> Stack a second layer</h3>
<p>Layers stack in the order you list them. Adding <a href="https://m.canouil.dev/gribouille/reference/geoms/geom-smooth.html"><code>geom-smooth()</code></a> draws one fitted line per colour group, because the <code>colour</code> aesthetic is inherited from the plot-level mapping.</p>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
Note
</div>
</div>
<div class="callout-body-container callout-body">
<p>Only <code>method: "lm"</code> is supported in this release; LOESS is deferred for the reason listed in the limitations section.</p>
</div>
</div>
<div class="quarto-layout-panel" data-layout-ncol="2">
<div class="quarto-layout-row quarto-layout-valign-center">
<div class="quarto-layout-cell" style="flex-basis: 50.0%;justify-content: flex-start;">
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb6" data-filename="typst" style="background: #f1f3f5;"><pre class="sourceCode typst cw-auto code-with-copy"><code class="sourceCode typst"><span id="cb6-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>plot<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb6-2">  data<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> penguins<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb6-3">  mapping<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> aes<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb6-4">    x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"flipper-len"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb6-5">    y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"body-mass"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb6-6">    colour<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"species"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb6-7">    fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"species"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb6-8">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb6-9">  layers<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb6-10">    geom-point<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> alpha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.6</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb6-11">    geom-smooth<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>method<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"lm"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> alpha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.2</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb6-12">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb6-13">  width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">12cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb6-14">  height<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">9cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb6-15"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span></code></pre></div></div>
</div>
<div class="quarto-layout-cell" style="flex-basis: 50.0%;justify-content: flex-start;">
<div class="light-content">
<p><a href="../../posts/2026-05-20-gribouille-grammar-of-graphics-for-typst/./assets/typst-render/step-3-light.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-10" title="Per-species scatter coloured by species with one linear fit and confidence ribbon per group, and a species legend on the right."><img src="https://mickael.canouil.fr/posts/2026-05-20-gribouille-grammar-of-graphics-for-typst/assets/typst-render/step-3-light.svg" class="img-fluid hero-art" alt="Per-species scatter coloured by species with one linear fit and confidence ribbon per group, and a species legend on the right."></a></p>
</div>
<div class="dark-content">
<p><a href="../../posts/2026-05-20-gribouille-grammar-of-graphics-for-typst/./assets/typst-render/step-3-dark.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-11" title="Per-species scatter coloured by species with one linear fit and confidence ribbon per group, and a species legend on the right."><img src="https://mickael.canouil.fr/posts/2026-05-20-gribouille-grammar-of-graphics-for-typst/assets/typst-render/step-3-dark.svg" class="img-fluid hero-art" alt="Per-species scatter coloured by species with one linear fit and confidence ribbon per group, and a species legend on the right."></a></p>
</div>
</div>
</div>
</div>
</section>
<section id="pick-a-palette" class="level3" data-number="4.4">
<h3 data-number="4.4" class="anchored" data-anchor-id="pick-a-palette"><span class="header-section-number">4.4</span> Pick a palette</h3>
<p><a href="https://m.canouil.dev/gribouille/reference/scales/scale-colour-discrete.html"><code>scale-colour-discrete()</code></a> replaces the default palette. A matching <a href="https://m.canouil.dev/gribouille/reference/scales/scale-fill-discrete.html"><code>scale-fill-discrete()</code></a> keeps the confidence ribbons in step with the points.</p>
<div class="quarto-layout-panel" data-layout-ncol="2">
<div class="quarto-layout-row quarto-layout-valign-center">
<div class="quarto-layout-cell" style="flex-basis: 50.0%;justify-content: flex-start;">
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb7" data-filename="typst" style="background: #f1f3f5;"><pre class="sourceCode typst cw-auto code-with-copy"><code class="sourceCode typst"><span id="cb7-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">cb</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb7-2">  rgb<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#0072B2"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb7-3">  rgb<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#D55E00"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb7-4">  rgb<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#009E73"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb7-5"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb7-6"></span>
<span id="cb7-7"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>plot<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb7-8">  data<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> penguins<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb7-9">  mapping<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> aes<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb7-10">    x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"flipper-len"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb7-11">    y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"body-mass"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb7-12">    colour<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"species"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb7-13">    fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"species"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb7-14">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb7-15">  layers<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb7-16">    geom-point<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> alpha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.6</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb7-17">    geom-smooth<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>method<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"lm"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> alpha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.2</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb7-18">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb7-19">  scales<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb7-20">    scale-colour-discrete<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>palette<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> cb<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb7-21">    scale-fill-discrete<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>palette<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> cb<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb7-22">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb7-23">  width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">12cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb7-24">  height<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">9cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb7-25"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span></code></pre></div></div>
</div>
<div class="quarto-layout-cell" style="flex-basis: 50.0%;justify-content: flex-start;">
<div class="light-content">
<p><a href="../../posts/2026-05-20-gribouille-grammar-of-graphics-for-typst/./assets/typst-render/step-4-light.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-12" title="Same per-species scatter and linear fits as step 3, now using a colour-blind-friendly Okabe-Ito palette (blue, vermillion, bluish green)."><img src="https://mickael.canouil.fr/posts/2026-05-20-gribouille-grammar-of-graphics-for-typst/assets/typst-render/step-4-light.svg" class="img-fluid hero-art" alt="Same per-species scatter and linear fits as step 3, now using a colour-blind-friendly Okabe-Ito palette (blue, vermillion, bluish green)."></a></p>
</div>
<div class="dark-content">
<p><a href="../../posts/2026-05-20-gribouille-grammar-of-graphics-for-typst/./assets/typst-render/step-4-dark.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-13" title="Same per-species scatter and linear fits as step 3, now using a colour-blind-friendly Okabe-Ito palette (blue, vermillion, bluish green)."><img src="https://mickael.canouil.fr/posts/2026-05-20-gribouille-grammar-of-graphics-for-typst/assets/typst-render/step-4-dark.svg" class="img-fluid hero-art" alt="Same per-species scatter and linear fits as step 3, now using a colour-blind-friendly Okabe-Ito palette (blue, vermillion, bluish green)."></a></p>
</div>
</div>
</div>
</div>
<div class="callout callout-style-default callout-tip callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
Tip
</div>
</div>
<div class="callout-body-container callout-body">
<p><a href="https://m.canouil.dev/gribouille/reference/scales/scale-colour-okabe-ito.html"><code>scale-colour-okabe-ito()</code></a> exposes the same Okabe-Ito palette used in the example but as a one-liner if you do not want to spell out the colour codes.</p>
</div>
</div>
</section>
<section id="split-into-small-multiples" class="level3" data-number="4.5">
<h3 data-number="4.5" class="anchored" data-anchor-id="split-into-small-multiples"><span class="header-section-number">4.5</span> Split into small multiples</h3>
<p><a href="https://m.canouil.dev/gribouille/reference/facets/facet-wrap.html"><code>facet-wrap()</code></a> lays out one panel per level of a discrete column, sharing scales across panels.</p>
<div class="quarto-layout-panel" data-layout-ncol="2">
<div class="quarto-layout-row quarto-layout-valign-center">
<div class="quarto-layout-cell" style="flex-basis: 50.0%;justify-content: flex-start;">
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb8" data-filename="typst" style="background: #f1f3f5;"><pre class="sourceCode typst cw-auto code-with-copy"><code class="sourceCode typst"><span id="cb8-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">cb</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb8-2">  rgb<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#0072B2"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb8-3">  rgb<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#D55E00"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb8-4">  rgb<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#009E73"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb8-5"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb8-6"></span>
<span id="cb8-7"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>plot<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb8-8">  data<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> penguins<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb8-9">  mapping<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> aes<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb8-10">    x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"flipper-len"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb8-11">    y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"body-mass"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb8-12">    colour<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"species"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb8-13">    fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"species"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb8-14">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb8-15">  layers<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb8-16">    geom-point<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> alpha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.6</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb8-17">    geom-smooth<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>method<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"lm"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> alpha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.2</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb8-18">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb8-19">  scales<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb8-20">    scale-colour-discrete<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>palette<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> cb<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb8-21">    scale-fill-discrete<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>palette<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> cb<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb8-22">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb8-23">  facet<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> facet-wrap<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"island"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb8-24">  width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">12cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb8-25">  height<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">9cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb8-26"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span></code></pre></div></div>
</div>
<div class="quarto-layout-cell" style="flex-basis: 50.0%;justify-content: flex-start;">
<div class="light-content">
<p><a href="../../posts/2026-05-20-gribouille-grammar-of-graphics-for-typst/./assets/typst-render/step-5-light.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-14" title="Three panels (Torgersen, Biscoe, Dream) arranged on a two-column grid, one per island, sharing colour and axis scales."><img src="https://mickael.canouil.fr/posts/2026-05-20-gribouille-grammar-of-graphics-for-typst/assets/typst-render/step-5-light.svg" class="img-fluid hero-art" alt="Three panels (Torgersen, Biscoe, Dream) arranged on a two-column grid, one per island, sharing colour and axis scales."></a></p>
</div>
<div class="dark-content">
<p><a href="../../posts/2026-05-20-gribouille-grammar-of-graphics-for-typst/./assets/typst-render/step-5-dark.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-15" title="Three panels (Torgersen, Biscoe, Dream) arranged on a two-column grid, one per island, sharing colour and axis scales."><img src="https://mickael.canouil.fr/posts/2026-05-20-gribouille-grammar-of-graphics-for-typst/assets/typst-render/step-5-dark.svg" class="img-fluid hero-art" alt="Three panels (Torgersen, Biscoe, Dream) arranged on a two-column grid, one per island, sharing colour and axis scales."></a></p>
</div>
</div>
</div>
</div>
<div class="callout callout-style-default callout-tip callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
Tip
</div>
</div>
<div class="callout-body-container callout-body">
<p>For two crossed factors, <a href="https://m.canouil.dev/gribouille/reference/facets/facet-grid.html"><code>facet-grid()</code></a> produces a matrix of panels instead.</p>
</div>
</div>
</section>
<section id="polish" class="level3" data-number="4.6">
<h3 data-number="4.6" class="anchored" data-anchor-id="polish"><span class="header-section-number">4.6</span> Polish</h3>
<p>A theme, axis labels, and the figure is ready for the page.</p>
<p><a href="https://m.canouil.dev/gribouille/reference/helpers/format-comma.html"><code>format-comma()</code></a>, passed to <code>scale-y-continuous(labels: ...)</code>, turns <code>5000</code> into <code>5,000</code> on the y-axis. Sibling helpers cover number, percent, currency, scientific notation, and case or wrap formatting.</p>
<p><a href="https://m.canouil.dev/gribouille/reference/themes/element-typst.html"><code>element-typst()</code></a> wires the subtitle to render as Typst markup, so the linear-fit formula <code>$hat(y) = beta_0 + beta_1 dot.c x$</code> and the per-species names appear with maths and inline colour without leaving the string.</p>
<p>A single <code>species-colours</code> dictionary feeds both <a href="https://m.canouil.dev/gribouille/reference/scales/scale-colour-discrete.html"><code>scale-colour-discrete()</code></a> (<code>limits:</code> plus <code>palette:</code>) and the second subtitle line. The entries are mapped to coloured <code>#text(...)</code> snippets, joined into one Typst-markup string.</p>
<p>Legend placement is controlled with <a href="https://m.canouil.dev/gribouille/reference/guides/guides.html"><code>guides()</code></a> bound to <a href="https://m.canouil.dev/gribouille/reference/guides/guide-legend.html"><code>guide-legend(position: ...)</code></a>. <code>position:</code> accepts the side strings <code>"top"</code>, <code>"right"</code>, <code>"bottom"</code>, <code>"left"</code>, a Typst alignment like <code>bottom + right</code> for an inside-panel corner, or a <code>(dx:, dy:)</code> offset for arbitrary placement.</p>
<p>The same constructor exposes <code>direction</code>, <code>byrow</code>, <code>nrow</code>, and <code>ncolumn</code> for swatch layout, and a <code>"top"</code> or <code>"bottom"</code> placement automatically lays a colourbar or size ladder out horizontally. The <code>ncol</code> argument was renamed to <code>ncolumn</code> in the run-up to this release, so older snippets need updating before they compile.</p>
<div class="quarto-layout-panel" data-layout-ncol="2">
<div class="quarto-layout-row quarto-layout-valign-center">
<div class="quarto-layout-cell" style="flex-basis: 50.0%;justify-content: flex-start;">
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb9" data-filename="typst" style="background: #f1f3f5;"><pre class="sourceCode typst cw-auto code-with-copy"><code class="sourceCode typst"><span id="cb9-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">species-colours</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb9-2">  Adelie:    rgb<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#0072B2"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb9-3">  Chinstrap: rgb<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#D55E00"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb9-4">  Gentoo:    rgb<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#009E73"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb9-5"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb9-6"></span>
<span id="cb9-7"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>plot<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb9-8">  data<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> penguins<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb9-9">  mapping<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> aes<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb9-10">    x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"flipper-len"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb9-11">    y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"body-mass"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb9-12">    colour<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"species"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb9-13">    fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"species"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb9-14">    shape<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"species"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb9-15">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb9-16">  layers<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb9-17">    geom-point<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> alpha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.6</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb9-18">    geom-smooth<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>method<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"lm"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> alpha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.2</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb9-19">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb9-20">  scales<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb9-21">    scale-colour-discrete<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb9-22">      limits<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> species-colours<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>keys<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(),</span></span>
<span id="cb9-23">      palette<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> species-colours<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>values<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(),</span></span>
<span id="cb9-24">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb9-25">    scale-fill-discrete<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb9-26">      limits<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> species-colours<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>keys<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(),</span></span>
<span id="cb9-27">      palette<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> species-colours<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>values<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(),</span></span>
<span id="cb9-28">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb9-29">    scale-y-continuous<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>labels<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> format-comma<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">()),</span></span>
<span id="cb9-30">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb9-31">  facet<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> facet-wrap<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"island"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb9-32">  guides<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> guides<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb9-33">    colour<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> guide-legend<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>position<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> bottom <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> right<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb9-34">    fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> guide-legend<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>position<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> bottom <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> right<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb9-35">    shape<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> guide-legend<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>position<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> bottom <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> right<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb9-36">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb9-37">  labs<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> labs<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb9-38">    title<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Penguin Body Mass Scales with Flipper Length"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb9-39">    subtitle<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb9-40">      <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span>Linear fit <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">$hat(y) = beta_0 + beta_1 dot.c x$</span> on every island for three species: <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span></span>
<span id="cb9-41">      species-colours<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>pairs<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">()</span></span>
<span id="cb9-42">        .map<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>p <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=&gt;</span> text<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> p<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>at<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span> weight<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"bold"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> p<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>at<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)))</span></span>
<span id="cb9-43">        .join<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">", "</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb9-44">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">},</span></span>
<span id="cb9-45">    x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Flipper Length (mm)"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb9-46">    y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Body Mass (g)"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb9-47">    colour<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Species"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb9-48">    fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Species"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb9-49">    shape<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Species"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb9-50">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb9-51">  theme<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> theme-minimal<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb9-52">    plot-title<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> element-text<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">12pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> weight<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"bold"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb9-53">    plot-subtitle<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> element-typst<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">9pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb9-54">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb9-55">  width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">12cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb9-56">  height<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">9cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb9-57"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span></code></pre></div></div>
</div>
<div class="quarto-layout-cell" style="flex-basis: 50.0%;justify-content: flex-start;">
<div class="light-content">
<p><a href="../../posts/2026-05-20-gribouille-grammar-of-graphics-for-typst/./assets/typst-render/step-6-light.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-16" title="Polished faceted scatter on a minimal theme with bold title, a subtitle pairing the linear-fit formula with the three species names rendered in their respective colours, thousands-separated y ticks, title-cased axis labels, and the species legend tucked inside the empty bottom-right facet panel."><img src="https://mickael.canouil.fr/posts/2026-05-20-gribouille-grammar-of-graphics-for-typst/assets/typst-render/step-6-light.svg" class="img-fluid hero-art" alt="Polished faceted scatter on a minimal theme with bold title, a subtitle pairing the linear-fit formula with the three species names rendered in their respective colours, thousands-separated y ticks, title-cased axis labels, and the species legend tucked inside the empty bottom-right facet panel."></a></p>
</div>
<div class="dark-content">
<p><a href="../../posts/2026-05-20-gribouille-grammar-of-graphics-for-typst/./assets/typst-render/step-6-dark.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-17" title="Polished faceted scatter on a minimal theme with bold title, a subtitle pairing the linear-fit formula with the three species names rendered in their respective colours, thousands-separated y ticks, title-cased axis labels, and the species legend tucked inside the empty bottom-right facet panel."><img src="https://mickael.canouil.fr/posts/2026-05-20-gribouille-grammar-of-graphics-for-typst/assets/typst-render/step-6-dark.svg" class="img-fluid hero-art" alt="Polished faceted scatter on a minimal theme with bold title, a subtitle pairing the linear-fit formula with the three species names rendered in their respective colours, thousands-separated y ticks, title-cased axis labels, and the species legend tucked inside the empty bottom-right facet panel."></a></p>
</div>
</div>
</div>
</div>
</section>
</section>
<section id="compose-multiple-plots" class="level2" data-number="5">
<h2 data-number="5" class="anchored" data-anchor-id="compose-multiple-plots"><span class="header-section-number">5</span> Compose multiple plots</h2>
<p>Sometimes a story needs more than one panel. <a href="https://m.canouil.dev/gribouille/reference/core/compose.html"><code>compose()</code></a> takes several deferred plots, lays them out on a grid or stack, and hoists their shared legend into a single guide block at the edge of the figure. The deferred plots are produced by passing <code>defer: true</code> to <a href="https://m.canouil.dev/gribouille/reference/core/plot.html"><code>plot()</code></a>, which returns a spec dictionary instead of rendering immediately.</p>
<div class="highlight">
<p><strong>Many plots, one legend.</strong> <code>compose()</code> hoists the shared aesthetics for you.</p>
</div>
<div class="quarto-layout-panel" data-layout-ncol="2">
<div class="quarto-layout-row quarto-layout-valign-center">
<div class="quarto-layout-cell" style="flex-basis: 50.0%;justify-content: flex-start;">
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb10" data-filename="typst" style="background: #f1f3f5;"><pre class="sourceCode typst cw-auto code-with-copy"><code class="sourceCode typst"><span id="cb10-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">panel</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>mapping<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> plot<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb10-2">  data<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> penguins<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb10-3">  mapping<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> mapping<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb10-4">  layers<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>geom-point<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> alpha<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.85</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),),</span></span>
<span id="cb10-5">  width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">6cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb10-6">  height<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">4.5cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb10-7">  defer<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">true</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb10-8"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb10-9"></span>
<span id="cb10-10"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>compose<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb10-11">  panel<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>aes<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"flipper-len"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"body-mass"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> colour<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"species"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)),</span></span>
<span id="cb10-12">  panel<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>aes<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"bill-len"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"body-mass"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> colour<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"species"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)),</span></span>
<span id="cb10-13">  layout<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"grid"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb10-14">  columns<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb10-15"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span></code></pre></div></div>
</div>
<div class="quarto-layout-cell" style="flex-basis: 50.0%;justify-content: flex-start;">
<div class="light-content">
<p><a href="../../posts/2026-05-20-gribouille-grammar-of-graphics-for-typst/./assets/typst-render/compose-light.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-18" title="Two side-by-side scatter plots, the left of penguin body mass against flipper length and the right against bill length, points coloured by species (Adelie, Chinstrap, Gentoo), with a single shared species legend on the right of the pair."><img src="https://mickael.canouil.fr/posts/2026-05-20-gribouille-grammar-of-graphics-for-typst/assets/typst-render/compose-light.svg" class="img-fluid hero-art" alt="Two side-by-side scatter plots, the left of penguin body mass against flipper length and the right against bill length, points coloured by species (Adelie, Chinstrap, Gentoo), with a single shared species legend on the right of the pair."></a></p>
</div>
<div class="dark-content">
<p><a href="../../posts/2026-05-20-gribouille-grammar-of-graphics-for-typst/./assets/typst-render/compose-dark.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-19" title="Two side-by-side scatter plots, the left of penguin body mass against flipper length and the right against bill length, points coloured by species (Adelie, Chinstrap, Gentoo), with a single shared species legend on the right of the pair."><img src="https://mickael.canouil.fr/posts/2026-05-20-gribouille-grammar-of-graphics-for-typst/assets/typst-render/compose-dark.svg" class="img-fluid hero-art" alt="Two side-by-side scatter plots, the left of penguin body mass against flipper length and the right against bill length, points coloured by species (Adelie, Chinstrap, Gentoo), with a single shared species legend on the right of the pair."></a></p>
</div>
</div>
</div>
</div>
<div class="callout callout-style-default callout-tip callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Tip</span>Hoisting knobs
</div>
</div>
<div class="callout-body-container callout-body">
<p>The hoisting is configurable. <code>collect:</code> controls which aesthetics are merged: <code>auto</code> hoists every legend identical across panels, <code>none</code> keeps each panel’s own legend in place, and an array such as <code>("colour",)</code> whitelists specific aesthetics. <code>guides-placement:</code> sits the shared legend on <code>"right"</code>, <code>"left"</code>, <code>"top"</code>, or <code>"bottom"</code>. <code>layout:</code> switches between <code>"grid"</code> (with <code>columns:</code> and <code>gutter:</code>) and <code>"stack"</code> (with <code>direction:</code> such as <code>ttb</code> or <code>ltr</code>).</p>
</div>
</div>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
Note
</div>
</div>
<div class="callout-body-container callout-body">
<p>You only need <a href="https://m.canouil.dev/gribouille/reference/core/compose.html"><code>compose()</code></a> when you want the shared legend hoisted. A rendered plot is ordinary Typst content, so the base <a href="https://typst.app/docs/reference/layout/grid/"><code>grid</code></a> and <a href="https://typst.app/docs/reference/layout/stack/"><code>stack</code></a> functions arrange several plots just as well when each keeps its own legend.</p>
</div>
</div>
</section>
<section id="drive-gribouille-from-any-computation" class="level2" data-number="6">
<h2 data-number="6" class="anchored" data-anchor-id="drive-gribouille-from-any-computation"><span class="header-section-number">6</span> Drive Gribouille from any computation</h2>
<p><code>Typst Render</code> 0.13.1 adds a small but powerful helper called <code>typst_define()</code>, available from both R and Python. It lets a code cell hand any value to every <code>{typst}</code> block in the document under a single Typst dictionary named <code>typst_define</code>. A scalar, a list, a dictionary, a NumPy array, a Polars DataFrame, anything JSON-serialisable, lands in Typst as the corresponding native value.</p>
<div class="highlight">
<p><strong>Compute anywhere, render in Typst.</strong> <code>typst_define()</code> is the bridge.</p>
</div>
<p>The mechanism is deliberately plain. The helper serialises the values you give it to JSON, hex-encodes them, and emits a tiny Pandoc metadata block of the form <code>---\ntypst-define: &lt;hex&gt;\n---\n</code>. The extension’s Lua filter, which runs at the <code>pre-quarto</code> stage, decodes that block and injects a <code>#let typst_define = (...)</code> preamble in front of every Typst compilation. You compute on one side, read <code>typst_define.something</code> on the other, and the two stay in step regardless of which language did the work.</p>
<p>That bridge is what makes the rest of this section possible. The example below uses Python with <a href="https://pola.rs"><code>polars</code></a>, but the same approach works with R (<code>typst_define()</code> ships in both languages), with plain Python collections, with database queries, with REST calls, or with anything else that can produce a value. Python and Polars are one concrete illustration, not a requirement.</p>
<section id="a-python-example" class="level3" data-number="6.1">
<h3 data-number="6.1" class="anchored" data-anchor-id="a-python-example"><span class="header-section-number">6.1</span> A Python example</h3>
<p>The Python helper has explicit converters for <code>polars.DataFrame</code> and <code>numpy.ndarray</code>, which is what keeps the example short.</p>
<div class="callout callout-style-default callout-important callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Important</span>Source the helper yourself
</div>
</div>
<div class="callout-body-container callout-body">
<p><code>typst_define()</code> is not auto-injected. Quarto runs the computational engine <em>before</em> any filter or shortcode, so the extension cannot stage the helper into the kernel for you. You must source it explicitly, typically inside a setup code cell marked <code>#| include: false</code>, by extending <code>sys.path</code> (Python) or <code>source()</code>-ing the file (R) from <code>_extensions/mcanouil/typst-render/_resources/</code>:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb11" data-filename="python" style="background: #f1f3f5;"><pre class="sourceCode python cw-auto code-with-copy"><code class="sourceCode python"><span id="cb11-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#| include: false</span></span>
<span id="cb11-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> os, sys</span>
<span id="cb11-3">sys.path.insert(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, os.path.join(</span>
<span id="cb11-4">  os.environ.get(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"QUARTO_PROJECT_DIR"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"."</span>),</span>
<span id="cb11-5">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"_extensions"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"mcanouil"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"typst-render"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"_resources"</span>,</span>
<span id="cb11-6">))</span>
<span id="cb11-7"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> typst_define <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> typst_define</span></code></pre></div></div>
<p>The code cell below inlines the same bootstrap with <code>#| echo: true</code> so you can read it in context.</p>
</div>
</div>
<div id="3cf60eb7" class="cell" data-execution_count="1">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb12" data-filename="python" style="background: #f1f3f5;"><pre class="sourceCode python cw-auto code-with-copy"><code class="sourceCode python"><span id="cb12-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> os</span>
<span id="cb12-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> sys</span>
<span id="cb12-3"></span>
<span id="cb12-4"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> polars <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> pl</span>
<span id="cb12-5"></span>
<span id="cb12-6">project_dir <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> os.environ.get(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"QUARTO_PROJECT_DIR"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"../.."</span>)</span>
<span id="cb12-7">sys.pycache_prefix <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> os.path.join(project_dir, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">".cache"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"pycache"</span>)</span>
<span id="cb12-8">extension_resources <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> os.path.join(</span>
<span id="cb12-9">  project_dir,</span>
<span id="cb12-10">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"_extensions"</span>,</span>
<span id="cb12-11">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"mcanouil"</span>,</span>
<span id="cb12-12">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"typst-render"</span>,</span>
<span id="cb12-13">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"_resources"</span>,</span>
<span id="cb12-14">)</span>
<span id="cb12-15">sys.path.insert(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, extension_resources)</span>
<span id="cb12-16"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> typst_define <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> typst_define</span>
<span id="cb12-17"></span>
<span id="cb12-18"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Network read at render time; swap for a local CSV or the `palmerpenguins`</span></span>
<span id="cb12-19"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Python package when working offline.</span></span>
<span id="cb12-20">penguins <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> pl.read_csv(</span>
<span id="cb12-21">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"https://raw.githubusercontent.com/allisonhorst/palmerpenguins/main/inst/extdata/penguins.csv"</span>,</span>
<span id="cb12-22">  null_values<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"NA"</span>,</span>
<span id="cb12-23">).with_columns(</span>
<span id="cb12-24">  pl.col(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"flipper_length_mm"</span>).cast(pl.Float64),</span>
<span id="cb12-25">  pl.col(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"body_mass_g"</span>).cast(pl.Float64),</span>
<span id="cb12-26">)</span>
<span id="cb12-27"></span>
<span id="cb12-28">summary <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (</span>
<span id="cb12-29">  penguins</span>
<span id="cb12-30">  .drop_nulls([<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"flipper_length_mm"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"body_mass_g"</span>])</span>
<span id="cb12-31">  .group_by(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"species"</span>)</span>
<span id="cb12-32">  .agg(</span>
<span id="cb12-33">    pl.col(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"flipper_length_mm"</span>).mean().<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">round</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>).alias(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"flipper_mean"</span>),</span>
<span id="cb12-34">    pl.col(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"body_mass_g"</span>).mean().<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">round</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>).cast(pl.Int64).alias(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"body_mass_mean"</span>),</span>
<span id="cb12-35">    pl.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>().alias(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"n"</span>),</span>
<span id="cb12-36">  )</span>
<span id="cb12-37">  .sort(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"species"</span>)</span>
<span id="cb12-38">)</span>
<span id="cb12-39"></span>
<span id="cb12-40">typst_define(summary<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>summary)</span>
<span id="cb12-41">summary</span></code></pre></div></div>
<div class="cell-output cell-output-display cell-output-markdown">

</div>
<div class="cell-output cell-output-display" data-execution_count="1">
<div><style>
.dataframe > thead > tr,
.dataframe > tbody > tr {
  text-align: right;
  white-space: pre-wrap;
}
</style>
<small>shape: (3, 4)</small>
<table class="dataframe caption-top table table-sm table-striped small" data-border="1">
<thead>
<tr class="header">
<th data-quarto-table-cell-role="th">species</th>
<th data-quarto-table-cell-role="th">flipper_mean</th>
<th data-quarto-table-cell-role="th">body_mass_mean</th>
<th data-quarto-table-cell-role="th">n</th>
</tr>
<tr class="even">
<td>str</td>
<td>f64</td>
<td>i64</td>
<td>u32</td>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>"Adelie"</td>
<td>190.0</td>
<td>3701</td>
<td>151</td>
</tr>
<tr class="even">
<td>"Chinstrap"</td>
<td>195.8</td>
<td>3733</td>
<td>68</td>
</tr>
<tr class="odd">
<td>"Gentoo"</td>
<td>217.2</td>
<td>5076</td>
<td>123</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<p>The trailing <code>summary</code> is there on purpose, so Jupyter prints the DataFrame in the rendered output and the reader can see exactly what crossed the language boundary. The same <code>plot()</code> call you wrote in the previous section now reads its data from <code>typst_define.summary</code> instead of the bundled <code>penguins</code> symbol.</p>
<div class="quarto-layout-panel" data-layout-ncol="2">
<div class="quarto-layout-row quarto-layout-valign-center">
<div class="quarto-layout-cell" style="flex-basis: 50.0%;justify-content: flex-start;">
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb13" data-filename="typst" style="background: #f1f3f5;"><pre class="sourceCode typst cw-auto code-with-copy"><code class="sourceCode typst"><span id="cb13-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">cb</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb13-2">  rgb<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#0072B2"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb13-3">  rgb<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#D55E00"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb13-4">  rgb<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#009E73"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb13-5"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb13-6"></span>
<span id="cb13-7"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>plot<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb13-8">  data<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> typst_define<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>summary<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb13-9">  mapping<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> aes<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb13-10">    x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"species"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb13-11">    y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"body_mass_mean"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb13-12">    fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"species"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb13-13">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb13-14">  layers<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>geom-col<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.6</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),),</span></span>
<span id="cb13-15">  scales<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>scale-fill-discrete<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>palette<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> cb<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),),</span></span>
<span id="cb13-16">  labs<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> labs<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb13-17">    title<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Mean Body Mass per Species"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb13-18">    subtitle<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Computed in Polars, drawn in Gribouille"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb13-19">    x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Species"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb13-20">    y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Body Mass (g)"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb13-21">    fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Species"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb13-22">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb13-23">  theme<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> theme-minimal<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(),</span></span>
<span id="cb13-24">  width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">12cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb13-25">  height<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">7cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb13-26"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span></code></pre></div></div>
</div>
<div class="quarto-layout-cell" style="flex-basis: 50.0%;justify-content: flex-start;">
<div class="light-content">
<p><a href="../../posts/2026-05-20-gribouille-grammar-of-graphics-for-typst/./assets/typst-render/polars-summary-light.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-20" title="Bar chart of mean body mass per species (Adelie, Chinstrap, Gentoo), three coloured bars on a minimal theme with bold title, sentence-case subtitle 'Computed in Polars, drawn in Gribouille', and a species legend on the right."><img src="https://mickael.canouil.fr/posts/2026-05-20-gribouille-grammar-of-graphics-for-typst/assets/typst-render/polars-summary-light.svg" class="img-fluid hero-art" alt="Bar chart of mean body mass per species (Adelie, Chinstrap, Gentoo), three coloured bars on a minimal theme with bold title, sentence-case subtitle 'Computed in Polars, drawn in Gribouille', and a species legend on the right."></a></p>
</div>
<div class="dark-content">
<p><a href="../../posts/2026-05-20-gribouille-grammar-of-graphics-for-typst/./assets/typst-render/polars-summary-dark.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-21" title="Bar chart of mean body mass per species (Adelie, Chinstrap, Gentoo), three coloured bars on a minimal theme with bold title, sentence-case subtitle 'Computed in Polars, drawn in Gribouille', and a species legend on the right."><img src="https://mickael.canouil.fr/posts/2026-05-20-gribouille-grammar-of-graphics-for-typst/assets/typst-render/polars-summary-dark.svg" class="img-fluid hero-art" alt="Bar chart of mean body mass per species (Adelie, Chinstrap, Gentoo), three coloured bars on a minimal theme with bold title, sentence-case subtitle 'Computed in Polars, drawn in Gribouille', and a species legend on the right."></a></p>
</div>
</div>
</div>
</div>
<div class="callout callout-style-default callout-tip callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Tip</span>Rendering notes
</div>
</div>
<div class="callout-body-container callout-body">
<ul>
<li>Set <code>engine: jupyter</code> explicitly in the post YAML so the Python code cells run via Jupyter.</li>
<li>Pin the kernel with <code>jupyter: &lt;name&gt;</code> (this post uses <code>python3</code>, but a dedicated kernel such as <code>gribouille-post</code> makes the env reproducible).</li>
<li>The Python environment needs <code>polars</code> and <a href="https://ipython.org"><code>IPython</code></a> available.</li>
<li>The Lua filter runs before any Quarto rendering, so <code>typst_define</code> is in scope for every <code>{typst}</code> block from that point in the document onward.</li>
</ul>
</div>
</div>
</section>
</section>
<section id="sec-limitations" class="level2" data-number="7">
<h2 data-number="7" class="anchored" data-anchor-id="sec-limitations"><span class="header-section-number">7</span> Current limitations</h2>
<p>This first release is deliberately focused. Some <code>ggplot2</code>/<code>plotnine</code> features are out of scope by design; others are pending while the project matures.</p>
<ul>
<li>No <code>geom-violin()</code>, <code>geom-density()</code>, or <code>geom-density-2d()</code>. Kernel density estimation needs numerical machinery that pure Typst cannot run efficiently, and the realistic escape hatch is a WebAssembly helper. That route is on hold for now because I do not yet have enough hands-on WASM experience to build and ship one with confidence, and the help I have had from LLMs has not closed that gap to a level I am comfortable releasing.</li>
<li><code>geom-smooth()</code> and <a href="https://m.canouil.dev/gribouille/reference/stats/stat-smooth.html"><code>stat-smooth()</code></a> support <code>method: "lm"</code> only. LOESS is deferred for the same reason.</li>
<li><a href="https://m.canouil.dev/gribouille/reference/geoms/geom-dotplot.html"><code>geom-dotplot()</code></a> uses histogram-style binning only; data-driven dot-density binning is deferred.</li>
<li>Maps and spatial geometries (<code>ggplot2</code>’s <code>sf</code> integration) are currently out of scope.</li>
</ul>
</section>
<section id="wrap-up" class="level2" data-number="8">
<h2 data-number="8" class="anchored" data-anchor-id="wrap-up"><span class="header-section-number">8</span> Wrap-up</h2>
<div class="highlight">
<p><strong>Small, focused, and modelled on the library that has shaped a generation of data visualisation.</strong></p>
</div>
<p>Next on the list is filling in more geoms, more themes, and more worked examples, and chasing feedback from the people writing Typst documents day to day.</p>
<ul>
<li>Gribouille
<ul>
<li>Repository: <a href="https://github.com/mcanouil/gribouille" class="uri">https://github.com/mcanouil/gribouille</a>.</li>
<li>Documentation: <a href="https://m.canouil.dev/gribouille" class="uri">https://m.canouil.dev/gribouille</a>.</li>
<li>Typst Universe: <a href="https://typst.app/universe/package/gribouille" class="uri">https://typst.app/universe/package/gribouille</a>.</li>
</ul></li>
<li>Typst Render
<ul>
<li>Repository: &lt;https:// github.com/mcanouil/quarto-typst-render&gt;.</li>
<li>Documentation: <a href="https://m.canouil.dev/quarto-typst-render" class="uri">https://m.canouil.dev/quarto-typst-render</a>.</li>
</ul></li>
</ul>
<div class="callout callout-style-default callout-tip callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Tip</span>A note on contributions
</div>
</div>
<div class="callout-body-container callout-body">
<p>Gribouille is an unfunded spare-time project, and the API is still settling. Bug reports and ideas are very welcome on the issue tracker. Pull requests are not being accepted for now: the internals shift between releases, every review costs time I have to take from the work that moves the library forward, and I am being especially careful in the current climate of unreviewed LLM-authored patches. Once the surface is stable I will revisit and open the door. Thanks in advance for your patience and your understanding.</p>
</div>
</div>


</section>

<a onclick="window.scrollTo(0, 0); return false;" id="quarto-back-to-top"><i class="bi bi-arrow-up"></i> Back to top</a><div id="quarto-appendix" class="default"><section class="quarto-appendix-contents" id="quarto-reuse"><h2 class="anchored quarto-appendix-heading">Reuse</h2><div class="quarto-appendix-contents"><div><a rel="license" href="https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en_gb">CC BY-NC-SA 4.0</a></div></div></section><section class="quarto-appendix-contents" id="quarto-citation"><h2 class="anchored quarto-appendix-heading">Citation</h2><div><div class="quarto-appendix-secondary-label">BibTeX citation:</div><pre class="sourceCode code-with-copy quarto-appendix-bibtex"><code class="sourceCode bibtex">@misc{canouil2026,
  author = {CANOUIL, Mickaël},
  title = {Gribouille: A {Grammar} of {Graphics} for {Typst}},
  date = {2026-05-20},
  url = {https://mickael.canouil.fr/posts/2026-05-20-gribouille-grammar-of-graphics-for-typst/},
  langid = {en-GB}
}
</code></pre><div class="quarto-appendix-secondary-label">For attribution, please cite this work as:</div><div id="ref-canouil2026" class="csl-entry quarto-appendix-citeas">
CANOUIL, M. (2026-05-20). Gribouille: a Grammar of Graphics for Typst.
<em>Mickael.canouil.fr</em>. <a href="https://mickael.canouil.fr/posts/2026-05-20-gribouille-grammar-of-graphics-for-typst/">https://mickael.canouil.fr/posts/2026-05-20-gribouille-grammar-of-graphics-for-typst/</a>
</div></div></section></div> ]]></description>
  <category>typst</category>
  <category>quarto</category>
  <category>computation</category>
  <category>grammar-of-graphics</category>
  <category>gribouille</category>
  <guid>https://mickael.canouil.fr/posts/2026-05-20-gribouille-grammar-of-graphics-for-typst/</guid>
  <pubDate>Wed, 20 May 2026 00:00:00 GMT</pubDate>
  <media:content url="https://mickael.canouil.fr/posts/2026-05-20-gribouille-grammar-of-graphics-for-typst/featured.png" medium="image" type="image/png" height="76" width="144"/>
</item>
<item>
  <title>Quarto Reveal.js Extensions to Sharpen Your Slides</title>
  <link>https://mickael.canouil.fr/posts/2026-04-21-quarto-revealjs-extensions/</link>
  <description><![CDATA[ 

<!--
@license MIT
@copyright 2026 Mickaël Canouil
@author Mickaël Canouil
-->
Skip to main content





<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="featured.png" class="lightbox" data-gallery="quarto-lightbox-gallery-1"><img src="https://mickael.canouil.fr/posts/2026-04-21-quarto-revealjs-extensions/featured.png" class="img-featured img-fluid quarto-figure quarto-figure-center figure-img" alt="Dark-background card grid titled &quot;Quarto Reveal.js Extensions&quot; with four
coloured tiles: fragmention in blue, codefrag in teal, cascade in amber,
and tabset in rose, each with a numbered badge and a short description.
" width="600"></a></p>
</figure>
</div>
<section id="introduction" class="level2" data-number="1">
<h2 data-number="1" class="anchored" data-anchor-id="introduction"><span class="header-section-number">1</span> Introduction</h2>
<p>Reveal.js is a flexible slide engine, but a few rough edges show up again and again when writing slides in Quarto. List fragments need a marker on every item, code annotations stay static, long talks repeat the same heading on continuation slides, and tabsets need a mouse click to advance. Each of the four small extensions below closes one of those gaps, and they compose cleanly on a single slidedeck.</p>
<p>Every section installs one extension, shows the minimal front-matter and body needed to activate it, and embeds a focused slidedeck you can step through with the arrow keys.</p>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Note</span>Four extensions at a glance
</div>
</div>
<div class="callout-body-container callout-body">
<ul>
<li><a href="https://github.com/mcanouil/quarto-revealjs-fragmention"><code>quarto-revealjs-fragmention</code></a> hoists <code>.fragment</code> markers onto list items.</li>
<li><a href="https://github.com/mcanouil/quarto-revealjs-codefrag"><code>quarto-revealjs-codefrag</code></a> turns code annotations into navigable fragments.</li>
<li><a href="https://github.com/mcanouil/quarto-revealjs-cascade"><code>quarto-revealjs-cascade</code></a> repeats heading chains after slide breaks.</li>
<li><a href="https://github.com/mcanouil/quarto-revealjs-tabset"><code>quarto-revealjs-tabset</code></a> makes <code>.panel-tabset</code> blocks keyboard and PDF friendly.</li>
</ul>
</div>
</div>
</section>
<section id="fragmention" class="level2" data-number="2">
<h2 data-number="2" class="anchored" data-anchor-id="fragmention"><span class="header-section-number">2</span> Fragmention</h2>
<p><code>quarto-revealjs-fragmention</code> lets you animate a whole list item as a single fragment. You place an empty span with the <code>.fragment</code> class (and optional <code>fragment-index</code>) at the start of the item, and the filter hoists those attributes onto the parent <code>&lt;li&gt;</code>, so there is no stray marker left in the rendered slide.</p>
<section id="install" class="level3" data-number="2.1">
<h3 data-number="2.1" class="anchored" data-anchor-id="install"><span class="header-section-number">2.1</span> Install</h3>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" data-filename="bash" style="background: #f1f3f5;"><pre class="sourceCode bash cw-auto code-with-copy"><code class="sourceCode bash"><span id="cb1-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">quarto</span> add mcanouil/quarto-revealjs-fragmention</span></code></pre></div></div>
</section>
<section id="activate-in-the-front-matter" class="level3" data-number="2.2">
<h3 data-number="2.2" class="anchored" data-anchor-id="activate-in-the-front-matter"><span class="header-section-number">2.2</span> Activate in the front-matter</h3>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb2" data-startfrom="4" data-filename="yaml" style="background: #f1f3f5;"><pre class="sourceCode yaml cw-auto code-with-copy"><code class="sourceCode yaml" style="counter-reset: source-line 3;"><span id="cb2-4"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">filters</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb2-5"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">  </span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> fragmention</span></span></code></pre></div></div>
</section>
<section id="mark-up-your-list" class="level3" data-number="2.3">
<h3 data-number="2.3" class="anchored" data-anchor-id="mark-up-your-list"><span class="header-section-number">2.3</span> Mark up your list</h3>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb3" data-shortcodes="false" data-startfrom="8" data-filename="markdown" style="background: #f1f3f5;"><pre class="sourceCode markdown cw-auto code-with-copy"><code class="sourceCode markdown" style="counter-reset: source-line 7;"><span id="cb3-8"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">## A staged list</span></span>
<span id="cb3-9"></span>
<span id="cb3-10"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">- </span>[]{.fragment fragment-index="1"} Load the data from disk.</span>
<span id="cb3-11"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">- </span>[]{.fragment fragment-index="2"} Clean and validate the inputs.</span>
<span id="cb3-12"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">- </span>[]{.fragment fragment-index="3"} Fit the model.</span>
<span id="cb3-13"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">- </span>[]{.fragment fragment-index="4"} Report the results.</span>
<span id="cb3-14"></span>
<span id="cb3-15"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">## Mixed fragments</span></span>
<span id="cb3-16"></span>
<span id="cb3-17"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">- </span>[]{.fragment .fade-in-then-semi-out fragment-index="1"} Step one fades in, then dims.</span>
<span id="cb3-18"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">- </span>[]{.fragment .highlight-red fragment-index="2"} Step two highlights in red.</span>
<span id="cb3-19"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">- </span>A static bullet always visible.</span>
<span id="cb3-20"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">- </span>[]{.fragment .grow fragment-index="3"} Step three grows for emphasis.</span></code></pre></div></div>
</section>
<section id="live-demo" class="level3" data-number="2.4">
<h3 data-number="2.4" class="anchored" data-anchor-id="live-demo"><span class="header-section-number">2.4</span> Live demo</h3>
<div style="text-align: center;">
<p><a href="assets/slides/demo-fragmention.html" class="lightbox" data-gallery="quarto-lightbox-gallery-2"><embed src="assets/slides/demo-fragmention.html" class="hero-art slide-deck img-fluid" loading="lazy" aria-label="Fragmention demo slidedeck"></a></p>
</div>
</section>
</section>
<section id="codefrag" class="level2" data-number="3">
<h2 data-number="3" class="anchored" data-anchor-id="codefrag"><span class="header-section-number">3</span> Codefrag</h2>
<p><code>quarto-revealjs-codefrag</code> pairs naturally with Quarto’s <code>code-annotations: select</code> mode. Each <code># &lt;n&gt;</code> marker becomes a Reveal.js fragment, so the matching annotation appears step by step as you press the arrow keys, and the tooltip stays in sync with the current annotation.</p>
<section id="install-1" class="level3" data-number="3.1">
<h3 data-number="3.1" class="anchored" data-anchor-id="install-1"><span class="header-section-number">3.1</span> Install</h3>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb4" data-filename="bash" style="background: #f1f3f5;"><pre class="sourceCode bash cw-auto code-with-copy"><code class="sourceCode bash"><span id="cb4-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">quarto</span> add mcanouil/quarto-revealjs-codefrag</span></code></pre></div></div>
</section>
<section id="activate-in-the-front-matter-1" class="level3" data-number="3.2">
<h3 data-number="3.2" class="anchored" data-anchor-id="activate-in-the-front-matter-1"><span class="header-section-number">3.2</span> Activate in the front-matter</h3>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb5" data-startfrom="5" data-filename="yaml" style="background: #f1f3f5;"><pre class="sourceCode yaml cw-auto code-with-copy"><code class="sourceCode yaml" style="counter-reset: source-line 4;"><span id="cb5-5"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">revealjs-plugins</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb5-6"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">  </span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> codefrag</span></span></code></pre></div></div>
</section>
<section id="annotate-your-code" class="level3" data-number="3.3">
<h3 data-number="3.3" class="anchored" data-anchor-id="annotate-your-code"><span class="header-section-number">3.3</span> Annotate your code</h3>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb6" data-shortcodes="false" data-startfrom="9" data-filename="markdown" style="background: #f1f3f5;"><pre class="sourceCode markdown cw-auto code-with-copy"><code class="sourceCode markdown" style="counter-reset: source-line 8;"><span id="cb6-9"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">## Step through an R pipeline</span></span>
<span id="cb6-10"></span>
<span id="cb6-11"><span class="in" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">```{.r code-line-numbers="|1|2-3|4-5|6"}</span></span>
<span id="cb6-12"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">library</span>(dplyr)                          <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># &lt;1&gt;</span></span>
<span id="cb6-13">mtcars <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb6-14">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">filter</span>(cyl <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">6</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span>                   <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># &lt;2&gt;</span></span>
<span id="cb6-15">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">group_by</span>(gear) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb6-16">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">summarise</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">mean_mpg =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">mean</span>(mpg)) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span>    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># &lt;3&gt;</span></span>
<span id="cb6-17">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">arrange</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">desc</span>(mean_mpg))               <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># &lt;4&gt;</span></span>
<span id="cb6-18"><span class="in" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">```</span></span>
<span id="cb6-19"></span>
<span id="cb6-20"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">1. </span>Load the <span class="in" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">`dplyr`</span> package.</span>
<span id="cb6-21"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">2. </span>Keep only six-cylinder cars.</span>
<span id="cb6-22"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">3. </span>Compute the mean mileage per gear.</span>
<span id="cb6-23"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">4. </span>Sort the resulting table.</span></code></pre></div></div>
</section>
<section id="live-demo-1" class="level3" data-number="3.4">
<h3 data-number="3.4" class="anchored" data-anchor-id="live-demo-1"><span class="header-section-number">3.4</span> Live demo</h3>
<div style="text-align: center;">
<p><a href="assets/slides/demo-codefrag.html" class="lightbox" data-gallery="quarto-lightbox-gallery-3"><embed src="assets/slides/demo-codefrag.html" class="hero-art slide-deck img-fluid" loading="lazy" aria-label="Codefrag demo slidedeck"></a></p>
</div>
</section>
</section>
<section id="cascade" class="level2" data-number="4">
<h2 data-number="4" class="anchored" data-anchor-id="cascade"><span class="header-section-number">4</span> Cascade</h2>
<p><code>quarto-revealjs-cascade</code> repeats the current heading chain whenever a <code>---</code> slide break creates a continuation slide. You keep one section heading (and optional subsection) at the top of a topic, then use <code>---</code> to start fresh slides without retyping the title.</p>
<section id="install-2" class="level3" data-number="4.1">
<h3 data-number="4.1" class="anchored" data-anchor-id="install-2"><span class="header-section-number">4.1</span> Install</h3>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb7" data-filename="bash" style="background: #f1f3f5;"><pre class="sourceCode bash cw-auto code-with-copy"><code class="sourceCode bash"><span id="cb7-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">quarto</span> add mcanouil/quarto-revealjs-cascade</span></code></pre></div></div>
</section>
<section id="activate-in-the-front-matter-2" class="level3" data-number="4.2">
<h3 data-number="4.2" class="anchored" data-anchor-id="activate-in-the-front-matter-2"><span class="header-section-number">4.2</span> Activate in the front-matter</h3>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb8" data-startfrom="4" data-filename="yaml" style="background: #f1f3f5;"><pre class="sourceCode yaml cw-auto code-with-copy"><code class="sourceCode yaml" style="counter-reset: source-line 3;"><span id="cb8-4"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">filters</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb8-5"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">  </span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> cascade</span></span></code></pre></div></div>
</section>
<section id="chain-your-headings" class="level3" data-number="4.3">
<h3 data-number="4.3" class="anchored" data-anchor-id="chain-your-headings"><span class="header-section-number">4.3</span> Chain your headings</h3>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb9" data-shortcodes="false" data-startfrom="8" data-filename="markdown" style="background: #f1f3f5;"><pre class="sourceCode markdown cw-auto code-with-copy"><code class="sourceCode markdown" style="counter-reset: source-line 7;"><span id="cb9-8"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;"># Cascade demo</span></span>
<span id="cb9-9"></span>
<span id="cb9-10"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">## Results</span></span>
<span id="cb9-11"></span>
<span id="cb9-12"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">### Experiment A</span></span>
<span id="cb9-13"></span>
<span id="cb9-14"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">- </span>Baseline accuracy: 0.82.</span>
<span id="cb9-15"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">- </span>Calibration error: 0.04.</span>
<span id="cb9-16"></span>
<span id="cb9-17">---</span>
<span id="cb9-18"></span>
<span id="cb9-19"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">- </span>Recall improves with larger batches.</span>
<span id="cb9-20"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">- </span>Precision plateaus after 10 epochs.</span>
<span id="cb9-21"></span>
<span id="cb9-22">---</span>
<span id="cb9-23"></span>
<span id="cb9-24"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">### Experiment B</span></span>
<span id="cb9-25"></span>
<span id="cb9-26"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">- </span>Baseline accuracy: 0.87.</span>
<span id="cb9-27"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">- </span>Calibration error: 0.03.</span>
<span id="cb9-28"></span>
<span id="cb9-29">---</span>
<span id="cb9-30"></span>
<span id="cb9-31"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">- </span>Results replicate across three seeds.</span>
<span id="cb9-32"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">- </span>No regression on the held-out split.</span></code></pre></div></div>
</section>
<section id="live-demo-2" class="level3" data-number="4.4">
<h3 data-number="4.4" class="anchored" data-anchor-id="live-demo-2"><span class="header-section-number">4.4</span> Live demo</h3>
<div style="text-align: center;">
<p><a href="assets/slides/demo-cascade.html" class="lightbox" data-gallery="quarto-lightbox-gallery-4"><embed src="assets/slides/demo-cascade.html" class="hero-art slide-deck img-fluid" loading="lazy" aria-label="Cascade demo slidedeck"></a></p>
</div>
</section>
</section>
<section id="tabset" class="level2" data-number="5">
<h2 data-number="5" class="anchored" data-anchor-id="tabset"><span class="header-section-number">5</span> Tabset</h2>
<p><code>quarto-revealjs-tabset</code> turns a <code>.panel-tabset</code> block into a keyboard-driven tabset. Each tab becomes a Reveal.js fragment, so the arrow keys cycle through them, and PDF export lays every tab out on its own page instead of collapsing them to the active one.</p>
<section id="install-3" class="level3" data-number="5.1">
<h3 data-number="5.1" class="anchored" data-anchor-id="install-3"><span class="header-section-number">5.1</span> Install</h3>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb10" data-filename="bash" style="background: #f1f3f5;"><pre class="sourceCode bash cw-auto code-with-copy"><code class="sourceCode bash"><span id="cb10-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">quarto</span> add mcanouil/quarto-revealjs-tabset</span></code></pre></div></div>
</section>
<section id="activate-in-the-front-matter-3" class="level3" data-number="5.2">
<h3 data-number="5.2" class="anchored" data-anchor-id="activate-in-the-front-matter-3"><span class="header-section-number">5.2</span> Activate in the front-matter</h3>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb11" data-startfrom="4" data-filename="yaml" style="background: #f1f3f5;"><pre class="sourceCode yaml cw-auto code-with-copy"><code class="sourceCode yaml" style="counter-reset: source-line 3;"><span id="cb11-4"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">revealjs-plugins</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb11-5"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">  </span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> tabset</span></span></code></pre></div></div>
</section>
<section id="wrap-your-tabs" class="level3" data-number="5.3">
<h3 data-number="5.3" class="anchored" data-anchor-id="wrap-your-tabs"><span class="header-section-number">5.3</span> Wrap your tabs</h3>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb12" data-shortcodes="false" data-startfrom="8" data-filename="markdown" style="background: #f1f3f5;"><pre class="sourceCode markdown cw-auto code-with-copy"><code class="sourceCode markdown" style="counter-reset: source-line 7;"><span id="cb12-8"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">## Dataset overview {.smaller}</span></span>
<span id="cb12-9"></span>
<span id="cb12-10">::: {.panel-tabset}</span>
<span id="cb12-11"></span>
<span id="cb12-12"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">### Description</span></span>
<span id="cb12-13"></span>
<span id="cb12-14"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">- </span>1,000 records collected in 2025.</span>
<span id="cb12-15"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">- </span>Three outcome labels, balanced across classes.</span>
<span id="cb12-16"></span>
<span id="cb12-17"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">### Summary</span></span>
<span id="cb12-18"></span>
<span id="cb12-19"><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">|</span> Feature <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">|</span> Min <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">|</span> Median <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">|</span> Max <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">|</span></span>
<span id="cb12-20"><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">|---------|----:|-------:|----:|</span></span>
<span id="cb12-21"><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">|</span> Age     <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">|</span>  18 <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">|</span>     42 <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">|</span>  91 <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">|</span></span>
<span id="cb12-22"><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">|</span> Score   <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">|</span>   0 <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">|</span>     57 <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">|</span> 100 <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">|</span></span>
<span id="cb12-23"></span>
<span id="cb12-24"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">### Notes</span></span>
<span id="cb12-25"></span>
<span id="cb12-26"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">- </span>Missing values imputed with the column median.</span>
<span id="cb12-27"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">- </span>Duplicates removed before splitting.</span>
<span id="cb12-28"></span>
<span id="cb12-29">:::</span></code></pre></div></div>
</section>
<section id="live-demo-3" class="level3" data-number="5.4">
<h3 data-number="5.4" class="anchored" data-anchor-id="live-demo-3"><span class="header-section-number">5.4</span> Live demo</h3>
<div style="text-align: center;">
<p><a href="assets/slides/demo-tabset.html" class="lightbox" data-gallery="quarto-lightbox-gallery-5"><embed src="assets/slides/demo-tabset.html" class="hero-art slide-deck img-fluid" loading="lazy" aria-label="Tabset demo slidedeck"></a></p>
</div>
</section>
</section>
<section id="all-four-together" class="level2" data-number="6">
<h2 data-number="6" class="anchored" data-anchor-id="all-four-together"><span class="header-section-number">6</span> All four together</h2>
<p>The four extensions compose without fuss on a single slidedeck. You declare the two filters, the two plugins, and <code>code-annotations: select</code>, then use each feature where it fits the story.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb13" data-startfrom="5" data-filename="yaml" style="background: #f1f3f5;"><pre class="sourceCode yaml cw-auto code-with-copy"><code class="sourceCode yaml" style="counter-reset: source-line 4;"><span id="cb13-5"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">filters</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb13-6"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">  </span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> fragmention</span></span>
<span id="cb13-7"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">  </span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> cascade</span></span>
<span id="cb13-8"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">revealjs-plugins</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb13-9"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">  </span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> codefrag</span></span>
<span id="cb13-10"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">  </span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> tabset</span></span></code></pre></div></div>
<div style="text-align: center;">
<p><a href="assets/slides/demo-combined.html" class="lightbox" data-gallery="quarto-lightbox-gallery-6"><embed src="assets/slides/demo-combined.html" class="hero-art slide-deck img-fluid" loading="lazy" aria-label="Combined demo slidedeck"></a></p>
</div>
</section>
<section id="wrap-up" class="level2" data-number="7">
<h2 data-number="7" class="anchored" data-anchor-id="wrap-up"><span class="header-section-number">7</span> Wrap-up</h2>
<p>Each extension is small, focused, and targets a friction point rather than a whole workflow. Install the ones you need, keep the rest out of your front-matter, and your next slidedeck can lean a little more on the keyboard and a little less on the mouse.</p>
<p>Repositories for the curious:</p>
<ul>
<li><a href="https://github.com/mcanouil/quarto-revealjs-fragmention"><code>quarto-revealjs-fragmention</code></a>.</li>
<li><a href="https://github.com/mcanouil/quarto-revealjs-codefrag"><code>quarto-revealjs-codefrag</code></a>.</li>
<li><a href="https://github.com/mcanouil/quarto-revealjs-cascade"><code>quarto-revealjs-cascade</code></a>.</li>
<li><a href="https://github.com/mcanouil/quarto-revealjs-tabset"><code>quarto-revealjs-tabset</code></a>.</li>
</ul>


</section>

<a onclick="window.scrollTo(0, 0); return false;" id="quarto-back-to-top"><i class="bi bi-arrow-up"></i> Back to top</a><div id="quarto-appendix" class="default"><section class="quarto-appendix-contents" id="quarto-reuse"><h2 class="anchored quarto-appendix-heading">Reuse</h2><div class="quarto-appendix-contents"><div><a rel="license" href="https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en_gb">CC BY-NC-SA 4.0</a></div></div></section><section class="quarto-appendix-contents" id="quarto-citation"><h2 class="anchored quarto-appendix-heading">Citation</h2><div><div class="quarto-appendix-secondary-label">BibTeX citation:</div><pre class="sourceCode code-with-copy quarto-appendix-bibtex"><code class="sourceCode bibtex">@misc{canouil2026,
  author = {CANOUIL, Mickaël},
  title = {Quarto {Reveal.js} {Extensions} to {Sharpen} {Your} {Slides}},
  date = {2026-04-21},
  url = {https://mickael.canouil.fr/posts/2026-04-21-quarto-revealjs-extensions/},
  langid = {en-GB}
}
</code></pre><div class="quarto-appendix-secondary-label">For attribution, please cite this work as:</div><div id="ref-canouil2026" class="csl-entry quarto-appendix-citeas">
CANOUIL, M. (2026-04-21). Quarto Reveal.js Extensions to Sharpen Your
Slides. <em>Mickael.canouil.fr</em>. <a href="https://mickael.canouil.fr/posts/2026-04-21-quarto-revealjs-extensions/">https://mickael.canouil.fr/posts/2026-04-21-quarto-revealjs-extensions/</a>
</div></div></section></div> ]]></description>
  <category>quarto</category>
  <category>reveal.js</category>
  <category>extensions</category>
  <category>presentations</category>
  <guid>https://mickael.canouil.fr/posts/2026-04-21-quarto-revealjs-extensions/</guid>
  <pubDate>Tue, 21 Apr 2026 00:00:00 GMT</pubDate>
  <media:content url="https://mickael.canouil.fr/posts/2026-04-21-quarto-revealjs-extensions/featured.png" medium="image" type="image/png" height="76" width="144"/>
</item>
<item>
  <title>Branded Figures and Tables in R and Python with Quarto</title>
  <link>https://mickael.canouil.fr/posts/2026-04-15-quarto-brand-figures-tables/</link>
  <description><![CDATA[ 

<!--
@license MIT
@copyright 2026 Mickaël Canouil
@author Mickaël Canouil
-->
Skip to main content





<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="featured.png" class="lightbox" data-gallery="quarto-lightbox-gallery-1"><img src="https://mickael.canouil.fr/posts/2026-04-15-quarto-brand-figures-tables/featured.png" class="img-featured img-fluid quarto-figure quarto-figure-center figure-img" alt="Workflow diagram showing how Quarto's brand feature feeds code-generated
outputs: quarto render sets QUARTO_EXECUTE_INFO, parsed by
get_brand_info(), which feeds configure_brand_fonts(), theme_brand(),
and gt_brand() to produce light and dark figures and tables. The Quarto
logo appears in the top right.
" width="600"></a></p>
</figure>
</div>
<div class="callout callout-style-default callout-important callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Important</span>Quarto Version
</div>
</div>
<div class="callout-body-container callout-body">
<p>This post was written and tested with <strong>Quarto CLI 1.9.37</strong>. Some APIs or behaviours may differ in earlier stable releases.</p>
</div>
</div>
<p>Quarto’s <code>brand</code> feature is a convenient way to keep a document’s look consistent across formats: define your colours, fonts, and logos once in a <code>_brand.yml</code> file, and everything from the navbar to the code blocks picks them up automatically. Code-generated outputs, however, are a different story. A <code>ggplot2</code> scatter plot or a <code>gt</code> summary table will cheerfully ignore your brand and render with their own defaults. This post walks through one approach to close that gap: reading the resolved brand configuration at render time and applying it to every figure and table your code produces.</p>
<div class="callout callout-style-default callout-tip callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Tip</span>Key Principles
</div>
</div>
<div class="callout-body-container callout-body">
<ul>
<li>Quarto sets <code>QUARTO_EXECUTE_INFO</code> for every code cell; parse it for brand colours, fonts, and palettes.</li>
<li>Register web fonts before any plotting; graphics devices do not see brand-defined fonts by default.</li>
<li>Wrap the styling logic in <code>theme_brand()</code> (figures) and <code>gt_brand()</code> (tables) so every output inherits brand values.</li>
<li>Use <code>renderings: [light, dark]</code> and <code>.light-content</code>/<code>.dark-content</code> to match the reader’s selected theme automatically.</li>
<li>The companion repository <a href="https://github.com/mcanouil/demo-quarto-brand-renderings">mcanouil/demo-quarto-brand-renderings</a> has production-ready implementations with full error handling.</li>
</ul>
</div>
</div>
<section id="the-problem" class="level2" data-number="1">
<h2 data-number="1" class="anchored" data-anchor-id="the-problem"><span class="header-section-number">1</span> The Problem</h2>
<p>The goal is to make every visual output match the document’s brand without hardcoding hex values or font names. Here is what branded outputs look like across six different brand configurations, each with light and dark variants:</p>
<section class="featured-carousel-section" aria-label="Branded outputs preview">
  <div class="featured-carousel featured-carousel--media featured-carousel--n6" aria-roledescription="carousel" aria-label="Branded outputs across six brand configurations">
    <div class="featured-track" style="--total: 7;">
      <a class="lightbox" href="assets/_featured-brand-1.png" data-gallery="brand-carousel"><img class="lightbox" src="https://mickael.canouil.fr/posts/2026-04-15-quarto-brand-figures-tables/assets/_featured-brand-1.png" alt="Brand 1 featured image showing light and dark mode branded figures and tables." loading="lazy"></a>
      <a class="lightbox" href="assets/_featured-brand-2.png" data-gallery="brand-carousel"><img class="lightbox" src="https://mickael.canouil.fr/posts/2026-04-15-quarto-brand-figures-tables/assets/_featured-brand-2.png" alt="Brand 2 featured image showing light and dark mode branded figures and tables." loading="lazy"></a>
      <a class="lightbox" href="assets/_featured-brand-3.png" data-gallery="brand-carousel"><img class="lightbox" src="https://mickael.canouil.fr/posts/2026-04-15-quarto-brand-figures-tables/assets/_featured-brand-3.png" alt="Brand 3 featured image showing light and dark mode branded figures and tables." loading="lazy"></a>
      <a class="lightbox" href="assets/_featured-brand-4.png" data-gallery="brand-carousel"><img class="lightbox" src="https://mickael.canouil.fr/posts/2026-04-15-quarto-brand-figures-tables/assets/_featured-brand-4.png" alt="Brand 4 featured image showing light and dark mode branded figures and tables." loading="lazy"></a>
      <a class="lightbox" href="assets/_featured-brand-5.png" data-gallery="brand-carousel"><img class="lightbox" src="https://mickael.canouil.fr/posts/2026-04-15-quarto-brand-figures-tables/assets/_featured-brand-5.png" alt="Brand 5 featured image showing light and dark mode branded figures and tables." loading="lazy"></a>
      <a class="lightbox" href="assets/_featured-brand-6.png" data-gallery="brand-carousel"><img class="lightbox" src="https://mickael.canouil.fr/posts/2026-04-15-quarto-brand-figures-tables/assets/_featured-brand-6.png" alt="Brand 6 featured image showing light and dark mode branded figures and tables." loading="lazy"></a>
      
    </div>
  </div>
  
</section>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Note</span>Alternative Approaches
</div>
</div>
<div class="callout-body-container callout-body">
<p>There is no single right way to achieve branded outputs in R and Python. The <a href="https://posit-dev.github.io/brand-yml/"><code>brand.yml</code></a> project provides dedicated <a href="https://posit-dev.github.io/brand-yml/pkg/r/">R</a> and <a href="https://posit-dev.github.io/brand-yml/pkg/py/">Python</a> packages that read a <code>_brand.yml</code> file directly and are the recommended starting point for most projects. At the time of writing, however, the <code>brand.yml</code> packages do not support font registration for graphics devices. You could also write your own wrappers, hard-code a shared palette, or use any other strategy that suits your workflow. This post demonstrates one particular approach: reading the resolved brand configuration from <code>QUARTO_EXECUTE_INFO</code> at render time, making it independent of external packages and portable across languages, with full font registration support.</p>
</div>
</div>
</section>
<section id="the-quarto_execute_info-approach" class="level2" data-number="2">
<h2 data-number="2" class="anchored" data-anchor-id="the-quarto_execute_info-approach"><span class="header-section-number">2</span> The <code>QUARTO_EXECUTE_INFO</code> Approach</h2>
<p>When Quarto renders a document, it sets the <code>QUARTO_EXECUTE_INFO</code> environment variable for every code cell. This variable points to a JSON file containing document metadata, format settings, and, crucially, the resolved brand configuration.</p>
<p>The brand configuration lives at <code>format.render.brand</code> in the JSON, with separate entries for each colour mode (<code>light</code>, <code>dark</code>). Reading it requires only a JSON parser.</p>
<div class="tabset-margin-container"></div><div class="panel-tabset" data-group="language">
<ul class="nav nav-tabs"><li class="nav-item"><a class="nav-link active" id="tabset-1-1-tab" data-bs-toggle="tab" data-bs-target="#tabset-1-1" aria-controls="tabset-1-1" aria-selected="true" href=""><iconify-icon inline="" icon="fa7-brands:r-project" aria-label="Icon r-project from fa7-brands Iconify.design set." title="Icon r-project from fa7-brands Iconify.design set."></iconify-icon> R</a></li><li class="nav-item"><a class="nav-link" id="tabset-1-2-tab" data-bs-toggle="tab" data-bs-target="#tabset-1-2" aria-controls="tabset-1-2" aria-selected="false" href=""><iconify-icon inline="" icon="fa7-brands:python" aria-label="Icon python from fa7-brands Iconify.design set." title="Icon python from fa7-brands Iconify.design set."></iconify-icon> Python</a></li></ul>
<div class="tab-content" data-group="language">
<div id="tabset-1-1" class="active tab-pane" aria-labelledby="tabset-1-1-tab">
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="annotated-cell-4" data-filename="r" style="background: #f1f3f5;"><pre class="sourceCode r cw-auto code-annotation-code code-with-copy code-annotated"><code class="sourceCode r"><span id="annotated-cell-4-1">get_brand_info <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span>() {</span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-4" data-target-annotation="2" onclick="event.preventDefault();">2</a><span id="annotated-cell-4-2" class="code-annotation-target">  jsonlite<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">fromJSON</span>(</span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-4" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-4-3" class="code-annotation-target">    <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">Sys.getenv</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"QUARTO_EXECUTE_INFO"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">unset =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">""</span>)</span>
<span id="annotated-cell-4-4">  )</span>
<span id="annotated-cell-4-5">}</span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-4" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-4" data-code-lines="3" data-code-annotation="1">Read the environment variable set by Quarto.</span>
</dd>
<dt data-target-cell="annotated-cell-4" data-target-annotation="2">2</dt>
<dd>
<span data-code-cell="annotated-cell-4" data-code-lines="2" data-code-annotation="2">Parse the JSON string into an R list.</span>
</dd>
</dl>
</div>
<div id="tabset-1-2" class="tab-pane" aria-labelledby="tabset-1-2-tab">
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="annotated-cell-5" data-filename="python" style="background: #f1f3f5;"><pre class="sourceCode python cw-auto code-annotation-code code-with-copy code-annotated"><code class="sourceCode python"><span id="annotated-cell-5-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> json</span>
<span id="annotated-cell-5-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> os</span>
<span id="annotated-cell-5-3"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> pathlib</span>
<span id="annotated-cell-5-4"></span>
<span id="annotated-cell-5-5"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> get_brand_info() <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">dict</span>:</span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-5" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-5-6" class="code-annotation-target">    raw <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> os.environ.get(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"QUARTO_EXECUTE_INFO"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">""</span>)</span>
<span id="annotated-cell-5-7">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">not</span> raw:</span>
<span id="annotated-cell-5-8">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> {}</span>
<span id="annotated-cell-5-9">    path <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> pathlib.Path(raw)</span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-5" data-target-annotation="2" onclick="event.preventDefault();">2</a><span id="annotated-cell-5-10" class="code-annotation-target">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> path.is_file():</span>
<span id="annotated-cell-5-11">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> json.loads(path.read_text())</span>
<span id="annotated-cell-5-12">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> json.loads(raw)</span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-5" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-5" data-code-lines="6" data-code-annotation="1">Read the environment variable set by Quarto.</span>
</dd>
<dt data-target-cell="annotated-cell-5" data-target-annotation="2">2</dt>
<dd>
<span data-code-cell="annotated-cell-5" data-code-lines="10" data-code-annotation="2">The value may be a file path or a raw JSON string; check which.</span>
</dd>
</dl>
</div>
</div>
</div>
<div class="callout callout-style-default callout-tip callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Tip</span>Exploring the Structure
</div>
</div>
<div class="callout-body-container callout-body">
<p>During development, print the full JSON to see what is available:</p>
<div class="tabset-margin-container"></div><div class="panel-tabset" data-group="language">
<ul class="nav nav-tabs"><li class="nav-item"><a class="nav-link active" id="tabset-2-1-tab" data-bs-toggle="tab" data-bs-target="#tabset-2-1" aria-controls="tabset-2-1" aria-selected="true" href=""><iconify-icon inline="" icon="fa7-brands:r-project" aria-label="Icon r-project from fa7-brands Iconify.design set." title="Icon r-project from fa7-brands Iconify.design set."></iconify-icon> R</a></li><li class="nav-item"><a class="nav-link" id="tabset-2-2-tab" data-bs-toggle="tab" data-bs-target="#tabset-2-2" aria-controls="tabset-2-2" aria-selected="false" href=""><iconify-icon inline="" icon="fa7-brands:python" aria-label="Icon python from fa7-brands Iconify.design set." title="Icon python from fa7-brands Iconify.design set."></iconify-icon> Python</a></li></ul>
<div class="tab-content" data-group="language">
<div id="tabset-2-1" class="active tab-pane" aria-labelledby="tabset-2-1-tab">
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" data-filename="r" style="background: #f1f3f5;"><pre class="sourceCode r cw-auto code-with-copy"><code class="sourceCode r"><span id="cb1-1">info <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">get_brand_info</span>()</span>
<span id="cb1-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">cat</span>(jsonlite<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">toJSON</span>(info[[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"format"</span>]][[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"render"</span>]][[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"brand"</span>]], <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">pretty =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">TRUE</span>))</span></code></pre></div></div>
</div>
<div id="tabset-2-2" class="tab-pane" aria-labelledby="tabset-2-2-tab">
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb2" data-filename="python" style="background: #f1f3f5;"><pre class="sourceCode python cw-auto code-with-copy"><code class="sourceCode python"><span id="cb2-1">info <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> get_brand_info()</span>
<span id="cb2-2"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(json.dumps(info[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"format"</span>][<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"render"</span>][<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"brand"</span>], indent<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>))</span></code></pre></div></div>
</div>
</div>
</div>
</div>
</div>
</section>
<section id="brand-data-structure" class="level2" data-number="3">
<h2 data-number="3" class="anchored" data-anchor-id="brand-data-structure"><span class="header-section-number">3</span> Brand Data Structure</h2>
<p>The brand configuration under <code>format.render.brand</code> contains one entry per colour mode. Each mode holds a <code>data</code> object with colour and typography definitions.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb3" data-filename="json" style="background: #f1f3f5;"><pre class="sourceCode json cw-auto code-with-copy"><code class="sourceCode json"><span id="cb3-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb3-2">  <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"light"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb3-3">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"data"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb3-4">      <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"color"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb3-5">        <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"palette"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb3-6">          <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"anchor-blue"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#2f5d8a"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb3-7">          <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"harbour-teal"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#2d8c8f"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb3-8">          <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"strait-violet"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#5f6fb3"</span></span>
<span id="cb3-9">        <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">},</span></span>
<span id="cb3-10">        <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"foreground"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#4f789e"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb3-11">        <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"background"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#f8fafc"</span></span>
<span id="cb3-12">      <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">},</span></span>
<span id="cb3-13">      <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"typography"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb3-14">        <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"base"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Chakra Petch"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb3-15">        <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"headings"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Great Vibes"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb3-16">        <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"monospace"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"JetBrains Mono"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb3-17">        <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"fonts"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">[</span></span>
<span id="cb3-18">          <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">{</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"family"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Chakra Petch"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"source"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"bunny"</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb3-19">        <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">]</span></span>
<span id="cb3-20">      <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb3-21">    <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb3-22">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">},</span></span>
<span id="cb3-23">  <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"dark"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb3-24">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"data"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">{</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"..."</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb3-25">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb3-26"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">}</span></span></code></pre></div></div>
<p>The key fields are:</p>
<ul>
<li><code>color.palette</code>: named colour values used for data series and accents.</li>
<li><code>color.foreground</code> / <code>color.background</code>: ink and paper colours for the given mode.</li>
<li><code>typography.base</code> / <code>typography.headings</code>: font family names.</li>
<li><code>typography.fonts</code>: font specifications including source (<code>bunny</code>, <code>google</code>, <code>file</code>, or <code>system</code>).</li>
</ul>
<p>The diagrams below summarise how each helper function fits into the rendering pipeline.</p>
<div class="tabset-margin-container"></div><div class="panel-tabset" data-group="language">
<ul class="nav nav-tabs"><li class="nav-item"><a class="nav-link active" id="tabset-3-1-tab" data-bs-toggle="tab" data-bs-target="#tabset-3-1" aria-controls="tabset-3-1" aria-selected="true" href=""><iconify-icon inline="" icon="fa7-brands:r-project" aria-label="Icon r-project from fa7-brands Iconify.design set." title="Icon r-project from fa7-brands Iconify.design set."></iconify-icon> R</a></li><li class="nav-item"><a class="nav-link" id="tabset-3-2-tab" data-bs-toggle="tab" data-bs-target="#tabset-3-2" aria-controls="tabset-3-2" aria-selected="false" href=""><iconify-icon inline="" icon="fa7-brands:python" aria-label="Icon python from fa7-brands Iconify.design set." title="Icon python from fa7-brands Iconify.design set."></iconify-icon> Python</a></li></ul>
<div class="tab-content" data-group="language">
<div id="tabset-3-1" class="active tab-pane" aria-labelledby="tabset-3-1-tab">
<div id="fig-workflow-r" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-workflow-r-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="../../posts/2026-04-15-quarto-brand-figures-tables/./typst-render/index/fig-workflow-r.png" class="lightbox" data-gallery="quarto-lightbox-gallery-2" title="Figure&nbsp;1: R workflow for branded figures and tables."><img src="https://mickael.canouil.fr/posts/2026-04-15-quarto-brand-figures-tables/typst-render/index/fig-workflow-r.png" class="img-fluid w-75 mx-auto d-block quarto-figure quarto-figure-center figure-img" alt="Diagram showing the R workflow: quarto render sets QUARTO_EXECUTE_INFO, which is parsed by get_brand_info(). That feeds configure_brand_fonts() using systemfonts, theme_brand() using ggplot2 and scales, and gt_brand() using gt, producing light/dark figures and tables."></a></p>
</figure>
</div>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-workflow-r-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1: R workflow for branded figures and tables.
</figcaption>
</figure>
</div>
</div>
<div id="tabset-3-2" class="tab-pane" aria-labelledby="tabset-3-2-tab">
<div id="fig-workflow-py" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-workflow-py-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="../../posts/2026-04-15-quarto-brand-figures-tables/./typst-render/index/fig-workflow-py.png" class="lightbox" data-gallery="quarto-lightbox-gallery-3" title="Figure&nbsp;2: Python workflow for branded figures and tables."><img src="https://mickael.canouil.fr/posts/2026-04-15-quarto-brand-figures-tables/typst-render/index/fig-workflow-py.png" class="img-fluid w-75 mx-auto d-block quarto-figure quarto-figure-center figure-img" alt="Diagram showing the Python workflow: quarto render sets QUARTO_EXECUTE_INFO, which is parsed by get_brand_info(). That feeds configure_brand_fonts() using pyfonts and matplotlib, theme_brand() using plotnine, and gt_brand() using great_tables, producing light/dark figures and tables."></a></p>
</figure>
</div>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-workflow-py-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;2: Python workflow for branded figures and tables.
</figcaption>
</figure>
</div>
</div>
</div>
</div>
</section>
<section id="dependencies" class="level2" data-number="4">
<h2 data-number="4" class="anchored" data-anchor-id="dependencies"><span class="header-section-number">4</span> Dependencies</h2>
<p>This approach relies on a handful of packages for each language.</p>
<div class="tabset-margin-container"></div><div class="panel-tabset" data-group="language">
<ul class="nav nav-tabs"><li class="nav-item"><a class="nav-link active" id="tabset-4-1-tab" data-bs-toggle="tab" data-bs-target="#tabset-4-1" aria-controls="tabset-4-1" aria-selected="true" href=""><iconify-icon inline="" icon="fa7-brands:r-project" aria-label="Icon r-project from fa7-brands Iconify.design set." title="Icon r-project from fa7-brands Iconify.design set."></iconify-icon> R</a></li><li class="nav-item"><a class="nav-link" id="tabset-4-2-tab" data-bs-toggle="tab" data-bs-target="#tabset-4-2" aria-controls="tabset-4-2" aria-selected="false" href=""><iconify-icon inline="" icon="fa7-brands:python" aria-label="Icon python from fa7-brands Iconify.design set." title="Icon python from fa7-brands Iconify.design set."></iconify-icon> Python</a></li></ul>
<div class="tab-content" data-group="language">
<div id="tabset-4-1" class="active tab-pane" aria-labelledby="tabset-4-1-tab">
<ul>
<li><a href="https://cran.r-project.org/package=jsonlite"><code>jsonlite</code></a>: parse the <code>QUARTO_EXECUTE_INFO</code> JSON.</li>
<li><a href="https://ggplot2.tidyverse.org/"><code>ggplot2</code></a>: the <code>ink</code>, <code>paper</code>, <code>accent</code>, and <code>palette.*</code> theme elements used by <code>theme_brand()</code> are not yet on CRAN.</li>
<li><a href="https://gt.rstudio.com/"><code>gt</code></a>: table styling.</li>
<li><a href="https://scales.r-lib.org/"><code>scales</code></a>: colour blending via <code>col_mix()</code>.</li>
<li><a href="https://systemfonts.r-lib.org/"><code>systemfonts</code></a>: font discovery, registration, and web font embedding for SVG output.</li>
</ul>
</div>
<div id="tabset-4-2" class="tab-pane" aria-labelledby="tabset-4-2-tab">
<ul>
<li><a href="https://plotnine.org/"><code>plotnine</code></a>: <code>ggplot2</code>-style plotting.</li>
<li><a href="https://posit-dev.github.io/great-tables/"><code>great_tables</code></a>: table styling.</li>
<li><a href="https://pola.rs/"><code>polars</code></a>: data manipulation.</li>
<li><a href="https://github.com/y-sunflower/pyfonts"><code>pyfonts</code></a>: font loading from Bunny Fonts and Google Fonts, created by <a href="https://github.com/JosephBARBIERDARNAL">Joseph Barbier</a>. The companion repository uses a development version of <code>pyfonts</code> for improved font subset selection.</li>
</ul>
</div>
</div>
</div>
</section>
<section id="font-registration" class="level2" data-number="5">
<h2 data-number="5" class="anchored" data-anchor-id="font-registration"><span class="header-section-number">5</span> Font Registration</h2>
<p>Web fonts defined in a brand configuration are not available to R or Python graphics devices by default. Before creating any plots or tables, a <code>configure_brand_fonts()</code> function registers each font so that graphics devices can find them. Call it once in a setup chunk at the top of your document.</p>
<div class="tabset-margin-container"></div><div class="panel-tabset" data-group="language">
<ul class="nav nav-tabs"><li class="nav-item"><a class="nav-link active" id="tabset-5-1-tab" data-bs-toggle="tab" data-bs-target="#tabset-5-1" aria-controls="tabset-5-1" aria-selected="true" href=""><iconify-icon inline="" icon="fa7-brands:r-project" aria-label="Icon r-project from fa7-brands Iconify.design set." title="Icon r-project from fa7-brands Iconify.design set."></iconify-icon> R</a></li><li class="nav-item"><a class="nav-link" id="tabset-5-2-tab" data-bs-toggle="tab" data-bs-target="#tabset-5-2" aria-controls="tabset-5-2" aria-selected="false" href=""><iconify-icon inline="" icon="fa7-brands:python" aria-label="Icon python from fa7-brands Iconify.design set." title="Icon python from fa7-brands Iconify.design set."></iconify-icon> Python</a></li></ul>
<div class="tab-content" data-group="language">
<div id="tabset-5-1" class="active tab-pane" aria-labelledby="tabset-5-1-tab">
<p>The <code>systemfonts</code> package handles font discovery and registration.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="annotated-cell-8" data-filename="r" style="background: #f1f3f5;"><pre class="sourceCode r cw-auto code-annotation-code code-with-copy code-annotated"><code class="sourceCode r"><span id="annotated-cell-8-1">configure_brand_fonts <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">brand_mode =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"light"</span>) {</span>
<span id="annotated-cell-8-2">  info <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">get_brand_info</span>()</span>
<span id="annotated-cell-8-3">  brand <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> info[[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"format"</span>]][[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"render"</span>]][[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"brand"</span>]]</span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-8" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-8-4" class="code-annotation-target">  doc_dir <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">dirname</span>(info[[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"document-path"</span>]])</span>
<span id="annotated-cell-8-5"></span>
<span id="annotated-cell-8-6">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> (mode <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">intersect</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"light"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dark"</span>), <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">names</span>(brand))) {</span>
<span id="annotated-cell-8-7">    fonts <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> brand[[mode]][[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"data"</span>]][[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"typography"</span>]][[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"fonts"</span>]]</span>
<span id="annotated-cell-8-8">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> (ifont <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">seq_len</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">nrow</span>(fonts))) {</span>
<span id="annotated-cell-8-9">      family <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> fonts[[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"family"</span>]][[ifont]]</span>
<span id="annotated-cell-8-10">      source <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> fonts[[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"source"</span>]][[ifont]]</span>
<span id="annotated-cell-8-11"></span>
<span id="annotated-cell-8-12">      <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> (<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">identical</span>(source, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"file"</span>)) {</span>
<span id="annotated-cell-8-13">        files <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> fonts[[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"files"</span>]][[ifont]]</span>
<span id="annotated-cell-8-14">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> (fp <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">as.character</span>(files[[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"path"</span>]])) {</span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-8" data-target-annotation="2" onclick="event.preventDefault();">2</a><span id="annotated-cell-8-15" class="code-annotation-target">          resolved <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">file.path</span>(doc_dir, fp)</span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-8" data-target-annotation="3" onclick="event.preventDefault();">3</a><span id="annotated-cell-8-16" class="code-annotation-target">          systemfonts<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">register_font</span>(family, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">plain =</span> resolved)</span>
<span id="annotated-cell-8-17">        }</span>
<span id="annotated-cell-8-18">      } <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> {</span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-8" data-target-annotation="4" onclick="event.preventDefault();">4</a><span id="annotated-cell-8-19" class="code-annotation-target">        systemfonts<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">require_font</span>(family, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">repositories =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Bunny Fonts"</span>)</span>
<span id="annotated-cell-8-20">      }</span>
<span id="annotated-cell-8-21">    }</span>
<span id="annotated-cell-8-22">  }</span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-8" data-target-annotation="5" onclick="event.preventDefault();">5</a><span id="annotated-cell-8-23" class="code-annotation-target">  systemfonts<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">reset_font_cache</span>()</span>
<span id="annotated-cell-8-24">}</span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-8" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-8" data-code-lines="4" data-code-annotation="1">Resolve the document directory from <code>QUARTO_EXECUTE_INFO</code> for relative font paths.</span>
</dd>
<dt data-target-cell="annotated-cell-8" data-target-annotation="2">2</dt>
<dd>
<span data-code-cell="annotated-cell-8" data-code-lines="15" data-code-annotation="2">Build the full path to each local font file.</span>
</dd>
<dt data-target-cell="annotated-cell-8" data-target-annotation="3">3</dt>
<dd>
<span data-code-cell="annotated-cell-8" data-code-lines="16" data-code-annotation="3">Register local font files with <code>systemfonts::register_font()</code>.</span>
</dd>
<dt data-target-cell="annotated-cell-8" data-target-annotation="4">4</dt>
<dd>
<span data-code-cell="annotated-cell-8" data-code-lines="19" data-code-annotation="4">Download web fonts from Bunny Fonts (or Google Fonts) with <code>systemfonts::require_font()</code>.</span>
</dd>
<dt data-target-cell="annotated-cell-8" data-target-annotation="5">5</dt>
<dd>
<span data-code-cell="annotated-cell-8" data-code-lines="23" data-code-annotation="5">Reset the font cache so newly registered fonts become available.</span>
</dd>
</dl>
</div>
<div id="tabset-5-2" class="tab-pane" aria-labelledby="tabset-5-2-tab">
<p>The <code>matplotlib.font_manager</code> module handles font registration, with <code>pyfonts</code> for downloading web fonts.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="annotated-cell-9" data-filename="python" style="background: #f1f3f5;"><pre class="sourceCode python cw-auto code-annotation-code code-with-copy code-annotated"><code class="sourceCode python"><span id="annotated-cell-9-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> matplotlib.font_manager <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> fm</span>
<span id="annotated-cell-9-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> pyfonts</span>
<span id="annotated-cell-9-3"></span>
<span id="annotated-cell-9-4"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> configure_brand_fonts() <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>:</span>
<span id="annotated-cell-9-5">    info <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> get_brand_info()</span>
<span id="annotated-cell-9-6">    brand <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> info[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"format"</span>][<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"render"</span>][<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"brand"</span>]</span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-9" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-9-7" class="code-annotation-target">    doc_dir <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> pathlib.Path(info[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"document-path"</span>]).parent</span>
<span id="annotated-cell-9-8"></span>
<span id="annotated-cell-9-9">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> mode <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> [<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"light"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dark"</span>]:</span>
<span id="annotated-cell-9-10">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> mode <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">not</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> brand:</span>
<span id="annotated-cell-9-11">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">continue</span></span>
<span id="annotated-cell-9-12">        fonts <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> brand[mode][<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"data"</span>][<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"typography"</span>][<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"fonts"</span>]</span>
<span id="annotated-cell-9-13">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> font_spec <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> fonts:</span>
<span id="annotated-cell-9-14">            family <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> font_spec[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"family"</span>]</span>
<span id="annotated-cell-9-15">            source <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> font_spec[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"source"</span>]</span>
<span id="annotated-cell-9-16"></span>
<span id="annotated-cell-9-17">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> source <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"file"</span>:</span>
<span id="annotated-cell-9-18">                <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> f <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> font_spec.get(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"files"</span>, []):</span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-9" data-target-annotation="2" onclick="event.preventDefault();">2</a><span id="annotated-cell-9-19" class="code-annotation-target">                    resolved <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> doc_dir <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> f[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"path"</span>]</span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-9" data-target-annotation="3" onclick="event.preventDefault();">3</a><span id="annotated-cell-9-20" class="code-annotation-target">                    fm.fontManager.addfont(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>(resolved))</span>
<span id="annotated-cell-9-21">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">elif</span> source <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> (<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"bunny"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"google"</span>):</span>
<span id="annotated-cell-9-22">                <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> source <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"bunny"</span>:</span>
<span id="annotated-cell-9-23">                    props <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> pyfonts.load_bunny_font(family)</span>
<span id="annotated-cell-9-24">                <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span>:</span>
<span id="annotated-cell-9-25">                    props <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> pyfonts.load_google_font(family)</span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-9" data-target-annotation="4" onclick="event.preventDefault();">4</a><span id="annotated-cell-9-26" class="code-annotation-target">                fm.fontManager.addfont(props.get_file())</span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-9" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-9" data-code-lines="7" data-code-annotation="1">Resolve the document directory for relative font paths.</span>
</dd>
<dt data-target-cell="annotated-cell-9" data-target-annotation="2">2</dt>
<dd>
<span data-code-cell="annotated-cell-9" data-code-lines="19" data-code-annotation="2">Build the full path to each local font file.</span>
</dd>
<dt data-target-cell="annotated-cell-9" data-target-annotation="3">3</dt>
<dd>
<span data-code-cell="annotated-cell-9" data-code-lines="20" data-code-annotation="3">Register local font files with <code>matplotlib.font_manager</code>.</span>
</dd>
<dt data-target-cell="annotated-cell-9" data-target-annotation="4">4</dt>
<dd>
<span data-code-cell="annotated-cell-9" data-code-lines="26" data-code-annotation="4">Download web fonts using <code>pyfonts</code> and register them.</span>
</dd>
</dl>
</div>
</div>
</div>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Note</span>SVG Font Rendering
</div>
</div>
<div class="callout-body-container callout-body">
<p>When generating figures as SVG, the SVG files must be inlined directly into the HTML page for fonts to render correctly. An SVG file referenced via an <code>&lt;img&gt;</code> tag is treated as an isolated document by the browser. It cannot access the page’s stylesheets or <code>@import</code> rules, so any <code>@font-face</code> declarations inside the SVG that reference external URLs are silently ignored. Inlining the SVG into the page DOM removes this boundary, allowing the browser to resolve font imports just like any other CSS resource.</p>
<p>The companion repository includes an <code>inline-svglite.lua</code> Lua filter that replaces <code>&lt;img&gt;</code> references with raw SVG content automatically. See the <a href="https://github.com/mcanouil/demo-quarto-brand-renderings">companion repository</a> for the full implementation.</p>
</div>
</div>
</section>
<section id="branded-figures" class="level2" data-number="6">
<h2 data-number="6" class="anchored" data-anchor-id="branded-figures"><span class="header-section-number">6</span> Branded Figures</h2>
<p>The <code>theme_brand()</code> function extracts colours and fonts from the brand configuration and builds a complete <code>ggplot2</code> or plotnine theme. The idea is straightforward: pull out the ink (foreground), paper (background), and palette colours, then pass them to the theme constructor. In R, the latest <code>ggplot2</code> supports <code>ink</code>, <code>paper</code>, and <code>accent</code> parameters directly in <code>theme_minimal()</code>; in Python, each theme element is styled individually.</p>
<div class="tabset-margin-container"></div><div class="panel-tabset" data-group="language">
<ul class="nav nav-tabs"><li class="nav-item"><a class="nav-link active" id="tabset-6-1-tab" data-bs-toggle="tab" data-bs-target="#tabset-6-1" aria-controls="tabset-6-1" aria-selected="true" href=""><iconify-icon inline="" icon="fa7-brands:r-project" aria-label="Icon r-project from fa7-brands Iconify.design set." title="Icon r-project from fa7-brands Iconify.design set."></iconify-icon> R</a></li><li class="nav-item"><a class="nav-link" id="tabset-6-2-tab" data-bs-toggle="tab" data-bs-target="#tabset-6-2" aria-controls="tabset-6-2" aria-selected="false" href=""><iconify-icon inline="" icon="fa7-brands:python" aria-label="Icon python from fa7-brands Iconify.design set." title="Icon python from fa7-brands Iconify.design set."></iconify-icon> Python</a></li></ul>
<div class="tab-content" data-group="language">
<div id="tabset-6-1" class="active tab-pane" aria-labelledby="tabset-6-1-tab">
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="annotated-cell-10" data-filename="r" style="background: #f1f3f5;"><pre class="sourceCode r cw-auto code-annotation-code code-with-copy code-annotated"><code class="sourceCode r"><span id="annotated-cell-10-1">theme_brand <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">base_size =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">11</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">brand_mode =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"light"</span>) {</span>
<span id="annotated-cell-10-2">  info <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">get_brand_info</span>()</span>
<span id="annotated-cell-10-3">  brand <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> info[[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"format"</span>]][[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"render"</span>]][[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"brand"</span>]]</span>
<span id="annotated-cell-10-4">  brand_data <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> brand[[brand_mode]][[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"data"</span>]]</span>
<span id="annotated-cell-10-5">  colors <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> brand_data[[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"color"</span>]]</span>
<span id="annotated-cell-10-6">  typography <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> brand_data[[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"typography"</span>]]</span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-10" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-10-7" class="code-annotation-target">  palette_values <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">unname</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">unlist</span>(colors[[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"palette"</span>]]))</span>
<span id="annotated-cell-10-8"></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-10" data-target-annotation="2" onclick="event.preventDefault();">2</a><span id="annotated-cell-10-9" class="code-annotation-target">  ink <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> colors[[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"foreground"</span>]]</span>
<span id="annotated-cell-10-10">  paper <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> colors[[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"background"</span>]]</span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-10" data-target-annotation="3" onclick="event.preventDefault();">3</a><span id="annotated-cell-10-11" class="code-annotation-target">  accent <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> scales<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">col_mix</span>(ink, paper, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">amount =</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.25</span>)</span>
<span id="annotated-cell-10-12"></span>
<span id="annotated-cell-10-13">  ggplot2<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">theme_minimal</span>(</span>
<span id="annotated-cell-10-14">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">base_size =</span> base_size,</span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-10" data-target-annotation="4" onclick="event.preventDefault();">4</a><span id="annotated-cell-10-15" class="code-annotation-target">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">base_family =</span> typography[[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"base"</span>]],</span>
<span id="annotated-cell-10-16">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">header_family =</span> typography[[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"headings"</span>]],</span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-10" data-target-annotation="5" onclick="event.preventDefault();">5</a><span id="annotated-cell-10-17" class="code-annotation-target">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">ink =</span> ink,</span>
<span id="annotated-cell-10-18">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">paper =</span> paper,</span>
<span id="annotated-cell-10-19">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">accent =</span> accent</span>
<span id="annotated-cell-10-20">  ) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="annotated-cell-10-21">    ggplot2<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">theme</span>(</span>
<span id="annotated-cell-10-22">      <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">plot.title =</span> ggplot2<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">element_text</span>(</span>
<span id="annotated-cell-10-23">        <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">colour =</span> accent,</span>
<span id="annotated-cell-10-24">        <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">size =</span> ggplot2<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">rel</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)</span>
<span id="annotated-cell-10-25">      ),</span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-10" data-target-annotation="6" onclick="event.preventDefault();">6</a><span id="annotated-cell-10-26" class="code-annotation-target">      <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">palette.colour.discrete =</span> palette_values,</span>
<span id="annotated-cell-10-27">      <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">palette.fill.discrete =</span> palette_values</span>
<span id="annotated-cell-10-28">    )</span>
<span id="annotated-cell-10-29">}</span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-10" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-10" data-code-lines="7" data-code-annotation="1">Flatten the named palette into a character vector.</span>
</dd>
<dt data-target-cell="annotated-cell-10" data-target-annotation="2">2</dt>
<dd>
<span data-code-cell="annotated-cell-10" data-code-lines="9,10" data-code-annotation="2">Extract foreground (ink) and background (paper) from the brand colours.</span>
</dd>
<dt data-target-cell="annotated-cell-10" data-target-annotation="3">3</dt>
<dd>
<span data-code-cell="annotated-cell-10" data-code-lines="11" data-code-annotation="3">Blend ink and paper to create an accent colour for titles and axes.</span>
</dd>
<dt data-target-cell="annotated-cell-10" data-target-annotation="4">4</dt>
<dd>
<span data-code-cell="annotated-cell-10" data-code-lines="15,16" data-code-annotation="4">Apply brand fonts to body text and headings.</span>
</dd>
<dt data-target-cell="annotated-cell-10" data-target-annotation="5">5</dt>
<dd>
<span data-code-cell="annotated-cell-10" data-code-lines="17,18,19" data-code-annotation="5">Pass ink, paper, and accent to <code>theme_minimal()</code>.</span>
</dd>
<dt data-target-cell="annotated-cell-10" data-target-annotation="6">6</dt>
<dd>
<span data-code-cell="annotated-cell-10" data-code-lines="26,27" data-code-annotation="6">Set the discrete colour palette for geoms.</span>
</dd>
</dl>
</div>
<div id="tabset-6-2" class="tab-pane" aria-labelledby="tabset-6-2-tab">
<p>R uses <code>scales::col_mix()</code> to blend colours. Python needs a small helper since there is no equivalent in plotnine:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb4" data-filename="python" style="background: #f1f3f5;"><pre class="sourceCode python cw-auto code-with-copy"><code class="sourceCode python"><span id="cb4-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> _col_mix(a: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>, b: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>, amount: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.25</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>:</span>
<span id="cb4-2">    ra, ga, ba <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>(a[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>:<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>], <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">16</span>), <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>(a[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>:<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span>], <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">16</span>), <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>(a[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span>:<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">7</span>], <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">16</span>)</span>
<span id="cb4-3">    rb, gb, bb <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>(b[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>:<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>], <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">16</span>), <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>(b[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>:<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span>], <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">16</span>), <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>(b[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span>:<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">7</span>], <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">16</span>)</span>
<span id="cb4-4">    r <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>(ra <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> (rb <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> ra) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> amount)</span>
<span id="cb4-5">    g <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>(ga <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> (gb <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> ga) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> amount)</span>
<span id="cb4-6">    b_val <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>(ba <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> (bb <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> ba) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> amount)</span>
<span id="cb4-7">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"#</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>r<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:02x}{</span>g<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:02x}{</span>b_val<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:02x}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span></span></code></pre></div></div>
<p>Because <code>plotnine</code> does not support <code>palette.*</code> theme elements, a separate helper extracts the colour palette for use with <code>scale_color_manual()</code>:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb5" data-filename="python" style="background: #f1f3f5;"><pre class="sourceCode python cw-auto code-with-copy"><code class="sourceCode python"><span id="cb5-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> get_brand_palette(brand_mode: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"light"</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>]:</span>
<span id="cb5-2">    info <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> get_brand_info()</span>
<span id="cb5-3">    brand <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> info[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"format"</span>][<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"render"</span>][<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"brand"</span>]</span>
<span id="cb5-4">    palette <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> brand[brand_mode][<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"data"</span>][<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"color"</span>][<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"palette"</span>]</span>
<span id="cb5-5">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>(palette.values())</span></code></pre></div></div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="annotated-cell-13" data-filename="python" style="background: #f1f3f5;"><pre class="sourceCode python cw-auto code-annotation-code code-with-copy code-annotated"><code class="sourceCode python"><span id="annotated-cell-13-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> plotnine <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> (</span>
<span id="annotated-cell-13-2">    element_line, element_rect, element_text, theme, theme_minimal</span>
<span id="annotated-cell-13-3">)</span>
<span id="annotated-cell-13-4"></span>
<span id="annotated-cell-13-5"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> theme_brand(base_size: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">11</span>, brand_mode: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"light"</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> theme:</span>
<span id="annotated-cell-13-6">    info <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> get_brand_info()</span>
<span id="annotated-cell-13-7">    brand <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> info[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"format"</span>][<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"render"</span>][<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"brand"</span>]</span>
<span id="annotated-cell-13-8">    brand_data <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> brand[brand_mode][<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"data"</span>]</span>
<span id="annotated-cell-13-9">    colors <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> brand_data[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"color"</span>]</span>
<span id="annotated-cell-13-10">    typography <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> brand_data[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"typography"</span>]</span>
<span id="annotated-cell-13-11"></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-13" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-13-12" class="code-annotation-target">    ink <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> colors[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"foreground"</span>]</span>
<span id="annotated-cell-13-13">    paper <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> colors[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"background"</span>]</span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-13" data-target-annotation="2" onclick="event.preventDefault();">2</a><span id="annotated-cell-13-14" class="code-annotation-target">    accent <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> _col_mix(ink, paper, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.25</span>)</span>
<span id="annotated-cell-13-15">    base_family <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> typography[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"base"</span>]</span>
<span id="annotated-cell-13-16">    heading_family <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> typography[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"headings"</span>]</span>
<span id="annotated-cell-13-17"></span>
<span id="annotated-cell-13-18">    base_line_size <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> base_size <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">22</span></span>
<span id="annotated-cell-13-19">    grid_major_colour <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> _col_mix(ink, paper, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.8</span>)</span>
<span id="annotated-cell-13-20">    grid_minor_colour <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> _col_mix(ink, paper, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.9</span>)</span>
<span id="annotated-cell-13-21"></span>
<span id="annotated-cell-13-22">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> theme_minimal(</span>
<span id="annotated-cell-13-23">        base_size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>base_size,</span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-13" data-target-annotation="3" onclick="event.preventDefault();">3</a><span id="annotated-cell-13-24" class="code-annotation-target">        base_family<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>base_family</span>
<span id="annotated-cell-13-25">    ) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> theme(</span>
<span id="annotated-cell-13-26">        plot_background<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>element_rect(fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>paper, colour<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"none"</span>),</span>
<span id="annotated-cell-13-27">        panel_background<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>element_rect(fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>paper, colour<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"none"</span>),</span>
<span id="annotated-cell-13-28">        panel_grid_major<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>element_line(colour<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>grid_major_colour, size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>base_line_size),</span>
<span id="annotated-cell-13-29">        panel_grid_minor<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>element_line(colour<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>grid_minor_colour, size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>base_line_size <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span>),</span>
<span id="annotated-cell-13-30">        text<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>element_text(colour<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>ink),</span>
<span id="annotated-cell-13-31">        axis_title<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>element_text(family<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>heading_family),</span>
<span id="annotated-cell-13-32">        axis_line_x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>element_line(colour<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>accent),</span>
<span id="annotated-cell-13-33">        axis_line_y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>element_line(colour<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>accent),</span>
<span id="annotated-cell-13-34">        plot_title<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>element_text(</span>
<span id="annotated-cell-13-35">            family<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>heading_family,</span>
<span id="annotated-cell-13-36">            colour<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>accent,</span>
<span id="annotated-cell-13-37">            size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>base_size <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>,</span>
<span id="annotated-cell-13-38">        ),</span>
<span id="annotated-cell-13-39">        plot_subtitle<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>element_text(colour<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>ink),</span>
<span id="annotated-cell-13-40">        legend_title<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>element_text(family<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>heading_family, colour<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>accent),</span>
<span id="annotated-cell-13-41">        legend_background<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>element_rect(fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>paper, colour<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"none"</span>),</span>
<span id="annotated-cell-13-42">    )</span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-13" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-13" data-code-lines="12,13" data-code-annotation="1">Extract foreground (ink) and background (paper) from the brand colours.</span>
</dd>
<dt data-target-cell="annotated-cell-13" data-target-annotation="2">2</dt>
<dd>
<span data-code-cell="annotated-cell-13" data-code-lines="14" data-code-annotation="2">Blend ink and paper to create an accent colour (custom <code>_col_mix()</code> helper).</span>
</dd>
<dt data-target-cell="annotated-cell-13" data-target-annotation="3">3</dt>
<dd>
<span data-code-cell="annotated-cell-13" data-code-lines="24,35" data-code-annotation="3">Apply brand fonts to body text and headings.</span>
</dd>
</dl>
</div>
</div>
</div>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
Note
</div>
</div>
<div class="callout-body-container callout-body">
<p>The latest version of <code>ggplot2</code> supports <code>ink</code>, <code>paper</code>, <code>accent</code>, and <code>palette.*</code> theme elements natively. <code>plotnine</code> does not have these parameters, so each element must be styled individually.</p>
</div>
</div>
<div class="callout callout-style-default callout-tip callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Tip</span>Companion Repository
</div>
</div>
<div class="callout-body-container callout-body">
<p>The code samples in this post are intentionally simplified. The <a href="https://github.com/mcanouil/demo-quarto-brand-renderings">companion repository</a> contains production-ready versions with full error handling, defensive <code>.get()</code> fallbacks, and additional styling such as continuous palette support in R.</p>
</div>
</div>
<section id="usage" class="level3" data-number="6.1">
<h3 data-number="6.1" class="anchored" data-anchor-id="usage"><span class="header-section-number">6.1</span> Usage</h3>
<p>With <code>theme_brand()</code> defined, applying it to a plot is straightforward.</p>
<div class="tabset-margin-container"></div><div class="panel-tabset" data-group="language">
<ul class="nav nav-tabs"><li class="nav-item"><a class="nav-link active" id="tabset-7-1-tab" data-bs-toggle="tab" data-bs-target="#tabset-7-1" aria-controls="tabset-7-1" aria-selected="true" href=""><iconify-icon inline="" icon="fa7-brands:r-project" aria-label="Icon r-project from fa7-brands Iconify.design set." title="Icon r-project from fa7-brands Iconify.design set."></iconify-icon> R</a></li><li class="nav-item"><a class="nav-link" id="tabset-7-2-tab" data-bs-toggle="tab" data-bs-target="#tabset-7-2" aria-controls="tabset-7-2" aria-selected="false" href=""><iconify-icon inline="" icon="fa7-brands:python" aria-label="Icon python from fa7-brands Iconify.design set." title="Icon python from fa7-brands Iconify.design set."></iconify-icon> Python</a></li></ul>
<div class="tab-content" data-group="language">
<div id="tabset-7-1" class="active tab-pane" aria-labelledby="tabset-7-1-tab">
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb6" data-filename="r" style="background: #f1f3f5;"><pre class="sourceCode r cw-auto code-with-copy"><code class="sourceCode r"><span id="cb6-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">library</span>(ggplot2)</span>
<span id="cb6-2">penguins <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">read.csv</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"penguins.csv"</span>)</span>
<span id="cb6-3"></span>
<span id="cb6-4"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">ggplot</span>(penguins) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb6-5">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">aes</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">x =</span> bill_length_mm, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">y =</span> bill_depth_mm, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">colour =</span> species) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb6-6">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">geom_point</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">na.rm =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">TRUE</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb6-7">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">theme_brand</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">brand_mode =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"light"</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb6-8">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">labs</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">title =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Light Mode"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">subtitle =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Using brand colours and fonts"</span>)</span></code></pre></div></div>
</div>
<div id="tabset-7-2" class="tab-pane" aria-labelledby="tabset-7-2-tab">
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb7" data-filename="python" style="background: #f1f3f5;"><pre class="sourceCode python cw-auto code-with-copy"><code class="sourceCode python"><span id="cb7-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> polars <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> pl</span>
<span id="cb7-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> plotnine <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> aes, geom_point, ggplot, labs, scale_color_manual</span>
<span id="cb7-3"></span>
<span id="cb7-4">penguins <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> pl.read_csv(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"penguins.csv"</span>, null_values<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"NA"</span>).drop_nulls(</span>
<span id="cb7-5">    [<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"bill_length_mm"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"bill_depth_mm"</span>]</span>
<span id="cb7-6">)</span>
<span id="cb7-7"></span>
<span id="cb7-8">palette <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> get_brand_palette(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"light"</span>)</span>
<span id="cb7-9"></span>
<span id="cb7-10">(</span>
<span id="cb7-11">    ggplot(penguins.to_pandas())</span>
<span id="cb7-12">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> aes(x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"bill_length_mm"</span>, y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"bill_depth_mm"</span>, color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"species"</span>)</span>
<span id="cb7-13">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> geom_point()</span>
<span id="cb7-14">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> theme_brand(brand_mode<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"light"</span>)</span>
<span id="cb7-15">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> scale_color_manual(values<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>palette)</span>
<span id="cb7-16">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> labs(title<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Light Mode"</span>, subtitle<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Using brand colours and fonts"</span>)</span>
<span id="cb7-17">)</span></code></pre></div></div>
</div>
</div>
</div>
</section>
</section>
<section id="branded-tables" class="level2" data-number="7">
<h2 data-number="7" class="anchored" data-anchor-id="branded-tables"><span class="header-section-number">7</span> Branded Tables</h2>
<p>The <code>gt_brand()</code> function follows the same pattern: extract brand colours and fonts, then apply them to the table. Column headers get the first palette colour as a background, body borders use an accent derived from blending ink and paper, and the base and heading fonts are applied throughout.</p>
<div class="tabset-margin-container"></div><div class="panel-tabset" data-group="language">
<ul class="nav nav-tabs"><li class="nav-item"><a class="nav-link active" id="tabset-8-1-tab" data-bs-toggle="tab" data-bs-target="#tabset-8-1" aria-controls="tabset-8-1" aria-selected="true" href=""><iconify-icon inline="" icon="fa7-brands:r-project" aria-label="Icon r-project from fa7-brands Iconify.design set." title="Icon r-project from fa7-brands Iconify.design set."></iconify-icon> R</a></li><li class="nav-item"><a class="nav-link" id="tabset-8-2-tab" data-bs-toggle="tab" data-bs-target="#tabset-8-2" aria-controls="tabset-8-2" aria-selected="false" href=""><iconify-icon inline="" icon="fa7-brands:python" aria-label="Icon python from fa7-brands Iconify.design set." title="Icon python from fa7-brands Iconify.design set."></iconify-icon> Python</a></li></ul>
<div class="tab-content" data-group="language">
<div id="tabset-8-1" class="active tab-pane" aria-labelledby="tabset-8-1-tab">
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="annotated-cell-16" data-filename="r" style="background: #f1f3f5;"><pre class="sourceCode r cw-auto code-annotation-code code-with-copy code-annotated"><code class="sourceCode r"><span id="annotated-cell-16-1">gt_brand <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span>(data, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">brand_mode =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"light"</span>) {</span>
<span id="annotated-cell-16-2">  info <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">get_brand_info</span>()</span>
<span id="annotated-cell-16-3">  brand <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> info[[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"format"</span>]][[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"render"</span>]][[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"brand"</span>]]</span>
<span id="annotated-cell-16-4">  brand_data <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> brand[[brand_mode]][[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"data"</span>]]</span>
<span id="annotated-cell-16-5">  colors <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> brand_data[[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"color"</span>]]</span>
<span id="annotated-cell-16-6">  typography <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> brand_data[[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"typography"</span>]]</span>
<span id="annotated-cell-16-7">  palette <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">unname</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">unlist</span>(colors[[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"palette"</span>]]))</span>
<span id="annotated-cell-16-8"></span>
<span id="annotated-cell-16-9">  ink <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> colors[[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"foreground"</span>]]</span>
<span id="annotated-cell-16-10">  paper <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> colors[[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"background"</span>]]</span>
<span id="annotated-cell-16-11">  accent <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> scales<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">col_mix</span>(ink, paper, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">amount =</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.25</span>)</span>
<span id="annotated-cell-16-12"></span>
<span id="annotated-cell-16-13">  gt<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">gt</span>(data) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-16" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-16-14" class="code-annotation-target">    gt<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">opt_table_font</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">font =</span> typography[[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"base"</span>]]) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="annotated-cell-16-15">    gt<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">tab_options</span>(</span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-16" data-target-annotation="2" onclick="event.preventDefault();">2</a><span id="annotated-cell-16-16" class="code-annotation-target">      <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">table.background.color =</span> paper,</span>
<span id="annotated-cell-16-17">      <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">table.font.color =</span> ink,</span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-16" data-target-annotation="3" onclick="event.preventDefault();">3</a><span id="annotated-cell-16-18" class="code-annotation-target">      <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">column_labels.background.color =</span> palette[[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>L]],</span>
<span id="annotated-cell-16-19">      <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">column_labels.font.weight =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"bold"</span>,</span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-16" data-target-annotation="4" onclick="event.preventDefault();">4</a><span id="annotated-cell-16-20" class="code-annotation-target">      <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">table_body.border.top.color =</span> accent,</span>
<span id="annotated-cell-16-21">      <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">table_body.border.bottom.color =</span> accent</span>
<span id="annotated-cell-16-22">    ) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="annotated-cell-16-23">    gt<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">tab_style</span>(</span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-16" data-target-annotation="5" onclick="event.preventDefault();">5</a><span id="annotated-cell-16-24" class="code-annotation-target">      <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">style =</span> gt<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">cell_text</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">font =</span> typography[[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"headings"</span>]], <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">color =</span> paper),</span>
<span id="annotated-cell-16-25">      <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">locations =</span> gt<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">cells_column_labels</span>()</span>
<span id="annotated-cell-16-26">    )</span>
<span id="annotated-cell-16-27">}</span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-16" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-16" data-code-lines="14" data-code-annotation="1">Set the base font for the entire table.</span>
</dd>
<dt data-target-cell="annotated-cell-16" data-target-annotation="2">2</dt>
<dd>
<span data-code-cell="annotated-cell-16" data-code-lines="16,17" data-code-annotation="2">Apply brand background and text colours.</span>
</dd>
<dt data-target-cell="annotated-cell-16" data-target-annotation="3">3</dt>
<dd>
<span data-code-cell="annotated-cell-16" data-code-lines="18" data-code-annotation="3">Use the first palette colour for column header background.</span>
</dd>
<dt data-target-cell="annotated-cell-16" data-target-annotation="4">4</dt>
<dd>
<span data-code-cell="annotated-cell-16" data-code-lines="20,21" data-code-annotation="4">Use the accent colour for table body borders.</span>
</dd>
<dt data-target-cell="annotated-cell-16" data-target-annotation="5">5</dt>
<dd>
<span data-code-cell="annotated-cell-16" data-code-lines="24" data-code-annotation="5">Style column labels with the heading font and contrasting colour.</span>
</dd>
</dl>
</div>
<div id="tabset-8-2" class="tab-pane" aria-labelledby="tabset-8-2-tab">
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="annotated-cell-17" data-filename="python" style="background: #f1f3f5;"><pre class="sourceCode python cw-auto code-annotation-code code-with-copy code-annotated"><code class="sourceCode python"><span id="annotated-cell-17-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> great_tables <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> GT, loc, style</span>
<span id="annotated-cell-17-2"></span>
<span id="annotated-cell-17-3"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> gt_brand(data, brand_mode: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"light"</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> GT:</span>
<span id="annotated-cell-17-4">    info <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> get_brand_info()</span>
<span id="annotated-cell-17-5">    brand <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> info[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"format"</span>][<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"render"</span>][<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"brand"</span>]</span>
<span id="annotated-cell-17-6">    brand_data <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> brand[brand_mode][<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"data"</span>]</span>
<span id="annotated-cell-17-7">    colors <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> brand_data[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"color"</span>]</span>
<span id="annotated-cell-17-8">    typography <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> brand_data[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"typography"</span>]</span>
<span id="annotated-cell-17-9">    palette <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>(colors[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"palette"</span>].values())</span>
<span id="annotated-cell-17-10"></span>
<span id="annotated-cell-17-11">    ink <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> colors[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"foreground"</span>]</span>
<span id="annotated-cell-17-12">    paper <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> colors[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"background"</span>]</span>
<span id="annotated-cell-17-13">    accent <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> _col_mix(ink, paper, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.25</span>)</span>
<span id="annotated-cell-17-14"></span>
<span id="annotated-cell-17-15">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> (</span>
<span id="annotated-cell-17-16">        GT(data)</span>
<span id="annotated-cell-17-17">        .tab_options(</span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-17" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-17-18" class="code-annotation-target">            table_font_names<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>typography[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"base"</span>],</span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-17" data-target-annotation="2" onclick="event.preventDefault();">2</a><span id="annotated-cell-17-19" class="code-annotation-target">            table_font_color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>ink,</span>
<span id="annotated-cell-17-20">            table_background_color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>paper,</span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-17" data-target-annotation="3" onclick="event.preventDefault();">3</a><span id="annotated-cell-17-21" class="code-annotation-target">            column_labels_background_color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>palette[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>] <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> palette <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> accent,</span>
<span id="annotated-cell-17-22">            column_labels_font_weight<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"bold"</span>,</span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-17" data-target-annotation="4" onclick="event.preventDefault();">4</a><span id="annotated-cell-17-23" class="code-annotation-target">            table_body_border_top_color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>accent,</span>
<span id="annotated-cell-17-24">            table_body_border_bottom_color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>accent,</span>
<span id="annotated-cell-17-25">        )</span>
<span id="annotated-cell-17-26">        .tab_style(</span>
<span id="annotated-cell-17-27">            style<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>style.text(</span>
<span id="annotated-cell-17-28">                font<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>typography[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"headings"</span>],</span>
<span id="annotated-cell-17-29">                color<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>paper,</span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-17" data-target-annotation="5" onclick="event.preventDefault();">5</a><span id="annotated-cell-17-30" class="code-annotation-target">                weight<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"bold"</span>,</span>
<span id="annotated-cell-17-31">            ),</span>
<span id="annotated-cell-17-32">            locations<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>loc.column_labels(),</span>
<span id="annotated-cell-17-33">        )</span>
<span id="annotated-cell-17-34">    )</span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-17" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-17" data-code-lines="18" data-code-annotation="1">Set the base font for the entire table.</span>
</dd>
<dt data-target-cell="annotated-cell-17" data-target-annotation="2">2</dt>
<dd>
<span data-code-cell="annotated-cell-17" data-code-lines="19,20" data-code-annotation="2">Apply brand background and text colours.</span>
</dd>
<dt data-target-cell="annotated-cell-17" data-target-annotation="3">3</dt>
<dd>
<span data-code-cell="annotated-cell-17" data-code-lines="21" data-code-annotation="3">Use the first palette colour for column header background.</span>
</dd>
<dt data-target-cell="annotated-cell-17" data-target-annotation="4">4</dt>
<dd>
<span data-code-cell="annotated-cell-17" data-code-lines="23,24" data-code-annotation="4">Use the accent colour for table body borders.</span>
</dd>
<dt data-target-cell="annotated-cell-17" data-target-annotation="5">5</dt>
<dd>
<span data-code-cell="annotated-cell-17" data-code-lines="30" data-code-annotation="5">Style column labels with the heading font and contrasting colour.</span>
</dd>
</dl>
</div>
</div>
</div>
</section>
<section id="light-and-dark-mode" class="level2" data-number="8">
<h2 data-number="8" class="anchored" data-anchor-id="light-and-dark-mode"><span class="header-section-number">8</span> Light and Dark Mode</h2>
<p>Quarto supports light and dark colour modes, and the brand configuration includes separate colour definitions for each. The helper functions accept a <code>brand_mode</code> parameter to select which variant to use.</p>
<p>For figures, Quarto’s <code>renderings</code> cell option generates both variants from a single code cell.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb8" data-filename="default" style="background: #f1f3f5;"><pre class="sourceCode default cw-auto code-with-copy"><code class="sourceCode default"><span id="cb8-1">#| renderings: [light, dark]</span></code></pre></div></div>
<p>The code loops over both modes and prints each plot. Quarto then displays the correct one based on the reader’s selected theme.</p>
<p>For tables, use Quarto’s <code>.light-content</code> and <code>.dark-content</code> CSS classes to conditionally display the appropriate version.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb9" data-filename="markdown" style="background: #f1f3f5;"><pre class="sourceCode markdown cw-auto code-with-copy"><code class="sourceCode markdown"><span id="cb9-1">::: {.light-content}</span>
<span id="cb9-2">Table rendered with <span class="in" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">`brand_mode = "light"`</span>.</span>
<span id="cb9-3">:::</span>
<span id="cb9-4"></span>
<span id="cb9-5">::: {.dark-content}</span>
<span id="cb9-6">Table rendered with <span class="in" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">`brand_mode = "dark"`</span>.</span>
<span id="cb9-7">:::</span></code></pre></div></div>
<div class="callout callout-style-default callout-tip callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Tip</span>Automatic Theme Matching
</div>
</div>
<div class="callout-body-container callout-body">
<p>Combining <code>renderings: [light, dark]</code> with branded themes means figures and tables automatically match the reader’s selected theme without any JavaScript.</p>
</div>
</div>
</section>
<section id="putting-it-all-together" class="level2" data-number="9">
<h2 data-number="9" class="anchored" data-anchor-id="putting-it-all-together"><span class="header-section-number">9</span> Putting It All Together</h2>
<p>The full workflow boils down to four steps:</p>
<ol type="1">
<li>Call <code>configure_brand_fonts()</code> once in a hidden setup chunk (<code>#| include: false</code>) to register brand fonts before any plotting.</li>
<li>Apply <code>theme_brand(brand_mode = ...)</code> to every <code>ggplot2</code> or <code>plotnine</code> figure.</li>
<li>Pass your data through <code>gt_brand(data, brand_mode = ...)</code> for every <code>gt</code> or <code>great_tables</code> table.</li>
<li>Use <code>renderings: [light, dark]</code> on figure cells and <code>.light-content</code>/<code>.dark-content</code> divs on tables so the reader’s theme preference is respected automatically.</li>
</ol>
<p>That is the entire pattern. Every helper reads from the same <code>QUARTO_EXECUTE_INFO</code> source, so switching to a different brand configuration is a matter of changing your <code>_brand.yml</code> file; no code changes are needed.</p>
</section>
<section id="conclusion" class="level2" data-number="10">
<h2 data-number="10" class="anchored" data-anchor-id="conclusion"><span class="header-section-number">10</span> Conclusion</h2>
<p>The <code>QUARTO_EXECUTE_INFO</code> environment variable provides language-agnostic access to Quarto’s resolved brand configuration at render time. With a small set of helper functions, you can apply consistent brand colours, fonts, and palettes to every figure and table your code produces.</p>
<p>This is one approach among several (see the note on <code>brand.yml</code> packages above), but it has the advantage of working identically in R and Python, supporting full font registration, and requiring no dependencies beyond a JSON parser and the plotting libraries you are already using. Browse the <a href="https://github.com/mcanouil/demo-quarto-brand-renderings">companion repository</a> for six complete brand configurations with production-ready implementations, and feel free to adapt the helpers to your own projects.</p>
</section>
<section id="further-resources" class="level2" data-number="11">
<h2 data-number="11" class="anchored" data-anchor-id="further-resources"><span class="header-section-number">11</span> Further Resources</h2>
<ul>
<li><a href="https://quarto.org/docs/authoring/brand.html">Multiformat Branding with <code>_brand.yml</code></a>: the official Quarto documentation on the brand feature.</li>
<li><a href="https://quarto.org/docs/advanced/quarto-execute-info.html">Execution Context Information</a>: how <code>QUARTO_EXECUTE_INFO</code> works and what data it exposes.</li>
<li><a href="https://quarto.org/docs/computations/execution-options.html">Execution Options</a>: code cell options including <code>renderings</code> for light/dark mode.</li>
<li><a href="https://quarto.org/docs/output-formats/html-themes.html">HTML Theming</a>: customising HTML themes, Sass variables, and dark mode support.</li>
<li><a href="https://posit-dev.github.io/brand-yml/"><code>brand.yml</code> Specification</a>: the full specification and documentation for <code>_brand.yml</code> files.</li>
</ul>


</section>

<a onclick="window.scrollTo(0, 0); return false;" id="quarto-back-to-top"><i class="bi bi-arrow-up"></i> Back to top</a><div id="quarto-appendix" class="default"><section class="quarto-appendix-contents" id="quarto-reuse"><h2 class="anchored quarto-appendix-heading">Reuse</h2><div class="quarto-appendix-contents"><div><a rel="license" href="https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en_gb">CC BY-NC-SA 4.0</a></div></div></section><section class="quarto-appendix-contents" id="quarto-citation"><h2 class="anchored quarto-appendix-heading">Citation</h2><div><div class="quarto-appendix-secondary-label">BibTeX citation:</div><pre class="sourceCode code-with-copy quarto-appendix-bibtex"><code class="sourceCode bibtex">@misc{canouil2026,
  author = {CANOUIL, Mickaël},
  title = {Branded {Figures} and {Tables} in {R} and {Python} with
    {Quarto}},
  date = {2026-04-15},
  url = {https://mickael.canouil.fr/posts/2026-04-15-quarto-brand-figures-tables/},
  langid = {en-GB}
}
</code></pre><div class="quarto-appendix-secondary-label">For attribution, please cite this work as:</div><div id="ref-canouil2026" class="csl-entry quarto-appendix-citeas">
CANOUIL, M. (2026-04-15). Branded Figures and Tables in R and Python
with Quarto. <em>Mickael.canouil.fr</em>. <a href="https://mickael.canouil.fr/posts/2026-04-15-quarto-brand-figures-tables/">https://mickael.canouil.fr/posts/2026-04-15-quarto-brand-figures-tables/</a>
</div></div></section></div> ]]></description>
  <category>quarto</category>
  <category>brand</category>
  <category>r</category>
  <category>python</category>
  <category>ggplot2</category>
  <category>plotnine</category>
  <guid>https://mickael.canouil.fr/posts/2026-04-15-quarto-brand-figures-tables/</guid>
  <pubDate>Wed, 15 Apr 2026 00:00:00 GMT</pubDate>
  <media:content url="https://mickael.canouil.fr/posts/2026-04-15-quarto-brand-figures-tables/featured.png" medium="image" type="image/png" height="76" width="144"/>
</item>
<item>
  <title>Building Quarto Typst Templates: Advanced Patterns (Part 2)</title>
  <link>https://mickael.canouil.fr/posts/2026-03-05-typst-template-tutorial-part2/</link>
  <description><![CDATA[ 

<!--
@license MIT
@copyright 2026 Mickaël Canouil
@author Mickaël Canouil
-->
Skip to main content





<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="featured.png" class="lightbox" data-gallery="quarto-lightbox-gallery-1"><img src="https://mickael.canouil.fr/posts/2026-03-05-typst-template-tutorial-part2/featured.png" class="img-featured img-fluid quarto-figure quarto-figure-center figure-img" alt="Feature image for &quot;Quarto + Typst Part 2&quot; blog post. Dark background
with the official Quarto logo (four blue quadrants with wordmark) and
Typst logo (teal wordmark) centered at the top. Below, &quot;Part 2&quot; in serif
font with subtitle &quot;Modern Scientific Publishing&quot;. File format pipeline
shows .qmd → .typ → .pdf. Decorative elements include floating code
snippets and gradient orbs in blue and teal.
" width="600"></a></p>
</figure>
</div>
<div class="callout callout-style-default callout-important callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Important</span>Quarto Version
</div>
</div>
<div class="callout-body-container callout-body">
<p>This tutorial was written and tested with <strong>Quarto CLI 1.9.29 (pre-release)</strong>. Some APIs, template syntax, or extension behaviours may differ in earlier stable releases.</p>
</div>
</div>
<div class="callout callout-style-default callout-tip callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Tip</span>TL;DR
</div>
</div>
<div class="callout-body-container callout-body">
<p>This tutorial covers advanced patterns for production-quality Quarto Typst extensions. Read <a href="../../posts/2026-02-27-typst-template-tutorial-part1/">Part 1</a> first for the foundational concepts.</p>
<ul>
<li><strong>Handler factories</strong>: eliminate boilerplate by generating handlers with a closure-captured configuration flag.</li>
<li><strong>Configuration system</strong>: let users extend the extension with their own class-to-function mappings via document metadata.</li>
<li><strong>Type-safe value conversion</strong>: <code>typst_value</code> maps Lua types to correct Typst syntax (strings, numbers, keywords, units).</li>
<li><strong>WCAG-compliant badges</strong>: iterative contrast adjustment produces accessible colour combinations.</li>
<li><strong>Shortcodes</strong>: explicit syntax for components without a natural markdown representation.</li>
</ul>
<div style="text-align: center;">
<p><a href="./tutorial-part2.zip" class="btn btn-outline-light" target="_blank" rel="noopener noreferrer">Part 2 (ZIP)</a></p>
</div>
</div>
</div>
<section id="continuing-from-part-1" class="level2" data-number="1">
<h2 data-number="1" class="anchored" data-anchor-id="continuing-from-part-1"><span class="header-section-number">1</span> Continuing from Part 1</h2>
<p><a href="../../posts/2026-02-27-typst-template-tutorial-part1/">Part 1</a> introduced the dual-layer architecture for building Quarto Typst templates. Here is a brief recap of the building blocks this tutorial relies on:</p>
<ul>
<li><strong>Lua filters</strong> intercept Pandoc AST elements (spans, divs) and generate raw Typst code via <code>pandoc.RawBlock</code> and <code>pandoc.RawInline</code>.</li>
<li><strong>Typst rendering functions</strong> receive that generated code and produce styled output with access to document context.</li>
<li><strong>Wrapper functions</strong> in <code>typst-show.typ</code> bridge parse-time (Lua) and render-time (Typst), injecting runtime context such as colour schemes.</li>
<li><strong><code>build_wrapped_content</code></strong> handles the common pattern of wrapping div content with opening and closing Typst function calls, optionally extracting a title from the first heading.</li>
</ul>
<p>This tutorial builds on those foundations with advanced patterns for production-quality extensions.</p>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
Note
</div>
</div>
<div class="callout-body-container callout-body">
<p>Part 1 used <code>filter.lua</code> as the filter filename in its conceptual examples. Part 2 uses <code>typst-markdown.lua</code>, which is the production filename used in the downloadable extension. The role is identical: it is the Pandoc filter entrypoint that loads shared modules and registers the <code>Div</code> and <code>Span</code> handlers. When building your own extension, you can choose any descriptive name, as long as it matches the filename declared under <code>filters:</code> in <code>_extension.yml</code>.</p>
</div>
</div>
<p>To keep the article and downloadable zip perfectly aligned, source-backed code blocks below show the full file content from the assets bundle. Each section highlights the exact functions to focus on, so you can scan purposefully instead of reading every line top-to-bottom.</p>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
Note
</div>
</div>
<div class="callout-body-container callout-body">
<p>This is <strong>Part 2</strong> of a two-part series. If you have not read <a href="../../posts/2026-02-27-typst-template-tutorial-part1/">Part 1</a>, start there first to understand the basic concepts of Lua filters, Typst functions, and wrapper patterns.</p>
<p>The <a href="https://github.com/mcanouil/quarto-mcanouil">‘mcanouil’ Quarto extension</a> demonstrates all patterns covered in this tutorial.</p>
</div>
</div>
</section>
<section id="handler-factories-reducing-repetition" class="level2" data-number="2">
<h2 data-number="2" class="anchored" data-anchor-id="handler-factories-reducing-repetition"><span class="header-section-number">2</span> Handler Factories: Reducing Repetition</h2>
<p>As you add components, the same handler structure keeps appearing. Part 1’s panel handler called <code>build_wrapped_content</code> directly. With one or two components that is fine, but each new handler duplicates the same boilerplate:</p>
<div class="code-with-filename">
<div class="code-with-filename-file">
<pre><strong>(conceptual) without factories</strong></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="annotated-cell-1" data-filename="(conceptual) without factories" style="background: #f1f3f5;"><pre class="sourceCode lua code-annotation-code code-with-copy code-annotated"><code class="sourceCode lua"><span id="annotated-cell-1-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">local</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">DIV_HANDLERS</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="annotated-cell-1-2">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'panel'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">div</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">config</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-1" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-1-3" class="code-annotation-target">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> build_wrapped_content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">div</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">config</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">true</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-1-4">  <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-1-5">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'highlight'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">div</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">config</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-1" data-target-annotation="2" onclick="event.preventDefault();">2</a><span id="annotated-cell-1-6" class="code-annotation-target">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> build_wrapped_content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">div</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">config</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">false</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-1-7">  <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-1-8"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
</div>
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-1" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-1" data-code-lines="3" data-code-annotation="1">Extract title from first heading.</span>
</dd>
<dt data-target-cell="annotated-cell-1" data-target-annotation="2">2</dt>
<dd>
<span data-code-cell="annotated-cell-1" data-code-lines="6" data-code-annotation="2">No title extraction; every new wrapped component repeats this same structure.</span>
</dd>
</dl>
<p>The only thing that varies is the <code>extract_title</code> flag. A factory function captures that variation in a <a href="https://www.lua.org/pil/6.1.html">closure</a>, eliminating the repetition entirely. A closure is a function that remembers variables from the scope where it was created. When <code>create_wrapped_handler(true)</code> returns a new function, that function carries <code>should_extract_title = true</code> inside it. Every call to the returned handler then uses the correct flag automatically, without passing it again. This is necessary because the filter loop (shown below in “Using Factories”) calls each handler as <code>DIV_FACTORY_HANDLERS[class](div, class_config)</code>, passing only the div element and the class config. The flag itself is never passed. By baking the flag into the closure at declaration time, each entry in <code>DIV_FACTORY_HANDLERS</code> becomes a self-contained handler with no extra parameters to manage.</p>
<section id="the-factory-pattern" class="level3" data-number="2.1">
<h3 data-number="2.1" class="anchored" data-anchor-id="the-factory-pattern"><span class="header-section-number">2.1</span> The Factory Pattern</h3>
<p>In this file, focus on <code>create_wrapped_handler</code> and how it forwards to <code>build_wrapped_content</code>; the rest are shared helpers used by multiple components.</p>
<div class="code-with-filename">
<div class="code-with-filename-file">
<pre><strong>_extensions/my-extension/_modules/wrapper.lua</strong></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="annotated-cell-2" data-filename="_extensions/my-extension/_modules/wrapper.lua" style="background: #f1f3f5;"><pre class="sourceCode lua code-annotation-code code-with-copy code-annotated"><code class="sourceCode lua"><span id="annotated-cell-2-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">local</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">typst_utils</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">require</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">quarto</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">utils</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>resolve_path<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"_modules/typst-utils.lua"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">):</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">gsub</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'%.lua$'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">''</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">))</span></span>
<span id="annotated-cell-2-2"></span>
<span id="annotated-cell-2-3"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">local</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">M</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="annotated-cell-2-4">  <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">attributes_to_table</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">typst_utils</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">attributes_to_table</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-2-5"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="annotated-cell-2-6"></span>
<span id="annotated-cell-2-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">--- Extract first heading as title attribute</span></span>
<span id="annotated-cell-2-8"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">M</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>extract_first_heading_as_title<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">el</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">attrs</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-2-9">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">not</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">attrs</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'title'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">and</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">el</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">content</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">then</span></span>
<span id="annotated-cell-2-10">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">local</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">first_elem</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">el</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">content</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span></span>
<span id="annotated-cell-2-11">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">first_elem</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">t</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'Header'</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">then</span></span>
<span id="annotated-cell-2-12">      <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">attrs</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'title'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">pandoc</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">utils</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>stringify<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">first_elem</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">content</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-2-13">      <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">local</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">new_content</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{}</span></span>
<span id="annotated-cell-2-14">      <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">i</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">el</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">content</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">do</span></span>
<span id="annotated-cell-2-15">        <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">table.insert</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">new_content</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">el</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">content</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">i</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">])</span></span>
<span id="annotated-cell-2-16">      <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end</span></span>
<span id="annotated-cell-2-17">      <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">el</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">content</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">new_content</span></span>
<span id="annotated-cell-2-18">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end</span></span>
<span id="annotated-cell-2-19">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end</span></span>
<span id="annotated-cell-2-20"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end</span></span>
<span id="annotated-cell-2-21"></span>
<span id="annotated-cell-2-22"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">--- Build Typst block wrappers with optional attributes</span></span>
<span id="annotated-cell-2-23"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">M</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>build_typst_block_wrappers<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">config</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">attrs</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-2-24">  <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">local</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">has_attributes</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">next</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">attrs</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">~=</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">nil</span></span>
<span id="annotated-cell-2-25"></span>
<span id="annotated-cell-2-26">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">has_attributes</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">or</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">config</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">arguments</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">then</span></span>
<span id="annotated-cell-2-27">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">local</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">attr_string</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">typst_utils</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>build_attribute_string<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">attrs</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-2-28">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">string.format</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'#%s(%s)['</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">config</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">wrapper</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">attr_string</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">']'</span></span>
<span id="annotated-cell-2-29">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span></span>
<span id="annotated-cell-2-30">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">string.format</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'#%s['</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">config</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">wrapper</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">']'</span></span>
<span id="annotated-cell-2-31">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end</span></span>
<span id="annotated-cell-2-32"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end</span></span>
<span id="annotated-cell-2-33"></span>
<span id="annotated-cell-2-34"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">--- Build wrapped content for a div</span></span>
<span id="annotated-cell-2-35"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">M</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>build_wrapped_content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">div</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">config</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">should_extract_title</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-2-36">  <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">local</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">attrs</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">typst_utils</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>attributes_to_table<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">div</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-2-37">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">should_extract_title</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">then</span></span>
<span id="annotated-cell-2-38">    <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">M</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>extract_first_heading_as_title<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">div</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">attrs</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-2-39">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end</span></span>
<span id="annotated-cell-2-40"></span>
<span id="annotated-cell-2-41">  <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">local</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">opening</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">closing</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">M</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>build_typst_block_wrappers<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">config</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">attrs</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-2-42">  <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">local</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">result</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">pandoc</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>RawBlock<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'typst'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">opening</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="annotated-cell-2-43">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">_</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">item</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">ipairs</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">div</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">content</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">do</span></span>
<span id="annotated-cell-2-44">    <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">table.insert</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">result</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">item</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-2-45">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end</span></span>
<span id="annotated-cell-2-46">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">table.insert</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">result</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">pandoc</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>RawBlock<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'typst'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">closing</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">))</span></span>
<span id="annotated-cell-2-47">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">result</span></span>
<span id="annotated-cell-2-48"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end</span></span>
<span id="annotated-cell-2-49"></span>
<span id="annotated-cell-2-50"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">--- Factory: create handler for wrapped content components</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-2" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-2-51" class="code-annotation-target"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">M</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>create_wrapped_handler<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">should_extract_title</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-2" data-target-annotation="2" onclick="event.preventDefault();">2</a><span id="annotated-cell-2-52" class="code-annotation-target">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">div</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">config</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-2-53">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">M</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>build_wrapped_content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">div</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">config</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">should_extract_title</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-2-54">  <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end</span></span>
<span id="annotated-cell-2-55"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end</span></span>
<span id="annotated-cell-2-56"></span>
<span id="annotated-cell-2-57"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">M</span></span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
</div>
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-2" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-2" data-code-lines="51" data-code-annotation="1"><code>should_extract_title</code> is captured in the returned closure; each call to <code>create_wrapped_handler</code> produces a distinct handler locked to that flag value.</span>
</dd>
<dt data-target-cell="annotated-cell-2" data-target-annotation="2">2</dt>
<dd>
<span data-code-cell="annotated-cell-2" data-code-lines="52" data-code-annotation="2">The returned function takes <code>div</code> (the Pandoc div element passed by the dispatcher) and <code>config</code> (the mapping entry for this class, e.g., <code>{ wrapper='my-panel', arguments=true }</code>); this signature matches exactly what the dispatcher passes when it calls <code>DIV_FACTORY_HANDLERS[class](div, class_config)</code>.</span>
</dd>
</dl>
</section>
<section id="using-factories" class="level3" data-number="2.2">
<h3 data-number="2.2" class="anchored" data-anchor-id="using-factories"><span class="header-section-number">2.2</span> Using Factories</h3>
<p>These factories let you declare handlers concisely. <code>DIV_FACTORY_HANDLERS</code> is where each class opts into title extraction or content-only wrapping; <code>DIV_HANDLERS</code> is reserved for components that need custom extraction logic.</p>
<div class="code-with-filename">
<div class="code-with-filename-file">
<pre><strong>_extensions/my-extension/typst-markdown.lua (handler tables excerpt)</strong></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="annotated-cell-3" data-filename="_extensions/my-extension/typst-markdown.lua (handler tables excerpt)" style="background: #f1f3f5;"><pre class="sourceCode lua code-annotation-code code-with-copy code-annotated"><code class="sourceCode lua"><span id="annotated-cell-3-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">local</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">DIV_HANDLERS</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-3" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-3-2" class="code-annotation-target">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'quote-card'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">quote_card</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">process_div</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-3-3"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="annotated-cell-3-4"></span>
<span id="annotated-cell-3-5"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">local</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">DIV_FACTORY_HANDLERS</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-3" data-target-annotation="2" onclick="event.preventDefault();">2</a><span id="annotated-cell-3-6" class="code-annotation-target">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'panel'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">wrapper</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>create_wrapped_handler<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">true</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-3" data-target-annotation="3" onclick="event.preventDefault();">3</a><span id="annotated-cell-3-7" class="code-annotation-target">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'highlight'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">wrapper</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>create_wrapped_handler<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">false</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-3-8"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
</div>
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-3" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-3" data-code-lines="2" data-code-annotation="1">Custom handler: handles component-specific extraction logic (author from heading).</span>
</dd>
<dt data-target-cell="annotated-cell-3" data-target-annotation="2">2</dt>
<dd>
<span data-code-cell="annotated-cell-3" data-code-lines="6" data-code-annotation="2">Factory handler with title extraction enabled; the first heading inside the div becomes the <code>title</code> argument.</span>
</dd>
<dt data-target-cell="annotated-cell-3" data-target-annotation="3">3</dt>
<dd>
<span data-code-cell="annotated-cell-3" data-code-lines="7" data-code-annotation="3">Factory handler without title extraction; content passes through unchanged.</span>
</dd>
</dl>
<p>The dispatcher checks each div class in priority order: custom handlers first, factory handlers second, and the default wrapper last.</p>
<div class="code-with-filename">
<div class="code-with-filename-file">
<pre><strong>_extensions/my-extension/typst-markdown.lua (Div filter excerpt)</strong></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="annotated-cell-4" data-filename="_extensions/my-extension/typst-markdown.lua (Div filter excerpt)" style="background: #f1f3f5;"><pre class="sourceCode lua code-annotation-code code-with-copy code-annotated"><code class="sourceCode lua"><span id="annotated-cell-4-1"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">_</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">class</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">ipairs</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">div</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">classes</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">do</span></span>
<span id="annotated-cell-4-2">  <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">local</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">class_config</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">merged_div_config</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">class</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span></span>
<span id="annotated-cell-4-3">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">class_config</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">then</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-4" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-4-4" class="code-annotation-target">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">DIV_HANDLERS</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">class</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">then</span></span>
<span id="annotated-cell-4-5">      <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">DIV_HANDLERS</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">class</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">](</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">div</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">class_config</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-4-6">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end</span></span>
<span id="annotated-cell-4-7"></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-4" data-target-annotation="2" onclick="event.preventDefault();">2</a><span id="annotated-cell-4-8" class="code-annotation-target">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">DIV_FACTORY_HANDLERS</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">class</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">then</span></span>
<span id="annotated-cell-4-9">      <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">DIV_FACTORY_HANDLERS</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">class</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">](</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">div</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">class_config</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-4-10">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end</span></span>
<span id="annotated-cell-4-11"></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-4" data-target-annotation="3" onclick="event.preventDefault();">3</a><span id="annotated-cell-4-12" class="code-annotation-target">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">wrapper</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>build_wrapped_content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">div</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">class_config</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">false</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-4-13">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end</span></span>
<span id="annotated-cell-4-14"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end</span></span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
</div>
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-4" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-4" data-code-lines="4" data-code-annotation="1">Custom handlers run first, before factory or default logic.</span>
</dd>
<dt data-target-cell="annotated-cell-4" data-target-annotation="2">2</dt>
<dd>
<span data-code-cell="annotated-cell-4" data-code-lines="8" data-code-annotation="2">Factory-generated handlers run next.</span>
</dd>
<dt data-target-cell="annotated-cell-4" data-target-annotation="3">3</dt>
<dd>
<span data-code-cell="annotated-cell-4" data-code-lines="12" data-code-annotation="3">Default fallback: simple content wrap, no title extraction. User-defined mappings added via YAML always reach this default branch; they cannot opt into title extraction or custom logic without a corresponding factory or custom handler in the extension code.</span>
</dd>
</dl>
<div class="highlight">
<p><strong>Handler factories</strong> encapsulate common patterns. Use <code>create_wrapped_handler(true)</code> for components with titles and <code>create_wrapped_handler(false)</code> for content-only wrappers.</p>
</div>
</section>
<section id="the-span-dispatcher" class="level3" data-number="2.3">
<h3 data-number="2.3" class="anchored" data-anchor-id="the-span-dispatcher"><span class="header-section-number">2.3</span> The Span Dispatcher</h3>
<p>Inline spans (badges and custom highlights) use a simpler dispatcher than divs. There are no factory handlers or custom handlers for spans: every inline class goes through the same lookup-and-format loop.</p>
<div class="code-with-filename">
<div class="code-with-filename-file">
<pre><strong>_extensions/my-extension/typst-markdown.lua (Span filter excerpt)</strong></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="annotated-cell-5" data-filename="_extensions/my-extension/typst-markdown.lua (Span filter excerpt)" style="background: #f1f3f5;"><pre class="sourceCode lua code-annotation-code code-with-copy code-annotated"><code class="sourceCode lua"><a class="code-annotation-anchor" data-target-cell="annotated-cell-5" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-5-1" class="code-annotation-target"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">_</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">class</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">ipairs</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">span</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">classes</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">do</span></span>
<span id="annotated-cell-5-2">  <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">local</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">class_config</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">merged_span_config</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">class</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span></span>
<span id="annotated-cell-5-3">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">class_config</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">then</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-5" data-target-annotation="2" onclick="event.preventDefault();">2</a><span id="annotated-cell-5-4" class="code-annotation-target">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">local</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">content</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">pandoc</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">utils</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>stringify<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">span</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">content</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-5-5">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">local</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">attrs</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">typst_utils</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>attributes_to_table<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">span</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-5" data-target-annotation="3" onclick="event.preventDefault();">3</a><span id="annotated-cell-5-6" class="code-annotation-target">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">local</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">attr_string</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">typst_utils</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>build_attribute_string<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">attrs</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-5-7"></span>
<span id="annotated-cell-5-8">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">local</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">typst_code</span></span>
<span id="annotated-cell-5-9">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">attr_string</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">~=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">''</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">then</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-5" data-target-annotation="4" onclick="event.preventDefault();">4</a><span id="annotated-cell-5-10" class="code-annotation-target">      <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">typst_code</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">string.format</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'#%s(%s)[%s]'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">class_config</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">wrapper</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">attr_string</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">content</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-5-11">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span></span>
<span id="annotated-cell-5-12">      <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">typst_code</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">string.format</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'#%s[%s]'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">class_config</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">wrapper</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">content</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-5-13">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end</span></span>
<span id="annotated-cell-5-14"></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-5" data-target-annotation="5" onclick="event.preventDefault();">5</a><span id="annotated-cell-5-15" class="code-annotation-target">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">pandoc</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>RawInline<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'typst'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">typst_code</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-5-16">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end</span></span>
<span id="annotated-cell-5-17"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end</span></span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
</div>
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-5" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-5" data-code-lines="1" data-code-annotation="1">Spans can carry multiple classes, for example <code>[text]{.badge .highlight}</code>. <code>span.classes</code> preserves declaration order, left to right as written in the markdown, so iterating finds the first mapped class. <code>return</code> exits immediately, so only one handler fires per span.</span>
</dd>
<dt data-target-cell="annotated-cell-5" data-target-annotation="2">2</dt>
<dd>
<span data-code-cell="annotated-cell-5" data-code-lines="4" data-code-annotation="2">Span content is flattened to plain text; inline elements cannot preserve nested structure the way block divs can.</span>
</dd>
<dt data-target-cell="annotated-cell-5" data-target-annotation="3">3</dt>
<dd>
<span data-code-cell="annotated-cell-5" data-code-lines="6" data-code-annotation="3">Build the attribute string from any key-value pairs on the span (e.g., <code>colour="success"</code> becomes <code>colour: "success"</code>).</span>
</dd>
<dt data-target-cell="annotated-cell-5" data-target-annotation="4">4</dt>
<dd>
<span data-code-cell="annotated-cell-5" data-code-lines="10" data-code-annotation="4">If attributes are present, generate <code>#wrapper(attr: value)[content]</code>; otherwise use the shorter <code>#wrapper[content]</code> form.</span>
</dd>
<dt data-target-cell="annotated-cell-5" data-target-annotation="5">5</dt>
<dd>
<span data-code-cell="annotated-cell-5" data-code-lines="15" data-code-annotation="5">Return <code>RawInline</code> (not <code>RawBlock</code>) so the result sits inline within the surrounding paragraph, not on its own line.</span>
</dd>
</dl>
</section>
<section id="choosing-your-approach" class="level3" data-number="2.4">
<h3 data-number="2.4" class="anchored" data-anchor-id="choosing-your-approach"><span class="header-section-number">2.4</span> Choosing Your Approach</h3>
<p>When building a new component, consider:</p>
<ul>
<li><strong>No handler (default)</strong>: Simple wrapper around content; add a mapping in document YAML.</li>
<li><strong><code>create_wrapped_handler(true)</code></strong>: Wraps content and extracts title from first heading (e.g., panels with titles).</li>
<li><strong><code>create_wrapped_handler(false)</code></strong>: Wraps content without title extraction (e.g., simple content wrappers).</li>
<li><strong>Custom handler</strong>: Complex data extraction from nested elements (e.g., quote cards with author attribution).</li>
<li><strong>Shortcode</strong>: No natural markdown syntax; uses explicit <code>{{&lt; shortcode &gt;}}</code> syntax.</li>
</ul>
</section>
</section>
<section id="sec-config" class="level2" data-number="3">
<h2 data-number="3" class="anchored" data-anchor-id="sec-config"><span class="header-section-number">3</span> The Configuration System</h2>
<p>A key design goal for reusable extensions is extensibility. Users can add their own components or override built-in ones without modifying the extension’s source code. The <a href="https://github.com/mcanouil/quarto-mcanouil/blob/main/_extensions/mcanouil/typst/_modules/config.lua">configuration system</a> enables this through metadata-driven element mappings.</p>
<section id="built-in-mappings" class="level3" data-number="3.1">
<h3 data-number="3.1" class="anchored" data-anchor-id="built-in-mappings"><span class="header-section-number">3.1</span> Built-in Mappings</h3>
<p>The extension defines default mappings for all its components. <code>get_builtin_mappings()</code> is the canonical baseline before any user metadata overrides are merged.</p>
<div class="code-with-filename">
<div class="code-with-filename-file">
<pre><strong>_extensions/my-extension/_modules/config.lua (built-in mappings excerpt)</strong></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="annotated-cell-6" data-filename="_extensions/my-extension/_modules/config.lua (built-in mappings excerpt)" style="background: #f1f3f5;"><pre class="sourceCode lua code-annotation-code code-with-copy code-annotated"><code class="sourceCode lua"><span id="annotated-cell-6-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">--- Return default div/span mappings for all components</span></span>
<span id="annotated-cell-6-2"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">M</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>get_builtin_mappings<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">()</span></span>
<span id="annotated-cell-6-3">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="annotated-cell-6-4">    <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">div</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-6" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-6-5" class="code-annotation-target">      <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'highlight'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">wrapper</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'my-highlight'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">arguments</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">false</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">},</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-6" data-target-annotation="2" onclick="event.preventDefault();">2</a><span id="annotated-cell-6-6" class="code-annotation-target">      <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'panel'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">wrapper</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'my-panel'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">arguments</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">true</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">},</span></span>
<span id="annotated-cell-6-7">      <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'quote-card'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">wrapper</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'quote-card'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">arguments</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">true</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">},</span></span>
<span id="annotated-cell-6-8">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">},</span></span>
<span id="annotated-cell-6-9">    <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">span</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="annotated-cell-6-10">      <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'badge'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">wrapper</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'my-badge'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">arguments</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">true</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">},</span></span>
<span id="annotated-cell-6-11">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">},</span></span>
<span id="annotated-cell-6-12">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="annotated-cell-6-13"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end</span></span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
</div>
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-6" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-6" data-code-lines="5" data-code-annotation="1"><code>arguments = false</code>: parameter list generated only when attributes are present.</span>
</dd>
<dt data-target-cell="annotated-cell-6" data-target-annotation="2">2</dt>
<dd>
<span data-code-cell="annotated-cell-6" data-code-lines="6" data-code-annotation="2"><code>arguments = true</code>: parameter list always generated, even when empty, because the Typst function always expects named arguments.</span>
</dd>
</dl>
<p>Each mapping specifies:</p>
<ul>
<li><strong><code>wrapper</code></strong>: The Typst function name to call.</li>
<li><strong><code>arguments</code></strong>: Whether to always pass attributes (even if empty).</li>
</ul>
</section>
<section id="user-configuration-via-metadata" class="level3" data-number="3.2">
<h3 data-number="3.2" class="anchored" data-anchor-id="user-configuration-via-metadata"><span class="header-section-number">3.2</span> User Configuration via Metadata</h3>
<p>Users can extend or override mappings through document metadata:</p>
<div class="code-with-filename">
<div class="code-with-filename-file">
<pre><strong>(conceptual) document.qmd</strong></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="annotated-cell-7" data-filename="(conceptual) document.qmd" style="background: #f1f3f5;"><pre class="sourceCode yaml code-annotation-code code-with-copy code-annotated"><code class="sourceCode yaml"><span id="annotated-cell-7-1"><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">---</span></span>
<span id="annotated-cell-7-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">title</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"My Document"</span></span>
<span id="annotated-cell-7-3"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">extensions</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-7" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-7-4" class="code-annotation-target"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">  </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">typst-markdown</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="annotated-cell-7-5"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">divs</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-7" data-target-annotation="2" onclick="event.preventDefault();">2</a><span id="annotated-cell-7-6" class="code-annotation-target"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">      </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">my-callout</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> my-custom-callout</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-7" data-target-annotation="3" onclick="event.preventDefault();">3</a><span id="annotated-cell-7-7" class="code-annotation-target"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">      </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">my-box</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="annotated-cell-7-8"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">        </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">function</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> my-custom-box</span></span>
<span id="annotated-cell-7-9"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">        </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">arguments</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">true</span></span>
<span id="annotated-cell-7-10"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">spans</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="annotated-cell-7-11"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">      </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">my-highlight</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> my-text-highlight</span></span>
<span id="annotated-cell-7-12"><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">---</span></span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
</div>
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-7" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-7" data-code-lines="4" data-code-annotation="1">User mappings live under the <code>extensions.typst-markdown</code> namespace.</span>
</dd>
<dt data-target-cell="annotated-cell-7" data-target-annotation="2">2</dt>
<dd>
<span data-code-cell="annotated-cell-7" data-code-lines="6" data-code-annotation="2">Simple format: class name maps directly to a Typst function name.</span>
</dd>
<dt data-target-cell="annotated-cell-7" data-target-annotation="3">3</dt>
<dd>
<span data-code-cell="annotated-cell-7" data-code-lines="7" data-code-annotation="3">Detailed format: allows setting <code>arguments: true</code> to always generate a parameter list. Both <code>function:</code> and <code>wrapper:</code> are accepted as the key name in the detailed format, and are true aliases. <code>wrapper:</code> matches the internal Lua variable name, whilst <code>function:</code> is more descriptive in a YAML context, so <code>function:</code> is preferred. Use either the simple or detailed format for each class; they are not combined: a class entry is either a bare string or an object, not both.</span>
</dd>
</dl>
<div class="callout callout-style-default callout-important callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
Important
</div>
</div>
<div class="callout-body-container callout-body">
<p>The YAML mapping tells the Lua dispatcher <em>which Typst function to call</em>, but you must also <strong>provide that function’s implementation</strong>. The extension has no built-in <code>my-custom-box</code> or <code>my-custom-callout</code>; those names are placeholders you define yourself. Add the Typst function to your document’s Typst preamble via <code>include-in-header</code>:</p>
<div class="code-with-filename">
<div class="code-with-filename-file">
<pre><strong>(conceptual) document.qmd front matter</strong></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" data-filename="(conceptual) document.qmd front matter" style="background: #f1f3f5;"><pre class="sourceCode yaml code-with-copy"><code class="sourceCode yaml"><span id="cb1-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">include-in-header</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb1-2"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">  - </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">text</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">|</span></span>
<span id="cb1-3">      #let my-custom-box(content) = {</span>
<span id="cb1-4">        block(fill: luma(240), radius: 4pt, inset: 1em, content)</span>
<span id="cb1-5">      }</span></code></pre></div></div>
</div>
<p>Alternatively, place the function in a <code>.typ</code> file and include it the same way. The function signature must match the arguments the dispatcher will generate based on your <code>arguments</code> setting.</p>
</div>
</div>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
Note
</div>
</div>
<div class="callout-body-container callout-body">
<p>If you want another real-world comparison, <a href="https://github.com/christopherkenny/typst-function"><code>christopherkenny/typst-function</code></a> uses a similar metadata-driven mapping idea with different naming and module structure. Comparing both implementations is useful for understanding which parts are pattern-level versus project-specific choices.</p>
</div>
</div>
</section>
<section id="loading-user-configuration" class="level3" data-number="3.3">
<h3 data-number="3.3" class="anchored" data-anchor-id="loading-user-configuration"><span class="header-section-number">3.3</span> Loading User Configuration</h3>
<p>The configuration module parses user settings from metadata. Two helpers keep the logic simple:</p>
<ul>
<li><code>meta_get(...)</code> reads metadata maps safely, whether keys are plain strings or Pandoc metadata objects.</li>
<li><code>parse_mapping(...)</code> normalises both supported user formats into one internal shape (<code>{wrapper=..., arguments=...}</code>).</li>
</ul>
<div class="code-with-filename">
<div class="code-with-filename-file">
<pre><strong>_extensions/my-extension/_modules/config.lua (user config excerpt)</strong></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="annotated-cell-8" data-filename="_extensions/my-extension/_modules/config.lua (user config excerpt)" style="background: #f1f3f5;"><pre class="sourceCode lua code-annotation-code code-with-copy code-annotated"><code class="sourceCode lua"><span id="annotated-cell-8-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">local</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span> meta_get<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">meta_map</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">wanted_key</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-8-2">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">type</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">meta_map</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">~=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'table'</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">then</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">nil</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end</span></span>
<span id="annotated-cell-8-3">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">meta_map</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">wanted_key</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">~=</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">nil</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">then</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">meta_map</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">wanted_key</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end</span></span>
<span id="annotated-cell-8-4">  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-- Pandoc meta maps may use non-string key objects; normalise via stringify.</span></span>
<span id="annotated-cell-8-5">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">key</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">value</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">pairs</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">meta_map</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">do</span></span>
<span id="annotated-cell-8-6">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">pandoc</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">utils</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>stringify<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">key</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">wanted_key</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">then</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">value</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end</span></span>
<span id="annotated-cell-8-7">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end</span></span>
<span id="annotated-cell-8-8">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">nil</span></span>
<span id="annotated-cell-8-9"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end</span></span>
<span id="annotated-cell-8-10"></span>
<span id="annotated-cell-8-11"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">--- Parse simple or detailed config format</span></span>
<span id="annotated-cell-8-12"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">local</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span> parse_mapping<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">config</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-8" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-8-13" class="code-annotation-target">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">type</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">config</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'string'</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">then</span></span>
<span id="annotated-cell-8-14">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">wrapper</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">config</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">arguments</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">false</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="annotated-cell-8-15">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end</span></span>
<span id="annotated-cell-8-16"></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-8" data-target-annotation="2" onclick="event.preventDefault();">2</a><span id="annotated-cell-8-17" class="code-annotation-target">  <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">local</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">fn_name</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> meta_get<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">config</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'function'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">or</span> meta_get<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">config</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'wrapper'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-8-18">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">fn_name</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">then</span></span>
<span id="annotated-cell-8-19">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">local</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">arg_value</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> meta_get<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">config</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'arguments'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-8-20">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="annotated-cell-8-21">      <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">wrapper</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">pandoc</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">utils</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>stringify<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">fn_name</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-8-22">      <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">arguments</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">arg_value</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">and</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">pandoc</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">utils</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>stringify<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">arg_value</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'true'</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">or</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">false</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-8-23">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="annotated-cell-8-24">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end</span></span>
<span id="annotated-cell-8-25"></span>
<span id="annotated-cell-8-26">  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-- Simple form: `custom-highlight: my-custom-highlight`.</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-8" data-target-annotation="3" onclick="event.preventDefault();">3</a><span id="annotated-cell-8-27" class="code-annotation-target">  <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">local</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">simple_fn</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">pandoc</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">utils</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>stringify<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">config</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-8-28">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">simple_fn</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">~=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">''</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">then</span></span>
<span id="annotated-cell-8-29">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">wrapper</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">simple_fn</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">arguments</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">false</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="annotated-cell-8-30">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end</span></span>
<span id="annotated-cell-8-31"></span>
<span id="annotated-cell-8-32">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">nil</span></span>
<span id="annotated-cell-8-33"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end</span></span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
</div>
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-8" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-8" data-code-lines="13" data-code-annotation="1">Simple string mapping: <code>my-callout: my-custom-callout</code>.</span>
</dd>
<dt data-target-cell="annotated-cell-8" data-target-annotation="2">2</dt>
<dd>
<span data-code-cell="annotated-cell-8" data-code-lines="17" data-code-annotation="2">Detailed mapping: both <code>function</code> and <code>wrapper</code> are accepted as the key name, normalised to <code>wrapper</code> internally. <code>arguments = arg_value and ... or false</code> is the Lua ternary idiom (<code>condition and x or y</code>) since Lua has no <code>?:</code> operator. The value is stringified before comparison because Pandoc delivers YAML booleans as metadata strings, not Lua booleans.</span>
</dd>
<dt data-target-cell="annotated-cell-8" data-target-annotation="3">3</dt>
<dd>
<span data-code-cell="annotated-cell-8" data-code-lines="27" data-code-annotation="3">Fallback: Pandoc may wrap a plain string in a metadata object; <code>stringify</code> unwraps it.</span>
</dd>
</dl>
</section>
<section id="merging-configurations" class="level3" data-number="3.4">
<h3 data-number="3.4" class="anchored" data-anchor-id="merging-configurations"><span class="header-section-number">3.4</span> Merging Configurations</h3>
<p>User configuration overrides built-in defaults. <code>merge_configurations(...)</code> performs a simple two-pass merge: built-ins first, then user mappings which overwrite any matching key.</p>
<div class="code-with-filename">
<div class="code-with-filename-file">
<pre><strong>_extensions/my-extension/_modules/config.lua (merge excerpt)</strong></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="annotated-cell-9" data-filename="_extensions/my-extension/_modules/config.lua (merge excerpt)" style="background: #f1f3f5;"><pre class="sourceCode lua code-annotation-code code-with-copy code-annotated"><code class="sourceCode lua"><span id="annotated-cell-9-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">--- Merge configurations: user overrides built-in</span></span>
<span id="annotated-cell-9-2"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">M</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>merge_configurations<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">builtin</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">user</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-9-3">  <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">local</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">merged</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{}</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-9" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-9-4" class="code-annotation-target">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">class</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">cfg</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">pairs</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">builtin</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">do</span></span>
<span id="annotated-cell-9-5">    <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">merged</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">class</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">cfg</span></span>
<span id="annotated-cell-9-6">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-9" data-target-annotation="2" onclick="event.preventDefault();">2</a><span id="annotated-cell-9-7" class="code-annotation-target">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">class</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">cfg</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">pairs</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">user</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">do</span></span>
<span id="annotated-cell-9-8">    <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">merged</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">class</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">cfg</span></span>
<span id="annotated-cell-9-9">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end</span></span>
<span id="annotated-cell-9-10">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">merged</span></span>
<span id="annotated-cell-9-11"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end</span></span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
</div>
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-9" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-9" data-code-lines="4" data-code-annotation="1">Copy all built-in mappings first.</span>
</dd>
<dt data-target-cell="annotated-cell-9" data-target-annotation="2">2</dt>
<dd>
<span data-code-cell="annotated-cell-9" data-code-lines="7" data-code-annotation="2">User mappings overwrite built-in ones with the same key; new keys are added as extensions.</span>
</dd>
</dl>
</section>
</section>
<section id="sec-value-conversion" class="level2" data-number="4">
<h2 data-number="4" class="anchored" data-anchor-id="sec-value-conversion"><span class="header-section-number">4</span> Value Conversion and Type Safety</h2>
<p>When passing values from Lua to Typst, type conversion matters. Strings need quoting. Numbers should not. Booleans map to Typst’s <code>true</code>/<code>false</code>. Getting this wrong produces syntax errors or unexpected behaviour.</p>
<section id="the-typst_value-function" class="level3" data-number="4.1">
<h3 data-number="4.1" class="anchored" data-anchor-id="the-typst_value-function"><span class="header-section-number">4.1</span> The <code>typst_value</code> Function</h3>
<p>The <a href="https://github.com/mcanouil/quarto-mcanouil/blob/main/_extensions/mcanouil/typst/_modules/typst-utils.lua"><code>typst-utils</code> module</a> provides a <code>typst_value</code> function that handles conversion.</p>
<p>Focus on <code>typst_value(...)</code>: it is the single conversion gate between Lua values and valid Typst syntax.</p>
<div class="code-with-filename">
<div class="code-with-filename-file">
<pre><strong>_extensions/my-extension/_modules/typst-utils.lua</strong></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="annotated-cell-10" data-filename="_extensions/my-extension/_modules/typst-utils.lua" style="background: #f1f3f5;"><pre class="sourceCode lua code-annotation-code code-with-copy code-annotated"><code class="sourceCode lua"><span id="annotated-cell-10-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">local</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">M</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{}</span></span>
<span id="annotated-cell-10-2"></span>
<span id="annotated-cell-10-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">--- Convert a Lua value to Typst syntax</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-10" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-10-4" class="code-annotation-target"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">M</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>typst_value<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">value</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-10-5">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">value</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">nil</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">then</span></span>
<span id="annotated-cell-10-6">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'none'</span></span>
<span id="annotated-cell-10-7">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">elseif</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">type</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">value</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'boolean'</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">then</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-10" data-target-annotation="2" onclick="event.preventDefault();">2</a><span id="annotated-cell-10-8" class="code-annotation-target">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">value</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">and</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'true'</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">or</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'false'</span></span>
<span id="annotated-cell-10-9">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">elseif</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">type</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">value</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'number'</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">then</span></span>
<span id="annotated-cell-10-10">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">tostring</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">value</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-10-11">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">elseif</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">type</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">value</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'string'</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">then</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-10" data-target-annotation="3" onclick="event.preventDefault();">3</a><span id="annotated-cell-10-12" class="code-annotation-target">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">value</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'none'</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">or</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">value</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'auto'</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">or</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">value</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'true'</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">or</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">value</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'false'</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">then</span></span>
<span id="annotated-cell-10-13">      <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">value</span></span>
<span id="annotated-cell-10-14">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-10" data-target-annotation="4" onclick="event.preventDefault();">4</a><span id="annotated-cell-10-15" class="code-annotation-target">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">value</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">match</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'^%-?%d+%.?%d*[a-z%%]+$'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">then</span></span>
<span id="annotated-cell-10-16">      <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">value</span></span>
<span id="annotated-cell-10-17">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-10" data-target-annotation="5" onclick="event.preventDefault();">5</a><span id="annotated-cell-10-18" class="code-annotation-target">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'"'</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">..</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">value</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">gsub</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'"'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">\\</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">..</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'"'</span></span>
<span id="annotated-cell-10-19">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span></span>
<span id="annotated-cell-10-20">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'"'</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">..</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">tostring</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">value</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">):</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">gsub</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'"'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">\\</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">..</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'"'</span></span>
<span id="annotated-cell-10-21">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end</span></span>
<span id="annotated-cell-10-22"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end</span></span>
<span id="annotated-cell-10-23"></span>
<span id="annotated-cell-10-24"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">--- Convert element attributes to a plain Lua table</span></span>
<span id="annotated-cell-10-25"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">M</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>attributes_to_table<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">element</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-10-26">  <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">local</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">attrs</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{}</span></span>
<span id="annotated-cell-10-27">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">key</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">value</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">pairs</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">element</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">attributes</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">do</span></span>
<span id="annotated-cell-10-28">    <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">attrs</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">key</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">value</span></span>
<span id="annotated-cell-10-29">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end</span></span>
<span id="annotated-cell-10-30">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">attrs</span></span>
<span id="annotated-cell-10-31"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end</span></span>
<span id="annotated-cell-10-32"></span>
<span id="annotated-cell-10-33"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">--- Build attribute string for Typst function calls</span></span>
<span id="annotated-cell-10-34"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">M</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>build_attribute_string<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">attrs</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-10-35">  <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">local</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">parts</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{}</span></span>
<span id="annotated-cell-10-36">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">key</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">value</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">pairs</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">attrs</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">do</span></span>
<span id="annotated-cell-10-37">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">local</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">typst_key</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">key</span></span>
<span id="annotated-cell-10-38">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">local</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">typst_val</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">M</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>typst_value<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">value</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-10-39">    <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">table.insert</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">parts</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">string.format</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'%s: %s'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">typst_key</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">typst_val</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">))</span></span>
<span id="annotated-cell-10-40">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end</span></span>
<span id="annotated-cell-10-41">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">table.concat</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">parts</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">', '</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-10-42"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end</span></span>
<span id="annotated-cell-10-43"></span>
<span id="annotated-cell-10-44"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">M</span></span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
</div>
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-10" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-10" data-code-lines="4" data-code-annotation="1">Entry point: dispatches on Lua type; all paths return a string of valid Typst syntax.</span>
</dd>
<dt data-target-cell="annotated-cell-10" data-target-annotation="2">2</dt>
<dd>
<span data-code-cell="annotated-cell-10" data-code-lines="8" data-code-annotation="2">Ternary pattern: <code>condition and true_value or false_value</code>.</span>
</dd>
<dt data-target-cell="annotated-cell-10" data-target-annotation="3">3</dt>
<dd>
<span data-code-cell="annotated-cell-10" data-code-lines="12" data-code-annotation="3">Typst keywords (<code>none</code>, <code>auto</code>, <code>true</code>, <code>false</code>) passed through without quotes.</span>
</dd>
<dt data-target-cell="annotated-cell-10" data-target-annotation="4">4</dt>
<dd>
<span data-code-cell="annotated-cell-10" data-code-lines="15" data-code-annotation="4"><a href="https://www.lua.org/manual/5.4/manual.html#6.4.1">Lua pattern</a> matches Typst measurement values like <code>1em</code>, <code>2.5pt</code>, <code>100%</code>.</span>
</dd>
<dt data-target-cell="annotated-cell-10" data-target-annotation="5">5</dt>
<dd>
<span data-code-cell="annotated-cell-10" data-code-lines="18" data-code-annotation="5">Escape double quotes inside string values to prevent broken Typst syntax.</span>
</dd>
</dl>
</section>
<section id="usage-examples" class="level3" data-number="4.2">
<h3 data-number="4.2" class="anchored" data-anchor-id="usage-examples"><span class="header-section-number">4.2</span> Usage Examples</h3>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="annotated-cell-11" data-filename="lua" style="background: #f1f3f5;"><pre class="sourceCode lua cw-auto code-annotation-code code-with-copy code-annotated"><code class="sourceCode lua"><a class="code-annotation-anchor" data-target-cell="annotated-cell-11" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-11-1" class="code-annotation-target">typst_value<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">nil</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-11" data-target-annotation="2" onclick="event.preventDefault();">2</a><span id="annotated-cell-11-2" class="code-annotation-target">typst_value<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">true</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-11" data-target-annotation="3" onclick="event.preventDefault();">3</a><span id="annotated-cell-11-3" class="code-annotation-target">typst_value<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">42</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-11" data-target-annotation="4" onclick="event.preventDefault();">4</a><span id="annotated-cell-11-4" class="code-annotation-target">typst_value<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"hello"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-11" data-target-annotation="5" onclick="event.preventDefault();">5</a><span id="annotated-cell-11-5" class="code-annotation-target">typst_value<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"1em"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-11" data-target-annotation="6" onclick="event.preventDefault();">6</a><span id="annotated-cell-11-6" class="code-annotation-target">typst_value<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'none'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-11" data-target-annotation="7" onclick="event.preventDefault();">7</a><span id="annotated-cell-11-7" class="code-annotation-target">typst_value<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'Say "hi"'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-11" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-11" data-code-lines="1" data-code-annotation="1">Returns: <code>none</code>.</span>
</dd>
<dt data-target-cell="annotated-cell-11" data-target-annotation="2">2</dt>
<dd>
<span data-code-cell="annotated-cell-11" data-code-lines="2" data-code-annotation="2">Returns: <code>true</code>.</span>
</dd>
<dt data-target-cell="annotated-cell-11" data-target-annotation="3">3</dt>
<dd>
<span data-code-cell="annotated-cell-11" data-code-lines="3" data-code-annotation="3">Returns: <code>42</code>.</span>
</dd>
<dt data-target-cell="annotated-cell-11" data-target-annotation="4">4</dt>
<dd>
<span data-code-cell="annotated-cell-11" data-code-lines="4" data-code-annotation="4">Returns: <code>"hello"</code>.</span>
</dd>
<dt data-target-cell="annotated-cell-11" data-target-annotation="5">5</dt>
<dd>
<span data-code-cell="annotated-cell-11" data-code-lines="5" data-code-annotation="5">Returns: <code>1em</code> (Typst length unit, not quoted).</span>
</dd>
<dt data-target-cell="annotated-cell-11" data-target-annotation="6">6</dt>
<dd>
<span data-code-cell="annotated-cell-11" data-code-lines="6" data-code-annotation="6">Returns: <code>none</code> (Typst keyword, not quoted).</span>
</dd>
<dt data-target-cell="annotated-cell-11" data-target-annotation="7">7</dt>
<dd>
<span data-code-cell="annotated-cell-11" data-code-lines="7" data-code-annotation="7">Returns: <code>"Say \"hi\""</code> (escaped quotes).</span>
</dd>
</dl>
</section>
<section id="building-attribute-strings" class="level3" data-number="4.3">
<h3 data-number="4.3" class="anchored" data-anchor-id="building-attribute-strings"><span class="header-section-number">4.3</span> Building Attribute Strings</h3>
<p>When generating Typst function calls, attributes need proper formatting. <code>build_attribute_string(...)</code> in <code>typst-utils.lua</code> centralises this so handlers never duplicate the logic.</p>
<div class="code-with-filename">
<div class="code-with-filename-file">
<pre><strong>_extensions/my-extension/_modules/typst-utils.lua (attribute string excerpt)</strong></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="annotated-cell-12" data-filename="_extensions/my-extension/_modules/typst-utils.lua (attribute string excerpt)" style="background: #f1f3f5;"><pre class="sourceCode lua code-annotation-code code-with-copy code-annotated"><code class="sourceCode lua"><span id="annotated-cell-12-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">--- Build attribute string for Typst function calls</span></span>
<span id="annotated-cell-12-2"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">M</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>build_attribute_string<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">attrs</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-12-3">  <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">local</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">parts</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{}</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-12" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-12-4" class="code-annotation-target">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">key</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">value</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">pairs</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">attrs</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">do</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-12" data-target-annotation="2" onclick="event.preventDefault();">2</a><span id="annotated-cell-12-5" class="code-annotation-target">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">local</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">typst_key</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">key</span></span>
<span id="annotated-cell-12-6">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">local</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">typst_val</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">M</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>typst_value<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">value</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-12-7">    <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">table.insert</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">parts</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">string.format</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'%s: %s'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">typst_key</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">typst_val</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">))</span></span>
<span id="annotated-cell-12-8">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-12" data-target-annotation="3" onclick="event.preventDefault();">3</a><span id="annotated-cell-12-9" class="code-annotation-target">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">table.concat</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">parts</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">', '</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-12-10"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end</span></span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
</div>
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-12" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-12" data-code-lines="4" data-code-annotation="1">Iterate over all attribute key-value pairs.</span>
</dd>
<dt data-target-cell="annotated-cell-12" data-target-annotation="2">2</dt>
<dd>
<span data-code-cell="annotated-cell-12" data-code-lines="5" data-code-annotation="2">Attribute keys are passed through unchanged; rename here if your Typst function uses a different parameter name.</span>
</dd>
<dt data-target-cell="annotated-cell-12" data-target-annotation="3">3</dt>
<dd>
<span data-code-cell="annotated-cell-12" data-code-lines="9" data-code-annotation="3">Join with comma separator for Typst named arguments.</span>
</dd>
</dl>
</section>
</section>
<section id="wcag-compliance-for-badges" class="level2" data-number="5">
<h2 data-number="5" class="anchored" data-anchor-id="wcag-compliance-for-badges"><span class="header-section-number">5</span> WCAG Compliance for Badges</h2>
<p>Production-quality badges need accessible colour contrast. The WCAG AA standard requires 4.5:1 contrast ratio for small text.</p>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
Note
</div>
</div>
<div class="callout-body-container callout-body">
<p>The PDF document generated in this tutorial is not a fully accessible PDF because of the use of a simplified template.</p>
</div>
</div>
<section id="colour-calculation" class="level3" data-number="5.1">
<h3 data-number="5.1" class="anchored" data-anchor-id="colour-calculation"><span class="header-section-number">5.1</span> Colour Calculation</h3>
<p><code>get-badge-colours</code> calls two helper functions defined earlier in <code>components.typ</code>. <code>get-accent-colour</code> maps semantic names (<code>"success"</code>, <code>"warning"</code>, <code>"danger"</code>, <code>"info"</code>) to a fixed palette of Quarto callout colours. <code>ensure-contrast</code> iteratively lightens or darkens a foreground colour until the perceived contrast against the background is sufficient. Both helpers are in the full <code>components.typ</code> file from the zip.</p>
<div class="code-with-filename">
<div class="code-with-filename-file">
<pre><strong>_extensions/my-extension/components.typ (badges excerpt)</strong></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="annotated-cell-13" data-filename="_extensions/my-extension/components.typ (badges excerpt)" style="background: #f1f3f5;"><pre class="sourceCode typst code-annotation-code code-with-copy code-annotated"><code class="sourceCode typst"><span id="annotated-cell-13-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// Compute badge colours with dark-mode support</span></span>
<span id="annotated-cell-13-2"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">get-badge-colours</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>colour<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> colours<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="annotated-cell-13-3">  <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">fg-components</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> colours<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>foreground<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>components<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">()</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-13" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-13-4" class="code-annotation-target">  <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">is-dark-mode</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> fg-components<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>at<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> default<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0%</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">50%</span></span>
<span id="annotated-cell-13-5"></span>
<span id="annotated-cell-13-6">  <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">base</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> colour <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"success"</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span> get-accent-colour<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"tip"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="annotated-cell-13-7">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> colour <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"warning"</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span> get-accent-colour<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"warning"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="annotated-cell-13-8">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> colour <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"danger"</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span> get-accent-colour<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"important"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="annotated-cell-13-9">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> colour <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"info"</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span> get-accent-colour<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"note"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-13" data-target-annotation="2" onclick="event.preventDefault();">2</a><span id="annotated-cell-13-10" class="code-annotation-target">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span> colours<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>muted <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="annotated-cell-13-11"></span>
<span id="annotated-cell-13-12">  <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">background</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> is-dark-mode <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span> base<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>darken<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">60%</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-13" data-target-annotation="3" onclick="event.preventDefault();">3</a><span id="annotated-cell-13-13" class="code-annotation-target">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span> base<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>lighten<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">80%</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="annotated-cell-13-14"></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-13" data-target-annotation="4" onclick="event.preventDefault();">4</a><span id="annotated-cell-13-15" class="code-annotation-target">  <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">text-colour</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ensure-contrast<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>base<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> background<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> min-ratio<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">4.5</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-13-16"></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-13" data-target-annotation="5" onclick="event.preventDefault();">5</a><span id="annotated-cell-13-17" class="code-annotation-target">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>background: background<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> border: base<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> text: text-colour<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-13-18"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
</div>
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-13" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-13" data-code-lines="4" data-code-annotation="1">Detect dark mode: light foreground (<code>&gt; 50%</code> lightness) means the page background is dark.</span>
</dd>
<dt data-target-cell="annotated-cell-13" data-target-annotation="2">2</dt>
<dd>
<span data-code-cell="annotated-cell-13" data-code-lines="10" data-code-annotation="2">Map semantic colour names to Quarto’s <a href="https://quarto.org/docs/authoring/callouts.html">callout theme colours</a>; fall back to <code>muted</code> for unknown names.</span>
</dd>
<dt data-target-cell="annotated-cell-13" data-target-annotation="3">3</dt>
<dd>
<span data-code-cell="annotated-cell-13" data-code-lines="13" data-code-annotation="3">Darken base colour for dark mode backgrounds; lighten for light mode.</span>
</dd>
<dt data-target-cell="annotated-cell-13" data-target-annotation="4">4</dt>
<dd>
<span data-code-cell="annotated-cell-13" data-code-lines="15" data-code-annotation="4">Adjust text colour iteratively until the contrast ratio meets the 4.5:1 WCAG AA threshold.</span>
</dd>
<dt data-target-cell="annotated-cell-13" data-target-annotation="5">5</dt>
<dd>
<span data-code-cell="annotated-cell-13" data-code-lines="17" data-code-annotation="5">Return a <a href="https://typst.app/docs/reference/foundations/dictionary/">dictionary</a> with background, border, and text colours.</span>
</dd>
</dl>
</section>
<section id="integrating-get-badge-colours-in-the-component" class="level3" data-number="5.2">
<h3 data-number="5.2" class="anchored" data-anchor-id="integrating-get-badge-colours-in-the-component"><span class="header-section-number">5.2</span> Integrating <code>get-badge-colours</code> in the Component</h3>
<p>Use <code>get-badge-colours</code> inside the badge component, then inject runtime colours from the wrapper.</p>
<p>Focus on the handoff: <code>my-badge</code> resolves runtime theme colours once, and <code>simple-badge</code> consumes the resulting dictionary.</p>
<div class="code-with-filename">
<div class="code-with-filename-file">
<pre><strong>_extensions/my-extension/typst-show.typ (badge wrapper excerpt)</strong></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="annotated-cell-14" data-filename="_extensions/my-extension/typst-show.typ (badge wrapper excerpt)" style="background: #f1f3f5;"><pre class="sourceCode typst code-annotation-code code-with-copy code-annotated"><code class="sourceCode typst"><span id="annotated-cell-14-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// Badge wrapper (WCAG-aware)</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-14" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-14-2" class="code-annotation-target"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">my-badge</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> colour<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"neutral"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="annotated-cell-14-3">  simple-badge<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-14-4">    content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-14-5">    colour<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> colour<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-14" data-target-annotation="2" onclick="event.preventDefault();">2</a><span id="annotated-cell-14-6" class="code-annotation-target">    colours<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> get-colours<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>mode<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> effective-brand-mode<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-14-7">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-14-8"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
</div>
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-14" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-14" data-code-lines="2" data-code-annotation="1"><code>my-badge</code> is the public function Lua calls; it resolves the colour scheme once and passes it into <code>simple-badge</code>.</span>
</dd>
<dt data-target-cell="annotated-cell-14" data-target-annotation="2">2</dt>
<dd>
<span data-code-cell="annotated-cell-14" data-code-lines="6" data-code-annotation="2"><code>effective-brand-mode</code> is the Pandoc template variable introduced in Part 1 that Quarto resolves at render time; <code>get-colours(mode: ...)</code> returns the full colour dictionary, and <code>simple-badge</code> delegates palette computation to <code>get-badge-colours</code>.</span>
</dd>
</dl>
<div class="highlight">
<p><strong>WCAG AA compliance</strong> requires 4.5:1 contrast ratio for small text (below 14pt bold or 18pt regular). Badge text at 0.85em typically falls into this category.</p>
</div>
</section>
</section>
<section id="shortcodes-an-alternative-approach" class="level2" data-number="6">
<h2 data-number="6" class="anchored" data-anchor-id="shortcodes-an-alternative-approach"><span class="header-section-number">6</span> Shortcodes: An Alternative Approach</h2>
<p>For components without a natural markdown representation, shortcodes provide explicit syntax.</p>
<section id="when-to-use-shortcodes" class="level3" data-number="6.1">
<h3 data-number="6.1" class="anchored" data-anchor-id="when-to-use-shortcodes"><span class="header-section-number">6.1</span> When to Use Shortcodes</h3>
<p>Shortcodes work well for:</p>
<ul>
<li>Components with many required parameters.</li>
<li>Elements that do not map naturally to divs or spans.</li>
<li>Functionality that needs explicit invocation.</li>
</ul>
</section>
<section id="basic-shortcode-structure" class="level3" data-number="6.2">
<h3 data-number="6.2" class="anchored" data-anchor-id="basic-shortcode-structure"><span class="header-section-number">6.2</span> Basic Shortcode Structure</h3>
<p>Focus on the returned raw Typst call: shortcode code should stay thin and delegate visual logic to shared Typst wrappers.</p>
<div class="code-with-filename">
<div class="code-with-filename-file">
<pre><strong>_extensions/my-extension/shortcodes.lua</strong></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="annotated-cell-15" data-filename="_extensions/my-extension/shortcodes.lua" style="background: #f1f3f5;"><pre class="sourceCode lua code-annotation-code code-with-copy code-annotated"><code class="sourceCode lua"><span id="annotated-cell-15-1"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-15" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-15-2" class="code-annotation-target">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'status-badge'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">args</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">kwargs</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">meta</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-15-3">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">not</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">quarto</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">doc</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>is_format<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'typst'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">then</span></span>
<span id="annotated-cell-15-4">      <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">pandoc</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>Null<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">()</span></span>
<span id="annotated-cell-15-5">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end</span></span>
<span id="annotated-cell-15-6"></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-15" data-target-annotation="2" onclick="event.preventDefault();">2</a><span id="annotated-cell-15-7" class="code-annotation-target">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">local</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">label</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">pandoc</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">utils</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>stringify<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">kwargs</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'label'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">or</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'Status'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-15-8">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">local</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">style</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">pandoc</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">utils</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>stringify<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">kwargs</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'style'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">or</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'info'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-15-9"></span>
<span id="annotated-cell-15-10">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">local</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">typst_code</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">string.format</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-15" data-target-annotation="3" onclick="event.preventDefault();">3</a><span id="annotated-cell-15-11" class="code-annotation-target">      <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'#my-badge(colour: "%s")[%s]'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-15-12">      <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">style</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-15-13">      <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">label</span></span>
<span id="annotated-cell-15-14">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-15-15"></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-15" data-target-annotation="4" onclick="event.preventDefault();">4</a><span id="annotated-cell-15-16" class="code-annotation-target">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">pandoc</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>RawInline<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'typst'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">typst_code</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-15-17">  <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-15-18"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
</div>
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-15" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-15" data-code-lines="2" data-code-annotation="1">Shortcode name maps directly to the handler function; <code>args</code> are positional values, <code>kwargs</code> are named.</span>
</dd>
<dt data-target-cell="annotated-cell-15" data-target-annotation="2">2</dt>
<dd>
<span data-code-cell="annotated-cell-15" data-code-lines="7,8" data-code-annotation="2">Always stringify metadata values to handle Pandoc’s <code>MetaInlines</code> type; the <code>or</code> default is a plain Lua string, so <code>stringify</code> on it is harmless.</span>
</dd>
<dt data-target-cell="annotated-cell-15" data-target-annotation="3">3</dt>
<dd>
<span data-code-cell="annotated-cell-15" data-code-lines="11" data-code-annotation="3">The shortcode’s <code>style</code> attribute maps to <code>my-badge</code>’s <code>colour</code> parameter; both name the same semantic concept but follow different naming conventions (HTML attribute names versus Typst parameter names).</span>
</dd>
<dt data-target-cell="annotated-cell-15" data-target-annotation="4">4</dt>
<dd>
<span data-code-cell="annotated-cell-15" data-code-lines="16" data-code-annotation="4">Return <code>RawInline</code> for inline output; use <code>RawBlock</code> for block-level shortcodes.</span>
</dd>
</dl>
</section>
<section id="usage" class="level3" data-number="6.3">
<h3 data-number="6.3" class="anchored" data-anchor-id="usage"><span class="header-section-number">6.3</span> Usage</h3>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb2" data-filename="markdown" style="background: #f1f3f5;"><pre class="sourceCode markdown cw-auto code-with-copy"><code class="sourceCode markdown"><span id="cb2-1">{{&lt; status-badge label="Hello" style="info" &gt;}}</span></code></pre></div></div>
</section>
</section>
<section id="complete-example-quote-card" class="level2" data-number="7">
<h2 data-number="7" class="anchored" data-anchor-id="complete-example-quote-card"><span class="header-section-number">7</span> Complete Example: Quote Card</h2>
<p>Let us build a complete quote card component from scratch, demonstrating all patterns covered.</p>
<section id="step-1-design-the-markdown-syntax" class="level3" data-number="7.1">
<h3 data-number="7.1" class="anchored" data-anchor-id="step-1-design-the-markdown-syntax"><span class="header-section-number">7.1</span> Step 1: Design the Markdown Syntax</h3>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb3" data-filename="markdown" style="background: #f1f3f5;"><pre class="sourceCode markdown cw-auto code-with-copy"><code class="sourceCode markdown"><span id="cb3-1">::: {.quote-card author="Alan Kay" source="1971"}</span>
<span id="cb3-2">The best way to predict the future is to invent it.</span>
<span id="cb3-3">:::</span></code></pre></div></div>
<p>Alternatively, support heading-based author attribution:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb4" data-filename="markdown" style="background: #f1f3f5;"><pre class="sourceCode markdown cw-auto code-with-copy"><code class="sourceCode markdown"><span id="cb4-1">::: {.quote-card source="1971"}</span>
<span id="cb4-2">The best way to predict the future is to invent it.</span>
<span id="cb4-3"></span>
<span id="cb4-4"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">## Alan Kay</span></span>
<span id="cb4-5"></span>
<span id="cb4-6">:::</span></code></pre></div></div>
</section>
<section id="step-2-create-the-typst-rendering-function" class="level3" data-number="7.2">
<h3 data-number="7.2" class="anchored" data-anchor-id="step-2-create-the-typst-rendering-function"><span class="header-section-number">7.2</span> Step 2: Create the Typst Rendering Function</h3>
<p>Focus on <code>render-quote-card(...)</code> and its conditional attribution block; the badge and panel functions already exist from earlier sections.</p>
<div class="code-with-filename">
<div class="code-with-filename-file">
<pre><strong>_extensions/my-extension/components.typ (quote-card excerpt)</strong></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb5" data-filename="_extensions/my-extension/components.typ (quote-card excerpt)" style="background: #f1f3f5;"><pre class="sourceCode typst code-with-copy"><code class="sourceCode typst"><span id="cb5-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">QUOTE-CARD-RADIUS</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">8pt</span></span>
<span id="cb5-2"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">QUOTE-CARD-INSET</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.5em</span></span>
<span id="cb5-3"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">QUOTE-MARK-SIZE</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">3em</span></span>
<span id="cb5-4"></span>
<span id="cb5-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// Render a styled quote card</span></span>
<span id="cb5-6"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">render-quote-card</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb5-7">  content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb5-8">  author<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">none</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb5-9">  source<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">none</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb5-10">  colours<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>:<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb5-11"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb5-12">  <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">bg</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> colours<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>at<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"background"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> default<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> luma<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">250</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">))</span></span>
<span id="cb5-13">  <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">fg</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> colours<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>at<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"foreground"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> default<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> luma<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">50</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">))</span></span>
<span id="cb5-14">  <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">muted</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> colours<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>at<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"muted"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> default<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> luma<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">128</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">))</span></span>
<span id="cb5-15">  <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">accent</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> get-accent-colour<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"note"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb5-16"></span>
<span id="cb5-17">  block<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb5-18">    width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">100%</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb5-19">    fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> bg<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb5-20">    stroke<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>left: <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">4pt</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> accent<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">--</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span></span>
<span id="cb5-21">    radius<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>right: QUOTE-CARD-RADIUS<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">--</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span></span>
<span id="cb5-22">    inset<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> QUOTE-CARD-INSET<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb5-23">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb5-24">      <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// Opening quote mark</span></span>
<span id="cb5-25">      place<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">--</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span></span>
<span id="cb5-26">        top <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> left<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb5-27">        dx<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5em</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb5-28">        dy<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.3em</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb5-29">        text<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> QUOTE-MARK-SIZE<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> accent<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>lighten<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">60%</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span> sym<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>quote<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>l<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>double<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb5-30">      <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb5-31"></span>
<span id="cb5-32">      <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// Quote content</span></span>
<span id="cb5-33">      pad<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>left<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1em</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> right<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1em</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)[</span></span>
<span id="cb5-34">        <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>text<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>style<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"italic"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> fg<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> -- &lt;4&gt;</span>
<span id="cb5-35">      <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span></span>
<span id="cb5-36"></span>
<span id="cb5-37">      <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// Attribution line</span></span>
<span id="cb5-38">      <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> author <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">!=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">none</span> or source <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">!=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">none</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">--</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span></span>
<span id="cb5-39">        v<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.8em</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb5-40">        align<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>right<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)[</span></span>
<span id="cb5-41">          <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>text<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> muted<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)[</span></span>
<span id="cb5-42">            <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>sym<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>dash<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>em</span>
<span id="cb5-43">            <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> author <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">!=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">none</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>author<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb5-44">            <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> source <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">!=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">none</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span>, <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>emph<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>source<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)]</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb5-45">          <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span></span>
<span id="cb5-46">        <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span></span>
<span id="cb5-47">      <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb5-48">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">},</span></span>
<span id="cb5-49">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb5-50"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span></code></pre></div></div>
</div>
<ol type="1">
<li><a href="https://typst.app/docs/reference/visualize/stroke/">Directional stroke</a>: only the left edge has a border.</li>
<li><a href="https://typst.app/docs/reference/layout/block/#parameters-radius">Directional radius</a>: rounds only the right corners.</li>
<li><a href="https://typst.app/docs/reference/layout/place/"><code>place</code></a> positions the opening quote mark absolutely within the container.</li>
<li>Quote content rendered in italic with the foreground colour.</li>
<li>Attribution line conditionally displayed when author or source is provided.</li>
</ol>
</section>
<section id="step-3-create-the-wrapper-function" class="level3" data-number="7.3">
<h3 data-number="7.3" class="anchored" data-anchor-id="step-3-create-the-wrapper-function"><span class="header-section-number">7.3</span> Step 3: Create the Wrapper Function</h3>
<p>Focus on argument forwarding (<code>..args</code>) and runtime colour injection; this keeps Lua extraction and Typst rendering concerns cleanly separated.</p>
<div class="code-with-filename">
<div class="code-with-filename-file">
<pre><strong>_extensions/my-extension/typst-show.typ (quote-card wrapper excerpt)</strong></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="annotated-cell-20" data-filename="_extensions/my-extension/typst-show.typ (quote-card wrapper excerpt)" style="background: #f1f3f5;"><pre class="sourceCode typst code-annotation-code code-with-copy code-annotated"><code class="sourceCode typst"><span id="annotated-cell-20-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// Quote card wrapper</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-20" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-20-2" class="code-annotation-target"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">quote-card</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> ..args<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="annotated-cell-20-3">  render-quote-card<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-20-4">    content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-20" data-target-annotation="2" onclick="event.preventDefault();">2</a><span id="annotated-cell-20-5" class="code-annotation-target">    colours<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> get-colours<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>mode<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> effective-brand-mode<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-20" data-target-annotation="3" onclick="event.preventDefault();">3</a><span id="annotated-cell-20-6" class="code-annotation-target">    ..args<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-20-7">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-20-8"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
</div>
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-20" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-20" data-code-lines="2" data-code-annotation="1">Wrapper captures all extra named arguments via <a href="https://typst.app/docs/reference/foundations/arguments/">sink parameter</a> <code>..args</code>.</span>
</dd>
<dt data-target-cell="annotated-cell-20" data-target-annotation="2">2</dt>
<dd>
<span data-code-cell="annotated-cell-20" data-code-lines="5" data-code-annotation="2">Injects the runtime colour scheme; Lua cannot know this at parse time.</span>
</dd>
<dt data-target-cell="annotated-cell-20" data-target-annotation="3">3</dt>
<dd>
<span data-code-cell="annotated-cell-20" data-code-lines="6" data-code-annotation="3"><a href="https://typst.app/docs/reference/foundations/arguments/#spreading">Spread operator</a> forwards <code>author</code>, <code>source</code>, and any other arguments to the rendering function.</span>
</dd>
</dl>
</section>
<section id="step-4-create-the-lua-handler" class="level3" data-number="7.4">
<h3 data-number="7.4" class="anchored" data-anchor-id="step-4-create-the-lua-handler"><span class="header-section-number">7.4</span> Step 4: Create the Lua Handler</h3>
<p>Focus on the last-header extraction branch and the shared wrapper reuse, which avoids reimplementing block wrapper assembly.</p>
<p>This handler reuses the same wrapped content pattern from Part 1’s panel, with one twist: instead of extracting the <em>first</em> heading as a title, it looks for a heading at the <em>end</em> of the content as an author attribution line. This kind of component-specific extraction logic is why some components need a custom handler rather than a factory-generated one.</p>
<div class="code-with-filename">
<div class="code-with-filename-file">
<pre><strong>_extensions/my-extension/_modules/quote-card.lua</strong></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="annotated-cell-21" data-filename="_extensions/my-extension/_modules/quote-card.lua" style="background: #f1f3f5;"><pre class="sourceCode lua code-annotation-code code-with-copy code-annotated"><code class="sourceCode lua"><a class="code-annotation-anchor" data-target-cell="annotated-cell-21" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-21-1" class="code-annotation-target"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">local</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">wrapper</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">require</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">quarto</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">utils</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>resolve_path<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"_modules/wrapper.lua"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">):</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">gsub</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'%.lua$'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">''</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">))</span></span>
<span id="annotated-cell-21-2"></span>
<span id="annotated-cell-21-3"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">local</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">M</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{}</span></span>
<span id="annotated-cell-21-4"></span>
<span id="annotated-cell-21-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">--- Process quote card: extract author from last heading or attributes</span></span>
<span id="annotated-cell-21-6"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">M</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>process_div<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">div</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">config</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-21" data-target-annotation="2" onclick="event.preventDefault();">2</a><span id="annotated-cell-21-7" class="code-annotation-target">  <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">local</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">attrs</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">wrapper</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>attributes_to_table<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">div</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-21-8"></span>
<span id="annotated-cell-21-9">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">not</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">attrs</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'author'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">and</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">div</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">content</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">then</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-21" data-target-annotation="3" onclick="event.preventDefault();">3</a><span id="annotated-cell-21-10" class="code-annotation-target">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">local</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">last_elem</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">div</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">content</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[#</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">div</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">content</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span></span>
<span id="annotated-cell-21-11">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">last_elem</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">t</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'Header'</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">then</span></span>
<span id="annotated-cell-21-12">      <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">attrs</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'author'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">pandoc</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">utils</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>stringify<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">last_elem</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">content</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-21-13">      <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">local</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">new_content</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{}</span></span>
<span id="annotated-cell-21-14">      <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">i</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">div</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">content</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">do</span></span>
<span id="annotated-cell-21-15">        <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">table.insert</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">new_content</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">div</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">content</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">i</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">])</span></span>
<span id="annotated-cell-21-16">      <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end</span></span>
<span id="annotated-cell-21-17">      <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">div</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">content</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">new_content</span></span>
<span id="annotated-cell-21-18">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end</span></span>
<span id="annotated-cell-21-19">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end</span></span>
<span id="annotated-cell-21-20"></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-21" data-target-annotation="4" onclick="event.preventDefault();">4</a><span id="annotated-cell-21-21" class="code-annotation-target">  <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">local</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">opening</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">closing</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">wrapper</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>build_typst_block_wrappers<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">config</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">attrs</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-21-22">  <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">local</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">result</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">pandoc</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>RawBlock<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'typst'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">opening</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="annotated-cell-21-23">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">_</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">item</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">ipairs</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">div</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">content</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">do</span></span>
<span id="annotated-cell-21-24">    <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">table.insert</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">result</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">item</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-21-25">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end</span></span>
<span id="annotated-cell-21-26">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">table.insert</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">result</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">pandoc</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>RawBlock<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'typst'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">closing</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">))</span></span>
<span id="annotated-cell-21-27">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">result</span></span>
<span id="annotated-cell-21-28"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end</span></span>
<span id="annotated-cell-21-29"></span>
<span id="annotated-cell-21-30"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">M</span></span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
</div>
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-21" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-21" data-code-lines="1" data-code-annotation="1">Load the shared wrapper module using Quarto’s path resolution from the extension root.</span>
</dd>
<dt data-target-cell="annotated-cell-21" data-target-annotation="2">2</dt>
<dd>
<span data-code-cell="annotated-cell-21" data-code-lines="7" data-code-annotation="2">Pandoc’s <code>div.attributes</code> is a metadata map, not a plain Lua table; this conversion produces a standard key-value table that can be iterated and passed to <code>build_attribute_string</code>.</span>
</dd>
<dt data-target-cell="annotated-cell-21" data-target-annotation="3">3</dt>
<dd>
<span data-code-cell="annotated-cell-21" data-code-lines="10" data-code-annotation="3">Check the <em>last</em> element, unlike panel’s first-heading extraction; this supports the <code>## Author Name</code> syntax at the end of the div.</span>
</dd>
<dt data-target-cell="annotated-cell-21" data-target-annotation="4">4</dt>
<dd>
<span data-code-cell="annotated-cell-21" data-code-lines="21" data-code-annotation="4">Delegates to the shared wrapper module; the opening/closing pattern is identical to Part 1’s panels.</span>
</dd>
</dl>
</section>
<section id="step-5-register-the-component" class="level3" data-number="7.5">
<h3 data-number="7.5" class="anchored" data-anchor-id="step-5-register-the-component"><span class="header-section-number">7.5</span> Step 5: Register the Component</h3>
<p>Add the <code>quote-card</code> entry to <code>get_builtin_mappings()</code> in <code>config.lua</code>:</p>
<div class="code-with-filename">
<div class="code-with-filename-file">
<pre><strong>_extensions/my-extension/_modules/config.lua (registration excerpt)</strong></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="annotated-cell-22" data-filename="_extensions/my-extension/_modules/config.lua (registration excerpt)" style="background: #f1f3f5;"><pre class="sourceCode lua code-annotation-code code-with-copy code-annotated"><code class="sourceCode lua"><span id="annotated-cell-22-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">M</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>get_builtin_mappings<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">()</span></span>
<span id="annotated-cell-22-2">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="annotated-cell-22-3">    <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">div</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="annotated-cell-22-4">      <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'highlight'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">wrapper</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'my-highlight'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">arguments</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">false</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">},</span></span>
<span id="annotated-cell-22-5">      <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'panel'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">wrapper</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'my-panel'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">arguments</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">true</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">},</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-22" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-22-6" class="code-annotation-target">      <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'quote-card'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">wrapper</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'quote-card'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">arguments</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">true</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">},</span></span>
<span id="annotated-cell-22-7">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">},</span></span>
<span id="annotated-cell-22-8">    <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">span</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="annotated-cell-22-9">      <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'badge'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">wrapper</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'my-badge'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">arguments</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">true</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">},</span></span>
<span id="annotated-cell-22-10">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">},</span></span>
<span id="annotated-cell-22-11">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="annotated-cell-22-12"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end</span></span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
</div>
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-22" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-22" data-code-lines="6" data-code-annotation="1"><code>arguments = true</code> ensures the parameter list is always generated, which is needed because <code>quote-card</code> always passes at least <code>colours:</code> via the wrapper function.</span>
</dd>
</dl>
<p>Then register the custom handler in <code>typst-markdown.lua</code>:</p>
<div class="code-with-filename">
<div class="code-with-filename-file">
<pre><strong>_extensions/my-extension/typst-markdown.lua (handler registration excerpt)</strong></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="annotated-cell-23" data-filename="_extensions/my-extension/typst-markdown.lua (handler registration excerpt)" style="background: #f1f3f5;"><pre class="sourceCode lua code-annotation-code code-with-copy code-annotated"><code class="sourceCode lua"><span id="annotated-cell-23-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">local</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">DIV_HANDLERS</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-23" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-23-2" class="code-annotation-target">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'quote-card'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">quote_card</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">process_div</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-23-3"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
</div>
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-23" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-23" data-code-lines="2" data-code-annotation="1"><code>DIV_HANDLERS</code> maps class names to custom handler functions; the quote card goes here because it has component-specific extraction logic (last-heading author) that a factory cannot express.</span>
</dd>
</dl>
</section>
</section>
<section id="advanced-topics" class="level2" data-number="8">
<h2 data-number="8" class="anchored" data-anchor-id="advanced-topics"><span class="header-section-number">8</span> Advanced Topics</h2>
<section id="checking-output-format" class="level3" data-number="8.1">
<h3 data-number="8.1" class="anchored" data-anchor-id="checking-output-format"><span class="header-section-number">8.1</span> Checking Output Format</h3>
<p>Filters should only run for Typst output; without this guard, transformation logic leaks into HTML, DOCX, and other targets.</p>
<div class="code-with-filename">
<div class="code-with-filename-file">
<pre><strong>_extensions/my-extension/typst-markdown.lua (format guard excerpt)</strong></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="annotated-cell-24" data-filename="_extensions/my-extension/typst-markdown.lua (format guard excerpt)" style="background: #f1f3f5;"><pre class="sourceCode lua code-annotation-code code-with-copy code-annotated"><code class="sourceCode lua"><span id="annotated-cell-24-1"><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">Div</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">div</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-24" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-24-2" class="code-annotation-target">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">not</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">quarto</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">doc</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>is_format<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'typst'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">then</span></span>
<span id="annotated-cell-24-3">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">div</span></span>
<span id="annotated-cell-24-4">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end</span></span>
<span id="annotated-cell-24-5">  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-- ... transformation logic</span></span>
<span id="annotated-cell-24-6"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
</div>
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-24" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-24" data-code-lines="2" data-code-annotation="1"><a href="https://quarto.org/docs/extensions/lua-api.html#quarto.doc.is_format"><code>quarto.doc.is_format()</code></a> checks the current render target; returning the element unchanged lets Pandoc handle it normally for non-Typst outputs.</span>
</dd>
</dl>
</section>
<section id="module-loading" class="level3" data-number="8.2">
<h3 data-number="8.2" class="anchored" data-anchor-id="module-loading"><span class="header-section-number">8.2</span> Module Loading</h3>
<p>Load shared modules using Quarto’s path resolution.</p>
<p>The <code>require_local(...)</code> helper keeps imports consistent and reduces repeated path-normalisation boilerplate.</p>
<div class="code-with-filename">
<div class="code-with-filename-file">
<pre><strong>_extensions/my-extension/typst-markdown.lua (module loading excerpt)</strong></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="annotated-cell-25" data-filename="_extensions/my-extension/typst-markdown.lua (module loading excerpt)" style="background: #f1f3f5;"><pre class="sourceCode lua code-annotation-code code-with-copy code-annotated"><code class="sourceCode lua"><a class="code-annotation-anchor" data-target-cell="annotated-cell-25" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-25-1" class="code-annotation-target"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">local</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span> require_local<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">path</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-25-2">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">require</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">quarto</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">utils</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>resolve_path<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">path</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">):</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">gsub</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'%.lua$'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">''</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">))</span></span>
<span id="annotated-cell-25-3"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end</span></span>
<span id="annotated-cell-25-4"></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-25" data-target-annotation="2" onclick="event.preventDefault();">2</a><span id="annotated-cell-25-5" class="code-annotation-target"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">local</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">config</span>      <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> require_local<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"_modules/config.lua"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-25-6"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">local</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">quote_card</span>  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> require_local<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"_modules/quote-card.lua"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-25-7"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">local</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">wrapper</span>     <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> require_local<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"_modules/wrapper.lua"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-25-8"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">local</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">typst_utils</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> require_local<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"_modules/typst-utils.lua"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
</div>
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-25" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-25" data-code-lines="1" data-code-annotation="1"><code>require_local</code> wraps <a href="https://quarto.org/docs/extensions/lua-api.html#quarto.utils.resolve_path"><code>quarto.utils.resolve_path</code></a> to anchor paths at the extension root; <code>gsub</code> strips <code>.lua</code> because <code>require</code> resolves by module name, not file path.</span>
</dd>
<dt data-target-cell="annotated-cell-25" data-target-annotation="2">2</dt>
<dd>
<span data-code-cell="annotated-cell-25" data-code-lines="5" data-code-annotation="2">With a top-level <code>typst-markdown.lua</code>, all shared modules live under <code>_modules/</code>; adjust this prefix if your structure differs.</span>
</dd>
</dl>
</section>
<section id="filter-ordering" class="level3" data-number="8.3">
<h3 data-number="8.3" class="anchored" data-anchor-id="filter-ordering"><span class="header-section-number">8.3</span> Filter Ordering</h3>
<p>When using multiple filters, order matters. Declare filters in <code>_extension.yml</code>:</p>
<div class="code-with-filename">
<div class="code-with-filename-file">
<pre><strong>_extensions/my-extension/_extension.yml</strong></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="annotated-cell-26" data-filename="_extensions/my-extension/_extension.yml" style="background: #f1f3f5;"><pre class="sourceCode yaml code-annotation-code code-with-copy code-annotated"><code class="sourceCode yaml"><span id="annotated-cell-26-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">title</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> My Extension</span></span>
<span id="annotated-cell-26-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">version</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2.0.0</span></span>
<span id="annotated-cell-26-3"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">contributes</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="annotated-cell-26-4"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">  </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">formats</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="annotated-cell-26-5"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">typst</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="annotated-cell-26-6"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">      </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">template</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> template.typ</span></span>
<span id="annotated-cell-26-7"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">      </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">template-partials</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="annotated-cell-26-8"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">        </span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> typst-show.typ</span></span>
<span id="annotated-cell-26-9"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">        </span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> components.typ</span></span>
<span id="annotated-cell-26-10"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">      </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">filters</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-26" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-26-11" class="code-annotation-target"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">        </span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> typst-markdown.lua</span></span>
<span id="annotated-cell-26-12"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">  </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">shortcodes</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="annotated-cell-26-13"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> shortcodes.lua</span></span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
</div>
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-26" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-26" data-code-lines="11" data-code-annotation="1">A single filter entry; a Lua filter file can return a table containing multiple filter tables rather than one flat filter table. Here <code>typst-markdown.lua</code> returns <code>{ {Meta=...}, {Div=..., Span=...} }</code>, an array of two filter tables. Pandoc applies each inner table as a separate pass in order; both passes operate on the same document. The first pass runs the <code>Meta</code> handler, which reads configuration from document metadata and stores it in module-level variables. The second pass runs the element handlers (<code>Div</code>, <code>Span</code>), which can then read the already-loaded configuration.</span>
</dd>
</dl>
</section>
<section id="debugging-tips" class="level3" data-number="8.4">
<h3 data-number="8.4" class="anchored" data-anchor-id="debugging-tips"><span class="header-section-number">8.4</span> Debugging Tips</h3>
<p>Use <code>quarto.log.warning()</code> to inspect values during development:</p>
<div class="code-with-filename">
<div class="code-with-filename-file">
<pre><strong>(conceptual) debugging logs</strong></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="annotated-cell-27" data-filename="(conceptual) debugging logs" style="background: #f1f3f5;"><pre class="sourceCode lua code-annotation-code code-with-copy code-annotated"><code class="sourceCode lua"><a class="code-annotation-anchor" data-target-cell="annotated-cell-27" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-27-1" class="code-annotation-target"><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">quarto</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">log</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>warning<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'Processing div with classes: '</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">..</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">table.concat</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">div</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">classes</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">', '</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">))</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-27" data-target-annotation="2" onclick="event.preventDefault();">2</a><span id="annotated-cell-27-2" class="code-annotation-target"><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">quarto</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">log</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>warning<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'Attributes: '</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">..</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">pandoc</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">utils</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>stringify<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">pandoc</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>MetaMap<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">attrs</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)))</span></span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
</div>
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-27" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-27" data-code-lines="1" data-code-annotation="1"><a href="https://www.lua.org/manual/5.4/manual.html#pdf-table.concat"><code>table.concat</code></a> joins the class list into a readable string.</span>
</dd>
<dt data-target-cell="annotated-cell-27" data-target-annotation="2">2</dt>
<dd>
<span data-code-cell="annotated-cell-27" data-code-lines="2" data-code-annotation="2">Wrap the attributes table in <a href="https://pandoc.org/lua-filters.html#type-metamap"><code>pandoc.MetaMap</code></a> so <code>stringify</code> can serialise it.</span>
</dd>
</dl>
<p>Output appears in the Quarto render log.</p>
<div class="quarto-layout-panel" data-layout-ncol="2">
<div class="quarto-layout-row">
<div class="quarto-layout-cell" style="flex-basis: 50.0%;justify-content: flex-start;">
<div class="code-with-filename">
<div class="code-with-filename-file">
<pre><strong>example.qmd</strong></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb6" data-filename="example.qmd" style="background: #f1f3f5;"><pre class="sourceCode markdown code-with-copy"><code class="sourceCode markdown"><span id="cb6-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">---</span></span>
<span id="cb6-2"><span class="an" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">title:</span><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"> "Part 2 Extension Demo"</span></span>
<span id="cb6-3"><span class="an" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">format:</span></span>
<span id="cb6-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">  my-extension-typst:</span></span>
<span id="cb6-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    syntax-highlighting: idiomatic</span></span>
<span id="cb6-6"><span class="an" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">extensions:</span></span>
<span id="cb6-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">  typst-markdown:</span></span>
<span id="cb6-8"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    divs:</span></span>
<span id="cb6-9"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">      custom-highlight: my-custom-highlight</span></span>
<span id="cb6-10"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">---</span></span>
<span id="cb6-11"></span>
<span id="cb6-12"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">## Badges (WCAG-Aware)</span></span>
<span id="cb6-13"></span>
<span id="cb6-14">Inline badges with accessible colours:</span>
<span id="cb6-15"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">Completed</span><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span>{.badge colour="success"},</span>
<span id="cb6-16"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">Pending</span><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span>{.badge colour="warning"},</span>
<span id="cb6-17"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">Failed</span><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span>{.badge colour="danger"},</span>
<span id="cb6-18"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">Information</span><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span>{.badge colour="info"},</span>
<span id="cb6-19">and <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">Default</span><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span>{.badge}.</span>
<span id="cb6-20"></span>
<span id="cb6-21"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">## Highlight (Factory Handler, No Title)</span></span>
<span id="cb6-22"></span>
<span id="cb6-23">::: {.highlight}</span>
<span id="cb6-24">This highlight block uses the factory handler **without** title extraction.</span>
<span id="cb6-25">It simply wraps content in a styled container.</span>
<span id="cb6-26">:::</span>
<span id="cb6-27"></span>
<span id="cb6-28"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">## Panel (Factory Handler)</span></span>
<span id="cb6-29"></span>
<span id="cb6-30">::: {.panel style="info"}</span>
<span id="cb6-31"></span>
<span id="cb6-32"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;"># Status Summary</span></span>
<span id="cb6-33"></span>
<span id="cb6-34">This panel uses the **info** style.</span>
<span id="cb6-35">The first heading is extracted as the title via the factory handler.</span>
<span id="cb6-36"></span>
<span id="cb6-37">:::</span>
<span id="cb6-38"></span>
<span id="cb6-39"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">## Quote Card (Attribute-Based Author)</span></span>
<span id="cb6-40"></span>
<span id="cb6-41">::: {.quote-card author="Alan Kay" source="1971"}</span>
<span id="cb6-42">The best way to predict the future is to invent it.</span>
<span id="cb6-43">:::</span>
<span id="cb6-44"></span>
<span id="cb6-45"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">## Quote Card (Heading-Based Author)</span></span>
<span id="cb6-46"></span>
<span id="cb6-47">::: {.quote-card source="1971"}</span>
<span id="cb6-48">The best way to predict the future is to invent it.</span>
<span id="cb6-49"></span>
<span id="cb6-50"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">## Alan Kay</span></span>
<span id="cb6-51"></span>
<span id="cb6-52">:::</span>
<span id="cb6-53"></span>
<span id="cb6-54"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">## Shortcode</span></span>
<span id="cb6-55"></span>
<span id="cb6-56">A live status indicator: {{&lt; status-badge label="Live" style="success" &gt;}}.</span>
<span id="cb6-57"></span>
<span id="cb6-58"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">## User-Defined Component (Config System)</span></span>
<span id="cb6-59"></span>
<span id="cb6-60">::: {.custom-highlight}</span>
<span id="cb6-61">This block uses a **user-defined mapping** declared in the YAML front matter.</span>
<span id="cb6-62">The <span class="in" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">`custom-highlight`</span> class maps to the <span class="in" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">`my-custom-highlight`</span> Typst function via the configuration system.</span>
<span id="cb6-63">:::</span></code></pre></div></div>
</div>
</div>
<div class="quarto-layout-cell" style="flex-basis: 50.0%;justify-content: center;">
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="assets/example.png" class="lightbox" data-gallery="quarto-lightbox-gallery-2"><img src="https://mickael.canouil.fr/posts/2026-03-05-typst-template-tutorial-part2/assets/example.png" class="img-fluid img-thumbnail rounded-3 border-light quarto-figure quarto-figure-center figure-img" alt="PDF render showing seven sections. Badges (WCAG-Aware): five WCAG-compliant inline badges labelled Completed, Pending, Failed, Information, and Default. Highlight: a light green content block with text explaining the factory handler requires no title extraction. Panel (Factory Handler): a blue info panel titled Status Summary, where the title was extracted from the first heading. Two Quote Card sections: each displays the quote The best way to predict the future is to invent it in italic with a left blue accent border, attributed to Alan Kay, 1971 — one using an attribute-based author, one using a heading-based author. Shortcode: a green Live badge inline in a sentence. User-Defined Component: an orange highlight block demonstrating a user-defined class mapped to a custom Typst function via the configuration system."></a></p>
</figure>
</div>
</div>
</div>
</div>
</section>
</section>
<section id="conclusion" class="level2" data-number="9">
<h2 data-number="9" class="anchored" data-anchor-id="conclusion"><span class="header-section-number">9</span> Conclusion</h2>
<p>This tutorial covered advanced patterns for building production-quality Quarto Typst extensions:</p>
<ol type="1">
<li><strong>Handler factories</strong> reduce repetition by generating handlers with configured behaviour.</li>
<li><strong>Configuration systems</strong> enable user extensibility through metadata-driven mappings.</li>
<li><strong>Type-safe value conversion</strong> prevents syntax errors when passing values between Lua and Typst.</li>
<li><strong>WCAG compliance</strong> ensures accessible colour contrast in styled components.</li>
<li><strong>Shortcodes</strong> provide explicit syntax for components without natural markdown representation.</li>
</ol>
<p>You can download the complete code for this part of the tutorial as a zip file containing the extension with all components covered above.</p>
<div style="text-align: center;">
<p><a href="./tutorial-part2.zip" class="btn btn-outline-light btn-lg" target="_blank" rel="noopener noreferrer">Part 2 (ZIP)</a></p>
</div>
<div class="highlight">
<p>The separation of concerns between Lua (transformation) and Typst (rendering) enables flexibility, testability, and maintainability as your extension grows.</p>
</div>
<section id="further-resources" class="level3" data-number="9.1">
<h3 data-number="9.1" class="anchored" data-anchor-id="further-resources"><span class="header-section-number">9.1</span> Further Resources</h3>
<ul>
<li><a href="https://quarto.org/docs/extensions/creating.html">Quarto Documentation: Creating Extensions</a>.</li>
<li><a href="https://pandoc.org/lua-filters.html">Pandoc Lua Filters</a>.</li>
<li><a href="https://typst.app/docs/">Typst Documentation</a>.</li>
<li><a href="https://github.com/mcanouil/quarto-mcanouil">quarto-mcanouil Extension</a>.</li>
<li><a href="../../posts/2026-02-27-typst-template-tutorial-part1/">Part 1: The Lua-Typst Bridge</a>.</li>
</ul>


</section>
</section>

<a onclick="window.scrollTo(0, 0); return false;" id="quarto-back-to-top"><i class="bi bi-arrow-up"></i> Back to top</a><div id="quarto-appendix" class="default"><section class="quarto-appendix-contents" id="quarto-reuse"><h2 class="anchored quarto-appendix-heading">Reuse</h2><div class="quarto-appendix-contents"><div><a rel="license" href="https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en_gb">CC BY-NC-SA 4.0</a></div></div></section><section class="quarto-appendix-contents" id="quarto-citation"><h2 class="anchored quarto-appendix-heading">Citation</h2><div><div class="quarto-appendix-secondary-label">BibTeX citation:</div><pre class="sourceCode code-with-copy quarto-appendix-bibtex"><code class="sourceCode bibtex">@misc{canouil2026,
  author = {CANOUIL, Mickaël},
  title = {Building {Quarto} {Typst} {Templates:} {Advanced} {Patterns}
    {(Part} 2)},
  date = {2026-03-05},
  url = {https://mickael.canouil.fr/posts/2026-03-05-typst-template-tutorial-part2/},
  langid = {en-GB}
}
</code></pre><div class="quarto-appendix-secondary-label">For attribution, please cite this work as:</div><div id="ref-canouil2026" class="csl-entry quarto-appendix-citeas">
CANOUIL, M. (2026-03-05). Building Quarto Typst Templates: Advanced
Patterns (Part 2). <em>Mickael.canouil.fr</em>. <a href="https://mickael.canouil.fr/posts/2026-03-05-typst-template-tutorial-part2/">https://mickael.canouil.fr/posts/2026-03-05-typst-template-tutorial-part2/</a>
</div></div></section></div> ]]></description>
  <category>quarto</category>
  <category>extensions</category>
  <category>typst</category>
  <category>lua</category>
  <category>pdf</category>
  <guid>https://mickael.canouil.fr/posts/2026-03-05-typst-template-tutorial-part2/</guid>
  <pubDate>Thu, 05 Mar 2026 00:00:00 GMT</pubDate>
  <media:content url="https://mickael.canouil.fr/posts/2026-03-05-typst-template-tutorial-part2/featured.png" medium="image" type="image/png" height="76" width="144"/>
</item>
<item>
  <title>Building Quarto Typst Templates: The Lua-Typst Bridge (Part 1)</title>
  <link>https://mickael.canouil.fr/posts/2026-02-27-typst-template-tutorial-part1/</link>
  <description><![CDATA[ 

<!--
@license MIT
@copyright 2026 Mickaël Canouil
@author Mickaël Canouil
-->
Skip to main content





<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="featured.png" class="lightbox" data-gallery="quarto-lightbox-gallery-1"><img src="https://mickael.canouil.fr/posts/2026-02-27-typst-template-tutorial-part1/featured.png" class="img-featured img-fluid quarto-figure quarto-figure-center figure-img" alt="Feature image for &quot;Quarto + Typst Part 1&quot; blog post. Dark background
with the official Quarto logo (four blue quadrants with wordmark) and
Typst logo (teal wordmark) centered at the top. Below, &quot;Part 1&quot; in serif
font with subtitle &quot;Modern Scientific Publishing&quot;. File format pipeline
shows .qmd → .typ → .pdf. Decorative elements include floating code
snippets and gradient orbs in blue and teal.
" width="600"></a></p>
</figure>
</div>
<div class="callout callout-style-default callout-important callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Important</span>Quarto Version
</div>
</div>
<div class="callout-body-container callout-body">
<p>This tutorial was written and tested with <strong>Quarto CLI 1.9.23 (pre-release)</strong>. Some APIs, template syntax, or extension behaviours may differ in earlier stable releases.</p>
</div>
</div>
<div class="callout callout-style-default callout-tip callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Tip</span>TL;DR
</div>
</div>
<div class="callout-body-container callout-body">
<p>This tutorial introduces the dual-layer architecture for building Quarto Typst extensions: Lua filters intercept markdown elements at parse time, Typst functions render styled output at render time.</p>
<ul>
<li><strong>Badges</strong>: inline pill-shaped indicators built with <code>pandoc.RawInline</code> and a simple Typst <code>box</code> function.</li>
<li><strong>Panels</strong>: block components that wrap rich nested content with opening and closing Typst calls via <code>pandoc.RawBlock</code>.</li>
<li><strong>Template variables</strong>: <code>effective-brand-mode</code> bridges Lua parse-time and Typst render-time for colour scheme selection.</li>
</ul>
<div style="text-align: center;">
<p><a href="./tutorial-part1.zip" class="btn btn-outline-light" target="_blank" rel="noopener noreferrer">Part 1 (ZIP)</a></p>
</div>
</div>
</div>
<section id="why-pdf-branding-matters" class="level2" data-number="1">
<h2 data-number="1" class="anchored" data-anchor-id="why-pdf-branding-matters"><span class="header-section-number">1</span> Why PDF Branding Matters</h2>
<p>In professional environments, document presentation is more than aesthetic. It reflects organisational identity and credibility. You might produce academic papers, corporate reports, client proposals, or technical documentation. Consistent branding turns these different documents into one recognisable, trustworthy portfolio.</p>
<p>Yet keeping this consistency by hand is difficult. Copy-pasting styles between documents introduces errors. Updating a colour scheme across dozens of files becomes a maintenance nightmare. And when multiple team members contribute to documents, stylistic drift is inevitable.</p>
<div class="highlight">
<p><strong>Reproducible branding</strong> through code eliminates manual styling errors, so every document reflects your organisation’s identity.</p>
</div>
<p>This is where programmatic document styling becomes valuable.</p>
<p>Typst offers a modern, programmable alternative to LaTeX for PDF generation. Its syntax is cleaner, compilation is faster, and its scripting capabilities make complex layouts achievable without the arcane incantations that LaTeX often requires. When paired with Quarto’s publishing ecosystem, Typst becomes a foundation for branded document production.</p>
</section>
<section id="prior-art-getting-started-with-typst-and-quarto" class="level2" data-number="2">
<h2 data-number="2" class="anchored" data-anchor-id="prior-art-getting-started-with-typst-and-quarto"><span class="header-section-number">2</span> Prior Art: Getting Started with Typst and Quarto</h2>
<p>Before covering advanced techniques, a few excellent foundational resources deserve a mention. David Keyes wrote an introduction to using Typst with Quarto at <a href="https://rfortherestofus.com/2025/11/quarto-typst-pdf">R for the Rest of Us</a>. If you are new to Typst or want a gentler introduction, that post provides an excellent starting point.</p>
<p>This tutorial builds upon that foundation. Where David’s post shows how to use Typst templates with Quarto, this tutorial explores how to <em>build</em> sophisticated templates that pair Lua filters with Typst rendering functions. What does this mean in practice? It means creating reusable components like badges, panels, and quote cards that authors can invoke with simple markdown syntax instead of writing Typst code directly.</p>
<p>In this tutorial, you will learn the architectural patterns that enable rich, interactive components whilst maintaining clean, extensible code.</p>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
Note
</div>
</div>
<div class="callout-body-container callout-body">
<p>This is <strong>Part 1</strong> of a two-part series. This part covers the foundational concepts. <a href="../../posts/2026-03-05-typst-template-tutorial-part2/">Part 2</a> explores advanced patterns including handler factories, configuration systems, and WCAG-compliant styling.</p>
<p>The examples here are generic, but the <a href="https://github.com/mcanouil/quarto-mcanouil">‘mcanouil’ Quarto extension</a> demonstrates these patterns in a production context.</p>
</div>
</div>
</section>
<section id="the-problem-bridging-markdown-and-typst" class="level2" data-number="3">
<h2 data-number="3" class="anchored" data-anchor-id="the-problem-bridging-markdown-and-typst"><span class="header-section-number">3</span> The Problem: Bridging Markdown and Typst</h2>
<p>Consider a simple scenario: you want users to write badges in their Quarto documents using familiar markdown syntax, but have them render as styled components in Typst.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" data-filename="markdown" style="background: #f1f3f5;"><pre class="sourceCode markdown cw-auto code-with-copy"><code class="sourceCode markdown"><span id="cb1-1">The task is <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">Completed</span><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span>{.badge colour="success"} and ready for review.</span></code></pre></div></div>
<p>This should produce a styled badge in the PDF output. But here is the challenge: Typst templates alone cannot intercept and transform markdown elements. Typst receives content <em>after</em> Quarto and Pandoc have processed it. Without a Lua filter, Pandoc’s default Typst writer drops the <code>.badge</code> class and outputs plain text with no styling. Your Typst template never sees the class at all.</p>
<p>The solution requires two layers working in concert:</p>
<ol type="1">
<li><strong>Lua filters</strong> that intercept markdown elements during Pandoc processing and generate Typst code.</li>
<li><strong>Typst functions</strong> that receive that generated code and render styled output.</li>
</ol>
<p>The transformation looks like this:</p>
<div id="fig-bridge" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-bridge-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="../../posts/2026-02-27-typst-template-tutorial-part1/./typst-render/index/fig-bridge.png" class="lightbox" data-gallery="quarto-lightbox-gallery-2" title="Figure&nbsp;1: The bridge between markdown and Typst."><img src="https://mickael.canouil.fr/posts/2026-02-27-typst-template-tutorial-part1/typst-render/index/fig-bridge.png" class="img-fluid w-75 mx-auto d-block quarto-figure quarto-figure-center figure-img" alt="Two-band cetz diagram. Top band labelled 'Lua layer (Pandoc + filter)' shows four boxes left to right: the markdown source `[Completed]{.badge colour=\&quot;success\&quot;}`, then 'Pandoc AST', then 'Lua filter', then the generated Typst code `#simple-badge(colour: \&quot;success\&quot;, mode: effective-brand-mode)[Completed]`. An arrow drops from that last box down into a second band labelled 'Typst layer (renderer)', which contains a 'Typst function' box and a rendered green pill badge reading 'Completed' with a tick."></a></p>
</figure>
</div>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-bridge-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1: The bridge between markdown and Typst.
</figcaption>
</figure>
</div>
</section>
<section id="the-dual-layer-architecture" class="level2" data-number="4">
<h2 data-number="4" class="anchored" data-anchor-id="the-dual-layer-architecture"><span class="header-section-number">4</span> The Dual-Layer Architecture</h2>
<p>This tutorial explores a dual-layer architecture where Lua handles document transformation and Typst handles rendering.</p>
<section id="layer-1-document-processing-lua" class="level3" data-number="4.1">
<h3 data-number="4.1" class="anchored" data-anchor-id="layer-1-document-processing-lua"><span class="header-section-number">4.1</span> Layer 1: Document Processing (Lua)</h3>
<p>Lua filters operate on Pandoc’s Abstract Syntax Tree (AST), the intermediate representation of your document after Quarto parses the markdown. At this stage, you can:</p>
<ul>
<li>Identify elements by their classes (<em>e.g.</em>, <code>.badge</code>, <code>.panel</code>).</li>
<li>Extract attributes (<em>e.g.</em>, <code>colour="success"</code>).</li>
<li>Generate raw Typst code to replace those elements.</li>
</ul>
<p>The key Pandoc functions for this bridge are:</p>
<div class="code-with-filename">
<div class="code-with-filename-file">
<pre><strong>(conceptual) raw element API</strong></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="annotated-cell-2" data-filename="(conceptual) raw element API" style="background: #f1f3f5;"><pre class="sourceCode lua code-annotation-code code-with-copy code-annotated"><code class="sourceCode lua"><span id="annotated-cell-2-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-- For block-level elements (divs, paragraphs)</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-2" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-2-2" class="code-annotation-target"><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">pandoc</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>RawBlock<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'typst'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'#my-function[content]'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-2-3"></span>
<span id="annotated-cell-2-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-- For inline elements (spans, text)</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-2" data-target-annotation="2" onclick="event.preventDefault();">2</a><span id="annotated-cell-2-5" class="code-annotation-target"><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">pandoc</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>RawInline<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'typst'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'#my-function[content]'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
</div>
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-2" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-2" data-code-lines="2" data-code-annotation="1">Creates a block-level raw element; see <a href="https://pandoc.org/lua-filters.html#type-rawblock">Pandoc Lua types</a>.</span>
</dd>
<dt data-target-cell="annotated-cell-2" data-target-annotation="2">2</dt>
<dd>
<span data-code-cell="annotated-cell-2" data-code-lines="5" data-code-annotation="2">Creates an inline raw element; see <a href="https://pandoc.org/lua-filters.html#type-rawinline">Pandoc Lua types</a>.</span>
</dd>
</dl>
<p>These functions tell Pandoc to pass the string directly to Typst without further processing.</p>
</section>
<section id="layer-2-template-rendering-typst" class="level3" data-number="4.2">
<h3 data-number="4.2" class="anchored" data-anchor-id="layer-2-template-rendering-typst"><span class="header-section-number">4.2</span> Layer 2: Template Rendering (Typst)</h3>
<p>On the Typst side, you define functions that receive the generated code and produce styled output. These functions have access to:</p>
<ul>
<li>The document’s colour scheme and typography settings.</li>
<li>Page dimensions and layout context.</li>
<li>Typst’s full styling capabilities.</li>
</ul>
<p>A simple Typst function might look like:</p>
<div class="code-with-filename">
<div class="code-with-filename-file">
<pre><strong>(conceptual) simple Typst function</strong></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="annotated-cell-3" data-filename="(conceptual) simple Typst function" style="background: #f1f3f5;"><pre class="sourceCode typst code-annotation-code code-with-copy code-annotated"><code class="sourceCode typst"><a class="code-annotation-anchor" data-target-cell="annotated-cell-3" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-3-1" class="code-annotation-target"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">simple-badge</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> colour<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"neutral"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-3" data-target-annotation="2" onclick="event.preventDefault();">2</a><span id="annotated-cell-3-2" class="code-annotation-target">  box<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-3" data-target-annotation="3" onclick="event.preventDefault();">3</a><span id="annotated-cell-3-3" class="code-annotation-target">    fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> luma<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">230</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-3-4">    radius<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">4pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-3-5">    inset<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.25em</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-3-6">    text<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.85em</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-3-7">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-3-8"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
</div>
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-3" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-3" data-code-lines="1" data-code-annotation="1">Function definition with content parameter and optional named parameter.</span>
</dd>
<dt data-target-cell="annotated-cell-3" data-target-annotation="2">2</dt>
<dd>
<span data-code-cell="annotated-cell-3" data-code-lines="2" data-code-annotation="2"><a href="https://typst.app/docs/reference/layout/box/"><code>box</code></a> creates an inline container.</span>
</dd>
<dt data-target-cell="annotated-cell-3" data-target-annotation="3">3</dt>
<dd>
<span data-code-cell="annotated-cell-3" data-code-lines="3" data-code-annotation="3"><a href="https://typst.app/docs/reference/visualize/color/#definitions-luma"><code>luma</code></a> creates a greyscale colour (0-255).</span>
</dd>
</dl>
</section>
<section id="why-this-separation" class="level3" data-number="4.3">
<h3 data-number="4.3" class="anchored" data-anchor-id="why-this-separation"><span class="header-section-number">4.3</span> Why This Separation?</h3>
<p>This dual-layer approach offers several advantages:</p>
<ul>
<li><strong>Separation of concerns</strong>: Lua handles “what to transform”, Typst handles “how to render”.</li>
<li><strong>Flexibility</strong>: Change the rendering without touching the transformation logic, or vice versa.</li>
<li><strong>Testability</strong>: Each layer can be developed and tested independently.</li>
<li><strong>Reusability</strong>: Typst functions can be called directly in Typst code, not just through the Lua bridge.</li>
</ul>
</section>
</section>
<section id="extension-structure-overview" class="level2" data-number="5">
<h2 data-number="5" class="anchored" data-anchor-id="extension-structure-overview"><span class="header-section-number">5</span> Extension Structure Overview</h2>
<p>Before covering component implementation, let us set out the minimal file organisation needed for a Typst extension.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb2" data-filename="txt" style="background: #f1f3f5;"><pre class="sourceCode txt cw-auto code-with-copy"><code class="sourceCode default"><span id="cb2-1">_extensions/my-extension/</span>
<span id="cb2-2">├── _extension.yml       # Extension manifest</span>
<span id="cb2-3">├── template.typ         # Main Typst template</span>
<span id="cb2-4">├── typst-show.typ       # Wrapper functions for components</span>
<span id="cb2-5">├── components.typ       # Component rendering functions</span>
<span id="cb2-6">├── wrapper.lua          # Shared wrapper helpers loaded by filter.lua</span>
<span id="cb2-7">└── filter.lua           # Lua filter entrypoint</span></code></pre></div></div>
<p>The <code>_extension.yml</code> manifest declares how these pieces fit together:</p>
<div class="code-with-filename">
<div class="code-with-filename-file">
<pre><strong>_extensions/my-extension/_extension.yml</strong></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb3" data-filename="_extensions/my-extension/_extension.yml" style="background: #f1f3f5;"><pre class="sourceCode yaml code-with-copy"><code class="sourceCode yaml"><span id="cb3-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">title</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> My Extension</span></span>
<span id="cb3-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">version</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0.0</span></span>
<span id="cb3-3"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">contributes</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb3-4"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">  </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">formats</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb3-5"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">typst</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb3-6"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">      </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">template</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> template.typ</span></span>
<span id="cb3-7"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">      </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">template-partials</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb3-8"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">        </span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> typst-show.typ</span></span>
<span id="cb3-9"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">        </span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> components.typ</span></span>
<span id="cb3-10"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">      </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">filters</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb3-11"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">        </span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> filter.lua</span></span></code></pre></div></div>
</div>
<p>The <code>template.typ</code> file is the main Typst entry point. It includes the component definitions and wrapper functions via Pandoc template syntax, and defines any shared helpers. For this tutorial, it defines the <code>get-colours</code> helper that the wrapper functions rely on:</p>
<div class="code-with-filename">
<div class="code-with-filename-file">
<pre><strong>_extensions/my-extension/template.typ</strong></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="annotated-cell-6" data-filename="_extensions/my-extension/template.typ" style="background: #f1f3f5;"><pre class="sourceCode typst code-annotation-code code-with-copy code-annotated"><code class="sourceCode typst"><span id="annotated-cell-6-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// Include component definitions via template partial</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-6" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-6-2" class="code-annotation-target"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">$components.typ()$</span></span>
<span id="annotated-cell-6-3"></span>
<span id="annotated-cell-6-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// Colour scheme for the document</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-6" data-target-annotation="2" onclick="event.preventDefault();">2</a><span id="annotated-cell-6-5" class="code-annotation-target"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">get-colours</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>mode<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"light"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="annotated-cell-6-6">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> mode <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dark"</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="annotated-cell-6-7">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>background: luma<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">30</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span> foreground: luma<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">230</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span> muted: luma<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">150</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">))</span></span>
<span id="annotated-cell-6-8">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="annotated-cell-6-9">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>background: luma<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">255</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span> foreground: luma<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">30</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span> muted: luma<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">100</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">))</span></span>
<span id="annotated-cell-6-10">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="annotated-cell-6-11"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="annotated-cell-6-12"></span>
<span id="annotated-cell-6-13"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// Include wrapper functions via template partial</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-6" data-target-annotation="3" onclick="event.preventDefault();">3</a><span id="annotated-cell-6-14" class="code-annotation-target"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">$typst-show.typ()$</span></span>
<span id="annotated-cell-6-15"></span>
<span id="annotated-cell-6-16"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">$for(header-includes)$</span></span>
<span id="annotated-cell-6-17"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">$header-includes$</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-6" data-target-annotation="4" onclick="event.preventDefault();">4</a><span id="annotated-cell-6-18" class="code-annotation-target"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">$endfor$</span></span>
<span id="annotated-cell-6-19"></span>
<span id="annotated-cell-6-20"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">$body$</span></span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
</div>
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-6" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-6" data-code-lines="2" data-code-annotation="1">Pandoc template syntax <code>$partial()$</code> includes another file; <code>components.typ</code> must be declared before <code>typst-show.typ</code> so the rendering functions are in scope when the wrappers reference them.</span>
</dd>
<dt data-target-cell="annotated-cell-6" data-target-annotation="2">2</dt>
<dd>
<span data-code-cell="annotated-cell-6" data-code-lines="5" data-code-annotation="2"><code>get-colours</code> returns a dictionary with <code>background</code>, <code>foreground</code>, and <code>muted</code> keys; <code>typst-show.typ</code>’s wrapper functions call this at render time.</span>
</dd>
<dt data-target-cell="annotated-cell-6" data-target-annotation="3">3</dt>
<dd>
<span data-code-cell="annotated-cell-6" data-code-lines="14" data-code-annotation="3">Wrapper functions are included after <code>get-colours</code> is defined so they can call it.</span>
</dd>
<dt data-target-cell="annotated-cell-6" data-target-annotation="4">4</dt>
<dd>
<span data-code-cell="annotated-cell-6" data-code-lines="18" data-code-annotation="4">Pandoc’s template loop injects any per-document Typst preamble content (e.g., items declared via <code>include-in-header</code> in document YAML) before the body.</span>
</dd>
</dl>
<div class="highlight">
<p>Production extensions like <a href="https://github.com/mcanouil/quarto-mcanouil">quarto-mcanouil</a> use more elaborate structures with shared modules and component libraries. <a href="../../posts/2026-03-05-typst-template-tutorial-part2/">Part 2</a> covers those architectural patterns.</p>
</div>
<p>With this foundation established, we can now explore how to build components, starting with simple inline badges and progressing to block-level panels.</p>
</section>
<section id="first-component-badges-inline-elements" class="level2" data-number="6">
<h2 data-number="6" class="anchored" data-anchor-id="first-component-badges-inline-elements"><span class="header-section-number">6</span> First Component: Badges (Inline Elements)</h2>
<p>Badges are compact inline indicators, perfect for showing status, categories, or tags. They represent the simplest component type: a span element with optional attributes that transforms into a styled box.</p>
<section id="understanding-pandoc-span-elements" class="level3" data-number="6.1">
<h3 data-number="6.1" class="anchored" data-anchor-id="understanding-pandoc-span-elements"><span class="header-section-number">6.1</span> Understanding Pandoc Span Elements</h3>
<p>When you write <code>[Completed]{.badge colour="success"}</code> in your Quarto document, Pandoc parses this into a Span element in its AST. The Span contains:</p>
<ul>
<li><strong><code>span.content</code></strong>: The text content (“Completed”).</li>
<li><strong><code>span.classes</code></strong>: A list of classes ([“badge”]).</li>
<li><strong><code>span.attributes</code></strong>: A table of key-value pairs (<code>{colour="success"}</code>).</li>
</ul>
<p>A Lua filter can inspect these properties and decide how to transform the element.</p>
</section>
<section id="building-the-typst-function-start-simple" class="level3" data-number="6.2">
<h3 data-number="6.2" class="anchored" data-anchor-id="building-the-typst-function-start-simple"><span class="header-section-number">6.2</span> Building the Typst Function: Start Simple</h3>
<p>Let us begin with the simplest possible badge function in Typst:</p>
<div class="code-with-filename">
<div class="code-with-filename-file">
<pre><strong>(conceptual) badges.typ v1</strong></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb4" data-filename="(conceptual) badges.typ v1" style="background: #f1f3f5;"><pre class="sourceCode typst code-with-copy"><code class="sourceCode typst"><span id="cb4-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">simple-badge</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb4-2">  box<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb4-3">    fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> luma<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">230</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb4-4">    radius<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">4pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb4-5">    inset<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.25em</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb4-6">    text<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.85em</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb4-7">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb4-8"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span></code></pre></div></div>
</div>
<p>This creates a grey box with rounded corners around any content. It works, but it lacks colour customisation and accessibility considerations.</p>
</section>
<section id="adding-colour-support" class="level3" data-number="6.3">
<h3 data-number="6.3" class="anchored" data-anchor-id="adding-colour-support"><span class="header-section-number">6.3</span> Adding Colour Support</h3>
<p>Next, let us add colour support with predefined semantic colours:</p>
<div class="code-with-filename">
<div class="code-with-filename-file">
<pre><strong>(conceptual) badges.typ v2</strong></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="annotated-cell-8" data-filename="(conceptual) badges.typ v2" style="background: #f1f3f5;"><pre class="sourceCode typst code-annotation-code code-with-copy code-annotated"><code class="sourceCode typst"><a class="code-annotation-anchor" data-target-cell="annotated-cell-8" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-8-1" class="code-annotation-target"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">get-badge-colour</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>colour-name<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> mode<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"light"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="annotated-cell-8-2">  <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">is-dark</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> mode <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dark"</span></span>
<span id="annotated-cell-8-3">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> colour-name <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"success"</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="annotated-cell-8-4">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> is-dark <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span> rgb<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#33cc88"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span> rgb<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#009955"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="annotated-cell-8-5">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> colour-name <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"warning"</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="annotated-cell-8-6">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> is-dark <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span> rgb<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#ee9944"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span> rgb<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#cc6600"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="annotated-cell-8-7">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> colour-name <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"danger"</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="annotated-cell-8-8">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> is-dark <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span> rgb<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#ff5555"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span> rgb<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#cc0000"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="annotated-cell-8-9">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> colour-name <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"info"</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="annotated-cell-8-10">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> is-dark <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span> rgb<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#5599ee"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span> rgb<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#0066cc"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="annotated-cell-8-11">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="annotated-cell-8-12">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> is-dark <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span> luma<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">170</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span> luma<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">128</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span>  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// neutral default</span></span>
<span id="annotated-cell-8-13">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="annotated-cell-8-14"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="annotated-cell-8-15"></span>
<span id="annotated-cell-8-16"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">simple-badge</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> colour<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"neutral"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> mode<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"light"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="annotated-cell-8-17">  <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">base</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> get-badge-colour<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>colour<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> mode<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> mode<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-8-18">  box<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-8-19">    fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> base<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>lighten<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">80%</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-8-20">    stroke<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5pt</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> base<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>lighten<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">40%</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-8-21">    radius<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">4pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-8-22">    inset<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.25em</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-8-23">    text<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.85em</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> base<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-8-24">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-8-25"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
</div>
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-8" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-8" data-code-lines="1" data-code-annotation="1">Here, colours are hard-coded for simplicity. In a production extension you could retrieve them from <code>_brand.yml</code> (Quarto’s brand configuration file that centralises colours, fonts, and logos for consistent styling) using the Quarto Brand Lua API (see Note&nbsp;1).</span>
</dd>
</dl>
<p>Now users can specify <code>colour: "success"</code> and get a green badge. The <code>mode</code> parameter selects light or dark colour palettes.</p>
</section>
<section id="building-the-lua-handler" class="level3" data-number="6.4">
<h3 data-number="6.4" class="anchored" data-anchor-id="building-the-lua-handler"><span class="header-section-number">6.4</span> Building the Lua Handler</h3>
<p>With the Typst function ready, we need a Lua handler to generate calls to it. In Typst, function calls use the pattern <code>#function-name(named-arg: value)[content]</code>: <code>#</code> marks a function call, named arguments go in parentheses, and the content the function wraps goes in square brackets. Unlike a plain string, square brackets in Typst create a <em>content block</em>: a distinct type that Typst renders as formatted output, preserving nested markup such as bold, italic, and inner function calls. The Lua handler builds this string and hands it to Pandoc as raw Typst code. Here is the simplest approach:</p>
<div class="code-with-filename">
<div class="code-with-filename-file">
<pre><strong>(conceptual) badges.lua v1</strong></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="annotated-cell-9" data-filename="(conceptual) badges.lua v1" style="background: #f1f3f5;"><pre class="sourceCode lua code-annotation-code code-with-copy code-annotated"><code class="sourceCode lua"><a class="code-annotation-anchor" data-target-cell="annotated-cell-9" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-9-1" class="code-annotation-target"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span> Span<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">span</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-9-2">  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-- Only process spans with the 'badge' class</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-9" data-target-annotation="2" onclick="event.preventDefault();">2</a><span id="annotated-cell-9-3" class="code-annotation-target">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">not</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">span</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">classes</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span>includes<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'badge'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">then</span></span>
<span id="annotated-cell-9-4">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">span</span></span>
<span id="annotated-cell-9-5">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end</span></span>
<span id="annotated-cell-9-6"></span>
<span id="annotated-cell-9-7">  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-- Extract content as plain text</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-9" data-target-annotation="3" onclick="event.preventDefault();">3</a><span id="annotated-cell-9-8" class="code-annotation-target">  <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">local</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">content</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">pandoc</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">utils</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>stringify<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">span</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">content</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-9-9"></span>
<span id="annotated-cell-9-10">  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-- Get the colour attribute (default to neutral)</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-9" data-target-annotation="4" onclick="event.preventDefault();">4</a><span id="annotated-cell-9-11" class="code-annotation-target">  <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">local</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">colour</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">span</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">attributes</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">colour</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">or</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'neutral'</span></span>
<span id="annotated-cell-9-12"></span>
<span id="annotated-cell-9-13">  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-- Generate Typst function call</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-9" data-target-annotation="5" onclick="event.preventDefault();">5</a><span id="annotated-cell-9-14" class="code-annotation-target">  <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">local</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">typst_code</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">string.format</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-9-15">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'#simple-badge(colour: "%s", mode: effective-brand-mode)[%s]'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-9-16">    <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">colour</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-9-17">    <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">content</span></span>
<span id="annotated-cell-9-18">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-9-19"></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-9" data-target-annotation="6" onclick="event.preventDefault();">6</a><span id="annotated-cell-9-20" class="code-annotation-target">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">pandoc</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>RawInline<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'typst'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">typst_code</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-9-21"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end</span></span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
</div>
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-9" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-9" data-code-lines="1" data-code-annotation="1"><a href="https://pandoc.org/lua-filters.html#type-span">Span filter function</a> receives each span element.</span>
</dd>
<dt data-target-cell="annotated-cell-9" data-target-annotation="2">2</dt>
<dd>
<span data-code-cell="annotated-cell-9" data-code-lines="3" data-code-annotation="2">Check if span has the target class; return unchanged if not.</span>
</dd>
<dt data-target-cell="annotated-cell-9" data-target-annotation="3">3</dt>
<dd>
<span data-code-cell="annotated-cell-9" data-code-lines="8" data-code-annotation="3"><a href="https://pandoc.org/lua-filters.html#pandoc.utils.stringify"><code>stringify</code></a> converts AST content to plain text, stripping all inline formatting (bold, italic, links); for badge labels this is intentional because short status words do not need inline formatting.</span>
</dd>
<dt data-target-cell="annotated-cell-9" data-target-annotation="4">4</dt>
<dd>
<span data-code-cell="annotated-cell-9" data-code-lines="11" data-code-annotation="4">Access attributes as a Lua table; provide default value.</span>
</dd>
<dt data-target-cell="annotated-cell-9" data-target-annotation="5">5</dt>
<dd>
<span data-code-cell="annotated-cell-9" data-code-lines="14" data-code-annotation="5">Build the Typst function call string with parameters; <code>effective-brand-mode</code> is a Typst variable name defined in <code>typst-show.typ</code> via Pandoc template syntax. Quarto resolves it before passing the file to Typst, so its value is never known to Lua. It is introduced and explained in the next section.</span>
</dd>
<dt data-target-cell="annotated-cell-9" data-target-annotation="6">6</dt>
<dd>
<span data-code-cell="annotated-cell-9" data-code-lines="20" data-code-annotation="6">Return raw Typst code that Pandoc passes through unchanged.</span>
</dd>
</dl>
<p>This works for our simple use case. The Lua filter checks if a span has the <code>badge</code> class, extracts its content and attributes, and generates a Typst function call.</p>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
Note
</div>
</div>
<div class="callout-body-container callout-body">
<p>The attribute value here is always a Lua string because Pandoc exposes HTML attributes as strings. Typst syntax distinguishes strings (<code>"hello"</code>), numbers (<code>42</code>), and keywords (<code>none</code>, <code>true</code>); passing the wrong form produces a syntax error. The <code>wrapper.lua</code> module includes a minimal <code>typst_value</code> helper that handles these common cases: string pass-through for Typst keywords (<code>"none"</code>, <code>"true"</code>, <code>"false"</code>), passing Lua numbers as bare values (so <code>42</code> becomes <code>42</code> in Typst, not <code>"42"</code>), and wrapping everything else in quotes. <a href="../../posts/2026-03-05-typst-template-tutorial-part2/">Part 2</a> replaces this with an implementation that also handles actual Lua booleans, <code>nil</code>, and Typst measurement units like <code>1em</code> or <code>2.5pt</code>.</p>
<p>This handler also does not escape Typst special characters in <code>content</code>. Badge labels containing <code>[</code>, <code>]</code>, or <code>#</code> would produce broken Typst syntax because Typst interprets those characters as markup inside <code>[...]</code> content blocks. For typical short labels like <code>"Completed"</code> or <code>"v1.2.0"</code> this is not an issue, but a production implementation would <em>escape</em> those characters before inserting content into the generated code: replacing <code>]</code> with <code>\]</code>, <code>[</code> with <code>\[</code>, and <code>#</code> with <code>\#</code> so Typst does not treat them as markup.</p>
</div>
</div>
</section>
<section id="resolving-brand-mode-at-render-time" class="level3" data-number="6.5">
<h3 data-number="6.5" class="anchored" data-anchor-id="resolving-brand-mode-at-render-time"><span class="header-section-number">6.5</span> Resolving Brand Mode at Render Time</h3>
<p>There is one more piece to the puzzle. The Typst rendering function needs to know whether the document uses a light or dark colour scheme, but Lua generates code at parse time before that value is resolved. The solution is a <strong>template variable</strong> in <code>typst-show.typ</code>.</p>
<div class="callout callout-style-default callout-tip callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Tip</span>Quarto Template Syntax
</div>
</div>
<div class="callout-body-container callout-body">
<p>Quarto’s Typst partials use Pandoc template syntax for variable interpolation:</p>
<ul>
<li><code>$variable$</code> inserts a metadata value.</li>
<li><code>$if(variable)$...$endif$</code> conditionally includes content.</li>
<li><code>$if(variable)$...$else$...$endif$</code> provides a fallback value.</li>
</ul>
<p>These are resolved at render time, not parse time. See <a href="https://quarto.org/docs/journals/templates.html#typst-partials">Typst partials</a> for details.</p>
</div>
</div>
<div class="code-with-filename">
<div class="code-with-filename-file">
<pre><strong>_extensions/my-extension/typst-show.typ</strong></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="annotated-cell-10" data-filename="_extensions/my-extension/typst-show.typ" style="background: #f1f3f5;"><pre class="sourceCode typst code-annotation-code code-with-copy code-annotated"><code class="sourceCode typst"><span id="annotated-cell-10-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// Define brand mode with default fallback</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-10" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-10-2" class="code-annotation-target"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">effective-brand-mode</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"$if(brand-mode)$$brand-mode$$else$light$endif$"</span></span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
</div>
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-10" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-10" data-code-lines="2" data-code-annotation="1">Quarto template syntax (<code>$if()$</code>) resolves at render time. If <code>brand-mode</code> is not set in the document’s YAML front matter, this expression falls back to <code>"light"</code>. To use dark mode, add <code>brand-mode: dark</code> to the document YAML. See <a href="https://quarto.org/docs/journals/templates.html#typst-partials">Typst partials</a>.</span>
</dd>
</dl>
<p>The Lua filter generates code that references <code>effective-brand-mode</code> by name (<em>e.g.</em>, <code>#simple-badge(colour: "success", mode: effective-brand-mode)[Completed]</code>). At render time, Typst resolves the variable and <code>simple-badge</code> passes it to <code>get-badge-colour</code> to select the right colour palette.</p>
<div class="highlight">
<p>The Lua filter generates code that references <code>effective-brand-mode</code> <strong>by name</strong>. Typst resolves the variable at render time via Pandoc template syntax, so each badge picks the right colour palette without Lua needing to know the mode.</p>
</div>
<div id="nte-brand-lua" class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
Note&nbsp;1: Quarto Brand Lua API
</div>
</div>
<div class="callout-body-container callout-body">
<p>Quarto also exposes a <code>quarto.brand</code> Lua module that lets filters query <code>_brand.yml</code> values directly at parse time:</p>
<ul>
<li><code>quarto.brand.has_mode(mode)</code>: returns <code>true</code> if the document has a brand definition for the given mode (<code>"light"</code> or <code>"dark"</code>).</li>
<li><code>quarto.brand.get_color(mode, name)</code>: returns a brand colour by name in the output format (<em>e.g.</em>, <code>quarto.brand.get_color("light", "primary")</code>).</li>
<li><code>quarto.brand.get_color_css(mode, name)</code>: returns the colour in CSS format.</li>
<li><code>quarto.brand.get_typography(mode, name)</code>: returns a table of typography options such as family, size, and weight for the given element.</li>
<li><code>quarto.brand.get_logo(mode, name)</code>: returns logo resource paths and alt text.</li>
</ul>
<p>This approach is useful when you need brand values <em>inside</em> the Lua filter itself, for instance to compute contrast ratios or choose between different rendering strategies before emitting Typst code. In this tutorial, we pass the mode to Typst instead, keeping colour logic in a single layer.</p>
<p>As a rule of thumb: use <code>quarto.brand</code> when the Lua filter itself needs to act on a brand value (computation, branching); use a template variable when the Typst rendering function should resolve the value at render time.</p>
</div>
</div>
</section>
</section>
<section id="block-components-panels-wrapped-content" class="level2" data-number="7">
<h2 data-number="7" class="anchored" data-anchor-id="block-components-panels-wrapped-content"><span class="header-section-number">7</span> Block Components: Panels (Wrapped Content)</h2>
<p>Badges are inline elements with simple text content. Panels are block elements that can contain rich, nested content: paragraphs, lists, images, even code blocks. This introduces a new challenge: how do we preserve and pass through complex content whilst still wrapping it in our Typst function?</p>
<section id="understanding-pandoc-div-elements" class="level3" data-number="7.1">
<h3 data-number="7.1" class="anchored" data-anchor-id="understanding-pandoc-div-elements"><span class="header-section-number">7.1</span> Understanding Pandoc Div Elements</h3>
<p>When you write a fenced div in Quarto:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb5" data-filename="markdown" style="background: #f1f3f5;"><pre class="sourceCode markdown cw-auto code-with-copy"><code class="sourceCode markdown"><span id="cb5-1">::: {.panel style="info" title="Important Note"}</span>
<span id="cb5-2">This panel contains **rich content** including:</span>
<span id="cb5-3"></span>
<span id="cb5-4"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">- </span>Bullet points.</span>
<span id="cb5-5"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">- </span>_Formatted text_.</span>
<span id="cb5-6"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">- </span>And more.</span>
<span id="cb5-7">:::</span></code></pre></div></div>
<p>Pandoc parses this into a Div element with:</p>
<ul>
<li><strong><code>div.content</code></strong>: An array of block elements, such as paragraphs and lists.</li>
<li><strong><code>div.classes</code></strong>: A list of classes ([“panel”]).</li>
<li><strong><code>div.attributes</code></strong>: A table of key-value pairs.</li>
</ul>
<p>The critical difference from spans: <code>div.content</code> is not simple text but a list of Pandoc AST nodes that need further processing.</p>
</section>
<section id="the-wrapped-content-pattern" class="level3" data-number="7.2">
<h3 data-number="7.2" class="anchored" data-anchor-id="the-wrapped-content-pattern"><span class="header-section-number">7.2</span> The Wrapped Content Pattern</h3>
<p>Unlike badges where we convert content to plain text, panels need to preserve their content structure. The solution is to wrap the content with opening and closing Typst code:</p>
<div class="code-with-filename">
<div class="code-with-filename-file">
<pre><strong>(conceptual) wrapped content pattern</strong></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb6" data-filename="(conceptual) wrapped content pattern" style="background: #f1f3f5;"><pre class="sourceCode lua code-with-copy"><code class="sourceCode lua"><span id="cb6-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-- Instead of: #panel[plain text]</span></span>
<span id="cb6-2"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-- We generate:</span></span>
<span id="cb6-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-- #panel(style: "info")[</span></span>
<span id="cb6-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">--   &lt;original content passes through&gt;</span></span>
<span id="cb6-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-- ]</span></span>
<span id="cb6-6"></span>
<span id="cb6-7"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">local</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">result</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb6-8">  <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">pandoc</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>RawBlock<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'typst'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'#my-panel(style: "info")['</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span>  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-- Opening</span></span>
<span id="cb6-9">  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-- Original div.content elements go here</span></span>
<span id="cb6-10">  <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">pandoc</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>RawBlock<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'typst'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">']'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span>  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-- Closing</span></span>
<span id="cb6-11"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span></code></pre></div></div>
</div>
<p>For example, given this markdown:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb7" data-filename="markdown" style="background: #f1f3f5;"><pre class="sourceCode markdown cw-auto code-with-copy"><code class="sourceCode markdown"><span id="cb7-1">::: {.panel style="info"}</span>
<span id="cb7-2">This note contains **bold** text.</span>
<span id="cb7-3">:::</span></code></pre></div></div>
<p>The filter builds this structure, with Pandoc converting the inner content to Typst:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb8" data-filename="typst" style="background: #f1f3f5;"><pre class="sourceCode typst cw-auto code-with-copy"><code class="sourceCode typst"><span id="cb8-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>my-panel<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>style<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"info"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)[</span></span>
<span id="cb8-2">  This note contains *bold* text.</span>
<span id="cb8-3"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span></span></code></pre></div></div>
<p>This approach lets Pandoc continue processing the inner content (converting markdown to Typst), whilst our wrapper provides the styling context.</p>
</section>
<section id="building-the-wrapped-content-handler" class="level3" data-number="7.3">
<h3 data-number="7.3" class="anchored" data-anchor-id="building-the-wrapped-content-handler"><span class="header-section-number">7.3</span> Building the Wrapped Content Handler</h3>
<p><code>wrapper.lua</code> uses the standard Lua module pattern. A <code>local M = {}</code> table is created at the top of the file, public functions are attached to it as <code>function M.fn_name(...)</code>, and <code>return M</code> at the end exports them. When <code>filter.lua</code> loads the module with <code>require_local("wrapper.lua")</code>, it gets back that table and calls <code>wrapper.build_wrapped_content(...)</code>. Private helper functions (like <code>attributes_to_table</code> and <code>build_attribute_string</code>) remain as plain <code>local function</code> inside the file and are not visible outside it.</p>
<p>The wrapper module provides two public utilities for this pattern:</p>
<div class="code-with-filename">
<div class="code-with-filename-file">
<pre><strong>_extensions/my-extension/wrapper.lua (excerpt)</strong></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="annotated-cell-15" data-filename="_extensions/my-extension/wrapper.lua (excerpt)" style="background: #f1f3f5;"><pre class="sourceCode lua code-annotation-code code-with-copy code-annotated"><code class="sourceCode lua"><span id="annotated-cell-15-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">local</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">M</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{}</span></span>
<span id="annotated-cell-15-2"></span>
<span id="annotated-cell-15-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">--- Build Typst block wrappers with optional attributes</span></span>
<span id="annotated-cell-15-4"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">M</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>build_typst_block_wrappers<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">config</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">attrs</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-15-5">  <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">local</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">has_attributes</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">next</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">attrs</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">~=</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">nil</span></span>
<span id="annotated-cell-15-6"></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-15" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-15-7" class="code-annotation-target">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">has_attributes</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">or</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">config</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">arguments</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">then</span></span>
<span id="annotated-cell-15-8">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">local</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">attr_string</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> build_attribute_string<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">attrs</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-15" data-target-annotation="2" onclick="event.preventDefault();">2</a><span id="annotated-cell-15-9" class="code-annotation-target">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">string.format</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'#%s(%s)['</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">config</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">wrapper</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">attr_string</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">']'</span></span>
<span id="annotated-cell-15-10">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span></span>
<span id="annotated-cell-15-11">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">string.format</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'#%s['</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">config</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">wrapper</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">']'</span></span>
<span id="annotated-cell-15-12">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end</span></span>
<span id="annotated-cell-15-13"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end</span></span>
<span id="annotated-cell-15-14"></span>
<span id="annotated-cell-15-15"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">--- Build wrapped content for components</span></span>
<span id="annotated-cell-15-16"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">M</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>build_wrapped_content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">div</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">config</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">extract_title</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-15-17">  <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">local</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">attrs</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> attributes_to_table<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">div</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-15-18"></span>
<span id="annotated-cell-15-19">  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-- Optionally extract first heading as title attribute</span></span>
<span id="annotated-cell-15-20">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">extract_title</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">then</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-15" data-target-annotation="3" onclick="event.preventDefault();">3</a><span id="annotated-cell-15-21" class="code-annotation-target">    <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">M</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>extract_first_heading_as_title<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">div</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">attrs</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-15-22">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end</span></span>
<span id="annotated-cell-15-23"></span>
<span id="annotated-cell-15-24">  <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">local</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">opening</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">closing</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">M</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>build_typst_block_wrappers<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">config</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">attrs</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-15-25"></span>
<span id="annotated-cell-15-26">  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-- Build result: opening wrapper, content, closing wrapper</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-15" data-target-annotation="4" onclick="event.preventDefault();">4</a><span id="annotated-cell-15-27" class="code-annotation-target">  <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">local</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">result</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">pandoc</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>RawBlock<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'typst'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">opening</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-15" data-target-annotation="5" onclick="event.preventDefault();">5</a><span id="annotated-cell-15-28" class="code-annotation-target">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">_</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">item</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">ipairs</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">div</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">content</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">do</span></span>
<span id="annotated-cell-15-29">    <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">table.insert</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">result</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">item</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-15-30">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-15" data-target-annotation="6" onclick="event.preventDefault();">6</a><span id="annotated-cell-15-31" class="code-annotation-target">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">table.insert</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">result</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">pandoc</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>RawBlock<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'typst'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">closing</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">))</span></span>
<span id="annotated-cell-15-32"></span>
<span id="annotated-cell-15-33">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">result</span></span>
<span id="annotated-cell-15-34"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end</span></span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
</div>
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-15" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-15" data-code-lines="7" data-code-annotation="1">Generate parameter list when attributes exist <em>or</em> when the config mandates it (<code>arguments = true</code>); without the <code>or config.arguments</code> branch, an empty attribute list would never be generated even when the Typst function expects it.</span>
</dd>
<dt data-target-cell="annotated-cell-15" data-target-annotation="2">2</dt>
<dd>
<span data-code-cell="annotated-cell-15" data-code-lines="9" data-code-annotation="2">Returns two values: opening bracket with function call, closing bracket.</span>
</dd>
<dt data-target-cell="annotated-cell-15" data-target-annotation="3">3</dt>
<dd>
<span data-code-cell="annotated-cell-15" data-code-lines="21" data-code-annotation="3">Public functions within the module call each other via <code>M.fn_name</code>; this ensures the call always hits the module’s own implementation.</span>
</dd>
<dt data-target-cell="annotated-cell-15" data-target-annotation="4">4</dt>
<dd>
<span data-code-cell="annotated-cell-15" data-code-lines="27" data-code-annotation="4">Opening wrapper becomes first element in result list.</span>
</dd>
<dt data-target-cell="annotated-cell-15" data-target-annotation="5">5</dt>
<dd>
<span data-code-cell="annotated-cell-15" data-code-lines="28" data-code-annotation="5">Original content passes through; Pandoc continues processing nested markdown.</span>
</dd>
<dt data-target-cell="annotated-cell-15" data-target-annotation="6">6</dt>
<dd>
<span data-code-cell="annotated-cell-15" data-code-lines="31" data-code-annotation="6">Closing bracket completes the Typst function call.</span>
</dd>
</dl>
</section>
<section id="title-extraction-a-useful-pattern" class="level3" data-number="7.4">
<h3 data-number="7.4" class="anchored" data-anchor-id="title-extraction-a-useful-pattern"><span class="header-section-number">7.4</span> Title Extraction: A Useful Pattern</h3>
<p>Many block components benefit from extracting the first heading as a title. Instead of requiring users to write:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb9" data-filename="markdown" style="background: #f1f3f5;"><pre class="sourceCode markdown cw-auto code-with-copy"><code class="sourceCode markdown"><span id="cb9-1">::: {.panel title="My Title"}</span>
<span id="cb9-2">Content here.</span>
<span id="cb9-3">:::</span></code></pre></div></div>
<p>You can let them write:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb10" data-filename="markdown" style="background: #f1f3f5;"><pre class="sourceCode markdown cw-auto code-with-copy"><code class="sourceCode markdown"><span id="cb10-1">::: {.panel}</span>
<span id="cb10-2"></span>
<span id="cb10-3"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;"># My Title</span></span>
<span id="cb10-4"></span>
<span id="cb10-5">Content here.</span>
<span id="cb10-6">:::</span></code></pre></div></div>
<p>The <code>extract_first_heading_as_title</code> function handles this:</p>
<div class="code-with-filename">
<div class="code-with-filename-file">
<pre><strong>_extensions/my-extension/wrapper.lua (excerpt)</strong></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="annotated-cell-18" data-filename="_extensions/my-extension/wrapper.lua (excerpt)" style="background: #f1f3f5;"><pre class="sourceCode lua code-annotation-code code-with-copy code-annotated"><code class="sourceCode lua"><span id="annotated-cell-18-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">M</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>extract_first_heading_as_title<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">el</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">attrs</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-18" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-18-2" class="code-annotation-target">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">not</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">attrs</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'title'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">and</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">el</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">content</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">then</span></span>
<span id="annotated-cell-18-3">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">local</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">first_elem</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">el</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">content</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-18" data-target-annotation="2" onclick="event.preventDefault();">2</a><span id="annotated-cell-18-4" class="code-annotation-target">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">first_elem</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">t</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'Header'</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">then</span></span>
<span id="annotated-cell-18-5">      <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-- Extract header text as title attribute</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-18" data-target-annotation="3" onclick="event.preventDefault();">3</a><span id="annotated-cell-18-6" class="code-annotation-target">      <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">attrs</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'title'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">pandoc</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">utils</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>stringify<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">first_elem</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">content</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-18-7">      <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-- Remove header from content</span></span>
<span id="annotated-cell-18-8">      <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">local</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">new_content</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{}</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-18" data-target-annotation="4" onclick="event.preventDefault();">4</a><span id="annotated-cell-18-9" class="code-annotation-target">      <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">i</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">el</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">content</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">do</span></span>
<span id="annotated-cell-18-10">        <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">table.insert</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">new_content</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">el</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">content</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">i</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">])</span></span>
<span id="annotated-cell-18-11">      <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end</span></span>
<span id="annotated-cell-18-12">      <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">el</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">content</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">new_content</span></span>
<span id="annotated-cell-18-13">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end</span></span>
<span id="annotated-cell-18-14">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end</span></span>
<span id="annotated-cell-18-15"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end</span></span>
<span id="annotated-cell-18-16"></span>
<span id="annotated-cell-18-17"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">M</span></span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
</div>
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-18" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-18" data-code-lines="2" data-code-annotation="1">Skip if title already provided via attributes or div is empty.</span>
</dd>
<dt data-target-cell="annotated-cell-18" data-target-annotation="2">2</dt>
<dd>
<span data-code-cell="annotated-cell-18" data-code-lines="4" data-code-annotation="2"><code>.t</code> is the element type; see <a href="https://pandoc.org/lua-filters.html#element-types">Pandoc AST types</a>.</span>
</dd>
<dt data-target-cell="annotated-cell-18" data-target-annotation="3">3</dt>
<dd>
<span data-code-cell="annotated-cell-18" data-code-lines="6" data-code-annotation="3">Convert header inline content to plain string for the title attribute.</span>
</dd>
<dt data-target-cell="annotated-cell-18" data-target-annotation="4">4</dt>
<dd>
<span data-code-cell="annotated-cell-18" data-code-lines="9" data-code-annotation="4">Rebuild content list without the first element (the extracted header).</span>
</dd>
</dl>
</section>
<section id="the-typst-panel-function" class="level3" data-number="7.5">
<h3 data-number="7.5" class="anchored" data-anchor-id="the-typst-panel-function"><span class="header-section-number">7.5</span> The Typst Panel Function</h3>
<p>On the Typst side, the panel function receives the attributes and content:</p>
<div class="code-with-filename">
<div class="code-with-filename-file">
<pre><strong>(conceptual) panels.typ excerpt</strong></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="annotated-cell-19" data-filename="(conceptual) panels.typ excerpt" style="background: #f1f3f5;"><pre class="sourceCode typst code-annotation-code code-with-copy code-annotated"><code class="sourceCode typst"><span id="annotated-cell-19-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">render-panel</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-19" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-19-2" class="code-annotation-target">  content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-19-3">  title<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">none</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-19-4">  style<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"subtle"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-19-5">  icon<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">none</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-19" data-target-annotation="2" onclick="event.preventDefault();">2</a><span id="annotated-cell-19-6" class="code-annotation-target">  colours<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>:<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="annotated-cell-19-7">  breakable<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">false</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-19-8"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-19" data-target-annotation="3" onclick="event.preventDefault();">3</a><span id="annotated-cell-19-9" class="code-annotation-target">  <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">panel-colours</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> get-panel-colours<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>style<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-19-10"></span>
<span id="annotated-cell-19-11">  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// Build optional title display</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-19" data-target-annotation="4" onclick="event.preventDefault();">4</a><span id="annotated-cell-19-12" class="code-annotation-target">  <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">title-display</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> has-content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>title<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="annotated-cell-19-13">    block<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-19-14">      below<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.8em</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-19-15">      text<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-19-16">        size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.1em</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-19-17">        weight<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"semibold"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-19-18">        fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> panel-colours<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>title<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-19-19">        title</span>
<span id="annotated-cell-19-20">      <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-19-21">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-19-22">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="annotated-cell-19-23"></span>
<span id="annotated-cell-19-24">  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// Render the panel</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-19" data-target-annotation="5" onclick="event.preventDefault();">5</a><span id="annotated-cell-19-25" class="code-annotation-target">  block<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-19-26">    width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">100%</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-19-27">    fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> panel-colours<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>background<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-19-28">    stroke<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1pt</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> panel-colours<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>border<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-19-29">    radius<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">8pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-19-30">    inset<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.2em</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-19" data-target-annotation="6" onclick="event.preventDefault();">6</a><span id="annotated-cell-19-31" class="code-annotation-target">    breakable<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> breakable<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-19-32">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="annotated-cell-19-33">      <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> title-display <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">!=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">none</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span> title-display <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="annotated-cell-19-34">      text<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>fill<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> panel-colours<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-19-35">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="annotated-cell-19-36">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-19-37"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
</div>
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-19" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-19" data-code-lines="2" data-code-annotation="1">First positional parameter receives the wrapped content from the Lua filter.</span>
</dd>
<dt data-target-cell="annotated-cell-19" data-target-annotation="2">2</dt>
<dd>
<span data-code-cell="annotated-cell-19" data-code-lines="6" data-code-annotation="2">Empty dictionary <code>(:)</code> as default; the parameter is declared now to keep the function signature stable, so <a href="../../posts/2026-03-05-typst-template-tutorial-part2/">Part 2</a> can add colour-aware logic without changing any call sites. The wrapper function in <code>typst-show.typ</code> injects the actual colour scheme, but this simplified Part 1 version of <code>get-panel-colours</code> ignores it.</span>
</dd>
<dt data-target-cell="annotated-cell-19" data-target-annotation="3">3</dt>
<dd>
<span data-code-cell="annotated-cell-19" data-code-lines="9" data-code-annotation="3">Returns a <a href="https://typst.app/docs/reference/foundations/dictionary/">dictionary</a> with background, border, title, and content colours based purely on the <code>style</code> name; <a href="../../posts/2026-03-05-typst-template-tutorial-part2/">Part 2</a> upgrades this to also use the injected <code>colours</code> for theme-aware text.</span>
</dd>
<dt data-target-cell="annotated-cell-19" data-target-annotation="4">4</dt>
<dd>
<span data-code-cell="annotated-cell-19" data-code-lines="12" data-code-annotation="4">Conditional returns <code>none</code> if title is empty; Typst’s <code>if</code> is an expression.</span>
</dd>
<dt data-target-cell="annotated-cell-19" data-target-annotation="5">5</dt>
<dd>
<span data-code-cell="annotated-cell-19" data-code-lines="25" data-code-annotation="5"><a href="https://typst.app/docs/reference/layout/block/"><code>block</code></a> creates a block-level container with styling.</span>
</dd>
<dt data-target-cell="annotated-cell-19" data-target-annotation="6">6</dt>
<dd>
<span data-code-cell="annotated-cell-19" data-code-lines="31" data-code-annotation="6"><code>breakable: false</code> prevents the panel from splitting across page breaks.</span>
</dd>
</dl>
<p>The <code>breakable: false</code> default prevents panels from splitting across pages, avoiding awkward orphaned titles or content fragments.</p>
</section>
<section id="the-panel-wrapper-injecting-the-colour-scheme" class="level3" data-number="7.6">
<h3 data-number="7.6" class="anchored" data-anchor-id="the-panel-wrapper-injecting-the-colour-scheme"><span class="header-section-number">7.6</span> The Panel Wrapper: Injecting the Colour Scheme</h3>
<p>Unlike badges, where the Lua filter passes <code>effective-brand-mode</code> directly to <code>simple-badge</code>, panels need a richer colour dictionary. The <code>render-panel</code> function accepts a <code>colours</code> parameter that the Lua filter cannot populate at parse time. A wrapper function in <code>typst-show.typ</code> bridges this gap:</p>
<div class="code-with-filename">
<div class="code-with-filename-file">
<pre><strong>_extensions/my-extension/typst-show.typ</strong></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="annotated-cell-20" data-filename="_extensions/my-extension/typst-show.typ" style="background: #f1f3f5;"><pre class="sourceCode typst code-annotation-code code-with-copy code-annotated"><code class="sourceCode typst"><span id="annotated-cell-20-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">my-panel</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> ..args<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="annotated-cell-20-2">  render-panel<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-20-3">    content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-20" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-20-4" class="code-annotation-target">    colours<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> get-colours<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>mode<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> effective-brand-mode<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-20" data-target-annotation="2" onclick="event.preventDefault();">2</a><span id="annotated-cell-20-5" class="code-annotation-target">    ..args<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-20-6">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-20-7"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
</div>
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-20" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-20" data-code-lines="4" data-code-annotation="1">Injects the full colour dictionary resolved from <code>effective-brand-mode</code> at render time.</span>
</dd>
<dt data-target-cell="annotated-cell-20" data-target-annotation="2">2</dt>
<dd>
<span data-code-cell="annotated-cell-20" data-code-lines="5" data-code-annotation="2">Forwards all other named arguments, such as <code>title</code> and <code>style</code>, unchanged.</span>
</dd>
</dl>
<p>The Lua filter generates <code>#my-panel(style: "info")[...]</code>, and at render time <code>my-panel</code> resolves the colour scheme before delegating to <code>render-panel</code>.</p>
<div class="highlight">
<p>For badges, passing <code>mode</code> directly to the Typst function is sufficient. For panels, a <strong>wrapper function</strong> injects a full colour dictionary that depends on runtime context. Choose the simpler approach when it works; use a wrapper when the rendering function needs derived values the Lua filter cannot provide.</p>
</div>
</section>
<section id="the-filter-entrypoint" class="level3" data-number="7.7">
<h3 data-number="7.7" class="anchored" data-anchor-id="the-filter-entrypoint"><span class="header-section-number">7.7</span> The Filter Entrypoint</h3>
<p>The conceptual snippets from earlier sections are real code from <code>filter.lua</code>, the Pandoc entrypoint that registers the <code>Span</code> and <code>Div</code> filter functions and loads <code>wrapper.lua</code>.</p>
<div class="code-with-filename">
<div class="code-with-filename-file">
<pre><strong>_extensions/my-extension/filter.lua</strong></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="annotated-cell-21" data-filename="_extensions/my-extension/filter.lua" style="background: #f1f3f5;"><pre class="sourceCode lua code-annotation-code code-with-copy code-annotated"><code class="sourceCode lua"><a class="code-annotation-anchor" data-target-cell="annotated-cell-21" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-21-1" class="code-annotation-target"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">local</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span> require_local<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">path</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-21-2">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">require</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">quarto</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">utils</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>resolve_path<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">path</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">):</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">gsub</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'%.lua$'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">''</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">))</span></span>
<span id="annotated-cell-21-3"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end</span></span>
<span id="annotated-cell-21-4"></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-21" data-target-annotation="2" onclick="event.preventDefault();">2</a><span id="annotated-cell-21-5" class="code-annotation-target"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">local</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">wrapper</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> require_local<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"wrapper.lua"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-21-6"></span>
<span id="annotated-cell-21-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">--- Badge handler for inline .badge spans</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-21" data-target-annotation="3" onclick="event.preventDefault();">3</a><span id="annotated-cell-21-8" class="code-annotation-target"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span> Span<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">span</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-21-9">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">not</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">quarto</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">doc</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>is_format<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'typst'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">then</span></span>
<span id="annotated-cell-21-10">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">span</span></span>
<span id="annotated-cell-21-11">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end</span></span>
<span id="annotated-cell-21-12"></span>
<span id="annotated-cell-21-13">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">not</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">span</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">classes</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span>includes<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'badge'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">then</span></span>
<span id="annotated-cell-21-14">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">span</span></span>
<span id="annotated-cell-21-15">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end</span></span>
<span id="annotated-cell-21-16"></span>
<span id="annotated-cell-21-17">  <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">local</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">content</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">pandoc</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">utils</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>stringify<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">span</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">content</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-21-18">  <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">local</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">colour</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">span</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">attributes</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">colour</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">or</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'neutral'</span></span>
<span id="annotated-cell-21-19"></span>
<span id="annotated-cell-21-20">  <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">local</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">typst_code</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">string.format</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-21-21">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'#simple-badge(colour: "%s", mode: effective-brand-mode)[%s]'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-21-22">    <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">colour</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-21-23">    <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">content</span></span>
<span id="annotated-cell-21-24">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-21-25"></span>
<span id="annotated-cell-21-26">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">pandoc</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>RawInline<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'typst'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">typst_code</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-21-27"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end</span></span>
<span id="annotated-cell-21-28"></span>
<span id="annotated-cell-21-29"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">--- Panel handler for block .panel divs</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-21" data-target-annotation="4" onclick="event.preventDefault();">4</a><span id="annotated-cell-21-30" class="code-annotation-target"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span> Div<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">div</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-21-31">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">not</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">quarto</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">doc</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>is_format<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'typst'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">then</span></span>
<span id="annotated-cell-21-32">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">div</span></span>
<span id="annotated-cell-21-33">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end</span></span>
<span id="annotated-cell-21-34"></span>
<span id="annotated-cell-21-35">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">not</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">div</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">classes</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span>includes<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'panel'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">then</span></span>
<span id="annotated-cell-21-36">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">div</span></span>
<span id="annotated-cell-21-37">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end</span></span>
<span id="annotated-cell-21-38"></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-21" data-target-annotation="5" onclick="event.preventDefault();">5</a><span id="annotated-cell-21-39" class="code-annotation-target">  <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">local</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">config</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">wrapper</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'my-panel'</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">arguments</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">true</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="annotated-cell-21-40">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">wrapper</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>build_wrapped_content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">div</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">config</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">true</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-21-41"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end</span></span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
</div>
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-21" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-21" data-code-lines="1" data-code-annotation="1"><code>require_local</code> wraps <code>quarto.utils.resolve_path</code> so paths resolve relative to the extension root regardless of where Quarto is run from.</span>
</dd>
<dt data-target-cell="annotated-cell-21" data-target-annotation="2">2</dt>
<dd>
<span data-code-cell="annotated-cell-21" data-code-lines="5" data-code-annotation="2"><code>wrapper.lua</code> is required at the top level; Lua caches the module, so every filter function shares the same instance.</span>
</dd>
<dt data-target-cell="annotated-cell-21" data-target-annotation="3">3</dt>
<dd>
<span data-code-cell="annotated-cell-21" data-code-lines="8" data-code-annotation="3"><code>Span</code> is the Pandoc filter hook; Pandoc calls it for every span in the document.</span>
</dd>
<dt data-target-cell="annotated-cell-21" data-target-annotation="4">4</dt>
<dd>
<span data-code-cell="annotated-cell-21" data-code-lines="30" data-code-annotation="4"><code>Div</code> is the Pandoc filter hook for block-level divs; both hooks are defined in the same file.</span>
</dd>
<dt data-target-cell="annotated-cell-21" data-target-annotation="5">5</dt>
<dd>
<span data-code-cell="annotated-cell-21" data-code-lines="39" data-code-annotation="5">The <code>config</code> table matches the shape used in <a href="../../posts/2026-03-05-typst-template-tutorial-part2/">Part 2</a>’s configuration system: <code>wrapper</code> is the Typst function name, <code>arguments = true</code> ensures the parameter list is always generated.</span>
</dd>
</dl>
<div class="quarto-layout-panel" data-layout-ncol="2">
<div class="quarto-layout-row">
<div class="quarto-layout-cell" style="flex-basis: 50.0%;justify-content: flex-start;">
<div class="code-with-filename">
<div class="code-with-filename-file">
<pre><strong>example.qmd</strong></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb11" data-filename="example.qmd" style="background: #f1f3f5;"><pre class="sourceCode markdown code-with-copy"><code class="sourceCode markdown"><span id="cb11-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">---</span></span>
<span id="cb11-2"><span class="an" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">title:</span><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"> "My Extension Demo"</span></span>
<span id="cb11-3"><span class="an" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">format:</span></span>
<span id="cb11-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">  my-extension-typst: default</span></span>
<span id="cb11-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">---</span></span>
<span id="cb11-6"></span>
<span id="cb11-7"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">## Badges</span></span>
<span id="cb11-8"></span>
<span id="cb11-9">Here are badges in different colours:</span>
<span id="cb11-10"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">Completed</span><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span>{.badge colour="success"},</span>
<span id="cb11-11"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">Pending</span><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span>{.badge colour="warning"},</span>
<span id="cb11-12"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">Failed</span><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span>{.badge colour="danger"},</span>
<span id="cb11-13"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">Information</span><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span>{.badge colour="info"},</span>
<span id="cb11-14">and <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">Default</span><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span>{.badge}.</span>
<span id="cb11-15"></span>
<span id="cb11-16"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">## Panels</span></span>
<span id="cb11-17"></span>
<span id="cb11-18">::: {.panel style="info" title="Important Note"}</span>
<span id="cb11-19">This panel uses the **info** style with an explicit title attribute.</span>
<span id="cb11-20"></span>
<span id="cb11-21"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">- </span>First point.</span>
<span id="cb11-22"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">- </span>Second point.</span>
<span id="cb11-23">:::</span>
<span id="cb11-24"></span>
<span id="cb11-25">::: {.panel style="success"}</span>
<span id="cb11-26"></span>
<span id="cb11-27"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;"># Heading as Title</span></span>
<span id="cb11-28"></span>
<span id="cb11-29">This panel extracts the first heading as its title automatically.</span>
<span id="cb11-30">:::</span>
<span id="cb11-31"></span>
<span id="cb11-32">::: {.panel style="warning" title="Caution"}</span>
<span id="cb11-33">Be careful with this content.</span>
<span id="cb11-34">It uses the **warning** style.</span>
<span id="cb11-35">:::</span>
<span id="cb11-36"></span>
<span id="cb11-37">::: {.panel style="danger" title="Error"}</span>
<span id="cb11-38">Something went wrong.</span>
<span id="cb11-39">This panel uses the **danger** style.</span>
<span id="cb11-40">:::</span>
<span id="cb11-41"></span>
<span id="cb11-42">::: {.panel}</span>
<span id="cb11-43">A subtle panel with default styling and no explicit title.</span>
<span id="cb11-44">:::</span>
<span id="cb11-45"></span>
<span id="cb11-46"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">## Mixed Content</span></span>
<span id="cb11-47"></span>
<span id="cb11-48">::: {.panel style="info" title="Status Overview"}</span>
<span id="cb11-49">The build is <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">Passing</span><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span>{.badge colour="success"} and deployment is <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">Pending</span><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span>{.badge colour="warning"}.</span>
<span id="cb11-50"></span>
<span id="cb11-51">Review the <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">Critical</span><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span>{.badge colour="danger"} items before release.</span>
<span id="cb11-52">:::</span></code></pre></div></div>
</div>
</div>
<div class="quarto-layout-cell" style="flex-basis: 50.0%;justify-content: center;">
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="assets/example.png" class="lightbox" data-gallery="quarto-lightbox-gallery-3"><img src="https://mickael.canouil.fr/posts/2026-02-27-typst-template-tutorial-part1/assets/example.png" class="img-fluid img-thumbnail rounded-3 border-light quarto-figure quarto-figure-center figure-img" alt="PDF render showing three sections. Badges: five inline pill-shaped badges labelled Completed in green, Pending in orange, Failed in red, Information in blue, and Default in grey. Panels: five rounded bordered panels — a blue info panel titled Important Note with a two-item bullet list, a green panel titled Heading as Title, an orange panel titled Caution, a red panel titled Error, and a plain grey panel with no title. Mixed Content: a blue info panel titled Status Overview containing inline Passing (green), Pending (orange), and Critical (red) badges embedded in sentences."></a></p>
</figure>
</div>
</div>
</div>
</div>
</section>
</section>
<section id="conclusion" class="level2" data-number="8">
<h2 data-number="8" class="anchored" data-anchor-id="conclusion"><span class="header-section-number">8</span> Conclusion</h2>
<p>This tutorial introduced the dual-layer architecture for building Quarto Typst templates. You have learned how Lua filters and Typst functions work together to transform markdown elements into styled PDF components.</p>
<p>You can download the complete code for this part of the tutorial as a zip file containing a minimal extension with badges and panels.</p>
<div style="text-align: center;">
<p><a href="./tutorial-part1.zip" class="btn btn-outline-light" target="_blank" rel="noopener noreferrer">Part 1 (ZIP)</a></p>
</div>
<section id="key-concepts-recap" class="level3" data-number="8.1">
<h3 data-number="8.1" class="anchored" data-anchor-id="key-concepts-recap"><span class="header-section-number">8.1</span> Key Concepts Recap</h3>
<p>The dual-layer architecture separates concerns effectively:</p>
<ol type="1">
<li><strong>Lua filters</strong> intercept markdown elements and generate Typst function calls using <code>pandoc.RawBlock</code> and <code>pandoc.RawInline</code>.</li>
<li><strong>Typst functions</strong> receive that generated code and render styled output with access to document context.</li>
<li><strong>Pandoc template variables</strong> (like <code>effective-brand-mode</code>) bridge parse-time (Lua) and render-time (Typst). Lua references the variable by name in the generated code; Typst resolves its value when rendering.</li>
</ol>
<p>For inline elements like badges, Lua extracts content and attributes, then generates a Typst function call. For block elements like panels, Lua wraps the content with opening and closing Typst code, preserving nested structure.</p>
</section>
<section id="what-comes-next" class="level3" data-number="8.2">
<h3 data-number="8.2" class="anchored" data-anchor-id="what-comes-next"><span class="header-section-number">8.2</span> What Comes Next</h3>
<p><a href="../../posts/2026-03-05-typst-template-tutorial-part2/">Part 2</a> builds on these foundations with advanced patterns:</p>
<ul>
<li>Handler factories for reducing repetitive code.</li>
<li>Configuration systems for user-extensible mappings.</li>
<li>Value conversion and type safety between Lua and Typst.</li>
<li>WCAG accessibility compliance for colour contrast.</li>
<li>Shortcodes for components without natural markdown syntax.</li>
<li>A complete quote card example tying all patterns together.</li>
</ul>
</section>
<section id="further-resources" class="level3" data-number="8.3">
<h3 data-number="8.3" class="anchored" data-anchor-id="further-resources"><span class="header-section-number">8.3</span> Further Resources</h3>
<ul>
<li><a href="https://quarto.org/docs/extensions/creating.html">Quarto Documentation: Creating Extensions</a>.</li>
<li><a href="https://pandoc.org/lua-filters.html">Pandoc Lua Filters</a>.</li>
<li><a href="https://typst.app/docs/">Typst Documentation</a>.</li>
<li><a href="https://github.com/mcanouil/quarto-mcanouil"><code>mcanouil</code> Extension</a>.</li>
<li><a href="https://rfortherestofus.com/2025/11/quarto-typst-pdf">R for the Rest of Us: Typst + Quarto</a>.</li>
<li><a href="../../posts/2026-03-05-typst-template-tutorial-part2/">Part 2: Advanced Patterns</a></li>
</ul>


</section>
</section>

<a onclick="window.scrollTo(0, 0); return false;" id="quarto-back-to-top"><i class="bi bi-arrow-up"></i> Back to top</a><div id="quarto-appendix" class="default"><section class="quarto-appendix-contents" id="quarto-reuse"><h2 class="anchored quarto-appendix-heading">Reuse</h2><div class="quarto-appendix-contents"><div><a rel="license" href="https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en_gb">CC BY-NC-SA 4.0</a></div></div></section><section class="quarto-appendix-contents" id="quarto-citation"><h2 class="anchored quarto-appendix-heading">Citation</h2><div><div class="quarto-appendix-secondary-label">BibTeX citation:</div><pre class="sourceCode code-with-copy quarto-appendix-bibtex"><code class="sourceCode bibtex">@misc{canouil2026,
  author = {CANOUIL, Mickaël},
  title = {Building {Quarto} {Typst} {Templates:} {The} {Lua-Typst}
    {Bridge} {(Part} 1)},
  date = {2026-02-27},
  url = {https://mickael.canouil.fr/posts/2026-02-27-typst-template-tutorial-part1/},
  langid = {en-GB}
}
</code></pre><div class="quarto-appendix-secondary-label">For attribution, please cite this work as:</div><div id="ref-canouil2026" class="csl-entry quarto-appendix-citeas">
CANOUIL, M. (2026-02-27). Building Quarto Typst Templates: The Lua-Typst
Bridge (Part 1). <em>Mickael.canouil.fr</em>. <a href="https://mickael.canouil.fr/posts/2026-02-27-typst-template-tutorial-part1/">https://mickael.canouil.fr/posts/2026-02-27-typst-template-tutorial-part1/</a>
</div></div></section></div> ]]></description>
  <category>quarto</category>
  <category>extensions</category>
  <category>typst</category>
  <category>lua</category>
  <category>pdf</category>
  <guid>https://mickael.canouil.fr/posts/2026-02-27-typst-template-tutorial-part1/</guid>
  <pubDate>Fri, 27 Feb 2026 00:00:00 GMT</pubDate>
  <media:content url="https://mickael.canouil.fr/posts/2026-02-27-typst-template-tutorial-part1/featured.png" medium="image" type="image/png" height="76" width="144"/>
</item>
<item>
  <title>Document-Type Dispatching in Quarto Typst Extensions</title>
  <link>https://mickael.canouil.fr/posts/2026-01-19-typst-document-dispatcher/</link>
  <description><![CDATA[ 

<!--
@license MIT
@copyright 2026 Mickaël Canouil
@author Mickaël Canouil
-->
Skip to main content





<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="featured.png" class="lightbox" data-gallery="quarto-lightbox-gallery-1"><img src="https://mickael.canouil.fr/posts/2026-01-19-typst-document-dispatcher/featured.png" class="img-featured img-fluid quarto-figure quarto-figure-center figure-img" alt="Feature image for &quot;Quarto + Typst Document Dispatcher&quot; blog post. Dark
background with the official Quarto logo (four blue quadrants with
wordmark) and Typst logo (teal wordmark) centered at the top. Below,
&quot;Document Dispatcher&quot; in serif font with subtitle &quot;Modern Scientific
Publishing&quot;. File format pipeline shows .qmd → .typ → .pdf. Decorative
elements include floating code snippets and gradient orbs in blue and
teal.
" width="600"></a></p>
</figure>
</div>
<section id="the-problem-multiple-document-types" class="level2" data-number="1">
<h2 data-number="1" class="anchored" data-anchor-id="the-problem-multiple-document-types"><span class="header-section-number">1</span> The Problem: Multiple Document Types</h2>
<p>When building a Quarto Typst extension, you may want to support multiple document types: reports, letters, CVs. Each type has distinct layouts, headers, and styling requirements.</p>
<p>While Quarto extensions can contribute multiple variants of the same base format<sup>1</sup>, this approach requires either sharing metadata across all contributed elements or duplicating it for each variant.</p>
<p>The dispatcher pattern offers an alternative: a single format entry point with document-type routing handled entirely in Typst code. This centralises shared logic and avoids metadata duplication.</p>
<p>Without a clear architecture, you end up with:</p>
<ul>
<li>Monolithic templates cluttered with conditional logic.</li>
<li>Duplicated code across separate template files.</li>
<li>Difficult maintenance as document types multiply.</li>
</ul>
<p>The solution is a <strong>dispatcher pattern</strong>: a central function that routes to the appropriate template based on document type.</p>
</section>
<section id="quartos-default-typst-partials" class="level2" data-number="2">
<h2 data-number="2" class="anchored" data-anchor-id="quartos-default-typst-partials"><span class="header-section-number">2</span> Quarto’s Default Typst Partials</h2>
<p>Before building a dispatcher, it helps to understand how Quarto generates Typst documents.</p>
<p>Quarto provides a set of <a href="https://quarto.org/docs/journals/templates.html#typst-partials">default Typst partials</a> that handle different aspects of document generation:</p>
<table class="caption-top table">
<colgroup>
<col style="width: 25%">
<col style="width: 74%">
</colgroup>
<thead>
<tr class="header">
<th>Partial</th>
<th>Purpose</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>definitions.typ</code></td>
<td>Pandoc/Quarto features (callouts, block quotes, subfloats).</td>
</tr>
<tr class="even">
<td><code>typst-template.typ</code></td>
<td>The main template function applied to document content.</td>
</tr>
<tr class="odd">
<td><code>typst-show.typ</code></td>
<td>Show rules that capture metadata and call the template.</td>
</tr>
<tr class="even">
<td><code>page.typ</code></td>
<td>Page properties (size, margins, numbering, background).</td>
</tr>
<tr class="odd">
<td><code>notes.typ</code></td>
<td>Footnote rendering.</td>
</tr>
<tr class="even">
<td><code>biblio.typ</code></td>
<td>Bibliography formatting.</td>
</tr>
</tbody>
</table>
<div class="callout callout-style-default callout-important callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
Important
</div>
</div>
<div class="callout-body-container callout-body">
<p>The <code>definitions.typ</code> partial is essential and must always be included in custom templates. It provides the foundation for Quarto features like callouts.</p>
</div>
</div>
<section id="overriding-partials" class="level3" data-number="2.1">
<h3 data-number="2.1" class="anchored" data-anchor-id="overriding-partials"><span class="header-section-number">2.1</span> Overriding Partials</h3>
<p>Extensions override default partials by providing replacement files via <code>template-partials</code> in <code>_extension.yml</code> (or document YAML front matter). When you list a partial, Quarto uses your version instead of the built-in one.</p>
<p>The dispatcher pattern requires customising two partials:</p>
<ul>
<li><strong><code>template.typ</code></strong>: Load document-type templates and the dispatcher function.</li>
<li><strong><code>typst-show.typ</code></strong>: Route document content through the dispatcher based on configuration.</li>
</ul>
<p>See the <a href="https://github.com/quarto-dev/quarto-cli/tree/main/src/resources/formats/typst/pandoc/quarto">Quarto source</a> for the default partial implementations.</p>
</section>
</section>
<section id="the-dispatcher-pattern" class="level2" data-number="3">
<h2 data-number="3" class="anchored" data-anchor-id="the-dispatcher-pattern"><span class="header-section-number">3</span> The Dispatcher Pattern</h2>
<p>The dispatcher acts as a routing hub. It receives a document type parameter, validates it, and calls the corresponding template function.</p>
<section id="core-implementation" class="level3" data-number="3.1">
<h3 data-number="3.1" class="anchored" data-anchor-id="core-implementation"><span class="header-section-number">3.1</span> Core Implementation</h3>
<div class="code-with-filename">
<div class="code-with-filename-file">
<pre><strong>document-dispatcher.typ</strong></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="annotated-cell-1" data-filename="document-dispatcher.typ" style="background: #f1f3f5;"><pre class="sourceCode typst code-annotation-code code-with-copy code-annotated"><code class="sourceCode typst"><span id="annotated-cell-1-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/// Supported document types.</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-1" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-1-2" class="code-annotation-target"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">DOCUMENT_TYPES</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"report"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"letter"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"cv"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-1-3"></span>
<span id="annotated-cell-1-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/// Main document dispatcher function.</span></span>
<span id="annotated-cell-1-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/// Routes to the appropriate template based on document-type.</span></span>
<span id="annotated-cell-1-6"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">document-dispatcher</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-1" data-target-annotation="2" onclick="event.preventDefault();">2</a><span id="annotated-cell-1-7" class="code-annotation-target">  document-type<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"report"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-1" data-target-annotation="3" onclick="event.preventDefault();">3</a><span id="annotated-cell-1-8" class="code-annotation-target">  ..args<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-1-9"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="annotated-cell-1-10">  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// Validate and normalise document type</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-1" data-target-annotation="4" onclick="event.preventDefault();">4</a><span id="annotated-cell-1-11" class="code-annotation-target">  <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">doc-type</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> document-type <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">in</span> DOCUMENT_TYPES <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="annotated-cell-1-12">    document-type</span>
<span id="annotated-cell-1-13">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="annotated-cell-1-14">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// Fall back to report with warning</span></span>
<span id="annotated-cell-1-15">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"report"</span></span>
<span id="annotated-cell-1-16">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="annotated-cell-1-17"></span>
<span id="annotated-cell-1-18">  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// Dispatch to appropriate template</span></span>
<span id="annotated-cell-1-19">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> doc-type <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"letter"</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="annotated-cell-1-20">    render-letter<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>..args<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-1-21">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> doc-type <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"cv"</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="annotated-cell-1-22">    render-cv<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>..args<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-1-23">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="annotated-cell-1-24">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// Default: report</span></span>
<span id="annotated-cell-1-25">    render-report<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>..args<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="annotated-cell-1-26">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="annotated-cell-1-27"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
</div>
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-1" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-1" data-code-lines="2" data-code-annotation="1">Define supported types as an <a href="https://typst.app/docs/reference/foundations/array/">array</a>.</span>
</dd>
<dt data-target-cell="annotated-cell-1" data-target-annotation="2">2</dt>
<dd>
<span data-code-cell="annotated-cell-1" data-code-lines="7" data-code-annotation="2">Default document type; used when not specified.</span>
</dd>
<dt data-target-cell="annotated-cell-1" data-target-annotation="3">3</dt>
<dd>
<span data-code-cell="annotated-cell-1" data-code-lines="8" data-code-annotation="3"><a href="https://typst.app/docs/reference/foundations/arguments/">Variadic arguments</a> capture all other parameters.</span>
</dd>
<dt data-target-cell="annotated-cell-1" data-target-annotation="4">4</dt>
<dd>
<span data-code-cell="annotated-cell-1" data-code-lines="11" data-code-annotation="4"><code>in</code> operator checks array membership.</span>
</dd>
</dl>
</section>
<section id="key-patterns" class="level3" data-number="3.2">
<h3 data-number="3.2" class="anchored" data-anchor-id="key-patterns"><span class="header-section-number">3.2</span> Key Patterns</h3>
<p><strong>Pattern 1: Type Validation</strong></p>
<p>The dispatcher validates the document type against a known list before routing. Invalid types fall back gracefully to a default (report) rather than failing.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb3" data-filename="typst" style="background: #f1f3f5;"><pre class="sourceCode typst cw-auto code-with-copy"><code class="sourceCode typst"><span id="cb3-1">let doc-type = if document-type in DOCUMENT_TYPES {</span>
<span id="cb3-2">  document-type</span>
<span id="cb3-3">} else {</span>
<span id="cb3-4">  "report"  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// Safe fallback</span></span>
<span id="cb3-5">}</span></code></pre></div></div>
<p><strong>Pattern 2: Variadic Argument Forwarding</strong></p>
<p>Each document type may accept different parameters. Rather than listing every possible parameter, the dispatcher captures them all with <code>..args</code> and forwards them to the target function.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb4" data-filename="typst" style="background: #f1f3f5;"><pre class="sourceCode typst cw-auto code-with-copy"><code class="sourceCode typst"><span id="cb4-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">document-dispatcher</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>document-type<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"report"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> ..args<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb4-2">  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// ...</span></span>
<span id="cb4-3">  render-report<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>..args<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span>  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// Forward all arguments</span></span>
<span id="cb4-4"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span></code></pre></div></div>
<p>This means:</p>
<ul>
<li>The dispatcher does not need to know about each template’s specific parameters.</li>
<li>Adding new parameters to a template requires no changes to the dispatcher.</li>
<li>Each template function handles only its own parameters.</li>
</ul>
<p><strong>Pattern 3: Conditional Routing</strong></p>
<p>Typst’s <code>if-else</code> statements handle the routing logic. For more document types, this could be extended with a dictionary lookup:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb5" data-filename="typst" style="background: #f1f3f5;"><pre class="sourceCode typst cw-auto code-with-copy"><code class="sourceCode typst"><span id="cb5-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">TEMPLATE_FUNCTIONS</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb5-2">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"report"</span>: render-report<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb5-3">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"letter"</span>: render-letter<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb5-4">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"cv"</span>: render-cv<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb5-5"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb5-6"></span>
<span id="cb5-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// Then dispatch with:</span></span>
<span id="cb5-8">let template-fn = TEMPLATE_FUNCTIONS.at(doc-type, default: render-report)</span>
<span id="cb5-9">template-fn(..args)</span></code></pre></div></div>
</section>
</section>
<section id="configuration-via-yaml" class="level2" data-number="4">
<h2 data-number="4" class="anchored" data-anchor-id="configuration-via-yaml"><span class="header-section-number">4</span> Configuration via YAML</h2>
<p>Users specify the document type in their Quarto document’s YAML front matter.</p>
<section id="user-facing-configuration" class="level3" data-number="4.1">
<h3 data-number="4.1" class="anchored" data-anchor-id="user-facing-configuration"><span class="header-section-number">4.1</span> User-Facing Configuration</h3>
<div class="code-with-filename">
<div class="code-with-filename-file">
<pre><strong>document.qmd</strong></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb6" data-filename="document.qmd" style="background: #f1f3f5;"><pre class="sourceCode yaml code-with-copy"><code class="sourceCode yaml"><span id="cb6-1"><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">---</span></span>
<span id="cb6-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">title</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"My Report"</span></span>
<span id="cb6-3"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">format</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb6-4"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">  </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">my-extension-typst</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb6-5"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">document-type</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> report</span></span>
<span id="cb6-6"><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">---</span></span></code></pre></div></div>
</div>
<p>The extension’s format name (<code>my-extension-typst</code>) comes from <code>_extension.yml</code>. The <code>document-type</code> key is passed through Quarto’s template processing.</p>
</section>
<section id="bridging-quarto-and-typst" class="level3" data-number="4.2">
<h3 data-number="4.2" class="anchored" data-anchor-id="bridging-quarto-and-typst"><span class="header-section-number">4.2</span> Bridging Quarto and Typst</h3>
<p>The <code>typst-show.typ</code> partial connects YAML metadata to the Typst dispatcher:</p>
<div class="code-with-filename">
<div class="code-with-filename-file">
<pre><strong>typst-show.typ</strong></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="annotated-cell-6" data-filename="typst-show.typ" style="background: #f1f3f5;"><pre class="sourceCode typst code-annotation-code code-with-copy code-annotated"><code class="sourceCode typst"><span id="annotated-cell-6-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#show</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> document-dispatcher<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>with<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="annotated-cell-6-2">  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// Document type selection</span></span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-6" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-6-3" class="code-annotation-target">  document-type<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">$if(document-type)$</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"$document-type$"</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">$else$</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"report"</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">$endif$</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="annotated-cell-6-4"></span>
<span id="annotated-cell-6-5">  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// Pass through other parameters</span></span>
<span id="annotated-cell-6-6">  title<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">$title$</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">],</span></span>
<span id="annotated-cell-6-7">  author<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">$for(author)$$author$$sep$</span>, <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">$endfor$</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">],</span></span>
<span id="annotated-cell-6-8">  date<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">$date$</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">],</span></span>
<span id="annotated-cell-6-9">  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// ... additional parameters</span></span>
<span id="annotated-cell-6-10"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
</div>
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-6" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-6" data-code-lines="3" data-code-annotation="1">Pandoc template syntax (<code>$if()$</code>) checks if the metadata key exists and provides a default.</span>
</dd>
</dl>
<p>The <code>$if(document-type)$...$else$...$endif$</code> pattern:</p>
<ul>
<li>Uses the user-provided value if present.</li>
<li>Falls back to “report” if not specified.</li>
<li>Always gives the dispatcher a valid string.</li>
</ul>
</section>
</section>
<section id="file-organisation" class="level2" data-number="5">
<h2 data-number="5" class="anchored" data-anchor-id="file-organisation"><span class="header-section-number">5</span> File Organisation</h2>
<p>A well-organised extension separates document types into distinct files.</p>
<section id="directory-structure" class="level3" data-number="5.1">
<h3 data-number="5.1" class="anchored" data-anchor-id="directory-structure"><span class="header-section-number">5.1</span> Directory Structure</h3>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb7" data-filename="txt" style="background: #f1f3f5;"><pre class="sourceCode txt cw-auto code-with-copy"><code class="sourceCode default"><span id="cb7-1">_extensions/my-extension/</span>
<span id="cb7-2">├── _extension.yml</span>
<span id="cb7-3">├── template.typ              # Main entry point</span>
<span id="cb7-4">├── typst-show.typ            # Quarto-Typst bridge</span>
<span id="cb7-5">└── partials/</span>
<span id="cb7-6">    ├── document-dispatcher.typ</span>
<span id="cb7-7">    └── document-types/</span>
<span id="cb7-8">        ├── report.typ</span>
<span id="cb7-9">        ├── letter.typ</span>
<span id="cb7-10">        └── cv.typ</span></code></pre></div></div>
</section>
<section id="extension-manifest" class="level3" data-number="5.2">
<h3 data-number="5.2" class="anchored" data-anchor-id="extension-manifest"><span class="header-section-number">5.2</span> Extension Manifest</h3>
<p>The <code>_extension.yml</code> declares template partials in loading order:</p>
<div class="code-with-filename">
<div class="code-with-filename-file">
<pre><strong>_extension.yml</strong></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb8" data-filename="_extension.yml" style="background: #f1f3f5;"><pre class="sourceCode yaml code-with-copy"><code class="sourceCode yaml"><span id="cb8-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">title</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> My Extension</span></span>
<span id="cb8-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">version</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0.0</span></span>
<span id="cb8-3"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">contributes</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb8-4"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">  </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">formats</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb8-5"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">my-extension-typst</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb8-6"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">      </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">template</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> template.typ</span></span>
<span id="cb8-7"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">      </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">template-partials</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb8-8"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">        </span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> partials/document-types/report.typ</span><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    # Load document types first</span></span>
<span id="cb8-9"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">        </span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> partials/document-types/letter.typ</span></span>
<span id="cb8-10"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">        </span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> partials/document-types/cv.typ</span></span>
<span id="cb8-11"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">        </span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> partials/document-dispatcher.typ</span><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">      # Dispatcher last (uses the above)</span></span></code></pre></div></div>
</div>
</section>
<section id="main-template" class="level3" data-number="5.3">
<h3 data-number="5.3" class="anchored" data-anchor-id="main-template"><span class="header-section-number">5.3</span> Main Template</h3>
<p>The <code>template.typ</code> file serves as the entry point that assembles all components. It must include Quarto’s essential definitions and load your custom partials in the correct order.</p>
<div class="code-with-filename">
<div class="code-with-filename-file">
<pre><strong>template.typ</strong></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb9" data-filename="template.typ" style="background: #f1f3f5;"><pre class="sourceCode typst code-with-copy"><code class="sourceCode typst"><span id="cb9-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// Essential: Quarto/Pandoc feature definitions (callouts, quotes, etc.)</span></span>
<span id="cb9-2"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">$definitions.typ()$</span></span>
<span id="cb9-3"></span>
<span id="cb9-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// Load document type templates</span></span>
<span id="cb9-5"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">$report.typ()$</span></span>
<span id="cb9-6"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">$letter.typ()$</span></span>
<span id="cb9-7"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">$cv.typ()$</span></span>
<span id="cb9-8"></span>
<span id="cb9-9"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// Load dispatcher (must come after document types)</span></span>
<span id="cb9-10"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">$document-dispatcher.typ()$</span></span></code></pre></div></div>
</div>
<p>The loading order matters:</p>
<ol type="1">
<li><strong><code>definitions.typ</code></strong> must come first; it provides Quarto features like callouts.</li>
<li><strong>Document type templates</strong> define the rendering functions (<code>render-report</code>, <code>render-letter</code>, <code>render-cv</code>).</li>
<li><strong>Dispatcher</strong> comes last because it references the rendering functions.</li>
</ol>
<div class="callout callout-style-default callout-tip callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
Tip
</div>
</div>
<div class="callout-body-container callout-body">
<p>The <code>$partial-name.typ()$</code> syntax is Quarto’s template interpolation. It inserts the contents of the corresponding partial file at that location.</p>
</div>
</div>
</section>
</section>
<section id="adding-new-document-types" class="level2" data-number="6">
<h2 data-number="6" class="anchored" data-anchor-id="adding-new-document-types"><span class="header-section-number">6</span> Adding New Document Types</h2>
<p>To add a new document type (e.g., “memo”):</p>
<p><strong>Step 1: Create the template function.</strong></p>
<div class="code-with-filename">
<div class="code-with-filename-file">
<pre><strong>partials/document-types/memo.typ</strong></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb10" data-filename="partials/document-types/memo.typ" style="background: #f1f3f5;"><pre class="sourceCode typst code-with-copy"><code class="sourceCode typst"><span id="cb10-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">render-memo</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb10-2">  title<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">none</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb10-3">  to<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">none</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb10-4">  from<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">none</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb10-5">  date<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">none</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb10-6">  body<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb10-7"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb10-8">  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// Memo-specific layout</span></span>
<span id="cb10-9">  <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">set</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">page</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>paper<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"a4"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> margin<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2cm</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb10-10"></span>
<span id="cb10-11">  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// Header</span></span>
<span id="cb10-12">  grid<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb10-13">    columns<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1fr</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1fr</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb10-14">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span>*TO:* <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>to<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">],</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span>*DATE:* <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>date<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">],</span></span>
<span id="cb10-15">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span>*FROM:* <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#</span>from<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">],</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[],</span></span>
<span id="cb10-16">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb10-17"></span>
<span id="cb10-18">  line<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>length<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">100%</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb10-19"></span>
<span id="cb10-20">  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// Subject</span></span>
<span id="cb10-21">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> title <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">!=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">none</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb10-22">    align<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>center<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> text<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>weight<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"bold"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">14pt</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> title<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">))</span></span>
<span id="cb10-23">  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb10-24"></span>
<span id="cb10-25">  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// Body</span></span>
<span id="cb10-26">  body</span>
<span id="cb10-27"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span></code></pre></div></div>
</div>
<p><strong>Step 2: Register in the dispatcher.</strong></p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb11" data-filename="typst" style="background: #f1f3f5;"><pre class="sourceCode typst cw-auto code-with-copy"><code class="sourceCode typst"><span id="cb11-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">#let</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">DOCUMENT_TYPES</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"report"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"letter"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"cv"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"memo"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb11-2"></span>
<span id="cb11-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// Add routing case</span></span>
<span id="cb11-4">if doc-type == "memo" {</span>
<span id="cb11-5">  render-memo(..args)</span>
<span id="cb11-6">}</span></code></pre></div></div>
<p><strong>Step 3: Add to extension manifest.</strong></p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb12" data-filename="yaml" style="background: #f1f3f5;"><pre class="sourceCode yaml cw-auto code-with-copy"><code class="sourceCode yaml"><span id="cb12-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">template-partials</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb12-2"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">  </span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> partials/document-types/memo.typ</span></span>
<span id="cb12-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">  # ... other partials</span></span></code></pre></div></div>
<p><strong>Step 4: Load in template.</strong></p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb13" data-filename="typst" style="background: #f1f3f5;"><pre class="sourceCode typst cw-auto code-with-copy"><code class="sourceCode typst"><span id="cb13-1"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">$memo.typ()$</span></span></code></pre></div></div>
</section>
<section id="benefits-of-this-pattern" class="level2" data-number="7">
<h2 data-number="7" class="anchored" data-anchor-id="benefits-of-this-pattern"><span class="header-section-number">7</span> Benefits of This Pattern</h2>
<p><strong>Clean Separation</strong></p>
<p>Each document type lives in its own file with its own logic. Changes to one type do not affect others.</p>
<p><strong>Extensibility</strong></p>
<p>Adding new document types requires minimal changes to existing code. The dispatcher’s validation list and routing logic are the only modifications needed.</p>
<p><strong>Graceful Fallbacks</strong></p>
<p>Invalid document types fall back to a sensible default rather than failing. Users receive working output even with typos in their configuration.</p>
<p><strong>Parameter Isolation</strong></p>
<p>Each template function defines only its own parameters. The dispatcher does not need to understand every possible parameter combination.</p>
</section>
<section id="conclusion" class="level2" data-number="8">
<h2 data-number="8" class="anchored" data-anchor-id="conclusion"><span class="header-section-number">8</span> Conclusion</h2>
<p>The document-type dispatcher pattern provides a clean architecture for Quarto Typst extensions that support multiple document types.</p>
<p>Key takeaways:</p>
<ol type="1">
<li><strong>Central routing</strong>: A single dispatcher function routes to appropriate templates.</li>
<li><strong>Validation with fallbacks</strong>: Check document types against a known list; default gracefully.</li>
<li><strong>Variadic forwarding</strong>: Use <code>..args</code> to pass parameters without coupling.</li>
<li><strong>YAML integration</strong>: Bridge Quarto metadata to Typst via <code>typst-show.typ</code>.</li>
<li><strong>Modular organisation</strong>: Separate files for each document type.</li>
</ol>
<div class="highlight">
<p>The dispatcher pattern scales well as your extension grows. Start with one or two document types, and the architecture supports adding more without refactoring.</p>
</div>
<section id="further-resources" class="level3" data-number="8.1">
<h3 data-number="8.1" class="anchored" data-anchor-id="further-resources"><span class="header-section-number">8.1</span> Further Resources</h3>
<ul>
<li><a href="https://typst.app/docs/reference/foundations/function/">Typst Documentation: Functions</a>.</li>
<li><a href="https://quarto.org/docs/journals/templates.html#typst-partials">Quarto Documentation: Typst Partials</a>.</li>
<li><a href="https://github.com/mcanouil/quarto-mcanouil">quarto-mcanouil Extension</a> - demonstrates this pattern in production.</li>
</ul>


</section>
</section>


<a onclick="window.scrollTo(0, 0); return false;" id="quarto-back-to-top"><i class="bi bi-arrow-up"></i> Back to top</a><div id="quarto-appendix" class="default"><section id="footnotes" class="footnotes footnotes-end-of-document"><h2 class="anchored quarto-appendix-heading">Footnotes</h2>

<ol>
<li id="fn1"><p>Format variants use the <code>+</code> syntax in <code>_extension.yml</code>:</p>
<div class="code-with-filename">
<div class="code-with-filename-file">
<pre><strong>_extension.yml</strong></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" data-filename="_extension.yml" style="background: #f1f3f5;"><pre class="sourceCode yaml code-with-copy"><code class="sourceCode yaml"><span id="cb1-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">contributes</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb1-2"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">  </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">formats</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb1-3"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">typst+report</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb1-4"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">      </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">title</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Typst Report"</span></span>
<span id="cb1-5"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">typst+article</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb1-6"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">      </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">title</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Typst Article"</span></span></code></pre></div></div>
</div>
<p>Then in a document:</p>
<div class="code-with-filename">
<div class="code-with-filename-file">
<pre><strong>YAML</strong></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb2" data-filename="YAML" style="background: #f1f3f5;"><pre class="sourceCode yaml code-with-copy"><code class="sourceCode yaml"><span id="cb2-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">format</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb2-2"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">  </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">myformat-typst+report</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> default</span></span></code></pre></div></div>
</div>
↩︎</li>
</ol>
</section><section class="quarto-appendix-contents" id="quarto-reuse"><h2 class="anchored quarto-appendix-heading">Reuse</h2><div class="quarto-appendix-contents"><div><a rel="license" href="https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en_gb">CC BY-NC-SA 4.0</a></div></div></section><section class="quarto-appendix-contents" id="quarto-citation"><h2 class="anchored quarto-appendix-heading">Citation</h2><div><div class="quarto-appendix-secondary-label">BibTeX citation:</div><pre class="sourceCode code-with-copy quarto-appendix-bibtex"><code class="sourceCode bibtex">@misc{canouil2026,
  author = {CANOUIL, Mickaël},
  title = {Document-Type {Dispatching} in {Quarto} {Typst} {Extensions}},
  date = {2026-01-19},
  url = {https://mickael.canouil.fr/posts/2026-01-19-typst-document-dispatcher/},
  langid = {en-GB}
}
</code></pre><div class="quarto-appendix-secondary-label">For attribution, please cite this work as:</div><div id="ref-canouil2026" class="csl-entry quarto-appendix-citeas">
CANOUIL, M. (2026-01-19). Document-Type Dispatching in Quarto Typst
Extensions. <em>Mickael.canouil.fr</em>. <a href="https://mickael.canouil.fr/posts/2026-01-19-typst-document-dispatcher/">https://mickael.canouil.fr/posts/2026-01-19-typst-document-dispatcher/</a>
</div></div></section></div> ]]></description>
  <category>quarto</category>
  <category>extensions</category>
  <category>typst</category>
  <category>pdf</category>
  <guid>https://mickael.canouil.fr/posts/2026-01-19-typst-document-dispatcher/</guid>
  <pubDate>Mon, 19 Jan 2026 00:00:00 GMT</pubDate>
  <media:content url="https://mickael.canouil.fr/posts/2026-01-19-typst-document-dispatcher/featured.png" medium="image" type="image/png" height="76" width="144"/>
</item>
</channel>
</rss>
