Output #
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:
- It renames an existing output folder into a
.tzdocs-backup-*folder beside it. - It renames the generated tree to the output path.
- 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.
- 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-shellwrapper contains thedocs-navnavigation and thedocs-mainarticle column. - The navigation is a
navelement containing auloflielements. Eachlicontains ana, or, for a section with other entries, adetailselement. Itssummaryis classeddocs-nav-sectionand contains the section's title, and itsullists the entries. Thedetailselement isopenon the pages the section contains and closed elsewhere, and the summary's::afterdraws the chevron. - Every heading the page's Markdown renders ends with an
aclasseddocs-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
pclasseddocs-sourcelinks toindex.md, the Markdown source the build writes beside the page. - A code block is a
figureclasseddocs-code. It hasdata-languagewhere the fence gives a language, and afigcaptionwhere a title was given. - Inside the figure are a
preand acodeelement, classedlanguage-*where the fence gives a language, with onedocs-linespan per line. - The copy control is a
buttonclasseddocs-copy, which the page's one script places inside eachpre. It contains two icons and has adata-copiedstate. - 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 hereheading listing the home page and the root's own pages; - one heading per top-level section, listing the pages that
--full-text-excludedoes not leave out, and no heading for a section whose every page is excluded; - a final
Optionalheading listing the pages that--full-text-excludeleft 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.