Learn how to create a custom template using Kida syntax. This guide walks through building a blog post template from scratch.
Goal
Create a custom blog post template that:
- Extends the base layout
- Displays post metadata (title, date, author)
- Shows tags and categories
- Includes a reading time estimate
Prerequisites
-
Bengal site initialized
-
Kida enabled in
bengal.toml:site: template_engine: kida
- 1
Create Template Directory
Create the template file in your project:
mkdir -p templates/blog touch templates/blog/single.htmlBengal searches templates in this order:
Project templates (highest priority)
templates/— your project's custom templates.Theme templates (theme chain)
For each theme in the theme chain:
- Site-level themes (
themes/{theme}/templates) - Installed themes (via package entry points)
- Bundled themes (
bengal/themes/{theme}/templates)
Default theme (fallback)
Default theme templates — ultimate fallback.
- Site-level themes (
- 2
Extend Base Layout
Start by extending the base layout:
{# templates/blog/single.html #} {% extends "baseof.html" %} - 3
Define Template Variables
Use
{% let %}for template-wide variables:{% extends "baseof.html" %} {% let post = page %} {% let reading_time = post.content | reading_time %} {% let author = site.authors[post.author] ?? {} %} - 4
Override Content Block
Override the
contentblock to display your post:{% extends "baseof.html" %} {% let post = page %} {% let reading_time = post.content | reading_time %} {% let author = site.authors[post.author] ?? {} %} {% block content %} <article class="blog-post"> <header> <h1>{{ post.title }}</h1> <div class="post-meta"> <time datetime="{{ post.date | dateformat('%Y-%m-%d') }}"> {{ post.date | dateformat('%B %d, %Y') }} </time> {% if author.name %} <span class="author">By {{ author.name }}</span> {% end %} <span class="reading-time">{{ reading_time }} min read</span> </div> </header> <div class="post-content"> {{ post.content | safe }} </div> {% if post.tags %} <footer class="post-footer"> <div class="tags"> {% for tag in post.tags %} <a href="{{ tag_url(tag) }}" class="tag">{{ tag }}</a> {% end %} </div> </footer> {% end %} </article> {% end %} - 5
Add Pattern Matching for Post Types
Use pattern matching to handle different post types:
{% block content %} {% match post.type %} {% case "blog" %} <article class="blog-post"> {# Blog post template #} <h1>{{ post.title }}</h1> {{ post.content | safe }} </article> {% case "tutorial" %} <article class="tutorial"> {# Tutorial template #} <h1>{{ post.title }}</h1> <div class="tutorial-content">{{ post.content | safe }}</div> </article> {% case _ %} <article> {# Default template #} <h1>{{ post.title }}</h1> {{ post.content | safe }} </article> {% end %} {% end %} - 6
Use Pipeline Operator for Data Processing
Process collections with the pipeline operator:
{% let recent_posts = site.pages |> where('type', 'blog') |> where('draft', false) |> sort_by('date', reverse=true) |> take(5) %} {% block content %} <article class="blog-post"> {# Post content #} </article> {% if recent_posts %} <aside class="related-posts"> <h2>Related Posts</h2> <ul> {% for related in recent_posts %} <li><a href="{{ related.href }}">{{ related.title }}</a></li> {% end %} </ul> </aside> {% end %} {% end %} - 7
Add Fragment Caching
Cache expensive operations:
{% block content %} <article class="blog-post"> {# Post content #} </article> {% cache "related-posts-" ~ post.id %} {% let related = site.pages |> where('type', 'blog') |> where('tags', post.tags[0]) |> where('id', '!=', post.id) |> take(3) %} {% if related %} <aside class="related-posts"> <h2>Related Posts</h2> <ul> {% for item in related %} <li><a href="{{ item.href }}">{{ item.title }}</a></li> {% end %} </ul> </aside> {% end %} {% end %} {% end %}
Complete Example
Here's a complete blog post template:
{# templates/blog/single.html #}
{% extends "baseof.html" %}
{% let post = page %}
{% let reading_time = post.content | reading_time %}
{% let author = site.authors[post.author] ?? {} %}
{% let related_posts = site.pages
|> where('type', 'blog')
|> where('tags', post.tags[0] ?? '')
|> where('id', '!=', post.id)
|> sort_by('date', reverse=true)
|> take(3) %}
{% block content %}
<article class="blog-post">
<header>
<h1>{{ post.title }}</h1>
<div class="post-meta">
<time datetime="{{ post.date | dateformat('%Y-%m-%d') }}">
{{ post.date | dateformat('%B %d, %Y') }}
</time>
{% if author.name %}
<span class="author">By {{ author.name }}</span>
{% end %}
<span class="reading-time">{{ reading_time }} min read</span>
</div>
</header>
<div class="post-content">
{{ post.content | safe }}
</div>
{% if post.tags %}
<footer class="post-footer">
<div class="tags">
{% for tag in post.tags %}
<a href="{{ tag_url(tag) }}" class="tag">{{ tag }}</a>
{% end %}
</div>
</footer>
{% end %}
</article>
{% cache "related-posts-" ~ post.id %}
{% if related_posts %}
<aside class="related-posts">
<h2>Related Posts</h2>
<ul>
{% for related in related_posts %}
<li>
<a href="{{ related.href }}">{{ related.title }}</a>
<span>{{ related.date | days_ago }} days ago</span>
</li>
{% end %}
</ul>
</aside>
{% end %}
{% end %}
{% end %}
Testing
Test your template:
# Build the site
bengal build
# Or run dev server
bengal serve
Visit a blog post page to see your custom template in action.
Next Steps
- Add Custom Filters — Extend Kida with your own filters
- Use Pattern Matching — Clean up conditional logic
- Cache Fragments — Improve performance
Seealso
- Kida Syntax Reference — Complete syntax documentation
- Template Functions — Available filters and functions