A Rust CLI that applies Base16/Base24 color schemes across 22 Linux applications — and themes Windows system colors — from a single config file.
- 32 application modules — terminals, editors, compositors, bars, launchers, and more
- Windows support — accent color, dark/light mode, Windows Terminal, and VSCode via
coat set - Base16 & Base24 — compatible with the full tinted-theming ecosystem (~700 schemes)
- Scheme browser — search and preview with live RGB color swatches in the terminal
- Wallpaper matching —
coat matchsamples whatever the wallpaper daemon is displaying and builds a scheme from it - Reversible —
coat remove <app>undoes an app's theming from a manifest the apply wrote - Font & opacity — centralized font and transparency settings across all modules
coat/
├── src/
│ ├── main.rs # CLI dispatch
│ ├── config.rs # coat.yaml deserialization
│ ├── scheme.rs # scheme loading, search, list
│ ├── modules.rs # all 32 module apply functions
│ └── windows.rs # Windows-specific theming (registry, WT, VSCode)
├── templates/ # Tera templates for each module
├── Cargo.toml
└── USAGE.md
cargo build --releaseThe binary appears at target/release/coat (or coat.exe on Windows). No external dependencies — all scheme parsing, JSON/YAML handling, and templating are compiled in.
# First time: clone the scheme library
coat clone
# Browse schemes
coat list --dark
coat search gruvbox
# Scroll through schemes interactively (↑/↓ or j/k, / to filter, Enter applies)
coat browse
coat browse --light
# Switch scheme and apply everywhere in one shot
coat set catppuccin-mocha # suggests close matches on a typo
# Pick a random scheme — previews it, then prompts [Y]es / [r]eroll / [n]o
coat random
coat random --dark # restrict to dark schemes
coat random -y # apply immediately, skip the prompt
coat random --dry # preview a random pick without applying
# Apply current scheme from coat.yaml to all enabled apps
coat apply
# Apply to a single app
coat apply foot
# Build a scheme from the wallpaper that is on screen right now (awww or swww),
# write it into the schemes directory, and apply it
coat match
coat match ~/walls/whatever.png # or a specific image
coat match --light # a light scheme instead (dark is the default)
coat match --auto # let the image's own brightness decide
coat match --slots # keep base16 slot meanings instead
coat match --dry # generate and preview, apply nothing
# Undo one app: delete the files coat generated for it, strip the include line
# it added to your own config, and drop it from the enabled list
coat remove foot
coat remove foot --dry # show what that would do
coat remove foot --keep-enabled # clean up but leave it enabledThe image supplies the colours; fixed per-polarity ladders supply the lightness, so a muddy photo cannot produce a scheme whose foreground is invisible on its background.
By default the accents are the image's own colours — the eight most prominent,
assigned one per slot (closest slot/colour pair first, never the same colour
twice), at their own saturation. That is pywal's bargain: a wallpaper with one
strong hue gives you eight shades of that hue, and base0B is not necessarily
green. --slots takes the other bargain — every accent holds the colour its name
promises, at the cost of varying less between wallpapers.
- the image is downscaled to 160px and clustered in Oklab (k=12, farthest-point seeded, so the same wallpaper always yields the same scheme)
- dark by default, whatever the image's brightness. Inferring polarity means a
snowy wallpaper turns the desktop white, which nobody asks for;
--autoopts back into inference,--lightforces the other way base00–base07are a lightness/chroma ladder tinted with the image's chroma-weighted mean hue, so the background reads as of the wallpaper. The ladder is the median of every scheme in the tinted-theming library measured in Oklch (406 dark, 127 light) rather than an invented one — darkbase00lands at L 0.229, where catppuccin-mocha (0.243), tokyo-night (0.226) and rose-pine (0.213) all sit. The image's overall brightness nudges it within a narrow band- chroma humps at
base02/base03rather than decaying from the background, which is what both the corpus and the tinted schemes actually do base08–base0Fare the image's colours, one per slot, at their own saturation lifted into a legible band
Under --slots instead:
- each slot takes the nearest matching image hue within 45°. A slot the image has no colour for keeps its identity but leans up to 30° toward the image's nearest hue — without that, every photo lacking a red produced the same red, and wildly different wallpapers came out with identical accent rows
- accent chroma scales with how colourful the image is overall, so the row reads as one family and a washed-out photo gives muted accents
- no two accents may sit within 20° of each other, so
base0Dandbase0Ecannot collapse into the same blue
Generated schemes are written to ~/.config/coat/schemes/generated/, so
coat set, coat list and coat browse see them like any other scheme.
Fish and PowerShell completions cover subcommands, flags, and (dynamically) scheme and module names. Install them with:
coat completions fish # → ~/.config/fish/completions/coat.fish
coat completions fish --print # print to stdout instead of installingcoat completions powershell # → coat.completion.ps1 beside $PROFILE, sourced from it
coat completions powershell --printPowerShell has no autoloaded completions directory, so coat writes the script
next to your $PROFILE and adds a line there to dot-source it. Both steps are
idempotent — re-running just refreshes the script. $PROFILE is resolved by
asking PowerShell itself, so a OneDrive-redirected Documents folder and the
5.1 / 7 split are handled.
The scripts also live in completions/ so distro packages can
install them automatically (e.g. cargo-deb drops the fish one in
/usr/share/fish/vendor_completions.d/). Note that a plain cargo install
places only the binary — run coat completions <shell> once afterward.
Create ~/.config/coat/coat.yaml:
scheme: gruvbox-dark-hard
prefer_base24: false
enabled:
- fish
- neovim
- sway
- swaybar
- tofi
- dunst
- gtk
- vesktop
font:
monospace: "JetBrains Mono"
sansserif: "Ubuntu"
sizes:
terminal: 12
desktop: 10
popups: 10
opacity:
terminal: 0.95
popups: 0.95On Windows, coat set <scheme> themes the OS directly — no config file required:
- System accent color (registry, AccentPalette, taskbar/Start)
- Dark/light mode
- Windows Terminal — scheme, selected via
profiles.defaults, plus font and opacity fromcoat.yaml - VSCode color customizations and editor font
coat set nord
coat set gruvbox-dark-hard
coat set nord --elevate # also theme the logon screen (one UAC prompt)The logon-screen keys live in HKU\.DEFAULT and need admin. --elevate spawns
a short-lived elevated helper for just those, so the rest of the apply stays in
your current terminal. Already running elevated? No flag needed.
coat writes colours and fonts. Nothing else. Where an app has an include mechanism, coat writes a fragment beside your config and adds one include line; where it does not, coat patches only its own keys into your file and leaves every other line alone. Your geometry, timeouts, keybindings and behaviour stay yours across a theme change.
| Category | Modules |
|---|---|
| Terminals | kitty, foot |
| Shell | fish |
| Editors | neovim, vscode |
| WM / Compositors | mango, hyprland, sway |
| Bars | waybar, swaybar |
| Screen locker | swaylock |
| Launchers | fuzzel, tofi |
| Notifications | fnott, dunst |
| System | gtk, xresources |
| Utilities | bat, btop, zathura, mpv |
| Browsers | firefox (chrome + content) |
| Other | vesktop |
The one exception is swaybar: sway's parser rejects include inside a
bar { } block, so coat has to emit the whole block. See USAGE.md.
See USAGE.md for per-application activation steps, or run coat docs <app>.
See LICENSE.