Theme Configuration Reference (v1.1)
January 24, 2026 · View on GitHub
Table of Contents
- Overview
- Theme Structure
- Style Inheritance
- Sections
- Property Reference
- Advanced Topics
- Complete Examples
- Migration from v0
Overview
Theme configuration v1.1 introduces role-based styling with inheritance, making themes more maintainable and consistent. All themes automatically inherit from a built-in @base theme that provides sensible defaults.
Key Features:
- Role-based styling: Define reusable styles
- Inheritance: Styles and elements can inherit from other styles
- Mode operations: Add/remove modes instead of replacing
- Multiple inheritance: Merge properties from multiple parent roles
- Built-in defaults: All undefined styles fall back to
@basetheme
Best Practices:
💡 Define generic styles first, specific elements only when necessary
For maximum compatibility with future versions:
- Start with styles - Define colors and modes in the
[styles]section using role names- Let elements inherit - Elements automatically inherit from their default style roles
- Override sparingly - Only define elements in
[elements]when you need element-specific stylingWhy? Future versions may add new elements. If you define generic styles (e.g.,
primary,secondary,accent), new elements will automatically pick up your theme's colors. If you only define specific elements, new elements will fall back to the default@basecolors, potentially breaking your theme's visual consistency.Example - Good (future-proof):
[styles] primary = { foreground = "#E5C07B" } accent = { foreground = "#98C379" } # New elements using these roles will automatically inherit your colorsExample - Less ideal:
[elements] time = { foreground = "#E5C07B" } key = { foreground = "#98C379" } # New elements won't inherit these colors
Supported Formats:
- TOML (
.toml) - Recommended - YAML (
.yaml,.yml) - JSON (
.json)
Theme Locations:
| OS | Location |
|---|---|
| macOS | ~/.config/hl/themes/*.{toml,yaml,yml,json} |
| Linux | ~/.config/hl/themes/*.{toml,yaml,yml,json} |
| Windows | %USERPROFILE%\AppData\Roaming\hl\themes\*.{toml,yaml,yml,json} |
Theme Structure
A v1 theme consists of six sections:
version = "1.1" # Required: Theme version
tags = ["dark", "256color"] # Optional: Theme metadata
[styles] # Optional: Reusable style definitions
# Define reusable styles with role names as keys
[elements] # Optional: Element-specific styles
# Override default element styles
[levels] # Optional: Per-level element overrides
# Override element styles for specific log levels
[indicators] # Optional: Status indicator styles
# Define indicator appearance
Minimal Valid Theme
The simplest valid v1 theme:
version = "1.1"
This theme inherits everything from @base.
Style Inheritance
Styles in the @base theme form an inheritance hierarchy. By default, all custom themes inherit these relationships:
You can override any style's inheritance using the style property. For example, to make warning inherit from error:
[styles.warning]
style = "error"
foreground = "yellow" # Override error's red with yellow
Sections
Version
Required. Must be "1.1" for the current version.
version = "1.1"
Supported versions:
"1.1"- Current version, addsunknownlevel support"1.0"- Previous version
Tags
Optional. Theme classification metadata.
tags = ["dark", "256color"]
Available tags:
dark- Dark themelight- Light theme16color- Optimized for 16-color terminals256color- Optimized for 256-color terminalstruecolor- Optimized for 24-bit color terminals
Tags can be combined (e.g., ["dark", "light"] means compatible with both).
Styles
Optional. Define reusable styles with role names as keys.
💡 Tip: Define your theme colors here first! Elements inherit from these styles by default, ensuring consistency and better forward compatibility. The more you use generic style roles instead of specific element overrides, the better your theme will adapt to future versions.
All 19 predefined role names:
| Role Name | Default Purpose |
|---|---|
default | Implicit base for all roles |
primary | Main content styling |
secondary | Secondary/dimmed content |
strong | Emphasized content |
muted | De-emphasized content |
accent | Highlighted content |
accent-secondary | Secondary highlights |
message | Log message text |
key | Key names in key-value pairs |
value | Values in key-value pairs |
syntax | Syntax elements (arrays, objects) |
status | Status indicators |
level | Log level styling |
unknown | Unrecognized log level (v1.1+) |
trace | Trace level specific |
debug | Debug level specific |
info | Info level specific |
warning | Warning level specific |
error | Error level specific |
Example:
[styles]
primary = { foreground = "#E0E0E0", modes = ["-faint"] }
secondary = { style = "primary", modes = ["faint"] }
warning = { style = "primary", foreground = "yellow", modes = ["bold"] }
error = { style = "primary", foreground = "bright-red", modes = ["bold"] }
Elements
Optional. Define styles for specific log elements.
💡 Tip: Only override elements when you need element-specific styling that differs from the inherited style roles. Most themes can achieve their look using just the
[styles]section, letting elements inherit naturally.
Elements in the @base theme inherit from style roles. The diagram below shows the default inheritance relationships:
Legend:
- Solid arrows - Style inheritance (element inherits from style role)
- Dashed arrows - Parent-inner relationship (inner element inherits from parent element by default)
All 28 predefined elements:
| Category | Elements |
|---|---|
| Input | input, input-number, input-number-inner, input-name, input-name-inner |
| Metadata | time, level, level-inner, logger, logger-inner, caller, caller-inner |
| Message | message, message-delimiter, field, key, ellipsis |
| Values | array, object, string, number, boolean, boolean-true, boolean-false, null |
Example:
[elements]
message = { style = "primary", modes = ["bold"] }
time = { style = "secondary" }
level-inner = { style = "level" }
key = { style = "accent", modes = ["italic"] }
string = { style = "value", foreground = "#98C379" }
Levels
Optional. Override element styles per log level. Supports six log levels:
unknown- Unrecognized log level (v1.1+)trace- Trace leveldebug- Debug levelinfo- Info levelwarning- Warning levelerror- Error level
Example:
[levels.warning]
level-inner = { style = ["level", "warning"] }
message = { style = ["message", "warning"] }
[levels.error]
level-inner = { style = ["level", "error"], modes = ["reverse"] }
message = { style = ["message", "error"] }
time = { style = "error" } # Even time gets error color
Indicators
Optional. Define styles for status indicators (used with --follow mode).
[indicators.sync]
synced = { text = " " }
failed = {
text = "!",
inner.style = { style = ["status", "warning"], modes = ["bold"] }
}
Structure:
text- The indicator character/textouter.prefix- Text before outer wrapperouter.suffix- Text after outer wrapperouter.style- Style for outer wrapperinner.prefix- Text before inner wrapperinner.suffix- Text after inner wrapperinner.style- Style for inner wrapper
Property Reference
Colors
Three formats supported:
1. ANSI Basic Colors
Named colors (case-sensitive):
foreground = "red"
background = "bright-white"
Available: default, black, red, green, yellow, blue, magenta, cyan, white, bright-black, bright-red, bright-green, bright-yellow, bright-blue, bright-magenta, bright-cyan, bright-white
2. ANSI Extended (256-color palette)
Integer from 0-255:
foreground = 139 # Tan
background = 235 # Dark gray
3. RGB (True color)
Hex format #RRGGBB:
foreground = "#E5C07B"
background = "#282C34"
Note: Hex letters (A-F) are case-insensitive.
Modes
Text rendering modes (case-sensitive):
bold- Bold textfaint- Dimmed textitalic- Italic textunderline- Underlined textslow-blink- Slow blinkingrapid-blink- Rapid blinkingreverse- Reversed colorsconceal- Hidden textcrossed-out- Strikethrough
Mode Operations (v1 feature)
Add mode:
modes = ["bold"] # Same as ["+bold"]
modes = ["+bold"] # Explicit add
Remove inherited mode:
modes = ["-faint"] # Remove faint mode
Combine operations:
modes = ["-faint", "bold", "italic"] # Remove faint, add bold and italic
Conflict resolution (last occurrence wins):
modes = ["+bold", "-bold"] # Bold is removed
modes = ["-bold", "+bold"] # Bold is added
Style Field
Reference one or more parent styles (by role name) for inheritance.
Single inheritance:
[styles.warning]
style = "primary"
foreground = "yellow"
Multiple inheritance:
[styles.accent-secondary]
style = ["accent", "secondary"]
When multiple role names are listed, properties are merged left-to-right (later values override earlier ones), then explicit properties override all inherited ones.
Advanced Topics
Inheritance Resolution
The complete resolution order for an element:
- Start with element from
@basetheme - Merge with base element from user theme
- Merge with level-specific element (if applicable)
- Resolve
stylefield recursively (up to 64 levels) - Apply explicit element properties (override role properties)
- Apply parent→inner element inheritance
Example:
# User theme
version = "1.0"
[styles]
warning = { foreground = "yellow" }
[elements]
level-inner = { modes = ["bold"] }
[levels.warning]
level-inner = { style = ["level", "warning"] }
For warning-level level-inner:
- Start with
@baseelements.level-inner - Merge user elements.level-inner (adds bold mode)
- Merge levels.warning.level-inner (adds style reference)
- Resolve style references: ["level", "warning"] → yellow foreground
- Apply explicit properties (bold mode already applied)
- Final result: yellow foreground, bold mode
Mode Operations
Mode operations modify inherited modes instead of replacing them:
[styles]
primary = { modes = ["bold", "italic"] }
secondary = { style = "primary", modes = ["-italic", "faint"] }
Result for secondary:
- Inherits:
bold,italic - Removes:
italic - Adds:
faint - Final:
bold,faint
Multiple Inheritance
Merge properties from multiple styles (by referencing multiple role names):
[styles]
accent = { foreground = "green" }
secondary = { modes = ["faint"] }
accent-secondary = { style = ["accent", "secondary"] }
Resolution of accent-secondary style:
- Start with
accentstyle → foreground: green - Merge
secondarystyle → adds modes: faint - Final: foreground: green, modes: faint
Parent-Inner Elements
Certain elements form parent-inner pairs where the inner element renders inside the parent's styling scope:
level/level-innerlogger/logger-innercaller/caller-innerinput-number/input-number-innerinput-name/input-name-innerboolean/boolean-true,boolean-false
Default behavior: Inner elements without explicit styles inherit the parent's resolved style.
Override behavior:
[elements]
level = { foreground = "cyan" }
level-inner = { foreground = "white", modes = ["bold"] }
The inner element's explicit properties override the parent's.
Complete Examples
Minimal Custom Theme
version = "1.1"
tags = ["dark"]
[styles]
warning = { foreground = "yellow" }
error = { foreground = "red" }
Medium Complexity Theme
version = "1.1"
tags = ["dark", "256color"]
[styles]
primary = { foreground = 250, modes = ["-faint"] }
secondary = { style = "primary", modes = ["faint"] }
accent = { foreground = 114 }
warning = { style = "accent", foreground = 214 }
error = { style = "accent", foreground = 196 }
[elements]
message = { style = "primary", modes = ["bold"] }
time = { style = "secondary" }
key = { style = "accent" }
string = { foreground = 114 }
number = { foreground = 170 }
[levels.warning]
level-inner = { style = "warning", modes = ["reverse"] }
[levels.error]
level-inner = { style = "error", modes = ["reverse", "bold"] }
message = { style = "error" }
Full-Featured Theme
version = "1.1"
tags = ["dark", "truecolor"]
# Define semantic styles with role names
[styles]
default = {}
primary = { foreground = "#E5C07B", modes = ["-faint"] }
secondary = { style = "primary", foreground = "#5C6370", modes = ["faint"] }
strong = { style = "primary", modes = ["bold"] }
accent = { foreground = "#98C379" }
accent-secondary = { style = "accent", modes = ["faint"] }
# Define message and value styles
message = { style = "strong", foreground = "#E06C75" }
key = { style = "accent", modes = ["italic"] }
value = { style = "primary" }
syntax = { style = "accent" }
# Define level-specific styles
warning = { style = "primary", foreground = "#E5C07B" }
error = { style = "primary", foreground = "#E06C75" }
# Map elements to styles (by role name)
[elements]
time = { style = "secondary" }
level = { style = "secondary" }
level-inner = { style = "primary" }
logger = { style = "accent-secondary" }
caller = { style = "secondary", modes = ["italic"] }
message = { style = "message" }
key = { style = "key" }
string = { style = "value", foreground = "#98C379" }
number = { style = "value", foreground = "#D19A66" }
boolean = { style = "value", foreground = "#56B6C2" }
null = { style = "secondary", foreground = "#C678DD" }
array = { style = "syntax" }
object = { style = "syntax" }
# Level-specific overrides
[levels.warning]
level-inner = { style = ["primary", "warning"], modes = ["reverse"] }
message = { style = ["message", "warning"] }
[levels.error]
level-inner = { style = ["primary", "error"], modes = ["reverse", "bold"] }
message = { style = ["message", "error"] }
time = { style = "error" }
# Indicators
[indicators.sync]
synced = { text = " " }
failed = {
text = "!",
inner.style = { style = "warning", modes = ["bold"] }
}
Migration from v0
v0 themes are still supported but v1 offers more powerful features.
Version 1.1 Changes
Added in v1.1:
- Support for
unknownstyle role - used for unrecognized log levels - Support for
unknowninlevelssection for styling entries with unrecognized levels
Migration: If you have a v1.0 theme, simply update the version:
version = "1.1"
Entries with unrecognized levels will use default styling from @base theme (muted style). To customize:
version = "1.1"
[styles]
unknown.foreground = "bright-black" # Custom styling for unrecognized levels
[levels.unknown]
level-inner.style = ["level", "unknown"]
Simple Migration from v0
v0 theme:
elements:
message:
foreground: "bright-white"
modes: ["bold"]
time:
foreground: "bright-black"
v1 equivalent:
version = "1.1"
[elements]
message = { foreground = "bright-white", modes = ["bold"] }
time = { foreground = "bright-black" }
DRY with Styles
v0 (repetitive):
elements:
message:
foreground: "#E0E0E0"
modes: ["bold"]
string:
foreground: "#E0E0E0"
number:
foreground: "#E0E0E0"
v1 (DRY with styles):
version = "1.1"
[styles]
primary = { foreground = "#E0E0E0" }
[elements]
message = { style = "primary", modes = ["bold"] }
string = { style = "primary" }
number = { style = "primary" }
Mode Semantics
v0: Child modes replace parent modes entirely.
v1: Child modes modify parent modes (add/remove operations).
v0 behavior:
levels:
warning:
level:
modes: ["bold"] # Replaces all inherited modes
v1 equivalent:
[levels.warning.level]
modes = ["bold"] # Adds bold to inherited modes
# To replicate v0 replacement behavior, remove all first:
# modes = ["-faint", "-italic", "bold"] # Explicit replacement
Key Differences
| Feature | v0 | v1 |
|---|---|---|
| Version field | Optional (missing = v0) | Required ("1.0") |
| Styles section | Not supported | 18 predefined role names |
| Inheritance | Parent-inner elements only | Styles + elements |
| Mode operations | Replace (override) | Add/remove (+/-) |
| Multiple inheritance | No | Yes (style = ["role1", "role2"]) |
| @base theme | No defaults | Inherits all defaults |
Tip: Start with a minimal v1 theme and override only what you need. The @base theme provides sensible defaults for everything!