Skip to content
Taliesin User Guide

10 Configuration reference

Every front-matter key and every _site.yml key, in one place: the closed vocabularies behind did-you-mean, with a fully annotated example of each.

Reference: Cheat sheet · CLI · Configuration · Cell options · Troubleshooting · Accessibility · Licensing

Taliesin reads configuration from two places: a document’s own front matter (the --- block at the top of a .tmd file), and, for a site or book, the project’s _site.yml. Both are closed vocabularies, validated with the same “did you mean” machinery, so this page covers both, in two parts: document front matter first, project _site.yml second. For per-cell #| options, see the Cell-options reference.

10.1 Document front matter

Every .tmd document can open with a YAML front-matter block: a --- fenced header that sets the document’s metadata, output, and behaviour. This section is the full vocabulary, every top-level key Taliesin reads, plus the nested blocks (execute:, listing:, hero:) and their children.

A minimal block is just a title:

---
title: "My post"
---

Taliesin validates this block against its own key set. Any key it does not implement, whether a typo (treme:) or a term it never honored (title-block-banner:), is reported with a “did you mean …” hint (see Closed vocabulary below). Validation only warns: the document still renders, and each warning is located so you can click it straight to the offending line in the live preview.

10.1.1 Every top-level key

The keys group into eight concerns. Types: a string is plain text; a bool is true/false; a list is a YAML sequence; an include is a path, a { text: … } or { file: … } map, or a list of those.

10.1.1.1 Identity and metadata

KeyTypeMeaning
titlestringThe document title. Drives <title>, the title block, OpenGraph/social meta, and the nav/listing label
subtitlestringA secondary line shown under the title in the title block
authorstring, list, or list of mapsAuthor name(s). A string or list of strings is a plain byline; the structured form adds affiliations and links
datestringPublication date (shown in the title block; sorts a listing:)
descriptionstringSummary used for the <meta name="description"> and the og:description a shared link unfurls with
categorieslistTags for the page; each becomes a <category> in the Atom feed

10.1.1.2 Layout, social card, and drafts

KeyTypeMeaning
imagestringThe image on a listing: card, and the og:image a shared link unfurls with (absolutized against _site.yml’s url:; an absolute URL is used as written)
image-altstringThe alt text on this page’s listing: card image. Not rendered on the page itself. Set it whenever you set image:: an image: with no image-alt: warns, since only you know whether the picture carries meaning. If it is purely decorative say so explicitly with image-alt: "", which silences the warning and emits an empty alt
draftbooltrue keeps the page (or book chapter) out of a build entirely (no output, nav, listing, or prev/next), and the build reports it as “not published”. It is also out of scope for build --check-only, since a page that does not ship should not fail a pre-flight, and the lint names the drafts it held back so the verdict is not read as wider than it is. preview still shows it, badged, and does lint it, so you see its diagnostics where you are writing
title-block-stylestringnone keeps the title metadata (still drives <title>, OpenGraph, nav) but hides the visible <h1> title block

10.1.1.3 Raw-HTML includes

There is no raw-injection key, per document or project-wide. css:, include-in-header:, include-before-body: and include-after-body: were removed on 2026-08-02, along with the _site.yml css:, body-start: and body-end: beside them; _site.yml’s head:, kept back then as the one surviving hatch, went on 2026-08-18 when it was still reaching zero documents. A leftover head: is reported as an unknown config key and injects nothing.

Raw HTML written in the document body still passes through, because Markdown says it does. That is the remaining way to put markup on a page, and it is per page rather than project-wide by design.

10.1.1.4 Contents, bibliography, and citations

KeyTypeMeaning
tocboolForces the table of contents on or off for this page. Leave it out and it is automatic: an article page with enough headings gets one. No effect in a book chapter: a book navigates by its Chapters drawer
bibliographystring | listA .bib file (or list of them) enabling [@key] citations and an auto-generated References section

10.1.1.5 Code execution

KeyTypeMeaning
executemapDocument-wide defaults for code cells (cache:); see execute:

10.1.1.6 Site page blocks

These two turn a page in a site or book into something special. They are detailed in their own sections below.

KeyTypeMeaning
listingmapTurns the page into an index: a card per document under contents:. See listing:, and Recipes for one in context
heromapA hero banner at the top of a landing page (eyebrow, headline, lead, call-to-action buttons). See hero:

10.1.2 author: (who wrote it)

The short spellings are what most pages want, and neither is going anywhere:

author: "Ada Lovelace"                      # one author
author: ["Ada Lovelace", "Grace Hopper"]    # several

For a paper or project page, an author is more than a name. The structured form takes a list of maps:

author:
  - name: "Ada Lovelace"
    affiliation: "Analytical Engine Institute"
    url: "https://example.org/ada"
    equal: true
  - name: "Charles Babbage"
    affiliation: ["Analytical Engine Institute", "Analytical Society"]
    equal: true
Sub-keyTypeMeaning
namestringThe name, as it should read in the byline. The only required part
affiliationstring or listInstitution(s). Written as names, not as index numbers
urlstringA personal page; makes the name a link in the byline
equalbooleanMarks an equal-contribution author (* in the byline, with a note under it)
contributionstringWhat this person did. Collected into the appendix’s Author Contributions section

You never write an affiliation number. Every other tool in this space asks you to keep a list of institutions and put affiliation: 1 against each author. Taliesin reads the institution’s name and derives the superscripts from the order they first appear, so two authors who name the same institution share a number automatically, there is no second list to keep in sync, and reordering authors cannot silently re-attribute anyone. In the example above both authors show 1, and Babbage adds 2.

One declaration feeds everything downstream that has to name a person: the byline, the appendix below, and the Atom feed. A typo in a sub-key is a warning, not a silent drop.

10.1.3 The appendix

Who did what, in a section of its own rather than a sentence prose tends to forget. Declare a contribution: on any author and Taliesin appends an appendix after the references.

---
title: "On the Analytical Engine"
author:
  - name: "Grace Hopper"
    contribution: "Designed the study and wrote the analysis."
  - name: "Charles Babbage"
    contribution: "Built the engine and ran the experiments."
---

A contribution is declared beside the name, not in a list of its own. Every other tool makes you write a separate block keyed by author name, which then has to match a name you typed somewhere else, and silently drops the statement when the two spellings drift apart. As an author: sub-key it cannot miss, and an author who declares none simply contributes no row. Declare none at all and there is no appendix.

Nothing in the appendix reads a clock: there is no “accessed” date and no build timestamp, so a rebuild of an unchanged page is byte-identical and the _freeze/ execution cache is never invalidated by it.

10.1.4 execute: (document-wide cell defaults)

A nested execute: block sets defaults for every code cell in the document. A cell’s own #| option always overrides the document default.

Sub-keyTypeDefaultMeaning
cachebooltruefalse opts every cell out of the _freeze/ execution cache (always re-run)
---
title: "Tables, not code"
execute:
  cache: false       # always re-run, never restore from _freeze/
---

cache: is the whole block on purpose. echo: and include: were retired on 2026-08-02: they were document-wide defaults for something every real document says per cell, and a default that silently changes every cell reads worse than saying it on the cells you mean. Write #| echo: false in each cell instead.

See the Cell-options reference for the per-cell #| equivalents that override these.

10.1.5 listing: (an index page)

A listing: block crawls a directory and renders a card or row per document it finds, newest first and capped however you ask. It is how you build a blog index or a projects page. There is no sort: key: a listing is always newest-first by date, which is what every listing that used to spell it out asked for.

Sub-keyTypeDefaultMeaning
contentsstring(required)A single directory to crawl for documents, relative to this page (one directory string, not a list or glob). Every .tmd under it with a title: becomes a card; without it, there is nothing to list
idstring(none)Fill a matching ::: {#id} block in the page body instead of appending the listing to the end
typestring(list)grid tiles the cards with their images; list is a reading-first stacked layout that keeps each card’s image: as a thumbnail beside the text; the default (anything else, or omitted) is the same stacked layout without images, for a formal text list
max-itemsint(none)Cap the number of items shown; omit it to list every match
---
title: "Blog"
listing:
  contents: posts        # crawl ./posts for documents
  type: grid             # grid of cards (vs. the default stacked list)
  max-items: 12
---
Tip

Set id: and place a ::: {#recent} block where you want the listing to land, so you can write an intro above it and the cards below it on the same page.

A row’s date and image come from each listed page’s own front matter, as does categories:, which no longer shows on the row itself but still tags the page in the Atom feed:

---
title: "A post about rust"
date: 2026-06-20
categories: [rust, performance]   # these become <category> in the Atom feed
image: cover.png                  # card thumbnail on a grid/list listing
image-alt: "A flame graph of the hot loop"   # required whenever you set `image:`
---

10.1.6 hero: (a landing banner)

A hero: block puts a full-width banner at the top of a landing page: an eyebrow label, a headline, a lead paragraph, and a row of call-to-action buttons.

Sub-keyTypeDefaultMeaning
eyebrowstring(none)A small label above the headline
headlinestring(falls back to title)The large banner heading
leadstring(none)A lead paragraph under the headline
actionslist(none)A list of buttons, each { text, href, primary }. An entry missing text or href is dropped, and a misspelled key in one is a warning

An actions: entry takes exactly three keys:

KeyTypeMeaning
textstringThe button’s visible label
hrefstringWhere it goes (a .tmd path is rewritten to .html)
primarybooltrue styles this as the filled, prominent button
---
title: "Taliesin"
hero:
  eyebrow: "A dev server for .tmd"
  headline: "Render .tmd to HTML, fast."
  lead: "Click-to-source, block-level incremental updates, a warm kernel."
  actions:
    - { text: "Get started", href: "using/getting-started.tmd", primary: true }
    - { text: "Reference",   href: "reference/cli.tmd" }
---

10.1.7 Closed vocabulary and “did you mean”

Taliesin’s front-matter vocabulary is a closed set: the keys above are the only ones it reads. Anything else warns, with a “did you mean …” hint offered when an unknown key is within edit distance 2 of a known one, so titel: is caught and nudged toward title:. The same closed-vocabulary check covers the immediate children of execute:, listing: and hero: (including each actions: entry).

One thing to know about references:

  • There is no CSL engine, and no key that asks for one. References always render in the single built-in IEEE style. A csl: line is an ordinary unknown key: the build says so and moves on.

10.1.8 Editor autocomplete

taliesin lsp completes every key on this page, shows its description on hover, and flags an unknown one as you type: the same closed set the validator enforces at render time, so the editor and the renderer always agree. Any LSP editor gets it; the VS Code companion starts the server for you. See Preview.

10.1.9 A fully annotated example

Putting it together, here is a front-matter block exercising many of the keys at once, with a comment on each:

---
# --- Identity / metadata ---
title: "Decoding the loss curve"
subtitle: "What the wiggles mean"
author: "Andreas Bogossian"
date: "2026-06-27"
description: "A close read of a training run."   # og:description + meta desc
categories: [machine-learning, training]  # tags -> Atom feed <category>

# --- Output / format ---

# --- Social / images ---
image: cover.png                          # listing card + og:image (needs url:)
image-alt: "A loss curve trending down"   # alt text for that card image

# --- Contents / citations ---
toc: true                                 # force the TOC on (else automatic)
bibliography: refs.bib                    # enables [@key] + References

# --- Code execution defaults (a cell's #| cache overrides this) ---
execute:
  cache: true                             # use the _freeze/ cache (default)

# --- Drafts ---
draft: false                              # true: preview shows it, build skips
---

10.2 Project _site.yml

A directory with a _site.yml is a site or book project. The schema is flat: Taliesin is HTML-only, so there’s no format: html: nesting, and the project type is inferred (presence of chapters: ⇒ a book). A nested config (a project: / website: / book: / format: block) is no longer parsed: its unknown top-level keys warn, and its nested values are ignored. Flatten it (see the key table below). There is no _metadata.yml cascade: the root config sets defaults, a page’s own front matter overrides them.

A website:

title: "My Site"
description: "Notes on things"
url: "https://example.com"
favicon: favicon.svg
logo: logo.svg           # brand image in the navbar (a book: topbar + drawer)
bibliography: refs.bib   # one .bib for every page (see below)

nav:                     # a list is the left side; or { left, right }
  - { text: Blog,     href: blog.tmd }
  - { text: Projects, href: projects.tmd }

footer:                  # a string is left text; or { left, center, right }
  left: "© 2026 Me"
  right:
    - { icon: github, href: "https://github.com/me" }

A book: just add chapters: (its presence makes it a book; no type:):

title: "My Book"
author: "Me"
chapters:
  - index.tmd
  - part: "Part One"     # group chapters under a part header
    chapters: [a.tmd, b.tmd]
  - file: c.tmd          # { file:, text: } overrides the chapter label
    text: "Conclusions"

A chapter entry is a bare path (- a.tmd), a { part:, chapters: } group header, or a { file:, text: } mapping whose text: overrides the chapter label (the override also works inside a part’s list). Without text:, the label is the chapter’s first # H1, then its front-matter title:, then the file stem.

KeyMeaning
title, author, description, urlProject metadata (title is the navbar brand / book title)
faviconTab icon (defaults to the Taliesin mark)
logoBrand image, replacing the wordmark in the navbar (a website) or the topbar and the chapter drawer (a book). Project-relative like favicon; title becomes its alt. There are no size or position sub-keys: the stylesheet caps it against the bar it sits in
navNavbar items: a list (left), or { left, right }
footerFooter: a string, or { left, center, right } of items
chaptersBook chapter order (a file, { file, text }, or { part, chapters }); presence ⇒ a book
bibliographyA .bib shared by every page of the project (a path, or a list of them), relative to _site.yml. Merged under each page’s own bibliography:, so a page can cite a shared key and still add or correct entries locally
pythonPin the interpreter this project runs code cells against (see Interpreter selection below). A path or command name; the highest-precedence source, so a committed project is reproducible regardless of the ambient shell

A shared bibliography. bibliography: in _site.yml is read by every page, so a series of posts citing one reference file declares it once instead of repeating the line in each front matter. It is a layer, not a fallback: a page that also declares its own bibliography: gets both, with the page’s entry winning on a shared key, the way to correct one reference for one post without editing the file every other page reads.

Two read-only checks come with it, reported against _site.yml by the lint and build. A duplicate key inside the shared file would otherwise silently last-write-win. An entry no page cites is dead weight; that one is site-wide by necessity, since a shared entry one page cites is in use however many pages leave it alone. It is advice rather than an error, so it never fails a build unless you ask with --strict. A page’s own bibliography: gets the same never-cited check scoped to that page. Neither check ever edits a .bib.

Interpreter selection. A {python} cell needs a Python with ipykernel. Taliesin picks the interpreter by the first source that exists, so a project can pin its own without fighting your global shell:

  1. the _site.yml python: field;
  2. a project-local .venv (<project>/.venv/bin/python, then .venv/bin/python3);
  3. the TALIESIN_PYTHON environment variable;
  4. an ancestor .venv, found by walking up from the project directory;
  5. the bare python3 on your PATH.

Because a project .venv (or a python: pin) beats a global TALIESIN_PYTHON, a committed project reproduces the same interpreter on any machine. The chosen interpreter and where it came from are logged the first time the kernel starts (python -> /proj/.venv/bin/python (from .venv)), and taliesin doctor prints an Environment section listing it plus whether its Jupyter kernel package is importable. A single .tmd with no _site.yml uses the same order minus the field (.venv beside the file, then env, then default).

Icons. A nav/footer item can be { icon: <name>, href: … } instead of raw HTML; bundled glyphs: github, linkedin, rss, x, mastodon, bluesky, email.

Table of contents. toc: is a page key, not a _site.yml key. The site-wide form was retired on 2026-08-02: it sat in front of a gate that already answers the same question per page, and answered it worse. Writing toc: in a _site.yml is an unknown config key error, in a website and a book alike; delete it.

Left out of a page’s front matter, the rail is automatic: an article page earns one by carrying at least three headings and being neither a listing nor a hero page, so a short page never gets a near-empty rail. An explicit toc: true / toc: false in the page’s own front matter always wins over that gate. On narrow screens the list appears inline after the article instead of floating as a sidebar.

A book never shows one, whatever any page says: the Chapters drawer is its navigation, and it is strictly more detailed than the rail was. That gate sits ahead of the page key on purpose, so a chapter’s toc: true cannot reinstate a removed surface.

Validation. _site.yml keys get the same closed, located, warn-only “did you mean” check as front matter (see Closed vocabulary). That check is what the tool itself enforces; the drift-locked Draft 2020-12 schema described below is for your editor, and is optional.

10.2.1 Editor autocomplete (config schema)

_site.yml is the one config surface no language server covers, so Taliesin ships a JSON Schema for it: your editor’s YAML language server can then autocomplete keys, show hovers, and flag unknown keys as you type, before you render. The schema describes the same key set Taliesin validates against at render time, so the editor and the renderer always agree.

Wiring it up is the same one step in every editor, VS Code included: copy crates/core/assets/schema/tali-site.schema.json out of the repository to somewhere your YAML language server can reach, then add the modeline at the very top:

# yaml-language-server: $schema=tali-site.schema.json
title: My site

Front matter needs no schema here. It lives inside a .tmd file, where no YAML language server looks; taliesin lsp is what completes and validates it, the same key set, from the same validator, in any LSP editor. See Preview.

10.2.2 Project structure & reserved names

A multi-page project is just a directory with a _site.yml. Taliesin discovers the pages by walking that directory, with one rule: anything whose name starts with _ or . is not a page. That single convention is what keeps config, partials, caches, and build output from turning into navigable pages.

my-site/
├── _site.yml            # project config; presence makes this a site/book
├── index.tmd            # a page (the home page)
├── about.tmd            # a page
├── posts/
│   └── first.tmd        # a page (nested pages are discovered too)
├── _includes/           # `_`-prefixed ⇒ skipped: partials for {{< include >}}
│   └── _disclaimer.tmd
│   └── my-theme/
├── _freeze/             # `_`-prefixed ⇒ skipped: execution cache (gitignored)
└── _site/               # build output (_book/ for a book; else build --out)

Reserved names, all skipped by page discovery:

NameWhat it is
_site.ymlthe project config (its presence is what defines a project)
_includes/a convention for {{< include >}} partials; the _ keeps them out of the page set (the leading _ on the partial files themselves, e.g. _intro.tmd, is also enough)
_freeze/the persistent execution cache, keyed by content hash; gitignore it
_site/ / _book/build output (build <dir> writes here; write elsewhere with build --out <dir>)
anything .-prefixedhidden files/dirs (.git, …) are skipped too

A 404.tmd is special-cased the other way: it builds to 404.html at the site root (most static hosts serve it for unknown URLs) but stays out of the nav.