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” check, so this page covers both: 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 and behaviour. The opening --- is the file’s first line, and the block ends at the next --- (or ...) line. 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 by concern. 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 you know the verdict does not cover them. preview still shows it, badged, and lints it, so you see its diagnostics where you are writing. A value that is not a boolean (draft: 1) is reported, and the page is held back as a draft until it is one
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:, the only injection key kept at that point, was removed on 2026-08-18, when no document used it. A leftover head: is reported as an unknown config key and injects nothing.

Raw HTML written in the document body still passes through, as Markdown specifies. That is the remaining way to put markup on a page, and it is per page, not 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 keys apply to a page in a site or book. Each is detailed in its own section 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)

Most pages use one of the two short spellings, and both remain supported:

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

For a paper or project page that needs 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.

Everything that names a person reads the same author: declaration: 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

The appendix lists who did what, in an Author Contributions section of its own. Declare a contribution: on any author and that section appears 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 always matches its author, and an author who declares none 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 only sub-key. 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. A contents: that names no directory is reported
idstring(none)Fill a matching ::: {#id} block in the page body (an empty one is enough) instead of appending the listing to the end. An id that matches nothing appends the listing and draws a warning
typestring(none)list keeps each card’s image: as a thumbnail beside the text; the default (omitted) is the same list without images, for a formal text list. Any other value is reported
max-itemsint(none)Cap the number of items shown (a whole number; any other value is reported and caps nothing); omit it to list every match
---
title: "Blog"
listing:
  contents: posts        # crawl ./posts for documents
  type: list             # show each post's image: thumbnail
  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 type: 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).

There is no CSL engine, and no key that selects one. References always render in the single built-in IEEE style. A csl: line is an ordinary unknown key: the build warns and continues.

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

This front-matter block uses 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>

# --- 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 (the presence of chapters: makes it 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 project does not contain another: a folder inside it with its own _site.yml is walked as part of the outer project, and that _site.yml is reported as ignored; build or preview the folder on its own to use it.

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: 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 }. Each item is a link, so an item with no href: is reported and left out of the navbar. A book has no navbar: its topbar and Chapters drawer replace it
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.

Read-only checks come with it, reported against _site.yml by build and build --check-only. A duplicate key inside the shared files would otherwise silently last-write-win. An entry that is never closed ends where the next entry starts, and the message names its file and line. Each file is read on its own, so an entry left open at the end of one file cannot swallow the start of the next. A key that [@…] cannot name (one containing & or ') is reported and its entry skipped, since no citation could reach it. An @string macro nothing defines is reported too. The shared files are read before a page’s own, so a page’s .bib may use a macro a shared file defines (the IEEEabrv.bib pattern). A file that is not UTF-8 is reported as such. These are warnings, so they fail build --check-only with or without --strict. A page’s own bibliography: gets the same checks, reported on its bibliography: line. The checks never edit 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 ahead of your global shell setting:

  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. Any other name is reported, and the item shows its text or URL instead.

Table of contents. toc: is a page key, not a _site.yml key. The site-wide form was retired on 2026-08-02: it overrode the per-page gate, which answers the same question better. Writing toc: in a _site.yml is reported as an unknown config key, in a website and a book alike; delete it.

Left out of a page’s front matter, the rail is automatic: an article page gets one when it has at least three headings and is 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. Below 960px the rail has no room, so the list moves into the navbar’s menu button under On this page, which leaves the top of the screen to the article and keeps the contents one tap away.

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 rule takes precedence over the page key, so a chapter’s toc: true cannot bring the rail back. A chapter built on its own (taliesin build chapter.tmd) has no drawer, so it gets the rail like any page.

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 only part of the configuration 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.

Setup is the same 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 completes and validates it in any LSP editor, using the same validator and key set. See Preview.

10.2.2 Project structure & reserved names

A multi-page project is 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 rule keeps config, partials, caches, and build output from becoming 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 (keep it out of git)
└── _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. Because a host serves that one file at any depth, every link, image and stylesheet in it is written from the site root (/blog.html), which assumes the site is deployed at the root of its domain.