Themes — A Deep Dive

Layouts, partials, blocks — and the gotchas.

A Squilla theme is a self-contained directory under themes/<slug>/. There is no separate “theme installer” — dropping the directory on disk and activating the theme is the whole flow. This post walks through what's inside one and which gotchas trip people up first.

The directory

themes/<slug>/
  theme.json               # manifest
  layouts/*.html           # page-level templates
  partials/*.html          # reusable fragments
  blocks/<slug>/           # content blocks (block.json + view.html)
  assets/                  # CSS, JS, images
  scripts/theme.tengo      # seed entry point
  templates/*.json         # page templates for editors
  forms/*.html             # themed form layouts

Three template types, three contexts

This is the #1 thing to internalise before you start writing markup.

  • Layouts are page-level. They see .node, .app, .user at the top level.
  • Partials are reusable fragments included via {{renderLayoutBlock "slug"}}. Same context as the layout, plus .partial.
  • Block views are atomic content units. They see only the block's own field values at root. .app is not in scope. Want a site setting? Pass it down via the seed.

Activation runs theme.tengo

Activating a theme runs scripts/theme.tengo in a sandboxed Tengo VM. The script registers node types, taxonomies, settings, and seeds pages — all idempotently. There is no server restart. The kernel atomically swaps the layout tree.

The pattern that scales is to keep theme.tengo as a small dispatcher and split the actual seeding work into modular sub-scripts:

nodetypes := import("./setup/nodetypes")
pages     := import("./seeds/pages")
posts     := import("./seeds/posts")

nodetypes.run()
pages.run()
posts.run()

The classic gotchas

  • Settings keys are dotted strings. Templates can't dot-traverse them — use {{ index $settings "squilla.brand.name" }}, not {{$settings.squilla.brand.name}}.
  • Menu items render via .title, not .label. The Tengo input is label:, but storage and render use title.
  • block.json schema uses "key"; Tengo nodetypes.register uses "name". Don't mix them.
  • Don't write fallback values in templates. {{or .heading "default"}} hides data bugs. Pass real defaults via fields_data in the seed.

Read themes/squilla/scripts/theme.tengo in the repo for a working reference — it's the theme rendering this site, and it exercises every part of the API.

← all posts