Templates and Themes

Customize your seite static site with Tera templates, overridable blocks, 10 bundled themes, and data-driven navigation. No JavaScript framework needed.

seite uses Tera templates to give you full control over your site's HTML output. The static site generator ships 10 bundled themes and supports AI-generated custom themes, all as single self-contained files with no external dependencies.

Template Engine

seite uses Tera, a Jinja2-compatible template engine. All templates extend base.html. User templates in templates/ override bundled defaults.

Template Variables

Site variables

VariableDescription
{{ site.title }}Site title
{{ site.description }}Site description
{{ site.base_url }}Base URL (e.g., https://example.com)
{{ site.language }}Default language code
{{ site.author }}Site author

Page variables

VariableDescription
{{ page.title }}Page title
{{ page.content }}Rendered HTML content (use with | safe)
{{ page.date }}Publication date (posts)
{{ page.updated }}Last modified date
{{ page.description }}Page description
{{ page.image }}Social preview image URL
{{ page.slug }}URL slug
{{ page.tags }}List of tags
{{ page.url }}Page URL path
{{ page.collection }}Collection name
{{ page.robots }}Robots meta directive
{{ page.word_count }}Word count
{{ page.reading_time }}Reading time in minutes
{{ page.excerpt }}Auto-extracted excerpt (HTML)
{{ page.toc }}Table of contents entries
{{ page.extra }}Custom frontmatter data
{{ prev_post }}Previous (older) post link: .title, .url, .date, .description
{{ next_post }}Next (newer) post link: .title, .url, .date, .description

Context variables

VariableDescription
{{ collections }}List of collections (index pages)
{{ nav }}Navigation sections (doc pages)
{{ data }}Data files from data/ directory
{{ lang }}Current language code
{{ translations }}Available translations
{{ default_language }}Default language code (from seite.toml)
{{ lang_prefix }}URL prefix for current language (empty for default, "/es" for others)
{{ t }}UI translation strings object (override via data/i18n/{lang}.yaml)
{{ pagination }}Pagination context

Translatable UI Strings

All bundled themes and default templates use the {{ t }} object for UI text. This allows multilingual sites to translate interface strings without overriding entire themes.

Default Keys

KeyDefault (English)
t.search_placeholderSearch…
t.skip_to_contentSkip to main content
t.no_resultsNo results
t.newerNewer
t.olderOlder
t.page_n_of_totalPage {n} of {total}
t.search_labelSearch site content
t.min_readmin read
t.contentsContents
t.tagsTags
t.all_tagsAll tags
t.taggedTagged
t.changelogChangelog
t.roadmapRoadmap
t.not_found_titlePage Not Found
t.not_found_messageThe page you requested could not be found.
t.go_homeGo to the homepage
t.prev_postPrevious
t.next_postNext
t.in_progressIn Progress
t.plannedPlanned
t.doneDone
t.otherOther

Overriding Strings

Create data/i18n/{lang}.yaml to override any key:

# data/i18n/es.yaml
search_placeholder: "Buscar…"
skip_to_content: "Ir al contenido principal"
no_results: "Sin resultados"
newer: "Más recientes"
older: "Más antiguos"

Overridable Blocks

All bundled themes provide these blocks for customization:

{% block title %}...{% endblock %}       <!-- Page title -->
{% block head %}{% endblock %}            <!-- Extra head content -->
{% block extra_css %}{% endblock %}       <!-- Page-specific CSS -->
{% block header %}...{% endblock %}       <!-- Header/nav area -->
{% block content %}...{% endblock %}      <!-- Main content -->
{% block footer %}{% endblock %}          <!-- Footer content -->
{% block extra_js %}{% endblock %}        <!-- Page-specific JS -->

To override a block, create a template that extends base.html:

{% extends "base.html" %}

{% block extra_css %}
<style>
  .custom-class { color: red; }
</style>
{% endblock %}

{% block content %}
<div class="custom-class">
  {{ page.content | safe }}
</div>
{% endblock %}

Here's a more complete example: a custom post.html with reading time, tags, and a back link:

{% extends "base.html" %}

{% block content %}
<article>
  <h1>{{ page.title }}</h1>
  <p>{{ page.date }} · {{ page.reading_time }} min read</p>
  {{ page.content | safe }}
  {% if page.tags %}
  <div class="tags">
    {% for tag in page.tags %}<span>{{ tag }}</span>{% endfor %}
  </div>
  {% endif %}
  <a href="/posts">Back to all posts</a>
</article>
{% endblock %}
Info

Always use | safe with {{ page.content }}. Tera escapes HTML by default, so without | safe your rendered content would display as raw HTML tags. Content can include rich elements via shortcodes: videos, callouts, figures, and contact forms.

Extra Frontmatter

Pass arbitrary data to templates using the extra field:

---
title: "My Page"
extra:
  hero_image: /static/hero.jpg
  show_sidebar: false
  custom_color: "#ff6600"
---

Access in templates:

{% if page.extra.hero_image %}
<img src="{{ page.extra.hero_image }}" alt="Hero">
{% endif %}
Tip

Create data/nav.yaml with your links and every bundled theme renders header navigation automatically, no template editing needed.

Data Files in Templates

Place YAML, JSON, or TOML files in the data/ directory to inject structured data into all templates. Files are accessible via {{ data.filename }}.

Create data/nav.yaml:

- title: Blog
  url: /posts
- title: About
  url: /about

Use in templates:

{% if data.nav %}
<nav>
  {% for item in data.nav %}
  <a href="{{ item.url }}">{{ item.title }}</a>
  {% endfor %}
</nav>
{% endif %}

Create data/footer.yaml:

links:
  - title: GitHub
    url: https://github.com/user/repo
copyright: "2026 My Company"

Use in templates:

{% if data.footer %}
  {% if data.footer.links %}
  <nav>
    {% for link in data.footer.links %}
    <a href="{{ link.url }}">{{ link.title }}</a>
    {% endfor %}
  </nav>
  {% endif %}
  <p>{{ data.footer.copyright }}</p>
{% endif %}

All 10 bundled themes render data.nav and data.footer automatically when present. See Configuration for supported formats and directory structure.

Bundled Themes

10 themes ship with the binary, no downloads needed:

default

Clean baseline. 720px centered column, system-ui font, blue links. Good starting point.

minimal

Literary/essay feel. 600px column, Georgia serif, bottom-border-only search input. Typography carries all personality.

dark

True black (#0a0a0a) background, violet (#8b5cf6) accent. Styled focus rings, high-contrast text.

docs

Documentation layout. Fixed 260px sidebar with auto-generated navigation, GitHub-style colors. Best for technical documentation.

brutalist

Neo-brutalist design. Cream background, 3px black borders, hard non-blurred shadows, yellow (#ffe600) accent. No border-radius.

bento

Card grid layout. 1000px column, CSS grid with mixed card sizes, border-radius 20px, soft shadows. Dark/indigo accent cards.

Applying Themes

seite theme list              # Show all bundled + installed themes
seite theme apply dark        # Apply a bundled theme
seite theme apply my-custom   # Apply an installed theme

See the Theme Gallery for visual previews of all bundled themes.

Installing & Sharing Themes

Download community themes from a URL, or export your own:

seite theme install https://example.com/themes/aurora.tera
seite theme install https://example.com/themes/aurora.tera --name my-aurora
seite theme export my-theme --description "Dark theme with green accents"

Installed themes are saved to templates/themes/<name>.tera and appear in seite theme list.

Custom Themes with AI

Generate a completely custom theme:

seite theme create "minimal serif with warm earth tones and generous whitespace"

This uses Claude Code to generate a templates/base.html with all required blocks, SEO tags, search, and accessibility features. Export AI-generated themes to share them:

seite theme export earth-tones --description "Warm serif theme with earth tones"

Table of Contents

Docs and posts automatically get a table of contents. Headings receive id anchors, and {{ page.toc }} provides the structured data:

{% if page.toc %}
<nav class="toc">
  {% for entry in page.toc %}
  <a href="#{{ entry.id }}" style="padding-left: {{ entry.level }}em">
    {{ entry.text }}
  </a>
  {% endfor %}
</nav>
{% endif %}

Next Steps