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

> Understand tuicr theme configuration precedence. Learn how CLI flags, config keys, appearance, local themes, and bundled themes interact to determine your UI's look.

- Repository: [Almog Gavra/tuicr](https://github.com/agavra/tuicr)
- Tags: internals
- Published: 2026-08-02

---

**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`](https://github.com/agavra/tuicr/blob/main/src/main.rs), [`src/config/mod.rs`](https://github.com/agavra/tuicr/blob/main/src/config/mod.rs), and [`src/theme/mod.rs`](https://github.com/agavra/tuicr/blob/main/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`](https://github.com/agavra/tuicr/blob/main/src/main.rs) |
| 2 | **`theme` key in config** | [`src/config/mod.rs`](https://github.com/agavra/tuicr/blob/main/src/config/mod.rs) |
| 3 | **Appearance‑aware selection** (`appearance`, `theme_dark`, `theme_light`) | [`src/config/mod.rs`](https://github.com/agavra/tuicr/blob/main/src/config/mod.rs) |
| 4 | **Local theme files** (`~/.config/tuicr/themes/`) | [`src/theme/mod.rs`](https://github.com/agavra/tuicr/blob/main/src/theme/mod.rs) (`load_custom_theme`) |
| 5 | **Bundled theme constructors** | [`src/theme/mod.rs`](https://github.com/agavra/tuicr/blob/main/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`](https://github.com/agavra/tuicr/blob/main/src/main.rs). When present, the supplied name bypasses all configuration file settings.

```bash
tuicr --theme github-dark

```

This forces `Theme::github_dark()` regardless of any [`config.toml`](https://github.com/agavra/tuicr/blob/main/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`](https://github.com/agavra/tuicr/blob/main/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.

```toml
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`

```toml
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`](https://github.com/agavra/tuicr/blob/main/src/config/mod.rs)) for a matching file:

```rust
// 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`](https://github.com/agavra/tuicr/blob/main/src/theme/mod.rs):

```rust
// 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`](https://github.com/agavra/tuicr/blob/main/src/app.rs) demonstrates how the precedence chain combines:

```rust
// 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

```bash
tuicr -t gruvbox-light

```

Runs with the Gruvbox Light palette even if [`config.toml`](https://github.com/agavra/tuicr/blob/main/config.toml) specifies a dark theme.

### System‑Aware with Local Customizations

```toml

# ~/.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

```toml
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`](https://github.com/agavra/tuicr/blob/main/src/main.rs)** — Parses `--theme` CLI argument
- **[`src/config/mod.rs`](https://github.com/agavra/tuicr/blob/main/src/config/mod.rs)** — Loads TOML configuration, defines `themes_dir()`, handles `appearance`/`theme`/`theme_dark`/`theme_light` keys
- **[`src/theme/mod.rs`](https://github.com/agavra/tuicr/blob/main/src/theme/mod.rs)** — Implements `Theme::from_name()` dispatcher, bundled constructors, and `load_custom_theme()` for local files
- **[`docs/CONFIG.md`](https://github.com/agavra/tuicr/blob/main/docs/CONFIG.md)** — User‑facing documentation of precedence rules
- **[`README.md`](https://github.com/agavra/tuicr/blob/main/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`](https://github.com/agavra/tuicr/blob/main/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`](https://github.com/agavra/tuicr/blob/main/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.