Make a theme
A practical guide to building lazysite layouts and themes by hand.
page explains the model, then walks you through making your own. No build step, no
framework - just Markdown, a little CSS, and one template file.
How it fits together
lazysite (D013 architecture) renders each page through three layers:
| Layer | What it is | Where it lives |
|---|---|---|
| Layout | The HTML structure - <head>, header, nav, footer. Brand-neutral: no colours or fonts baked in. |
lazysite/layouts/NAME/layout.tt |
| Theme | Colours, fonts and the CSS that paints the layout. Declares which layouts it fits. | lazysite/layouts/LAYOUT/themes/THEME/ |
| Content | Your Markdown pages, the nav, your images. | the docroot |
The rule of thumb: a new look is a new theme; new structure is a new layout. Most of the time you only want a new theme - a recolour and a font change of a layout that already exists.
The fastest start: fork a theme
Copy an existing theme folder, rename it, and change its tokens:
cp -r layouts/default/themes/default layouts/default/themes/mytheme
Edit mytheme/theme.json: change name to mytheme, and adjust the colours and
fonts. That's usually all it takes - the stylesheet is driven by those tokens, so the
whole site recolours without touching CSS.
theme.json - your tokens
{
"name": "mytheme",
"version": "1.0.0",
"description": "My warm reading theme.",
"author": "you",
"layouts": ["default"],
"config": {
"colours": {
"primary": "#2f6f63",
"text": "#2a2620",
"background": "#faf7f0"
},
"fonts": {
"body": "Georgia, serif"
}
}
}
config is grouped group -> key -> value. lazysite turns every entry into a CSS
custom property named --theme-GROUP-KEY and injects them at the top of the page:
:root {
--theme-colours-primary: #2f6f63;
--theme-colours-text: #2a2620;
--theme-fonts-body: Georgia, serif;
}
Only tokenise brand choices - colours and fonts. Leave spacing, radius and layout
maths as plain values in your CSS. The point of tokens is that a fork recolours by
editing theme.json, not that everything is a variable.
main.css - using the tokens
Your theme's assets/main.css references those properties:
body {
color: var(--theme-colours-text);
background: var(--theme-colours-background);
font-family: var(--theme-fonts-body);
}
a { color: var(--theme-colours-primary); }
Add a var(--theme-..., fallback) fallback so the design still holds if a token is
missing. Put fonts and images the theme needs under assets/ and reference them with
relative paths (url("fonts/inter.woff2")).
layout.tt - only if you need new structure
A layout is one Template Toolkit file. It owns the chrome and renders the page body. The essentials:
<head>
[% theme_css %] <!-- the :root tokens -->
<link rel="stylesheet" href="[% theme_assets %]/main.css">
</head>
<body>
<nav>
[% FOREACH item IN nav %]<a href="[% item.url %]">[% item.label %]</a>[% END %]
</nav>
<main>
<h1>[% page_title %]</h1>
[% content %] <!-- the rendered page -->
</main>
</body>
Keep it brand-neutral: no colours, no fonts, no inline <style> beyond [% theme_css %].
Give elements clear class names; the theme's CSS styles them.
Install and activate
In the Manager (/manager/themes): upload your theme zip, or "Install from
Releases". Then set the active layout and theme - either in the manager, or in
lazysite.conf:
layout: default
theme: mytheme
Activating clears the cache, so your change shows immediately.
A theme is the same layout.tt plus a different theme.json. Ship several "looks" as
themes on one layout rather than copying whole layouts - it's the Squarespace
family/variant model, and it keeps your work small.
Want the technical detail?
For the full Template Toolkit contract, the extended functions (scan:, url:,
fenced blocks, forms) and the schema, see For AI - it's written for
agents but it's the complete reference. And the platform docs live at
lazysite.io.