Templating Guide
NDG supports a flexible templating system powered by Tera and backed by our own templating logic. Using this system, you may customize the appearance and structure of your generated documentation while avoiding what could be referred to as a "vendor lock-in".
Template Types
There are several "special" templates that is expected during documentation generation. NDG bundles its own templates that will be used by default, but you may override them as you wish to make your documentation site truly unique.
Core Templates
default.html- Main template for documentation pagesoptions.html- Template for NixOS module options pagesearch.html- Template for the search pagenavbar.html- Navigation bar template (shared across all pages)footer.html- Footer template (shared across all pages)options_toc.html- Table of contents template for options page
Assets
default.css- Default stylesheetmain.js- Scripts loaded for interactivitysearch.js- Secondary script used for the search functionality
Using Custom Templates
As mentioned before, you may
Template Directory
Using the template directory is the recommended way of overriding default templates of NDG. In this method, you must use a template directory containing all your custom templates:
ndg html --template-dir ./my-templates
Or in your ndg.toml:
template_dir = "./my-templates"
Tip
The ndg export-templates command can be used to create a directory with the
default templates, meant to be overridden by the user. ndg will prioritize
templates provided by the user wherever provided. Missing page templates will
cause ndg to use the default template for that page variant. E.g., providing a
template directory with just default.html would use the default.html
provided by the user, but the built-in templates for rest of the templatable
components.
Individual Template File
A dirty legacy method of quickly overriding default.html exists as
--template. This allows providing a quick override for only the default
template, which may be useful if you only want to override styles for
arbitrary generated pages, and not special pages or components such as the
navbar.
ndg html --template ./my-custom-template.html
This will use my-custom-template.html as the default.html template.
Per-File Templates
You can create file-specific templates that will be used instead of
default.html for matching markdown files.
Example:
foo.md-> will usefoo.htmltemplate if it exists, otherwise falls back todefault.htmlabout.md-> will useabout.htmltemplate if it exists, otherwise falls back todefault.html
This is useful when you want different layouts for different sections of your
documentation. It can also be used for, e.g., 404.html to generate your own
404 page with Markdown.
Template Components
Navbar Template (navbar.html)
Customize the navigation bar that appears in the header of all pages.
Available Variables:
{{ has_options }}- HTML attributes for options link styling (e.g.,class="active"){{ generate_search }}- Boolean indicating if search is enabled{{ options_path }}- Relative path to options page{{ search_path }}- Relative path to search page
Example
<nav class="header-nav">
<ul>
<li {{ has_options|safe }}>
<a href="{{ options_path }}">Options</a>
</li>
{% if generate_search %}
<li><a href="{{ search_path }}">Search</a></li>
{% endif %}
<li><a href="/about.html">About</a></li>
</ul>
</nav>
Footer Template (footer.html)
Customize the footer that appears at the bottom of all pages.
Available Variables:
{{ footer_text }}- Footer text from configuration
Example
<footer>
<p>{{ footer_text }}</p>
<p>Built with <a href="https://github.com/feel-co/ndg">ndg</a></p>
</footer>
Exporting Default Templates
To export all default templates for customization:
ndg export-templates --output ./templates
This will create a directory with all default templates that you can then modify.
Use --force to overwrite existing files:
ndg export-templates --output ./templates --force
Template Variables
Common Variables (Available in all templates)
{{ title }}- Page title{{ site_title }}- Site title from configuration{{ footer_text }}- Footer text from configuration{{ content|safe }}- Rendered markdown content{{ toc|safe }}- Table of contents HTML{{ doc_nav|safe }}- Document navigation HTML{{ navbar_html|safe }}- Rendered navbar HTML{{ footer_html|safe }}- Rendered footer HTML{{ has_options }}- Options link styling attributes{{ generate_search }}- Boolean for search feature{{ custom_scripts|safe }}- Custom scripts HTML{{ meta_tags_html|safe }}- Meta tags HTML{{ opengraph_html|safe }}- OpenGraph tags HTML
Asset Paths
{{ stylesheet_path }}- Path to stylesheet{{ main_js_path }}- Path to main JavaScript file{{ search_js_path }}- Path to search JavaScript file{{ index_path }}- Path to index page{{ options_path }}- Path to options page{{ search_path }}- Path to search page
Options Page Specific
{{ heading }}- Page heading{{ options|safe }}- Rendered options HTML
Template Syntax
Templates use the Tera templating engine, which is similar to Jinja2/Django templates. Below are some examples provided for your convenience. Please refer to the Tera's documentation for more details. Everything supported by Tera is supported in NDG's templated documents.
Variables
{{ variable_name }}
Use the safe filter to render HTML without escaping:
{{ html_content|safe }}
Conditionals
{% if generate_search %}
<div class="search">...</div>
{% endif %}
Loops
{% for item in items %}
<li>{{ item }}</li>
{% endfor %}
Example: Custom Theme
Here's an example of creating a custom theme:
-
Export the default templates:
ndg export-templates --output ./my-theme -
Customize the templates in
./my-theme/ -
Modify
navbar.htmlto add custom links:<nav class="header-nav">
<ul>
<li><a href="/docs.html">Docs</a></li>
<li {{ has_options|safe }}>
<a href="{{ options_path }}">Options</a>
</li>
<li><a href="/blog.html">Blog</a></li>
{% if generate_search %}
<li><a href="{{ search_path }}">Search</a></li>
{% endif %}
</ul>
</nav> -
Update
footer.html:<footer>
<div class="footer-content">
<p>{{ footer_text }}</p>
<p>
© 2025 My Project | <a href="/privacy.html">Privacy</a> | <a
>href="/terms.html">Terms</a>
</p>
</div>
</footer> -
Create a custom template for your homepage (
index.html):
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>{{ title }}</title>
<link rel="stylesheet" href="{{ stylesheet_path }}" />
</head>
<body>
<header>
<h1 class="site-title">
<a href="{{ index_path }}">{{ site_title }}</a>
</h1>
{{ navbar_html|safe }}
</header>
<main class="hero">
<h1>Welcome to My Documentation</h1>
{{ content|safe }}
</main>
{{ footer_html|safe }}
</body>
</html>
Now you may use the custom theme freely using the --template-dir option.
ndg option --template-dir ./my-theme
Configuration Example
In ndg.toml:
title = "My Awesome Site"
footer_text = "Built with ☕ and ♥️ "
template_dir = "./templates"
stylesheet_paths = ["./assets/custom.css"]
script_paths = ["./assets/custom.js"]
[opengraph]
"og:title" = "My Documentation"
"og:description" = "Comprehensive documentation for my project"
"og:image" = "./assets/banner.png"
[meta_tags]
description = "My amazing documentation"
keywords = "docs, markdown, documentation"
Best Practices
For a clean(er) experience, we recommend that you:
- Start with defaults: Export the default templates and modify them rather than starting from scratch
- Keep it modular: Use the navbar and footer templates to keep common elements consistent
- Preserve CSS classes: Keep the default CSS classes to maintain functionality (sidebar toggle, search, etc.)
The nature of a mixed-approach that NDG supports may sometimes yield results that can be best described as annoying. Though it is perfectly possible to have a good design and keep your sanity.
Troubleshooting
Template not found
Make sure your template directory contains the required templates or that fallback templates are available.
Variables not rendering
- Check variable names match the documentation
- Use
{{ variable|safe }}for HTML content - Check for typos in variable names
Styling issues
- Ensure your custom CSS doesn't conflict with default styles
- Check that CSS classes used by JavaScript features are preserved
- Test responsive design on mobile devices
Per-file template not working
- Ensure the template filename matches the markdown filename (e.g.,
foo.htmlforfoo.md) - Check that the template is in the configured template directory
- Verify the template has valid Tera syntax