Output #

View this page as Markdown

This page lists what a build writes: the page tree, the copied files, the page head, the callouts, diagrams, and pager, and the styling names. It also covers the machine files, the 404 page, and the redirect stubs. It says what a failed replacement leaves behind, and it lists every name in the generated HTML and the stylesheet.

The tree #

The build writes each page to <slug>/index.html inside the output folder, the root page as index.html. Pages share docs.css and, when the build was given --stylesheet, site.css. Each page includes the full navigation with the current page marked. On a window wider than 800 pixels, the navigation stays in view while the article scrolls. Where it is taller than the window, it scrolls on its own, and its scrollbar shows only while the pointer or the focus is in it.

The stylesheet, navigation, and redirect links are relative, so the tree works under any base path on a static server. Each page or redirect link ends in a folder slash, such as ../guides/, and the server returns that folder's index.html. A browser that opens the tree from disk shows the folder instead, so only the first page opened from disk displays correctly. The tool does not rewrite the links a shell supplies, so the shell's author must keep them correct.

Every other file in the source folder, an image or a download for example, is copied byte for byte to the same path in the tree. A file in a folder whose name begins with a dot, or whose own name begins with one, is not copied. Nor is a file that only hidden pages reference, unless the build passes --include-hidden. A page's images and file links are rewritten to the copied file's address relative to the page. Images and other files gives the rules.

The root names docs.css, site.css, docs.json, llms.txt, llms-full.txt, sitemap.xml, 404.html, and mermaid.min.js are reserved for the build's own files, so no page and no copied file can take one.

A built tree has this shape.

site/
  index.html
  index.md
  docs.css
  site.css          where --stylesheet named one
  docs.json
  llms.txt
  llms-full.txt
  sitemap.xml       where --origin named one
  404.html
  mermaid.min.js    where a page draws a diagram
  getting-started/
    index.html
    index.md
  guides/
    diagram.png       a copied file, at its source path
    code-blocks/
      index.html
      index.md
  old-slug/
    index.html        a redirect stub

Each page carries one short inline script. It places the copy control on code blocks, a button with two icons at each block's right, which copies the block's text as authored. On a page that draws a diagram, it also starts Mermaid from the tree's own mermaid.min.js, which that page alone loads (Diagrams). Nothing else runs in the browser.

The page head #

Each page's head names the page and the corpus it belongs to. The <title> element holds the page's title, a spaced em dash, and the home page's title, such as Add a page — Turn Zero Docs. The home page's title already names the corpus, so the home page carries its title once. In a corpus without a home page, each page carries its own title alone. The navigation, the index, and the manifest keep the page's own title.

The head also carries the share tags that a chat app or a social site reads when someone shares a link to the page:

Tag Value
<meta name="description"> The page's description.
<link rel="canonical"> The origin given with --origin, followed by the page's route, such as https://example.com/docs/guides/add-a-page/. Present only with --origin.
og:title, twitter:title The same text as the <title> element.
og:description, twitter:description The page's description. Absent where the description is empty.
og:url The same address as the canonical link. Present only with --origin.

The canonical link, og:url, and the sitemap are the only absolute addresses the tool writes. Give the origin of the site that search engines should list. Then a tree served at more than one address names the same page as canonical in every copy. The share tags that every page has in common, such as the site's name, its share image, and the card form, belong to your site: put them in the head of your shell.

Publication and recovery #

The build checks the source and the output folder before it writes anything. It generates the whole site in a temporary staging folder beside the output folder, named .tzdocs-stage-*. A validation, rendering, or staging-write failure leaves the previous output unchanged.

After generation succeeds, the build replaces the output folder in these steps:

  1. It renames an existing output folder into a .tzdocs-backup-* folder beside it.
  2. It renames the generated tree to the output path.
  3. If that rename fails, it renames the previous output back. If that fails too, the error message says where the previous output remains, inside the backup folder. Do not delete that folder before you recover the output from it.
  4. After a successful replacement, it removes the backup. If that cleanup fails, the error message says that the new site is published and gives the remaining backup folder.

After a nonzero exit, read the message: the output may have changed. Refusals and findings gives the messages.

The replacement is not atomic. A crash or a concurrent change to the file system during it can require manual restoration or cleanup. Run one build at a time into a given output folder, and deploy only after you inspect a successful result. A missing or unreadable source file can produce a Node.js error rather than a formatted finding.

Callouts, diagrams, and the pager #

Callouts #

A block quote that opens with one of the five callout markers (Page source) becomes a div classed docs-callout. Its data-callout attribute holds the type, such as note. Its first child is a p classed docs-callout-title holding the title, such as Note, and the rest of the quote follows.

<div class="docs-callout" data-callout="warning">
<p class="docs-callout-title">Warning</p>
<p>The build replaces the whole output folder.</p>
</div>

Diagrams #

Where Mermaid is installed and the build does not pass --diagrams-as-code, a mermaid fence becomes a div classed docs-diagram. It holds the diagram's source, escaped, with its line breaks kept. The build copies Mermaid's browser file, byte for byte, to mermaid.min.js at the tree root. Each page holding a diagram loads it with one deferred script tag in its head.

Once the page has loaded, its inline script draws each diagram as an SVG image. Under a dark prefers-color-scheme it uses Mermaid's dark theme. The div is not a pre, so a diagram has no copy control.

Otherwise the fence renders as a code block in the language mermaid, no page loads a script file, and the tree holds no mermaid.min.js. Which file the build found, and its version, are inputs of the build like the source files.

Next and previous #

With --next-previous, each page's last element in the docs-main column is the pager. It is a nav classed docs-pager, labelled aria-label="Previous and next pages". It holds a link to the previous page and a link to the next one, in the navigation order the manifest and the whole text follow. The first page has no previous link and the last page no next link.

<nav class="docs-pager" aria-label="Previous and next pages">
<a class="docs-pager-prev" rel="prev" href="../"><span>Previous</span>Home</a>
<a class="docs-pager-next" rel="next" href="../guides/add-a-page/"><span>Next</span>Add a page</a>
</nav>

The links are classed docs-pager-prev and docs-pager-next and carry rel="prev" and rel="next". A section in the navigation that opens and closes is no page, so the pager never links one, and neither does it link a hidden page. Without the flag, no page carries the pager.

The styling surface #

docs.css is the tool's own stylesheet, complete in itself: the layout, the typography, the navigation, the code blocks, the callouts, the diagrams, the pager, and twenty-five color properties. On a narrow screen, the page stays within the screen's width: a long word in the text wraps, a file path in inline code included, and a wide table scrolls sideways inside itself. A site restyles the tree with a site stylesheet or with a shell's own stylesheet, and both target the names below. Style the site is the guide for the stylesheet and for the shell.

The colors #

The twenty-five custom properties are colors, declared on the .docs-shell element. Each has a light default and, for all but the code block's own and token properties, a dark default under the viewer's prefers-color-scheme. A site redefines them on that element. A value declared on :root, html, or body is inherited and loses to the tool's own declaration.

Property Role
--docs-bg page background
--docs-text body text
--docs-muted secondary text, section names, captions
--docs-nav-text navigation links
--docs-link links, the current page, the emphasized line's bar
--docs-border rules and table borders
--docs-code-bg inline code background
--docs-pre-bg code block background
--docs-pre-text code block text
--docs-quote block quotes
--docs-tok-comment code tokens: comments and quotes
--docs-tok-keyword code tokens: keywords, literals, selector tags, template tags, doc tags
--docs-tok-string code tokens: strings, regular expressions, additions
--docs-tok-number code tokens: numbers, symbols, bullets
--docs-tok-title code tokens: function and class names, sections, tag names, selectors
--docs-tok-attr code tokens: attributes, variables, properties
--docs-tok-type code tokens: types, built-ins, pseudo selectors
--docs-tok-meta code tokens: meta lines and their keywords
--docs-tok-deletion code tokens: deletions in a diff
--docs-code-hl the emphasized line's band
--docs-callout-note a Note callout's bar and title
--docs-callout-tip a Tip callout's bar and title
--docs-callout-important an Important callout's bar and title
--docs-callout-warning a Warning callout's bar and title
--docs-callout-caution a Caution callout's bar and title

The code block keeps one dark background under both schemes, so its own and the token properties are declared once and their light and dark defaults are the same. The example below is a complete restyle, and the tool's own test suite builds with this exact text:

.docs-shell {
  --docs-bg: #0b1020;
  --docs-text: #e6edf6;
  --docs-link: #7cc4ff;
  --docs-pre-bg: #05080f;
}
.docs-main pre code .docs-line-hl {
  background: rgba(124, 196, 255, 0.12);
}

The selectors and the names #

A site rule that uses the tool's own selector wins, because the site's stylesheet comes later. A rule of lower specificity than the tool's does not win, whatever the link order. The parts, with the selector to write and the names the markup uses:

Part Selector to write Names
the wrapper .docs-shell docs-shell
the navigation .docs-nav, .docs-nav a, .docs-nav a[aria-current="page"] docs-nav, aria-current="page", aria-label="Documentation"
a section's name, which opens and closes it .docs-nav-section, .docs-nav details[open] > .docs-nav-section::after docs-nav-section
the article column .docs-main, .docs-main h1, .docs-main h2, .docs-main h3, .docs-main p, .docs-main a, .docs-main code, .docs-main table, .docs-main blockquote docs-main
a heading's anchor link .docs-main .docs-anchor, .docs-main :hover > .docs-anchor, .docs-main .docs-anchor:focus-visible, .docs-main .docs-anchor:hover docs-anchor
the page's Markdown link .docs-main .docs-source docs-source
a code block .docs-main .docs-code, .docs-main .docs-code figcaption, .docs-main pre, .docs-main pre code docs-code, data-language, the language-* class
a line, an emphasized line .docs-main pre code .docs-line, .docs-main pre code .docs-line-hl docs-line, docs-line-hl
the copy control .docs-main pre .docs-copy, .docs-main pre .docs-copy:hover docs-copy, docs-copy-icon, docs-copied-icon, data-copied
a callout .docs-main .docs-callout, .docs-main .docs-callout-title, .docs-main .docs-callout[data-callout="note"], .docs-main .docs-callout[data-callout="note"] .docs-callout-title, and the same two for tip, important, warning, and caution docs-callout, docs-callout-title, data-callout
a diagram, a drawn diagram .docs-main .docs-diagram, .docs-main .docs-diagram[data-processed] docs-diagram, data-processed
the pager .docs-main .docs-pager, .docs-main .docs-pager a, .docs-main .docs-pager-next docs-pager, docs-pager-prev, docs-pager-next, aria-label="Previous and next pages", rel="prev", rel="next"
the default layout's body .tzdoc-page tzdoc-page

The markup is the same on every page:

  • The docs-shell wrapper contains the docs-nav navigation and the docs-main article column.
  • The navigation is a nav element containing a ul of li elements. Each li contains an a, or, for a section with other entries, a details element. Its summary is classed docs-nav-section and contains the section's title, and its ul lists the entries. The details element is open on the pages the section contains and closed elsewhere, and the summary's ::after draws the chevron.
  • Every heading the page's Markdown renders ends with an a classed docs-anchor, reading #, with the accessible name Link to this section, that links to the heading's own anchor.
  • The anchor link is hidden until a reader points at its heading or moves keyboard focus to the link. It keeps its place after the heading's text while hidden, so the heading does not shift when it appears.
  • Beneath the first first-level heading of the page's content, or at its top where it has none, a p classed docs-source links to index.md, the Markdown source the build writes beside the page.
  • A code block is a figure classed docs-code. It has data-language where the fence gives a language, and a figcaption where a title was given.
  • Inside the figure are a pre and a code element, classed language-* where the fence gives a language, with one docs-line span per line.
  • The copy control is a button classed docs-copy, which the page's one script places inside each pre. It contains two icons and has a data-copied state.
  • A callout, a diagram, and the pager have the markup Callouts, diagrams, and the pager gives.

The token classes, such as hljs-keyword and hljs-string, are the highlighter's class names at its pinned version. The highlighter adds a second class after some of them, such as function_. A site colors tokens through the token properties rather than by class.

A copied stylesheet's own relative references, such as url(...), resolve from the tree root. The body rules (the margin reset, the font stack, and the color scheme) target the default layout's body class, tzdoc-page, and never a shell's body. A site's own base typography is therefore untouched.

Machine files #

Every build writes four files for scripts and AI agents. Start with llms.txt, the index: one line per page with its title, route, and description. Read docs.json to find headings and sections, then read only the relevant Markdown copies. Read llms-full.txt when the whole corpus is needed.

The index #

llms.txt at the tree root contains, in this order:

  • a first-level heading with the home page's title;
  • a block quote with the home page's description, where it has one;
  • a Start here heading listing the home page and the root's own pages;
  • one heading per top-level section, listing the pages that --full-text-exclude does not leave out, and no heading for a section whose every page is excluded;
  • a final Optional heading listing the pages that --full-text-exclude left out of the whole text.

Every page line has the form - [title](route): description, without the colon and the description where the description is empty. For a corpus without a home page, the index's first-level heading is Documentation. The index reserves the Start here and Optional headings for itself (Source-level refusals).

The index of a corpus built with --base /docs has this form.

# Turn Zero Docs

> One command turns a folder of Markdown files into a static documentation site.

## Start here

- [Turn Zero Docs](/docs/): One command turns a folder of Markdown files into a static documentation site.

## Getting started

- [Getting started](/docs/getting-started/): Install the published tzdocs package, check a folder of Markdown, and build the site.

## Optional

The manifest #

docs.json at the tree root lists every page in navigation order:

Member Contains
base The base path.
version The corpus version, present only where --corpus-version gave one.
pages[].route The page's route.
pages[].title The page's title.
pages[].description The page's description.
pages[].section The section word: the word the page's section key declares, or else the page's first path segment, its own slug word at the root, and home for the home page.
pages[].in_full_text Whether the page is in the whole text.
pages[].headings Each heading's depth, text, and anchor.
moved[].from A route a page's redirects_from names, under the base path. The list is empty where no page moved.
moved[].to The route of the page that names it.

The manifest has this form.

{
  "base": "/docs",
  "version": "0.2.0",
  "pages": [
    {
      "route": "/docs/",
      "title": "Turn Zero Docs",
      "description": "One command turns a folder of Markdown files into a static documentation site.",
      "section": "home",
      "in_full_text": true,
      "headings": [
        { "depth": 1, "text": "Turn Zero Docs", "anchor": "turn-zero-docs" },
        { "depth": 2, "text": "Start here", "anchor": "start-here" }
      ]
    }
  ],
  "moved": [
    { "from": "/docs/output/", "to": "/docs/reference/output/" }
  ]
}

The Markdown copy #

<slug>/index.md beside every page contains the page's source Markdown, front matter included, line endings normalized. An agent that needs the source text rather than the rendered HTML reads this file.

The whole text #

llms-full.txt at the tree root contains every page's source, joined in navigation order. Each page's source follows an HTML comment giving its route under the base path. The pages under the slug prefix given to --full-text-exclude are left out. They stay pages, with their Markdown copies and their manifest rows, and appear under the index's Optional heading.

Each entry has this form.

<!-- /docs/ -->
---
title: Turn Zero Docs
...

The tool writes the index, the manifest, and the whole text at the tree root and nowhere else. It does not write an llms.txt at the site's root. Where several documentation trees share one site, the site's owner writes the root index that lists them.

Which pages the four files include follows the page's visibility and the --include-hidden flag (Add a page, step 4).

The sitemap #

With --origin, the build also writes sitemap.xml at the tree root, for search engines. It lists one <loc> per built page, in navigation order: the origin followed by the page's route under the base path. It lists no redirect stub and carries no lastmod date, since the output carries no timestamp. Without --origin the build writes no sitemap, because a sitemap must give absolute addresses.

<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url><loc>https://example.com/docs/</loc></url>
<url><loc>https://example.com/docs/getting-started/</loc></url>
</urlset>

The 404 page #

Every build writes 404.html at the tree root, the name GitHub Pages, Netlify, and Cloudflare Pages show for an address that matches no file. It holds the heading Page not found, one sentence, and a link to the home page, and it carries <meta name="robots" content="noindex">. Under --shell it renders into the shell.

A host serves the page at any depth, so its stylesheet links and its home link begin with the base path, such as /docs/docs.css. They are the only root-relative paths the tool writes. The page carries no navigation, no pager, and no Markdown link, and it is no page of the corpus: the machine files and the sitemap do not list it. Publish the site is the guide.

Redirect stubs #

Each redirects_from entry produces <old-slug>/index.html, a minimal page that refreshes to the new slug by a relative path. With --origin, a stub also carries a canonical link to the new page's address. Stubs have no navigation and no shell, and no page entry in the machine files. docs.json lists each one under moved, so a script or an AI tool reading the manifest reaches the new page without reading a stub.

Reproducible builds #

Building with identical sources, supplied files, options, Mermaid file, and tool version produces byte-identical output:

  • directory listings are sorted before they are read;
  • written paths use one separator on every platform;
  • input line endings are normalized at read;
  • heading anchors follow the tool's own anchor rule, duplicates suffixed -2, -3, and so on;
  • code tokens depend only on the block's text, its language, and the pinned highlighter version;
  • no timestamp is written anywhere.

A diff between two such builds shows content changes and nothing else. A change to the tool, its renderer, its highlighter, or the build options can also change the output.