Documentation
ZeroPress Theme Authoring
ZeroPress themes are static template packages for the current runtime: "0.5" contract.
ZeroPress Theme Authoring
ZeroPress themes are static template packages for the current runtime: "0.5" contract.
A theme decides how ZeroPress content looks and behaves in the browser. The build pipeline prepares structured data, renders Markdown content, resolves widgets, copies theme assets, and emits static output. The theme owns the HTML templates, CSS, and optional client-side enhancement.
What A Theme Contains
A minimal theme directory contains:
my-theme/
theme.json
layout.html
index.html
post.html
page.html
assets/
style.css
Common optional files:
my-theme/
archive.html
category.html
tag.html
404.html
partials/
*.html
assets/
theme.js
Required files for a usable v0.5 theme:
theme.jsonlayout.htmlindex.htmlpost.htmlpage.htmlassets/style.css
ZeroPress Site Shape
When creating a complete ZeroPress site, the practical authoring unit is:
my-site/
preview-data.json
theme/
theme.json
layout.html
index.html
post.html
page.html
partials/
tracker.html
content-enhancements.html
assets/
style.css
theme.js
public/
favicon.ico
vendor/
preview-data.jsondefines site data, content, menus, widgets, and permalink policy.theme/defines deterministic rendering through the v0.5 theme runtime.theme/assets/contains theme-owned CSS and JavaScript referenced by the theme.public/contains site-owned passthrough files such as favicons, PDFs, source files, and third-party vendor assets.
Reusable themes should avoid hard-coding site-specific analytics tokens, vendor URLs, or product copy. Prefer a documented partial or public/ integration point that a site owner can fill.
theme.json
The theme manifest identifies the package and declares the v0.5 runtime.
{
"$schema": "./theme.v0.5.runtime.schema.json",
"name": "My Theme",
"namespace": "your-namespace",
"slug": "my-theme",
"version": "0.5.0",
"license": "MIT",
"runtime": "0.5",
"description": "Short summary of the theme.",
"features": {
"comments": true,
"newsletter": true,
"postIndex": true
},
"menuSlots": {
"primary": {
"title": "Primary Menu",
"description": "Main navigation menu"
}
},
"widgetAreas": {
"sidebar": {
"title": "Sidebar Widgets",
"description": "Sidebar widget area"
}
}
}
The current runtime accepts only runtime: "0.5". There is no fallback to older theme runtimes.
Use the Theme Manifest Runtime v0.5 schema as the source of truth for manifest fields.
Templates And Partials
ZeroPress renders one route template inside layout.html.
layout.htmldefines the shared document shell.index.htmlrenders the front page, post index, or combined default root route.post.htmlrenders an individual post.page.htmlrenders an individual page.archive.html,category.html,tag.html, and404.htmlare optional route templates.partials/*.htmlare reusable template fragments.
The layout must include the content slot:
<main>
{{slot:content}}
</main>
Common helpers:
{{menu:primary}}
{{partial:header}}
{{partial:post-card variant="compact" show_excerpt=true}}
Template syntax supports variables, if, if_eq, else_if, else_if_eq, for, loop metadata, partial arguments, and template comments. See Theme Runtime v0.5 for the full contract.
If a theme needs to iterate a menu manually, use menus.<slot>.items with the same slot id declared in theme.json. Both plain ids such as primary and hyphenated ids such as docs-sidebar are valid:
{{#for section in menus.docs-sidebar.items}}
<section>
<h2>{{section.title}}</h2>
</section>
{{/for}}
For example, menus.primary.items and menus.docs-sidebar.items are both valid. Hyphens must stay inside a path segment, so menus.-docs.items, menus.docs-.items, and menus.docs--sidebar.items are invalid.
Common Render Context
Every rendered template receives common build data:
sitecurrentUrllanguageroutemenuswidgetsmetataxonomies.categories[]taxonomies.tags[]
Global taxonomy items are generated by build for theme rendering. They are useful for home filters, sidebars, tag clouds, and navigation chips without scanning post card attributes in client JavaScript.
Each taxonomy item provides:
nameslugurlcountdescription
The url field follows the active permalink policy. Declared categories and tags remain present even when count is 0, so themes can decide whether to show or hide empty taxonomy links.
<nav class="taxonomy-filter" aria-label="Topics">
{{#for category in taxonomies.categories}}
{{#if category.count}}
<a href="{{category.url}}">{{category.name}}</a>
{{/if}}
{{/for}}
</nav>
Route Data
Build output exposes structured route data to templates.
Post lists should render from:
posts.items[]pagination
The route object identifies the current render target:
route.typeroute.is_front_pageroute.is_post_indexroute.pathroute.url
Use route.is_post_index before rendering post-index-only UI. Use pagination.enabled before rendering page navigation. A site may request a non-paginated post index, and a theme may declare "postIndex": false to opt out of post index rendering entirely.
When a site uses a page as the front page, the selected page is rendered at / through page.html, and its normal page route is not emitted. The root render has route.type: "front_page" and route.is_front_page: true. Use that flag when front-page markup should differ from normal document pages.
Category and tag routes also receive:
taxonomy.kindtaxonomy.slugtaxonomy.nametaxonomy.count
Posts should render from fields such as:
post.titlepost.urlpost.excerptpost.featured_imagepost.htmlpost.published_atpost.updated_atpost.reading_timepost.authorpost.categories[]post.tags[]post.prevpost.nextpost.comments_enabledpost.toc[]
Pages should render from fields such as:
page.titlepage.htmlpage.toc[]
For Markdown-first document pages, remember that page.html is the rendered Markdown body. If the source Markdown starts with # Title, then page.html already contains that H1. In that case, do not render a second <h1>{{page.title}}</h1> in page.html; use the body HTML as the document heading source.
Front pages often use Markdown that already starts with an H1. A simple pattern is:
{{#if route.is_front_page}}
<div class="prose">{{page.html}}</div>
{{#else}}
<header>
<h1>{{page.title}}</h1>
</header>
<div class="prose">{{page.html}}</div>
{{/if}}
Menus And Widgets
Menus are declared by preview data and rendered by theme templates through menu helpers:
<nav aria-label="Primary">
{{menu:primary}}
</nav>
Widget areas are resolved before template rendering. Themes should consume the resolved widget data, not raw widget settings.
Example:
{{#if widgets.sidebar.items}}
<aside>
{{#for widget in widgets.sidebar.items}}
<section>
<h2>{{widget.title}}</h2>
{{widget.html}}
</section>
{{/for}}
</aside>
{{/if}}
Markdown Content And TOC
For document_type: "markdown", ZeroPress renders Markdown to HTML and assigns stable id attributes to headings.
Markdown pages and posts also receive generated TOC data:
levelidtitlehref
Example:
{{#if page.toc}}
<aside aria-label="Table of contents">
<ol>
{{#for item in page.toc}}
<li class="toc-level-{{item.level}}">
<a href="{{item.href}}">{{item.title}}</a>
</li>
{{/for}}
</ol>
</aside>
{{/if}}
Heading anchor UI is optional theme UI. Build output provides heading ids and TOC data, but it does not wrap heading text in permalink anchors.
Markdown rendering also includes common authoring conventions that themes may style:
- GFM tables render as
<table>markup. ~~deleted~~renders as<s>deleted</s>.- Task lists render disabled checkbox inputs and use
contains-task-list,task-list-item, andtask-list-item-checkboxclasses. - GitHub alert blockquotes such as
> [!NOTE]render as<aside class="zp-alert zp-alert--note" role="note">with azp-alert__titletitle paragraph. - Fenced code language info is preserved as
language-*classes, includinglanguage-mermaid.
Supported alert markers are NOTE, TIP, IMPORTANT, WARNING, and CAUTION. Mermaid remains a code block at build time; themes can progressively enhance pre code.language-mermaid on the client.
For document_type: "html" and document_type: "plaintext", build-generated TOC data is empty. Themes may add client-side progressive enhancement if they want TOC behavior for non-Markdown content.
Progressive Enhancement
The initial static document should be useful without JavaScript.
Theme-owned progressive enhancement is appropriate for:
- search UI
- comment islands
- newsletter feedback
- active TOC states
- non-Markdown TOC behavior
Site Integrations With Partials And Public Files
layout.html should remain a strict document shell and should not contain direct <script> tags. When a site needs shared analytics, third-party loaders, or content enhancement code, include a named partial from the layout instead:
<head>
{{partial:tracker}}
</head>
<body>
{{slot:content}}
{{partial:content-enhancements}}
</body>
This keeps the layout source strict while giving the theme or site a clear integration point. A site-specific tracker partial can contain the provider snippet.
Google Analytics example:
<!-- partials/tracker.html -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-XXXXXXXXXX');
</script>
Cloudflare Web Analytics example:
<!-- partials/tracker.html -->
<script
defer
src="https://static.cloudflareinsights.com/beacon.min.js/..."
integrity="..."
data-cf-beacon='{"token":"..."}'
crossorigin="anonymous"
></script>
For Markdown body enhancement, load the integration only on post and page routes. The v0.5 template syntax does not support or, so use else_if:
{{#if post}}
{{partial:mermaid-loader}}
{{#else_if page}}
{{partial:mermaid-loader}}
{{/if}}
cdnjs Mermaid example:
<!-- partials/mermaid-loader.html -->
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/mermaid/11.12.0/mermaid.min.js"></script>
<script defer src="/assets/mermaid-renderer.js"></script>
/assets/mermaid-renderer.js can scan rendered code blocks such as pre code.language-mermaid, replace them with Mermaid containers, and call Mermaid after load. Without JavaScript, the original code block remains readable.
Third-party files that belong to a site rather than a reusable theme should live in public/:
public/
vendor/
highlight.js-11.11.1/
highlight.min.js
They are referenced from the output root:
<!-- partials/content-enhancements.html -->
<script defer src="/vendor/highlight.js-11.11.1/highlight.min.js"></script>
Use this pattern for analytics, Mermaid, highlight.js, code-copy buttons, heading UI, and other optional integrations. Core document content should still render meaningfully before these scripts run.
Preview data may also provide custom_html for trusted site/admin customization. Use partials when the theme wants a named template integration point; use custom_html when trusted preview-data generation should inject final site snippets before </head> or </body> without editing the theme.
For reusable themes, footer branding should be driven by preview-data rather than hard-coded site names. site.footer.copyright_text is optional footer text, and supporting themes should hide Published with ZeroPress. style attribution when site.footer.attribution.enabled is false.
Comments are gated by both features.comments in theme.json and post.comments_enabled in the render context.
{{#if post.comments_enabled}}
{{partial:comments-island}}
{{/if}}
Newsletter support currently means the theme may expose static newsletter UI. Storage and third-party integrations are not part of the v0.5 runtime contract.
Recommended Workflow
Start with the ZeroPress CLI tools:
- Create a starter theme with
create-zeropress-theme. - Preview, validate, and package the theme with
@zeropress/theme. - Build static output with
@zeropress/build.
See CLI Tools for package roles and npm references.
Checklist
- Use
runtime: "0.5". - Keep
theme.jsonvalid against the v0.5 theme schema. - Render post lists from
posts.items[]. - Render pagination from structured
paginationdata whenpagination.enabledis true. - Render taxonomy from
post.categories[],post.tags[], and routetaxonomy. - Render global taxonomy filters from
taxonomies.categories[]andtaxonomies.tags[]. - Render Markdown TOC from
page.toc[]orpost.toc[]when the theme includes TOC UI. - Avoid duplicate page headings when
page.htmlalready contains a Markdown H1. - Keep class names, data attributes, CSS selectors, and JS selectors aligned across templates and assets.
- Keep common analytics and content enhancement scripts in named partials instead of writing them directly in
layout.html. - Put site-owned third-party assets in
public/and reference them from root paths such as/vendor/.... - Keep comments, search, newsletter behavior, and non-Markdown TOC behavior as progressive enhancement.
- Document public CSS variables if the theme exposes customization hooks.