Template Reference
TemplateEngine renders text and files from a theme token map. It supports inline expressions, block syntax for loops and conditionals, color formatting, filter pipelines, and TOML-driven batch processing.
This document describes the supported template syntax and data model.
TemplateEngine is responsible for:
- Rendering template strings and files
- Reading values from theme token maps
- Applying color and string filters
- Executing loops and conditionals
- Processing TOML template configs
- Computing custom colors and
closest_color
It is not responsible for image loading or palette extraction.
Syntax
Section titled “Syntax”Expressions
Section titled “Expressions”Inline expressions use:
{{ ... }}Examples:
{{ colors.primary.default.hex }}{{ colors.surface.dark.rgb }}{{ mode }}{{ image }}Blocks
Section titled “Blocks”Control blocks use:
<* ... *>Supported block tags:
forifelseendifendfor
Examples:
<* for name, value in colors *>{{ name }}={{ value.default.hex }}<* endfor *><* if {{ loop.first }} *>first<* else *>not first<* endif *>Standalone Block Trimming
Section titled “Standalone Block Trimming”If a block tag appears alone on a line, with only whitespace around it, that whole line is removed from the rendered output. This matters for whitespace-sensitive templates.
Data Model
Section titled “Data Model”Palette Colors
Section titled “Palette Colors”Colors are accessed with:
{{ colors.<name>.<mode>.<format> }}Examples:
{{ colors.primary.default.hex }}{{ colors.surface.light.hsl }}{{ colors.terminal_foreground.default.hex_stripped }}Supported modes:
darklightdefault
default resolves to the configured default mode.
Special Values
Section titled “Special Values”The following values are available directly:
modeimageclosest_color
mode is the current default mode.
image is the source image path when rendering from an image-driven theme.
closest_color is populated during config-driven rendering when compare_to and colors_to_compare are used.
Color Aliases
Section titled “Color Aliases”Supported aliases:
hover->surface_container_highon_hover->on_surface
Formats
Section titled “Formats”Supported output formats:
hexhex_strippedrgbrgb_csvrgbahslhslaredgreenbluealphahuesaturationlightness
Format behavior:
hex:#rrggbbhex_stripped:rrggbbrgb:rgb(r, g, b)rgb_csv:r,g,brgba:rgba(r, g, b, a)hsl:hsl(h, s%, l%)hsla:hsla(h, s%, l%, a)red,green,blue: integer channelsalpha: floating-point alphahue: integer huesaturation,lightness: integer percentages
Filters
Section titled “Filters”Filters are chained with pipe syntax:
{{ colors.primary.default.hex | grayscale }}{{ colors.primary.default.hex | set_alpha 0.5 }}{{ colors.primary.default.hex | blend: "#ff0000", 0.5 }}Supported syntaxes:
| filter| filter arg| filter: arg
Numeric filter arguments are locale-invariant and use . as the decimal separator, for example set_alpha 0.5.
Color Filters
Section titled “Color Filters”These operate on colors:
grayscale— no argument; converts to gray using luminance weightinginvert— no argument; inverts each RGB channelset_alpha— alpha0–1, e.g.set_alpha 0.5set_lightness— sets HSL lightness,0–100set_hue— sets absolute hue in degrees,0–360rotate_hue— rotates hue by a relative amount in degrees, e.g.rotate_hue 30set_saturation— sets HSL saturation,0–100set_red— sets the red channel,0–255set_green— sets the green channel,0–255set_blue— sets the blue channel,0–255lighten— adds to lightness in percentage points, e.g.lighten 10darken— subtracts from lightness in percentage points, e.g.darken 10saturate— adds to saturation in percentage points, e.g.saturate 10desaturate— subtracts from saturation in percentage points, e.g.desaturate 10auto_lightness— shifts lightness by the given percentage points away from mid (lightens dark colors, darkens light ones)
Color-Argument Filters
Section titled “Color-Argument Filters”These accept another color:
blend— interpolates hue toward the given color by an amount0–1(default1), e.g.blend: "#ff0000", 0.5harmonize— nudges hue toward the given color, capped at 15°to_color— reinterprets a string value as a color so color filters can follow
Examples:
{{ colors.primary.default.hex | blend: "#ff0000", 0.5 }}{{ colors.primary.default.hex | harmonize: "#00ff88" }}{{ "#ffaa00" | to_color | darken 0.1 }}String Filters
Section titled “String Filters”These operate on strings:
replacelower_casecamel_casepascal_casesnake_casekebab_case
Examples:
{{ mode | replace: "dark", "night" }}{{ "surface container high" | snake_case }}Control Flow
Section titled “Control Flow”For Loops
Section titled “For Loops”Supported forms:
<* for item in iterable *>...<* endfor *><* for key, value in colors *>...<* endfor *>Conditionals
Section titled “Conditionals”Supported forms:
<* if {{ expr }} *>...<* endif *><* if not {{ expr }} *>...<* else *>...<* endif *>Truthiness rules:
falseis false- numeric
0is false - empty strings are false
- case-insensitive
"false","0", and"none"are false - empty arrays and maps are false
- everything else is true
Iterables
Section titled “Iterables”Supported iterable expressions:
colorspalettes.primarypalettes.secondarypalettes.tertiarypalettes.errorpalettes.neutralpalettes.neutral_variant- numeric ranges like
0..10and-5..5 - arrays and maps from the current scope
colors
Section titled “colors”colors iterates all available token names and their mode maps:
<* for name, value in colors *>{{ name }}={{ value.default.hex }}<* endfor *>palettes.*
Section titled “palettes.*”palettes.* iterates derived tone steps for the selected palette family.
Supported families:
primarysecondarytertiaryerrorneutralneutral_variant
Tone values:
05101520253035405060708090959899100
Loop Metadata
Section titled “Loop Metadata”Inside for loops, the loop object provides:
loop.indexloop.firstloop.last
loop.index is zero-based.
File Rendering
Section titled “File Rendering”When rendering files, TemplateEngine:
- reads the template file
- renders the output
- creates parent directories when needed
- skips rewriting unchanged files
- reports render failures through the file render result
Skipping unchanged output avoids unnecessary timestamp updates.
Config Processing
Section titled “Config Processing”TemplateEngine supports TOML-driven template processing.
Top-level sections:
[config][templates]
Custom Colors
Section titled “Custom Colors”[config.custom_colors] supports both forms:
[config.custom_colors]brand = "#ff0000"[config.custom_colors.brand]color = "#ff0000"blend = trueIn Noctalia’s main config, use [theme.templates.custom_colors] with the same value forms.
Generated tokens per mode:
{name}_source{name}_value{name}on_{name}{name}_containeron_{name}_container
Template Entries
Section titled “Template Entries”Each template entry supports:
input_pathoutput_pathoutput_path_dynamiccolors_to_comparecompare_topre_hookpost_hookinput_path_modesindexrequires_path
output_path accepts either a single string or an array of strings:
[templates.qt]input_path = "./qtct.conf"output_path = [ "$XDG_CONFIG_HOME/qt5ct/colors/noctalia.conf", "$XDG_CONFIG_HOME/qt6ct/colors/noctalia.conf",]output_path_dynamic is optional. It is a shell command string (same templating as hooks: {{ config_dir }}, {{ mode }}, etc.). After rendering, the shell runs it; on exit status 0, each non-empty line of stdout is appended as an output path (after resolveConfigPath). Lines starting with # are ignored. Use this when destinations depend on the machine (for example Emacs config layout) without hard-coding app-specific logic in the engine. Static output_path entries, if any, are kept; dynamic lines are added after them.
input_path_modes selects a different template input for dark and light mode:
[templates.foo]input_path_modes = { dark = "./dark.css", light = "./light.css" }output_path = "$XDG_CONFIG_HOME/foo/theme.css"Relative input_path and output_path values are resolved from the config file directory.
A leading $XDG_CONFIG_HOME, $XDG_DATA_HOME, $XDG_STATE_HOME, or $XDG_CACHE_HOME token in
input_path/output_path is expanded per the XDG Base Directory spec:
the environment variable when it is set, otherwise the spec default (~/.config, ~/.local/share,
~/.local/state, ~/.cache). Prefer these tokens over a hardcoded ~/.config so output lands in the right
place when a config/data/cache home is relocated. A leading ~ still expands to $HOME only.
index controls processing order and defaults to 0.
requires_path is optional. When set, the entry is skipped unless that path already exists on disk. Use it for explicit install checks (for example a Flatpak data directory).
When several entries share the same input_path but write to different client config roots (for example Discord forks or VS Code vs VSCodium), the engine infers each output’s client root and skips outputs whose root is missing. Single-app templates (one entry per input) still create missing directories as before.
Closest Color
Section titled “Closest Color”compare_to and colors_to_compare can be used to compute closest_color.
Behavior:
compare_tois rendered firstcolors_to_compareprovides named comparison candidates- the closest candidate becomes available as
{{ closest_color }}
Supported hooks:
pre_hookpost_hook
Behavior:
- hook commands are rendered through the template engine first
pre_hookruns before any output files are writtenpost_hookruns after a template is processed successfully, even when rendered output was unchangedclosest_coloris available inside hooks
Additional variables available inside hooks and templates:
{{ mode }}{{ image }}{{ closest_color }}{{ config_dir }}{{ config_file }}
Terminal Tokens
Section titled “Terminal Tokens”Every resolved theme exposes flattened terminal tokens. Built-in and community palettes provide curated terminal colors, while wallpaper-derived themes synthesize terminal colors from the generated palette.
terminal_foregroundterminal_backgroundterminal_cursorterminal_cursor_textterminal_selection_fgterminal_selection_bgterminal_normal_blackterminal_normal_redterminal_normal_greenterminal_normal_yellowterminal_normal_blueterminal_normal_magentaterminal_normal_cyanterminal_normal_whiteterminal_bright_blackterminal_bright_redterminal_bright_greenterminal_bright_yellowterminal_bright_blueterminal_bright_magentaterminal_bright_cyanterminal_bright_white
These are accessed like any other color token:
{{ colors.terminal_background.default.hex }}{{ colors.terminal_normal_red.default.rgb }}CLI Usage
Section titled “CLI Usage”Template rendering is available through noctalia theme.
Render one file:
noctalia theme <image> -r input.txt:output.txtProcess a TOML config:
noctalia theme <image> -c templates.tomlList available templates:
noctalia theme --list-templatesPass -c <file> with --list-templates to include entries from a standalone template TOML config.
Process the shipped built-in template catalog:
noctalia theme <image> --builtin-configThe shipped built-in catalog lives at assets/templates/builtin.toml.
Render from a precomputed theme JSON:
noctalia theme --theme-json theme.json -r input.txt:output.txtProcess a TOML config:
noctalia theme <image> -c templates.tomlSet the default template mode:
noctalia theme <image> --default-mode light -r input.txt:output.txt