v1.0.0

Templates

Templates use tempura syntax, a Handlebars-like syntax. Layouts, partials, .hbs content, and output files are all templates.

Syntax

These are the tempura forms that these docs use:

Syntax Meaning
{{ value }} Print a value, escaped for HTML.
{{{ value }}} Print a value as it is. Use it for HTML, such as $page.content.
{{! comment }} A comment. It prints nothing.
{{#if test}} … {{#elif other}} … {{#else}} … {{/if}} Print the first block whose test is true.
{{#each list as item}} … {{/each}} Print the block once for each item. as (item, i) also names the index.
{{#var name = value}} Declare a variable. See Your own variables.
{{#expect a, b}} Declare the arguments that a partial reads.
{{#partial src="name"}} Call a partial. See Partials.

Values are JavaScript expressions, so {{ $pages.length }} and {{#each $section.filter(post => post.params.date) as post}} work. See the tempura docs for the full syntax.

tempura removes the line break just before each {{ }} tag and at the end of the file. Put a tag at the end of its line to keep the output tidy.

Layouts

A layout is a .hbs file in layouts/. It wraps the content of a page. {{{ $page.content }}} prints the page body.

layouts/html.hbshbs
<!doctype html>
<html lang="{{ $site.lang }}">
<head>
  <meta charset="utf-8"/>
  <title>{{ $page.title }} — {{ $site.title }}</title>
  <meta name="description" content="{{ $page.summary }}"/>
</head>
<body>
  {{{ $page.content }}}
</body>
</html>

Find the layout

For each page, bongo uses the first layout that exists in a fixed order. It looks in the folder of layouts/ that matches the page's folder first, then in each parent folder, then in layouts/ itself. For content/blog/2025/post.md, the order is:

  1. layouts/blog/2025/post.hbs
  2. layouts/blog/2025/single.hbs
  3. layouts/blog/2025/index.hbs
  4. layouts/blog/2025/html.hbs
  5. layouts/blog/post.hbs
  6. layouts/blog/single.hbs
  7. layouts/blog/index.hbs
  8. layouts/blog/html.hbs
  9. layouts/post.hbs
  10. layouts/single.hbs
  11. layouts/index.hbs
  12. layouts/html.hbs

A page uses the index.hbs of its folder when there is no single.hbs, so one index.hbs can serve a whole folder.

For an index page, bongo looks for index.hbs in place of the file name and single.hbs. For content/blog/index.md, the order is:

  1. layouts/blog/index.hbs
  2. layouts/blog/html.hbs
  3. layouts/index.hbs
  4. layouts/html.hbs

A page in the root of content/ uses only the root of layouts/. For content/about.md, the order is layouts/about.hbs, layouts/single.hbs, layouts/index.hbs, then layouts/html.hbs.

Layout chain

When the first layout is not an html.hbs, bongo renders the page through it, then through the nearest html.hbs. The output of the first layout becomes $page.content in the html.hbs. Thus a page renders through one or two layouts.

The starter site uses this chain for its posts. layouts/blog/single.hbs wraps each post, and layouts/html.hbs wraps the result:

layouts/blog/single.hbshbs
<article class="post">
	<header>
		<h1>{{ $page.title }}</h1>
		{{#partial src="postmeta" date=$page.params.date tags=$page.tags}}
	</header>
	<div class="post-body">
		{{{ $page.content }}}
	</div>
</article>

When bongo finds no html.hbs for a page, the build fails with Missing "html.hbs" template.

Variables

Templates read the build through reserved variables: $site, $page, $pages, $section, $tags, $data, $utils, and $assets. {{ $page.title }} prints the title of the current page, and {{#each $section as post}} loops over the pages of its folder. See Variables for what each one holds, with examples.

Your own variables

{{#var name = value}} declares a variable. The value is a JavaScript expression. The variable holds the value from that tag to the end of the file. Use a variable to calculate a value once and use it more than once, or to give a long expression a short name:

layouts/blog/single.hbshbs
{{#var date = $page.params.date}}
{{#var related = $section.filter(post => post.path !== $page.path).slice(0, 3)}}
<article>
	<h1>{{ $page.title }}</h1>
	{{#if date}}
	<time datetime="{{ date }}">{{ $utils.date.format(date, { dateStyle: 'long' }) }}</time>
	{{/if}}
	{{{ $page.content }}}
	{{#if related.length}}
	<h2>More posts</h2>
	<ul>
		{{#each related as (post, i)}}
		<li>{{ i + 1 }}. <a href="{{ post.path }}">{{ post.title }}</a></li>
		{{/each}}
	</ul>
	{{/if}}
</article>
  • To change a variable, declare it again: {{#var count = count + 1}}.
  • {{#each list as post}} declares post for each item. {{#each list as (post, i)}} also declares the index, i, which starts at 0.
  • Each template has its own variables. A partial does not see the variables of the layout that calls it. Give them to the partial as arguments, such as {{#partial src="byline" date=date}}, and declare them in the partial with {{#expect date}}. See Partials.

Your own values can also come from:

  • the frontmatter of a page, as $page.params. See Frontmatter.
  • the files in data/, as $data. See Data.
  • your own helper functions, as $utils. See Utilities.

Special elements

bongo has its own elements for the scripts, styles, and other files of assets/. Put them in a layout or a partial. After the layouts render, bongo builds each file, gives it a content-hashed name, and replaces the element with the HTML that loads the file:

layouts/html.hbshbs
<!doctype html>
<html lang="{{ $site.lang }}">
<head>
	<meta charset="utf-8"/>
	<title>{{ $page.title }}</title>
	<bongo:asset preload src="fonts/inter.woff2" />
	<link rel="icon" href="<bongo:asset src='img/favicon.svg' />"/>
	<bongo:style src="app.css" />
</head>
<body>
	{{{ $page.content }}}
	<bongo:client transitions />
	<bongo:script src="main.ts" />
</body>
</html>
Element Use it to
<bongo:style> build a stylesheet with LightningCSS, as a <link> or an inline <style>. See CSS.
<bongo:script> bundle a script with Rolldown, as a module <script> or an inline script.
<bongo:asset> publish any file, such as a font or an icon, as a URL or a <link rel="preload">.
<bongo:client> add the client runtime, for client-side navigation and view transitions.
<bongo:image> resize and convert an image, as a <picture> with a srcset. See Images.
<bongo:alternate> link another version of the page, such as a Markdown copy that bongo writes. See Alternates.

bongo publishes only the files that these elements and your CSS use. A src that does not exist fails the build. See Special elements for each attribute and the HTML that each element becomes, and TypeScript for type checks of these elements.

Edit this page on GitHub MIT license · bongo v1.0.0

    Type to search page titles and sections.