How Theme Configuration Precedence Is Determined in Tuicr (Bundled vs Local)

Tuicr determines theme precedence through a five‑layer hierarchy: CLI flags override the theme config key, which overrides appearance‑based theme_dark/theme_light selection, with local theme files taking priority over built‑in bundled themes.

Tuicr, a terminal UI crate authored by agavra, implements a cascading resolution system for its colour palettes. Understanding this theme configuration precedence chain lets you predict exactly which palette renders when multiple settings conflict. The resolution logic spans src/main.rs, src/config/mod.rs, and src/theme/mod.rs, with clear rules governing bundled versus local theme sources.

The Five‑Layer Precedence Chain

Tuicr evaluates theme sources in strict priority order. Higher‑ranked sources completely override lower ones.

Rank Source Implementation Location
1 --theme CLI flag src/main.rs
2 theme key in config src/config/mod.rs
3 Appearance‑aware selection (appearance, theme_dark, theme_light) src/config/mod.rs
4 Local theme files (~/.config/tuicr/themes/) src/theme/mod.rs (load_custom_theme)
5 Bundled theme constructors src/theme/mod.rs (Theme::from_name)

1. Command‑Line Flag (--theme / -t)

The highest‑priority source is the --theme argument parsed in src/main.rs. When present, the supplied name bypasses all configuration file settings.

tuicr --theme github-dark

This forces Theme::github_dark() regardless of any config.toml entries. The flag value is passed verbatim to the application initialization.

2. Explicit theme Config Key

After CLI processing, load_config() in src/config/mod.rs reads the theme key from $XDG_CONFIG_HOME/tuicr/config.toml (or %APPDATA%\tuicr\config.toml on Windows). If set, this key wins over the dark/light appearance keys.

theme = "tokyo-night-storm"

With this configuration, Tuicr skips appearance detection entirely and attempts to resolve "tokyo-night-storm".

3. Appearance‑Driven Selection (theme_dark / theme_light)

When no explicit theme key exists, Tuicr evaluates the appearance setting:

  • appearance = "system" — detects terminal background brightness and selects theme_dark (dark background) or theme_light (light background)
  • appearance = "dark" — forces theme_dark
  • appearance = "light" — forces theme_light
appearance = "system"
theme_dark = "gruvbox-dark"
theme_light = "gruvbox-light"

If terminal detection fails or keys are unset, the resolver defaults to "dark".

4. Local Theme Files

Once a theme name is determined, Tuicr checks themes_dir() (defined in src/config/mod.rs) for a matching file:

// src/theme/mod.rs — simplified local theme resolution
let path = themes_dir()?.join(&chosen_name).with_extension("toml");
if path.exists() {
    return Theme::load_from_file(&path);  // 4️⃣ local theme wins
}

Local definitions reside at ~/.config/tuicr/themes/<NAME>.toml and override bundled equivalents of the same name.

5. Bundled (Built‑in) Themes

Finally, Tuicr falls back to compiled‑in constructors via Theme::from_name() in src/theme/mod.rs:

// src/theme/mod.rs — bundled theme dispatch
match name {
    "dark" => Theme::dark(),
    "light" => Theme::light(),
    "github-dark" => Theme::github_dark(),
    "github-light" => Theme::github_light(),
    "gruvbox-dark" => Theme::gruvbox_dark(),
    "gruvbox-light" => Theme::gruvbox_light(),
    "tokyo-night-storm" => Theme::tokyo_night_storm(),
    _ => Theme::dark(),  // default fallback
}

Unknown theme names resolve to Theme::dark().

Complete Resolution Flow in Code

The following simplified excerpt from src/app.rs demonstrates how the precedence chain combines:

// CLI flag extraction (src/main.rs)
let cli_theme = matches.value_of("theme");

// Config loading (src/config/mod.rs)
let cfg = load_config()?.config.unwrap_or_default();

// Final resolution (application entry point)
let chosen_name = match cli_theme {
    Some(name) => name.to_string(),                // 1️⃣ CLI flag
    None => match cfg.theme {
        Some(name) => name,                       // 2️⃣ config.theme
        None => {                                 // 3️⃣ appearance logic
            match cfg.appearance.as_deref() {
                Some("dark") => cfg.theme_dark.clone(),
                Some("light") => cfg.theme_light.clone(),
                Some("system") | None => {
                    if terminal_is_dark() { cfg.theme_dark.clone() }
                    else { cfg.theme_light.clone() }
                }
                _ => None,
            }
        }.unwrap_or_else(|| "dark".to_string())
    }
};

// Local file check before bundled fallback
if let Ok(theme) = load_custom_theme(&chosen_name) {
    theme                                          // 4️⃣ local file
} else {
    Theme::from_name(&chosen_name)                 // 5️⃣ bundled
}

Practical Configuration Examples

Override Everything with a CLI Flag

tuicr -t gruvbox-light

Runs with the Gruvbox Light palette even if config.toml specifies a dark theme.

System‑Aware with Local Customizations


# ~/.config/tuicr/config.toml

appearance = "system"
theme_dark = "custom-dark"
theme_light = "solarized-light"

Create ~/.config/tuicr/themes/custom-dark.toml to override the bundled definition; solarized-light will use the built‑in version unless a local file exists.

Explicit Theme Without Appearance Detection

theme = "tokyo-night-storm"

This disables dark/light switching entirely. The named theme resolves to local file first, bundled constructor second.

Key Source Files

  • src/main.rs — Parses --theme CLI argument
  • src/config/mod.rs — Loads TOML configuration, defines themes_dir(), handles appearance/theme/theme_dark/theme_light keys
  • src/theme/mod.rs — Implements Theme::from_name() dispatcher, bundled constructors, and load_custom_theme() for local files
  • docs/CONFIG.md — User‑facing documentation of precedence rules
  • README.md — Quick reference for bundled theme names

Summary

  • CLI flag (--theme) always wins when present
  • theme config key overrides appearance‑based selection
  • appearance + theme_dark/theme_light provides automatic or forced light/dark handling
  • Local theme files (~/.config/tuicr/themes/) shadow bundled themes of the same name
  • Bundled constructors provide guaranteed fallbacks via Theme::from_name()

Frequently Asked Questions

What happens if I specify both --theme and a theme key in config?

The CLI flag takes precedence. According to src/main.rs, the --theme value is passed directly to the application and bypasses configuration file parsing for theme selection.

Can I override a bundled theme without modifying source code?

Yes. Create a file at $XDG_CONFIG_HOME/tuicr/themes/<NAME>.toml matching the bundled theme name. The load_custom_theme() function in src/theme/mod.rs checks local files before falling back to Theme::from_name(), so your local definition wins.

How does appearance = "system" detect terminal background?

Tuicr queries the terminal for background colour information. If detection succeeds and indicates a dark background, theme_dark is selected; otherwise theme_light applies. Detection failures default to dark mode.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →