Creating Templates

Laika comes with a lightweight template engine that allows to customize the output of the transformed markup documents.

When using a theme, creating a custom template is often not necessary, as the theme will have higher level configuration options that drive the look and feel of the output, based on its own internal templates.

When subtle tweaks to a theme are not sufficient or when working without a theme, adding custom templates will give you additional flexibility in controlling the output.

A template lets you specify where the content from text markup documents gets inserted and allows to add additional dynamic elements like navigation bars. It does not give control over how individual AST nodes from the markup document get rendered. For customizing this aspect, see Overriding Renderers.

Document Names and Locations

There are two kinds of template documents you can add to your input directories:

Default Templates

A default template for each output format can be placed into the root directory of the input tree. If you have multiple input directories, it needs to be placed into one of the roots.

The naming pattern for default templates is default.template.<suffix>. Concretely this would be:

This template is then used for all markup documents unless overridden by one of the two mechanisms described in the following sections.

Overrides per Directory

A sub-directory may contain an overriding default template. It would be detected by the same naming pattern as the root default template, and would be applied to all markup documents in this directory and its sub-directories.

Overrides per Document

Finally, when you want to apply a custom template to individual markup documents, you can explicitly refer to it in the configuration header of the markup document:

  laika.template = ../custom.template.html

The path is interpreted relative to the document, but has to be somewhere within one of the specified input directories, as templates are parsed and cached upfront before getting applied to documents.

All non-default templates must have a name with the pattern <name>.template.<suffix> where <name> is anything other than default and <suffix> is the same matching suffix as for default templates.

Template Syntax

The following, minimal example shows a template for HTML output:

    @:navigationTree { entries = [{ target = "/" }] } 
    <div class="content">

Apart from raw content in the target format HTML, it contains:

Inserting Content from Markup Documents

When a template gets applied to text markup documents, you need to explicitly specify where the content from those documents should be inserted.

Main Document Body

As shown in the example above, the substitution reference ${cursor.currentDocument.content} causes the main body of the associated text markup document to be rendered in that position.

The main body is the entire content of the markup document except for regions marked as fragments.

The internal mechanics of this insertion, which is technically just a merging of two ASTs, is described in more detail in Internal Representation.

Document Fragments

In some cases you might want the rendered content from the markup document to appear in more than just one place. You may have a central column in the template for the main content and a sidebar or footer that needs to be populated separately.

The separate sections can be marked in the text markup with the @:fragment directive:


This content will be *parsed* like all other
content, but will be available separately from the document content.
The block elements extend until the `@:@` fence below.
Therefore this line still belongs to the fragment.

This line doesn't and will be part of the main document content.

Within templates you can then refer to this content with the substitution variable:


The fragment names (sidebar in this case) can be freely chosen, they do not have any predefined meaning within Laika.

Substitution Variables

The previous section already introduced the substitution variables for inserting content from markup documents, but there are plenty of other predefined variables you can use, in addition to your own definitions.

The syntax is part of Laika's HOCON support and matches the HOCON spec.

A mandatory reference can be inserted with the key of the variable enclosed between ${ and }:


If such a value is undefined, the transformation will fail (unless you tweaked the configuration for Error Handling).

An optional reference can be expressed by adding an additional ? right before the variable key:


If an optional reference points to an undefined value, the reference in the template is substituted with an empty string.

For a full list of predefined values see Substitution Variables in the Reference section. For instructions on defining your own see User-Defined Variables.


Directives are Laika's mechanism for extending the syntax of templates or text markup without the need to implement a custom parser.

A directive always starts with an @:, the name of the directive and optionally attributes or body elements.

Some directive implementations require a set of mandatory attributes and may cause the transformation to fail should they be missing.

The most relevant directives for templates are those for generating navigation elements, like Generating Navigation Trees or Breadcrumbs.

For a full reference of the directives provided by Laika out of the box, see Standard Directives.

For instructions on how to write your own, see Implementing Directives.

Internal Representation

Templates get parsed into an AST just like text markup documents. This has several convenient consequences.

First, custom elements like directives can be implemented in a way that they produce AST nodes and not immediately string content in the output format. Therefore a single implementation of a template directive can usually be used for all supported output formats.

Secondly the implementation of "applying a template to a markup document" becomes close to trivial. It is merely the insertion of one AST at a particular node in another AST. Let's illustrate this with an example.

This is the template shown earlier in this chapter, further simplified to keep the AST output concise:


The AST for this template looks like this:

TemplateRoot - TemplateSpans: 5
. TemplateString - '<html>|  <head>|    <title>'
. TemplateContextReference(cursor.currentDocument.title,true)
. TemplateString - '</title>|  </head>|  <body>|    '
. TemplateContextReference(cursor.currentDocument.content,true)
. TemplateString - '|  </body>|</html>'

The raw content gets represented by TemplateString nodes. The AST renderer truncates beyond a certain length and replaces newlines with | for putting more emphasis on the structure. The variable reference gets represented by a TemplateContextReference which will be resolved later.

More complex real-world scenarios will have additional node types, like those for representing a directive.

Next we create an equally minimal text markup document:


Some Text.

This produces just the Title node and a Paragraph in the resulting AST:

RootElement - Blocks: 2
. Title(Id(headline) + Styles(title)) - Spans: 1
. . Text - 'Headline'
. Paragraph - Spans: 1
. . Text - 'Some Text.'

This is just the tip of iceberg of available node types, for a general overview see The Document AST.

We then finally apply the template to the document. In a normal Laika transformation this happens automatically as the final step before rendering, but when working directly with the document model, you can alternatively call applyTo on a TemplateDocument instance.

The result will look like this:

RootElement - Blocks: 1
. TemplateRoot - TemplateSpans: 5
. . TemplateString - '<html>|  <head>|    <title>'
. . TemplateElement(0)
. . . SpanSequence - Spans: 1
. . . . Text - 'Headline'
. . TemplateString - '</title>|  </head>|  <body>|    '
. . EmbeddedRoot(4) - Blocks: 2
. . . Title(Id(headline) + Styles(title)) - Spans: 1
. . . . Text - 'Headline'
. . . Paragraph - Spans: 1
. . . . Text - 'Some Text.'
. . TemplateString - '|  </body>|</html>'

The two nodes which previously represented the two context references have been replaced by the corresponding AST from the parsed markup.

In case of the main content, the node EmbeddedRoot represents the insertion point of one AST into another.

This final AST is the model that will be passed on to renderers.