Back to Noctalia

Template Reference

v5-theming-templates.md

latest15.6 KB
Original Source

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.

Scope

Section titled “Scope”

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:

  • for
  • if
  • else
  • endif
  • endfor

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 }}

Available Color Tokens

Section titled “Available Color Tokens”

The built-in Material color tokens are:

GroupTokens
Primaryprimary, on_primary, primary_container, on_primary_container, primary_fixed, primary_fixed_dim, on_primary_fixed, on_primary_fixed_variant
Secondarysecondary, on_secondary, secondary_container, on_secondary_container, secondary_fixed, secondary_fixed_dim, on_secondary_fixed, on_secondary_fixed_variant
Tertiarytertiary, on_tertiary, tertiary_container, on_tertiary_container, tertiary_fixed, tertiary_fixed_dim, on_tertiary_fixed, on_tertiary_fixed_variant
Errorerror, on_error, error_container, on_error_container
Surfacesurface, on_surface, surface_variant, on_surface_variant, surface_dim, surface_bright, surface_container_lowest, surface_container_low, surface_container, surface_container_high, surface_container_highest
Outline and utilityoutline, outline_variant, shadow, scrim
Inverseinverse_surface, inverse_on_surface, inverse_primary
Backgroundbackground, on_background

This list covers the 48 Material color tokens exposed through colors. Terminal tokens are listed in Terminal Tokens. Custom colors add their generated token names at render time; see Custom Colors.

Modes

Section titled “Modes”

Supported modes:

  • dark
  • light
  • default

default resolves to the configured default mode.

Special Values

Section titled “Special Values”

The following values are available directly:

  • mode
  • image
  • closest_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_high
  • on_hover -> on_surface

Formats

Section titled “Formats”

Supported output formats:

  • hex
  • hex_stripped
  • rgb
  • rgb_csv
  • rgba
  • hsl
  • hsla
  • red
  • green
  • blue
  • alpha
  • hue
  • saturation
  • lightness

Format behavior:

  • hex: #rrggbb
  • hex_stripped: rrggbb
  • rgb: rgb(r, g, b)
  • rgb_csv: r,g,b
  • rgba: rgba(r, g, b, a)
  • hsl: hsl(h, s%, l%)
  • hsla: hsla(h, s%, l%, a)
  • red, green, blue: integer channels
  • alpha: floating-point alpha
  • hue: integer hue
  • saturation, 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 weighting
  • invert - no argument; inverts each RGB channel
  • set_alpha - alpha 01, e.g. set_alpha 0.5
  • set_lightness - sets HSL lightness, 0100
  • set_hue - sets absolute hue in degrees, 0360
  • rotate_hue - rotates hue by a relative amount in degrees, e.g. rotate_hue 30
  • set_saturation - sets HSL saturation, 0100
  • set_red - sets the red channel, 0255
  • set_green - sets the green channel, 0255
  • set_blue - sets the blue channel, 0255
  • lighten - adds to lightness in percentage points, e.g. lighten 10
  • darken - subtracts from lightness in percentage points, e.g. darken 10
  • saturate - adds to saturation in percentage points, e.g. saturate 10
  • desaturate - subtracts from saturation in percentage points, e.g. desaturate 10
  • auto_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 amount 01 (default 1), e.g. blend: "#ff0000", 0.5
  • harmonize - 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:

  • replace
  • lower_case
  • camel_case
  • pascal_case
  • snake_case
  • kebab_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:

  • false is false
  • numeric 0 is 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:

  • colors
  • palettes.primary
  • palettes.secondary
  • palettes.tertiary
  • palettes.error
  • palettes.neutral
  • palettes.neutral_variant
  • numeric ranges like 0..10 and -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:

  • primary
  • secondary
  • tertiary
  • error
  • neutral
  • neutral_variant

Tone values:

  • 0
  • 5
  • 10
  • 15
  • 20
  • 25
  • 30
  • 35
  • 40
  • 50
  • 60
  • 70
  • 80
  • 90
  • 95
  • 98
  • 99
  • 100

Loop Metadata

Section titled “Loop Metadata”

Inside for loops, the loop object provides:

  • loop.index
  • loop.first
  • loop.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] defines named colors that are expanded into Material 3-style roles before templates render. It supports both forms:

[config.custom_colors]warning = "#f97316"
[config.custom_colors.warning]color = "#f97316"blend = true

Use the generated roles through colors.<role>.<mode>.<format> just like built-in palette colors:

.warning-banner { color: {{ colors.on_warning.default.hex }}; background: {{ colors.warning_container.default.hex }}; border-color: {{ colors.warning.default.hex }};}

In Noctalia’s main config, put the same custom color definitions under [theme.templates.custom_colors] and reference them from any enabled user template:

[theme.templates.custom_colors]warning = "#f97316"
[theme.templates.user.my_app]input_path = "$XDG_CONFIG_HOME/noctalia/templates/my-app.css"output_path = "$XDG_CONFIG_HOME/my-app/theme.css"

blend defaults to true, which harmonizes the custom color with the active palette. Set blend = false in the table form when the generated roles should keep the source hue as closely as possible.

Generated tokens per mode:

  • {name}_source
  • {name}_value
  • {name}
  • on_{name}
  • {name}_container
  • on_{name}_container

Template Entries

Section titled “Template Entries”

Each template entry supports:

  • input_path
  • output_path
  • output_path_dynamic
  • colors_to_compare
  • compare_to
  • pre_hook
  • post_hook
  • input_path_modes
  • index
  • requires_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_to is rendered first
  • colors_to_compare provides named comparison candidates
  • the closest candidate becomes available as {{ closest_color }}

Hooks

Section titled “Hooks”

Supported hooks:

  • pre_hook
  • post_hook

Behavior:

  • hook commands are rendered through the template engine first
  • pre_hook runs before any output files are written
  • post_hook runs after a template is processed successfully, even when rendered output was unchanged
  • closest_color is 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_foreground
  • terminal_background
  • terminal_cursor
  • terminal_cursor_text
  • terminal_selection_fg
  • terminal_selection_bg
  • terminal_normal_black
  • terminal_normal_red
  • terminal_normal_green
  • terminal_normal_yellow
  • terminal_normal_blue
  • terminal_normal_magenta
  • terminal_normal_cyan
  • terminal_normal_white
  • terminal_bright_black
  • terminal_bright_red
  • terminal_bright_green
  • terminal_bright_yellow
  • terminal_bright_blue
  • terminal_bright_magenta
  • terminal_bright_cyan
  • terminal_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:

Terminal window

noctalia theme <image> -r input.txt:output.txt

Process a TOML config:

Terminal window

noctalia theme <image> -c templates.toml

List available templates:

Terminal window

noctalia theme --list-templates

Pass -c <file> with --list-templates to include entries from a standalone template TOML config.

Process the shipped built-in template catalog:

Terminal window

noctalia theme <image> --builtin-config

The shipped built-in catalog lives at assets/templates/builtin.toml.

Render from a precomputed theme JSON:

Terminal window

noctalia theme --theme-json theme.json -r input.txt:output.txt

Process a TOML config:

Terminal window

noctalia theme <image> -c templates.toml

Set the default template mode:

Terminal window

noctalia theme <image> --default-mode light -r input.txt:output.txt