How themes work
A theme is a folder of files, copied into your site when installed, that decides how content is arranged and styled. Tovu themes come in tiers — levels of power — so a site trades exactly as much safety for capability as it needs. This page covers the tier system, the manifest every theme ships, how templates differ by tier, and how a theme gains behavior beyond markup and styling.
Tiers
Tovu defines five capability tiers (ThemeTier in apps/website/src/features/theme/theme.ts): declarative, templated, handlebars, static, and code. A theme's theme.json declares its tier; an absent value defaults to declarative.
How tiers work
- Declarative — pure data: a manifest, design tokens, and a JSON block tree per template. No template language and no code run, so any declarative theme is safe to install. This install ships one:
basic-declarative. - Templated — the same data plus real arranging logic (loops, conditionals, includes) through LiquidJS, a sandboxed template language. No JavaScript runs. This install ships two:
storefrontandfashion-modern. - Handlebars — a sibling of Templated: the same sandboxed-logic guarantee, through Handlebars instead of Liquid, with its own lint allowlist and render worker. No theme on this install currently uses it.
- Static — plain HTML, CSS, and JS files, one page per route instead of one template map. Every byte is editable, including JavaScript that runs in the visitor's browser. This install ships eight, and its active theme (
basic, rendering this page) is one of them. - Code — reserved for trusted, signed plugin JavaScript with full framework support. Designed, not built: no theme can use this tier yet.
Capability rises from Declarative to Code; so does what you are trusting a theme's author with. A Declarative theme cannot harm your site. A Static or (eventually) Code theme runs its own code in the browser, so it should come from an author you trust — the same judgment you would apply to any dependency.
Manifest
Every theme, in every tier, ships a theme.json manifest at its root. It declares the theme's identity, its tier, and tier-specific fields (pages/templates for a static theme, modes/defaultMode for a theme shipping dark and light token sets).
How the manifest works
This is basic's real manifest, trimmed:
{
"apiVersion": 2,
"id": "basic",
"name": "Basic",
"version": "0.1.0",
"tier": "static",
"modes": ["dark", "light"],
"defaultMode": "dark",
"fonts": ["Geist:wght@400;500", "Geist+Mono:wght@400;500"],
"pages": ["index", "about", "pricing", "docs", "blog", "changelog", "download", "signin", "signup"],
"templates": ["blog-post.html", "blog-sidebar-template.html", "page-shell.html"]
}
pages lists the theme's own standalone marketing pages (home, pricing, and so on); templates lists the files a Post or Page can pick as its own templateChoice — this page renders through blog-sidebar-template.html, one of the three listed here.
Templates
Every tier shares the manifest and design-token model above. What differs is how a tier arranges tokens into an actual page — and that format is tier-specific, not portable between tiers.
How templates work
A declarative theme's template is a JSON block tree over a fixed component vocabulary — no HTML is written by hand:
{ "type": "component", "id": "tovu/entry-content", "props": {} }
A templated (Liquid) theme's template is real markup with Liquid tags for logic and data access:
<h1 class="journal-entry__title">{{ post.title }}</h1>
<p><time>{{ post.date | date: "%B %e, %Y" }}</time></p>
<div class="prose">{{ post.content | raw }}</div>
A static theme's template is a complete, plain HTML file. The one thing every static template shares with every other tier is the marker convention: a data-embed-config attribute is how a static page marks where dynamic content is injected, the same marker vocabulary Pages use for widgets and media:
<div data-embed-config='{"type":"content"}'></div>
Plugins and bundles
A theme, at any tier, gains new behavior the same one way: from a plugin, the trusted plane where code runs with its permissions declared up front. A theme never has to move to a more powerful tier just to add a feature.
How plugins and bundles work
When a design needs a plugin — a newsletter box, a pricing table, a live search — a theme can ship as a bundle that declares the plugins it needs and installs them in one consented step, instead of leaving an operator to find and install each one separately. See How Plugins Work and The Plugin API for the plugin system itself.