Make a theme

A practical guide to building lazysite layouts and themes by hand.

Every theme in this gallery is two small things: a **layout** and a **theme**. This
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.