Handlebars templates and theme configuration.
Page Types
Two patterns exist for web pages:
| Pattern | Template Variables |
|---|---|
| Content (markdown) | {{TITLE}}, {{DESCRIPTION}}, {{CONTENT}} |
| Configured (YAML) | {{site.homepage.*}}, {{feature.*}} |
See the Web Pages Architecture playbook for when to use each.
Template Location
services/web/templates/
├── templates.yaml # Template-to-content-type mapping
├── homepage.html # Homepage
├── blog-post.html # Individual blog posts
├── blog-list.html # Blog listing page
├── docs-page.html # Documentation pages
├── docs-list.html # Documentation index
└── legal-post.html # Legal pages
Never create templates in
web/templates/. Onlyservices/web/templates/.
templates.yaml
Maps content kind values to templates:
templates:
homepage:
content_types:
- homepage
blog-post:
content_types:
- blog
- article
- paper
- guide
- tutorial
legal-post:
content_types:
- legal
- page
Handlebars Syntax
| Syntax | Purpose |
|---|---|
{{VAR}} |
Escaped output |
{{{VAR}}} |
Raw HTML output |
{{#if VAR}}...{{/if}} |
Conditional |
{{#each items}}...{{/each}} |
Loop |
{{../name}} |
Parent context in nested loop |
{{this.field}} |
Current item in loop |
Template Variables
Frontmatter Mapping
| Frontmatter | Variable |
|---|---|
title |
TITLE |
description |
DESCRIPTION |
author |
AUTHOR |
slug |
SLUG |
keywords |
KEYWORDS |
image |
IMAGE |
published_at |
DATE, DATE_ISO, DATE_PUBLISHED |
updated_at |
DATE_MODIFIED, DATE_MODIFIED_ISO |
Site Variables (from config.yaml)
| Variable | Source |
|---|---|
ORG_NAME |
branding.name |
ORG_URL |
branding.url |
ORG_LOGO |
branding.logo.primary.svg |
TWITTER_HANDLE |
branding.twitter_handle |
DISPLAY_SITENAME |
branding.display_sitename |
Rendered Content
| Variable | Description |
|---|---|
CONTENT |
Markdown → HTML |
TOC_HTML |
Table of contents |
RELATED_CONTENT |
Related articles |
FOOTER_NAV |
Footer navigation |
Theme Configuration
services/web/config.yaml:
Required Branding Fields
| Field | Example |
|---|---|
branding.copyright |
"© 2024 Company" |
branding.twitter_handle |
"@handle" |
branding.display_sitename |
true |
branding.favicon |
"/favicon.ico" |
branding.logo.primary.svg |
"/logo.svg" |
Colors
colors:
light:
primary:
hsl: "hsl(0, 0%, 35%)"
text:
primary: "#111111"
secondary: "#666666"
dark:
primary:
hsl: "hsl(0, 0%, 60%)"
text:
primary: "#FFFFFF"
Typography
typography:
sizes:
xs: "12px"
sm: "14px"
md: "15px"
lg: "18px"
weights:
regular: 400
medium: 500
bold: 700
Adding a New Content Type
-
Define kind in frontmatter:
kind: case-study -
Add to content source (
services/content/config.yaml):content_sources: case-studies: path: services/content/case-studies allowed_content_types: - case-study -
Map to template (
templates.yaml):templates: case-study-page: content_types: - case-study -
Create template file:
services/web/templates/case-study-page.html
Troubleshooting
| Error | Solution |
|---|---|
| No template for content type | Add to templates.yaml |
| Missing branding config | Add to services/web/config.yaml |
| Variable not rendering | Check frontmatter field name |
Quick Reference
| Task | Location |
|---|---|
| Templates | services/web/templates/*.html |
| Template mapping | services/web/templates/templates.yaml |
| Theme config | services/web/config.yaml |
| SEO metadata | services/web/metadata.yaml |