HERON

Data contract · part 4

theme.yaml

The fourth part of the data contract: theme.yaml, the theme's passport. What the theme can draw, what it expects from the site and how it links pages together.

This page is for designers and theme developers. Someone who only writes texts does not need to read it.

In plain words

If the content is the text of a book, the theme is its layout: fonts, colours, where the pictures go. theme.yaml is the theme's ID card. It lists which kinds of pages the theme can draw (a home page, a service page, a blog post) and which sections each kind expects.

This split is what lets you change the design without rewriting a single page: a new theme reads the same files and draws them its own way.

Example. A theme that knows two kinds of pages:

name: my-theme
types:
  home:
    uses: [intro, "services:checklist", "faq:faq"]
  service:
    uses: [intro, "what:list", "prices:table", "faq:faq"]

A page of type service with sections what, prices and faq gets exactly the blocks the designer made for them. A section the theme does not know is reported by the build, so a typo in an anchor never disappears silently.

When you need this page. When you make or change a theme. If you only write texts, you can skip it.

The theme passport

theme.yaml serves as documentation and as the basis of the heron check report, not as a strict validator.

name: main
version: "1.0"
title: Site theme

# what the theme expects from site.yaml. HERON checks presence without going into meaning
requires:
  - contact.phone
  - organization.name

# forms the theme draws. Their description must be in site.yaml
forms: [callback]

modules:
  - breadcrumbs
  - prose
  - facts
  - list
  - alert-list
  - steps
  - timeline
  - faq
  - cards
  - quote
  - table
  - gallery
  - author

types:
  home:
    uses: [intro, about, services, faq]
    jsonld: [WebSite]
  category:
    uses: [intro, outro]
    jsonld: [CollectionPage]
  service:
    uses: [intro, quick-facts, what, who, how, faq]
    jsonld: [Service]
  article:
    uses: [intro]
    jsonld: [Article]
  page:
    uses: [intro]

# links between pages. HERON finds them, checks them and builds the reverse link
# without knowing what the relation means
links:
  - field: related
    type: service
    back: mentioned_in
  - field: topic           # field in the front matter of the source page
    type: topic            # type of the pages it points to (optional)
    on: service            # pages of which type should have the field
    required: 1            # minimum number of links

# ratios and widths HERON fits the masters into
images:
  widths: [400, 800, 1200, 1600]
  ratios: ["1:1", "16:9", "4:3"]   # what templates may ask for; only what is asked for gets cut
  formats: [avif, webp]
  fallback_width: 800              # fallback PNG/JPEG for a plain image: one width

A page type is a template

A page type is implemented by a templates/TYPE.html file, an ordinary Jinja2 template where the order of blocks reads top to bottom:

{% extends "base.html" %}
{% block content %}
  {{ mod.breadcrumbs() }}
  {{ mod.hero() }}
  {{ mod.facts('quick-facts') }}
  {{ mod.prose('what') }}
  {{ mod.prose('who') }}
  {{ mod.list('contra') }}
  {{ mod.steps('how') }}
  {{ mod.timeline('recovery') }}
  {{ mod.prose('price') }}
  {{ mod.faq('faq') }}
  {% for p in page.related.get('related', []) %}
    {{ p.h1 }} → {{ p.url }}   {# a link to the related page #}
  {% endfor %}
  {{ mod.call() }}
  {{ mod.author() }}
{% endblock %}

mod.prose('what') takes the what section; if there is none, it outputs nothing — no heading, no empty frame. No conditions around each module are needed.

How sections get their form and what reaches a module is in section forms; how it looks on the site is in the forms reference.

Source

This page mirrors section 6 of docs/spec/20-data-contract.md on the prod branch. The repository is updated first: open the file on GitHub.