Skip to content

Templates

Learn to use Handlebars templates to create dynamic pages.

Balzac uses Handlebars templating. Templates use {{variable}} for variables and {{#helper}}...{{/helper}} for helpers.

Output variable values:

<h1>{{site_name}}</h1>
<p>{{author}}</p>

HTML-escaped by default. Use triple braces for raw HTML:

<div class="content">
{{{content}}}
</div>
{{#if is_published}}
<span>Published</span>
{{else}}
<span>Draft</span>
{{/if}}
<ul>
{{#each tags}}
<li>{{this}}</li>
{{/each}}
</ul>

Variables from your balzac.toml [global] section:

[global]
site_name = "My Site"
author = "John Doe"

Use anywhere:

<h1>{{site_name}}</h1>
<p>By {{author}}</p>

When rendering collection items:

<h1>{{fm.title}}</h1>
<p>{{fm.description}}</p>
<div class="content">
{{{content}}}
</div>

Reusable template components.

Create files in partials/ directory:

partials/header.hbs:

<header>
<a href="/">{{site_name}}</a>
<nav>
<a href="/about.html">About</a>
<a href="/contact.html">Contact</a>
</nav>
</header>

partials/footer.hbs:

<footer>
<p>&copy; 2024 {{site_name}}</p>
</footer>

Include partials with {{> partial-name}}:

<!DOCTYPE html>
<html>
<body>
{{> header}}
<main>
<h1>{{title}}</h1>
</main>
{{> footer}}
</body>
</html>
{{> post-card title=fm.title date=fm.date}}

partials/post-card.hbs:

<article>
<h2>{{title}}</h2>
<time>{{date}}</time>
</article>

Reusable page layouts.

layouts/default.hbs:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{{title}}</title>
<link rel="stylesheet" href="/assets/style.css">
</head>
<body>
{{> header}}
<main>
{{{body}}}
</main>
{{> footer}}
</body>
</html>

You can use partials to implement layouts:

pages/index.hbs:

{{> header}}
<main>
<h1>Welcome to {{site_name}}</h1>
</main>
{{> footer}}

Or create a layout partial with slots:

layouts/main.hbs:

<!DOCTYPE html>
<html>
<body>
{{> header}}
{{{yield}}}
{{> footer}}
</body>
</html>

pages/index.hbs:

{{> main}}
{{#*inline "yield"}}
<h1>Welcome to {{site_name}}</h1>
{{/inline}}
{{/main}}

When Vite bundler is enabled, resolve asset URLs:

<script src='{{vite_url "main.js"}}'></script>
<link rel="stylesheet" href='{{vite_url "style.css"}}'>

Requires Vite bundler configuration:

[bundler.vite]
enabled = true
manifest_path = "dist/.vite/manifest.json"

Create custom helpers by registering them in the renderer. This requires using Balzac as a library rather than the CLI.

See Configuration for more details.

pages/posts/details.hbs:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="description" content="{{fm.description}}">
<title>{{fm.title}} - {{site_name}}</title>
<link rel="stylesheet" href="/assets/style.css">
</head>
<body>
{{> header}}
<article class="post">
<header class="post-header">
<h1>{{fm.title}}</h1>
<div class="meta">
<time>{{fm.date}}</time>
{{#if fm.author}}
<span>By {{fm.author}}</span>
{{/if}}
</div>
{{#if fm.tags}}
<div class="tags">
{{#each fm.tags}}
<span class="tag">{{this}}</span>
{{/each}}
</div>
{{/if}}
</header>
<div class="content">
{{{content}}}
</div>
<footer class="post-footer">
<a href="/index.html">← Back to posts</a>
</footer>
</article>
{{> footer}}
</body>
</html>

pages/index.hbs:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{{site_name}}</title>
<link rel="stylesheet" href="/assets/style.css">
</head>
<body>
{{> header}}
<main>
<section class="hero">
<h1>Welcome to {{site_name}}</h1>
<p>{{description}}</p>
</section>
<section class="posts">
<h2>Latest Posts</h2>
<!-- Manual post listing - or generate with a hook -->
<ul>
<li>
<a href="/posts/hello-world.html">Hello World</a>
<span class="date">2024-01-15</span>
</li>
<li>
<a href="/posts/getting-started.html">Getting Started</a>
<span class="date">2024-01-10</span>
</li>
</ul>
</section>
</main>
{{> footer}}
</body>
</html>
  1. Use partials - Reuse common elements (header, footer, navigation)
  2. Separate concerns - Keep templates focused on presentation
  3. Escape user input - Use {{variable}} by default, only use {{{variable}}} for trusted HTML
  4. Use conditionals - Handle missing data gracefully
  5. Keep templates simple - Complex logic belongs in preprocessing scripts or hooks
  6. Organize by purpose - Group related partials and layouts