Template Formatting Reference

Everything Leed Markdown produces is plain HTML with a small number of agreed class names on it. The bundled stylesheet and documentation.js then find those class names at page load and enhance them: highlight the code, typeset the math, render the diagram, wire the tabs. Nothing on this page is magic markup — it is the contract, and a .hbs file of yours that emits the same structure gets the same treatment.

The libraries are not loaded on every page. After a page is rendered, the build scans the finished HTML and injects only the libraries that page needs: a <code> inside a <pre> pulls in highlight.js, a code.math pulls in KaTeX, a pre.mermaid pulls in Mermaid. Emitting the correct structure is therefore all the configuration there is — there is no per-page setting to enable a feature, and a page with no code block never pays for highlight.js. The other side of that deal is that a structure with the wrong class attracts nothing at all: it renders as bare HTML, with no error anywhere.

All of this depends on your layout calling {{> leed/head }}, which is what loads documentation.js and the stylesheet in the first place. A layout that hand-rolls its own <head> gets none of it — see Head and Component Partials.

Structure summary

FeatureElementRequired class or attributeEnhanced byMarkdown equivalent
Inline code<code>nonestylesheet onlyBasic Formatting
Code block<pre><code>class="language-<name>" on the <code>highlight.js 11.11.2, plus a copy buttonCode Blocks
Inline math<code>class="math"KaTeX 0.18.4Math
Block math<pre><code>class="math" on the <code>KaTeX 0.18.4Math
Diagram<pre>class="mermaid"Mermaid 11.17.2, plus click-to-zoomDiagrams
Tab group<div>class="leed-tabs__container" and data-tabs-groupIddocumentation.js, synced through localStorageTabs
Alert<aside>class="alert alert-<type>"stylesheet only (icon and palette)Alerts

Which of these an author can produce from the editor is a per-page-type decision, not a plan one: each page type has its own formatting toggles, described in What Each Page Type Lets You Format. A template is not subject to those toggles — it emits whatever you write.

Inline code

No class, no wrapper. A bare <code> outside a <pre> is styled as an inline code span and is left alone by highlight.js.

<p>
    this is a <code>code example</code>, nothing more.
</p>

Code blocks with syntax highlighting

A code block is a <code> inside a <pre>, with the language named as a language-* class on the <code> element and every HTML entity escaped:

<pre><code class="language-json">{
  "name": "my-worker",
  "version": "1.0.0"
}</code></pre>

At page load, documentation.js adds not-prose to both the <pre> and the <code>, wraps the pair in a div.code-block-wrapper, appends the copy button, and passes the element to hljs.highlightElement.

A missing language class does not mean plain text. Every pre > code that is not .math is handed to highlight.js, and with no language-* class it runs automatic language detection — which on a short block guesses, often wrongly. Name the language you mean. To opt a block out of highlighting entirely, give the <code> the class nohighlight; highlight.js skips it before doing anything else.

Beyond the languages in the default highlight.js bundle, a site can register extra ones. The build reads highlighter.extraLanguages from the site’s configuration and, for each entry, looks for src/static/js/hljs-<language>.js in your repository; if the file is there it is added to the scripts loaded alongside highlight.js, and if it is not you get the build log line Extra language <language> not found: <path> and no highlighting for that language. Where that list is configured is covered in the Documentation Configuration Reference.

Colors come from the code theme, not from this markup. Picking one, and writing your own, is covered in Themes, Fonts and Code Themes and Custom Documentation Themes.

Math (KaTeX)

Math is found by the math class on a <code> element — inline when the <code> stands alone, as a block when it is inside a <pre>. KaTeX replaces the element’s contents in place, in non-strict mode.

<p>
    this is a sick math equation <code class="math">x = {-b \pm \sqrt{b^2-4ac} \over 2a}</code> ... wouldn't you agree?
</p>
<pre><code class="math">x = {-b \pm \sqrt{b^2-4ac} \over 2a}</code></pre>

The math class also takes the block out of the code pipeline: highlight.js skips it, and no copy button is attached. That is deliberate — a rendered equation has no source worth copying.

How it renders

The equation x = {-b \pm \sqrt{b^2-4ac} \over 2a} sits inline in this sentence, and the same expression as a block:

x = {-b \pm \sqrt{b^2-4ac} \over 2a}

Diagrams (Mermaid)

Put the mermaid class on a <pre> and leave the diagram source as plain text inside it. Do not wrap it in a <code> — Mermaid reads the element’s textContent and replaces its contents with the rendered SVG.

<pre class="mermaid">
    flowchart LR
        A[Template] --> B[HTML]
        B --> C[Mermaid renders SVG]
</pre>

Three facts a template author needs and cannot learn from the markup.

Colors come from the site, not the diagram. The site’s palette is read from src/static/js/mermaid.theme.json and passed to Mermaid before the first render, and most of its values are var(--token) strings that resolve in the browser against :root — which is why a diagram follows the reader’s color scheme with no re-render. A diagram your template emits is themed exactly like one a Markdown fence emits; there is nothing extra to do. The file, the values that can be a var() and the ones that cannot, and how to adopt your own palette are owned by Theming Diagrams.

Not every diagram type is covered. Confirmed themed by live render at 11.17.2, the version the published site pins: flowchart (and the older graph), sequenceDiagram, stateDiagram and stateDiagram-v2, classDiagram, erDiagram, requirementDiagram, gantt, gitGraph, pie, journey, timeline and quadrantChart. Types outside that list may render, but their colors are not part of the contract.

Diagrams are click-to-zoom. A click anywhere inside a pre.mermaid svg opens the diagram full-screen in a zoom stage, on every page, without any attribute from you. That means a diagram may legitimately carry more detail than fits the reading column. The stage paints var(--component-bg-color, #ffffff), so a theme that leaves that token unset puts a dark diagram on hard white — another reason the docs background token is effectively required rather than optional.

How it renders

flowchart LR
    A[Your .hbs template] --> B["&lt;pre class=&quot;mermaid&quot;&gt;"]
    B --> C[Build scans output]
    C --> D[Mermaid loaded for this page]
    D --> E[SVG, themed and zoomable]

Tabs

A tab group is one container holding a tab list and a set of panels. The container carries the class leed-tabs__container and a data-tabs-groupId; each li[role="tab"] needs a unique id and an aria-controls pointing at its panel, and each panel points back with aria-labelledby. The first tab is the one that starts selected.

<div class="leed-tabs__container" data-tabs-groupId="deploy-target">
  <div class="leed-tabs__wrapper">
    <ul role="tablist" class="leed-tabs">
      <li role="tab" id="tab-0-0" aria-controls="panel-0-0"
          tabindex="0" aria-selected="true"
          class="leed-tabs__item leed-tabs__item--active">
        <span>Staging</span>
      </li>
      <li role="tab" id="tab-0-1" aria-controls="panel-0-1"
          tabindex="-1" aria-selected="false"
          class="leed-tabs__item">
        <span>Production</span>
      </li>
    </ul>
  </div>

  <div class="leed-tabs__panels">
    <div id="panel-0-0" role="tabpanel" aria-labelledby="tab-0-0" class="block">
      <pre><code class="language-bash">deploy --env staging</code></pre>
    </div>
    <div id="panel-0-1" role="tabpanel" aria-labelledby="tab-0-1" class="hidden">
      <pre><code class="language-bash">deploy --env production</code></pre>
    </div>
  </div>
</div>

Panels hold anything — code blocks, diagrams, images, prose. Visibility is the block / hidden class pair, swapped at runtime. The <ul role="tablist"> gets not-prose added automatically at page load, so you do not need to write it.

Four things go wrong when this markup is emitted by hand rather than by the Markdown parser. Each of them fails quietly.

The attribute is data-tabs-groupId, with a capital I

The build matches that literal string when it looks the group up in your documentation configuration. A lower-case data-tabs-groupid is still valid HTML and still syncs in the browser, because attribute selectors are case-insensitive — so the group looks fine. What you lose is the build-time lookup: the configured title and icon for each tab are never found, and you get whatever raw label you typed, with no icon and no warning. Copy the attribute exactly.

Sync is by array index, not by key

When a reader picks a tab, the browser stores a bare integer under leed-tabs-<groupId> in localStorage and replays it on every container in that group, on this page and every later page. It matches by position, never by label. So two containers sharing a group id must list the same tabs, the same number of them, in the same order. Identical labels in a different order silently show the wrong panel; a container with fewer tabs than the stored index is skipped entirely and stays on its first tab.

That mechanism is why a group id should name a choice the reader makes once and keeps — their operating system, their MCP client, their language. For a per-task choice, give the container an id unique to that page: it then syncs with nothing, which is exactly what you want. The three predefined groups (mcp-clients, operating-system, and the reserved, product-generated api-languages) and how to configure titles and icons for them are covered in Tab Groups for Consistent Examples.

Style the selected state on [aria-selected="true"]

Bind every container to a group id

Give every container a data-tabs-groupId, including one-off containers that sync with nothing. In the Markdown parser the current group id is set when a container declares one and is never cleared, so an unbound container that follows a bound one inherits the previous group’s titles and icons while emitting no group attribute of its own — it looks grouped, and it does not sync. An explicit unique id costs one attribute and removes the whole class of problem.

How it renders

The container below is bound to template-formatting-layout, an id used on this page and nowhere else. Because it belongs to no configured group, its labels are exactly the text written on each tab and it carries no icons — that is the normal shape for a container answering a one-off question, and the reason nothing here syncs with a container on any other page.

layoutName: "alpha" — one of the three documentation layouts a docs set can choose between. Nothing about this panel is configured: the label is the literal text after @tab.

Alerts

Alerts — the callout boxes Leed Markdown writes as :::note, :::tip, :::info, :::warning and :::danger — are an <aside> carrying the class alert plus alert-<type>, with a .heading holding the icon and the label and a .content holding the body:

<aside class="alert alert-danger">
  <div class="heading">
    <i class="fa-icon"></i>
    <span class="description">Danger</span>
  </div>
  <div class="content">
    Some <b>content</b> with <i>markup</i> and <code>code</code>. Check this <a href="#">link</a>.
  </div>
</aside>

The five types are the whole set; there is no sixth and an unknown type gets no colors. Write <i class="fa-icon"></i> empty — the stylesheet supplies the glyph per type through the alert-<type> class, so an icon you name yourself is overwritten. The text in .description is the alert’s title and is what a custom title in Markdown sets; it is rendered uppercase by the stylesheet, so write it in normal case. Colors, borders and icons are token-driven and are restyled the way any Leed component is — see Theming Alerts for the tokens, and Cascade Layers and Overriding Leed for where your rule has to sit to win.

How it renders

The table of contents

The “On this page” rail is built by the toc helper from the rendered content, and it collects only headings that already carry an id. Its parameters, the wrapper classes it accepts and the element ids the bundled JavaScript recognizes are documented with the rest of the documentation navigation helpers, at Documentation Navigation Helpers.

A worked example

The block below combines three of the structures on this page: a tab group whose panels hold a highlighted code block and a diagram. It is the shape most hand-written tab groups take.

Complete tab group with a code block and a diagram
<div class="leed-tabs__container" data-tabs-groupId="pipeline-example">
  <div class="leed-tabs__wrapper">
    <ul role="tablist" class="leed-tabs">
      <li role="tab" id="tab-9-0" aria-controls="panel-9-0"
          tabindex="0" aria-selected="true"
          class="leed-tabs__item leed-tabs__item--active">
        <span>Command</span>
      </li>
      <li role="tab" id="tab-9-1" aria-controls="panel-9-1"
          tabindex="-1" aria-selected="false"
          class="leed-tabs__item">
        <span>Flow</span>
      </li>
    </ul>
  </div>

  <div class="leed-tabs__panels">
    <div id="panel-9-0" role="tabpanel" aria-labelledby="tab-9-0" class="block">
      <pre><code class="language-bash">leed site build --serve</code></pre>
    </div>
    <div id="panel-9-1" role="tabpanel" aria-labelledby="tab-9-1" class="hidden">
      <pre class="mermaid">
        flowchart LR
          A[src/] --&gt; B[Eleventy]
          B --&gt; C[_site/]
      </pre>
    </div>
  </div>
</div>

Three things to notice. The tab ids are unique within the page, not just within the container — tab-9-0 rather than tab-0. The group id is specific to this one example, so it syncs with nothing. And the diagram’s arrows are written as entities inside the HTML fence because they are being shown as source; in a real template you write them literally, since Mermaid reads the element’s text.

If you are migrating content the other way — from a template into CMS pages — every structure here has a Markdown form: Code Blocks, Math, Diagrams, Tabs and Alerts. Emitting the markup by hand is worth it only when the content is coming from data your template holds — a partial rendering a list, a scaffolded index page, a component you invoke with parameters. Those are covered in How Templates Work and Writing Your Own Partials.

ESC