Easy to read. Safe to edit.
A docs theme for people and agents, built on Mizzen.
Quick start · Documentation · Contributing · Mizzen
A docs theme for people and agents, built on Mizzen: layouts, chrome, components, design tokens, and styles. The engine reads it only by listing the folders below; adding a unit never edits a shared index file.
- Easy to read. An editorial layout with a sidebar, table of contents, and search that works for people and for agents.
- Safe to edit. Every visual decision is a design token, and a component is a folder, so a change stays in one place.
- Accessible. Tested against WCAG 2.2 AA, with a keyboard gate, a contrast gate, and an RTL gate.
- Right-to-left ready. Direction, language, and mirrored icons are handled for RTL languages.
Check out Mizzen next to it (../mizzen) and run npm ci in both; the tests and the docs site build with it.
The theme may import type from @velella/mizzen/contracts, by package name and never by relative path, and imports nothing else from the engine. Preact is a peer dependency, with jsx: react-jsx and jsxImportSource: preact in tsconfig.json.
| Folder | One unit is | Guide |
|---|---|---|
layouts/ |
<name>.ts |
layouts/README.md |
chrome/ |
<name>/ |
chrome/README.md |
components/ |
<name>/ |
components/README.md |
tokens/ |
<group>.json |
tokens/README.md |
styles/ |
<name>.css |
styles/README.md |
npm testnpm run typechecknpm run tokenswritesstyles/tokens.cssfromtokens/*.json;tokens:checkfails when it is stale.
A page picks a layout with layout in its front matter, or gets one from defaults in the site config. mizzen build uses doc when neither sets one.
| Layout | What it renders |
|---|---|
base |
The document shell: <head>, skip link, theme script, and slots for the header, sidebar, main column, aside, and footer. The other layouts build on it; a page does not name it. |
doc |
The docs page: header, sidebar, breadcrumb, article, table of contents, pager, and page footer. Front matter status, since, and replaced_by add the page-status banner. |
home |
The docs home: a hero, action buttons, and section cards from hero, actions, and sections in front matter. |
not-found |
The 404 page, with a search form and links to the home page and the top-level sections. |
versions |
The versions index: a page's versions from site.data.versions or the versions front matter key, each row linked to the same page when it exists and marked with Latest, Deprecated, or Dev in text. |
Each layout is layouts/<name>.ts; the rules for adding one are in layouts/README.md.
Components are the pieces pages use inside their content. Each name is its folder under components/ and, where the table shows a directive, the syntax the Markdown writes.
| Component | What it does | Reference |
|---|---|---|
badge |
A small inline label for a version or status (:badge[New]{variant=tip}). |
Badge |
callout |
A note, tip, important, warning, or caution (:::callout{type=warning}, and GitHub alert syntax). |
Callout |
cards |
A responsive grid of linked cards. | Cards |
code |
Fenced code highlighted at build time with Shiki in light and dark, with a copy button. | Code |
code-tabs |
The same example in several languages or package managers, as tabs that stack without JavaScript (:::code-tabs). |
Code tabs |
details |
A native disclosure that starts closed unless given open (:::details{summary="…"}). |
Details |
embed |
A lazy iframe that requests nothing from the provider until it loads (::embed{src="…" title="…"}). |
Embed |
endpoint |
An API endpoint: method, path with parameters, parameter list, and body (:::endpoint). |
Endpoint |
figure |
A responsive image with alt text, an optional caption, and an optional credit (:::figure{src="…"}). |
Figure |
file-tree |
A nested Markdown list drawn as a folder tree (:::file-tree). |
File tree |
heading-anchor |
Adds a link icon to every h2 to h4; automatic on rendered pages. | Heading anchor |
kbd |
A keyboard key or combination (:kbd[Ctrl+K]). |
Kbd |
page-status |
An experimental, beta, or deprecated banner from status, since, and replaced_by in front matter. |
Page status |
schema-table |
A JSON Schema from _data as a table of properties, types, defaults, and descriptions (::schema-table{data="widget"}). |
Schema table |
steps |
A numbered sequence, wrapped around an ordered list (:::steps). |
Steps |
table |
Wraps every Markdown table in a labelled scroll region; automatic on rendered pages. | Table |
terminal |
A shell session where a line starting with $ is a command and the rest is output (:::terminal). |
Terminal |
Each reference page has the syntax, every option, and a live example, and is built with the docs site.
Every visual decision is a design token, defined once and read from CSS. tokens/names.json freezes the names; one file per group (color.json, type.json, space.json, and the rest) sets the values. Run npm run tokens after editing a group: it writes styles/tokens.css, which npm test keeps current. Never edit that CSS by hand, and never put a raw color, length, or duration in a unit's stylesheet; use var(--color-accent), var(--space-4), and the other token variables.
A site changes a token in its config, not in the theme. The site config is mizzen.yml; the engine passes theme.options to the theme unread, and the token overrides live under theme.options.tokens:
theme:
options:
tokens:
accent: "#0b6e4f"
canvas: "#ffffff"
text: "#222222"
font_body: '"Source Serif 4", Georgia, serif'
radius: "0.5rem"The keys are the token names that the config maps into the theme's CSS variables: accent, canvas, text, font_body, and radius. An unset color keeps the theme's light and dark pair; a set one replaces both, so check the contrast in both modes.
The rest of theme.options is the theme's as well. Lateen reads these blocks:
theme:
options:
announcement:
id: v0-4
text: Mizzen 0.4 is out.
href: /changelog/
editPage:
headingLinks: true
logo:
light: /assets/logo.svg
dark: /assets/logo-dark.svg
footer:
copyright: © 2026 Example
license: MIT
links:
- title: Source
url: https://github.com/example/site
pageAgent:
providers:
- title: Claude
url: "https://claude.ai/new?q=Read+{markdown}"
feedback:
endpoint: https://feedback.example.com/vote| Option | What it changes |
|---|---|
announcement.id, announcement.text, announcement.href |
A dismissible bar above the header. text is required; the bar renders nothing without it. href links the text; http, https, mailto, and root-relative paths are shown. Dismissal is remembered per id. |
editPage.headingLinks |
true adds a per-heading "suggest an edit" issue link under the "Edit this page" link; absent or any other value leaves it out. |
logo.light, logo.dark |
The header mark. A root-relative path gets the site's base; an absolute URL is used as written. With both, the header swaps them with the theme; with one, it shows that one in both modes. |
footer.copyright, footer.license |
The two lines of the site footer. |
footer.links[] |
Footer links with title and url; only http, https, mailto, and root-relative paths are shown. |
pageAgent.providers[] |
The "Use with an agent" menu. Each provider has a title and a url; {markdown}, {url}, and {title} are filled per page. Without the block the menu offers Claude, ChatGPT, and Cursor. |
feedback.endpoint |
The address the "Was this page helpful?" buttons POST {url, helpful} to. Without the block the feedback widget renders nothing. |
A site cannot add a component without changing the theme or its fork: components are theme units, not content files. Adding one never edits a shared index file, because the registry discovers components/<name>/ by listing the folder.
- Make
components/<name>/, using lowercase kebab-case. The folder name is the component's name and the directive an author writes. - Add
index.tswith the Preact component written withh(), never JSX, and a default export typed by the engine's component contract. The props arrive as strings from the directive's attributes, and the directive's body arrives as rendered HTML. - Add
style.cssonly when the component has styles. Read tokens withvar(--…), use logical properties, and name classes after the component: the root is<name>, its parts add a suffix such as<name>-title. - Add
client.tsonly when the component needs the browser. It is an island: it ships only on pages that use the component, and every control it enhances startshiddenso the page still reads without JavaScript. - Add
test/component-<name>.test.tsbeside the other component tests. Test the rendered element, each variant, escaping, and the source position. - Run
npm run typecheckandnpm test. The registry finds the new folder, and an unknown directive or a missing default export becomes a diagnostic naming the file.
The full rules for a unit are in components/README.md.
src/index.ts: exports the theme manifest (nameandfolders) and nothing else.test/:*.test.tsfiles fornode --test.
Bug reports, fixes, and docs improvements are welcome. See CONTRIBUTING.md to get started.
MIT.
Lateen is part of Velella. Set the sail. Let the docs carry.