Worktree Colors
A VS Code extension that gives every git repo and worktree a distinct visual
identity — window chrome color, OS window title with the branch, and a stable
per-repo emoji — so you can tell them apart at a glance, even in the window
switcher, Mission Control or the Dock.
Features
- Per-repo color family. Each repository gets a distinct base hue, assigned
from a shared registry so hues stay spread-apart and stable across sessions.
- Per-worktree shade. Worktrees of the same repo share that hue but differ
by a small lightness step, so they read as one family while staying tellable
apart. The main worktree gets the base shade.
- OS window title with repo and branch. Uses
${activeRepositoryBranchName}
so the branch follows checkouts automatically. Visible before a window is
focused — the only cue that helps in the window switcher, Mission Control and
the Dock.
- Per-repo emoji in the window title. Distinguishes repos where color isn't
available (again: switcher, Dock). Registry-assigned so distinct repos get
distinct glyphs, and the glyph is matched to the repo's hue where possible
(a red repo gets 🍅/🌶/🍄, a blue one 🌊/🐬/🧊).
- Native window frame in the repo color. Via
window.activeBorder — drawn
by the OS, so it survives modern UI and bounds cleanly to the window edge.
Needs OS accent support (recent macOS / Windows 11).
- Optional soft sidebar tint.
worktreeColors.sidebarTint (0–100 %) paints
sideBar.background as an alpha overlay so it blends with the theme —
reinforces repo identity in both classic and modern-UI modes.
- Modern UI aware. VS Code's
workbench.experimental.modernUI forces the
title, activity and status bar backgrounds transparent (upstream:
microsoft/vscode#326126).
When it's on, the extension detects it, warns once with a one-click opt-out,
and falls back to tinting the foregrounds (or a badge / accent / sidebar,
configurable) — see Modern UI below.
- Name a worktree for a short label in the title and status badge, since
generated branch names like
pfollia/sage-ap/create-bucket-ap-northeast-1
are the least readable thing in a switcher.
- Warm, dark defaults — tunable to any palette, dark or light, via settings
or the Choose Palette command.
- Theme-aware. Works under light and dark editor themes; text color is
auto-picked for contrast.
- Manual tab colors. Assign a fixed color to individual file tabs,
independent of the worktree theming.
- Diagnose command + output channel. Every "why isn't this coloured?" is
answerable without a debugger.
How the colors work
A color is generated in HSL from two parts:
- Hue comes from the repository identity (its shared git common dir), so
it's the same across every worktree of that repo and distinct between repos.
- Lightness comes from the worktree — a monochromatic shade of the repo hue.
Saturation and the hue range are fixed per palette. By default the hue arc is
warm (300°→75°: magenta → rose → red → orange → amber → gold).
Modern UI
VS Code 1.129 shipped workbench.experimental.modernUI as a preview. Its
declared default is false, but an experiment auto-enrols users who never set
it, and its CSS forces background-color: transparent !important on the title,
activity and status bars — no workbench.colorCustomizations value can beat
!important, so the extension appears to stop working with no visible cause.
This affects every window-colouring extension identically (Peacock included).
Upstream tracking issue:
microsoft/vscode#326126.
When modern UI is detected, the extension:
- Warns once with a one-click Disable modern UI + reload, or a Show
details button that opens the Diagnose report.
- Falls back to tinting foregrounds with the repo hue (re-lightened to
read against the shell), so the bar text and activity-bar icons still carry
the identity. Configurable via
worktreeColors.modernUIStrategy:
foreground (default) — tint the bar text and icons.
badge — a repo · branch badge in the status bar (bounded to the
extension's own item).
accent — coloured borders on the active tab, side bar and panel.
sidebar — paint the side bar (also paints the auxiliary bar, since
modern UI mirrors sideBar.background onto it).
sidebar+accent — both.
- Keeps writing the native window frame (
window.activeBorder /
inactiveBorder) — the one coloured surface modern UI cannot flatten.
- Logs everything in the Worktree Colors output channel and the
Diagnose command, including which of our keys can actually take effect
in the current layout.
Switching the setting re-tints live, without a reload.
Commands
Run from the Command Palette (⌘⇧P):
| Command |
Description |
| Worktree Colors: Choose Palette |
Pick a preset (Warm, Cool, Vibrant, Full wheel, Muted/dark); written to User settings. |
| Worktree Colors: Import Palette |
Paste a coolors.co URL or hex list to use as repo base colors. |
| Worktree Colors: Set Color for This Repo |
Pin the current repo to a specific color (or clear the pin). |
| Worktree Colors: Set Emoji for This Repo |
Pin the current repo to a specific emoji (or clear the pin). |
| Worktree Colors: Name This Worktree |
Give this worktree a short label used instead of the branch in the title and status badge. |
| Worktree Colors: Reset Repo Colors |
Forget all repo color assignments and pins, then reshuffle. |
| Worktree Colors: Remove Colors from Workspace |
Strip the chrome customizations and window title from this workspace's settings. |
| Worktree Colors: Toggle Sidebar Tint |
Flip sidebarTint on/off from the palette. On restores your previous non-zero value (or defaults to 5%). |
| Worktree Colors: Exclude IDE Colors from Git |
Set up a clean filter or gitignore so the colors aren't committed. |
| Worktree Colors: Assign Color to Active Tab |
Give the current file tab a fixed color. |
| Worktree Colors: Clear Color for Active Tab |
Remove a manual tab color. |
| Worktree Colors: Refresh |
Recompute and re-apply the chrome colors. |
| Worktree Colors: Inspect Tabs |
Dump current tab/color state to an output channel. |
| Worktree Colors: Diagnose |
Report the effective layout settings, which of our keys are present, and the known worktrees — the one-shot answer to "why is this window not coloured?". |
Settings
All under worktreeColors.*. Set them in User settings so they don't end up
in a repo's .vscode/settings.json. Changes apply live.
| Setting |
Default |
Description |
customPalette |
[] |
Hex colors used as repo base colors instead of generated hues. Empty = generated. Use Import Palette to set. |
tint |
all |
Which chrome elements to color: titleBar, activityBar, statusBar. |
colorSingleWorktreeRepos |
true |
Tint repos that have only one worktree; turn off to tint only multi-worktree repos. |
sidebarTint |
0 |
Soft repo-coloured tint on the side bar background, 0–100 % (decimals allowed). Written as an opaque mix of the repo colour into a theme-neutral grey — keeps --vscode-sideBar-background solid so webviews inside the sidebar (Claude Code, GitLens, …) stay legible. sideBar.background is the only key VS Code exposes, so the tint covers the whole sidebar (Explorer, Search, Git…) and — under modern UI — the auxiliary bar too. 5–15 reads as a soft wash; 30+ feels heavy. 0 disables. Flip on/off with Toggle Sidebar Tint. |
modernUIStrategy |
foreground |
What to color under VS Code's modern UI: foreground, badge, accent, sidebar, sidebar+accent. Ignored when modern UI is off. See Modern UI. |
windowTitle |
true |
Set window.title for this workspace so the repo and branch appear in the OS window title. A title already set in the workspace is never overwritten. |
windowTitlePattern |
${rootName} ▸ ${activeRepositoryBranchName}${separator}${activeEditorShort} |
The pattern written to window.title. Uses VS Code's title variables. Change the ▸ here to use ·, /, › or anything else. |
windowTitleEmoji |
true |
Prefix the window title with a stable per-repo emoji, keyed off the git common dir so every worktree of a repo shares it. |
customEmoji |
[] |
Your own glyph set for the window-title emoji, replacing the built-in 72. Single emoji only — ZWJ sequences and skin-tone modifiers render inconsistently and are dropped. |
matchEmojiToColor |
true |
Prefer glyphs whose dominant color agrees with the repo's hue. Distinctness still wins: a hue-matched glyph is only used if free. |
hueRangeStart |
300 |
Start of the hue arc (degrees) repos draw from. Wraps if start > end. |
hueRangeEnd |
75 |
End of the hue arc. Use 0/360 for the full wheel. |
hueBuckets |
12 |
Number of distinct repo hues within the arc. |
dark.saturation |
58 |
Saturation (%) under a dark editor theme. |
dark.baseLightness |
34 |
Base lightness (%) under a dark theme. |
dark.lightnessStep |
5 |
Lightness gap between worktree shades (dark). |
light.saturation |
58 |
Saturation (%) under a light editor theme. |
light.baseLightness |
38 |
Base lightness (%) under a light theme. |
light.lightnessStep |
5 |
Lightness gap between worktree shades (light). |
The Set Color for This Repo / Reset Repo Colors pins and assignments are
stored globally (shared across windows), so colors stay stable between sessions.
Keeping the IDE colors out of your commits
To color a window, the extension must write workbench.colorCustomizations into
that workspace's .vscode/settings.json — that's the only place VS Code reads
per-window chrome colors from. There's no machine-local per-workspace settings
file, so this write is unavoidable and the file lives inside your repo.
The quickest path is the Worktree Colors: Exclude IDE Colors from Git command,
which sets this up for the current repo — pick either gitignore the file or
clean filter. The rest of this section explains what it does (and how to set it
up globally / by hand).
If you don't otherwise commit .vscode/settings.json, just gitignore it. The
tricky case is when you do need to commit other settings in that file (env
vars, formatter config, …) but not the colors. Git tracks whole files, not
regions — so the clean way to commit part of the file is a git clean
filter that strips the color block on staging.
One-time global setup
This makes git strip workbench.colorCustomizations from any
.vscode/settings.json when it's staged, while leaving your working copy (and
the live colors) untouched. Requires jq.
mkdir -p ~/.config/git
# 1. The filter script (falls back to passthrough if a file isn't valid JSON)
cat > ~/.config/git/strip-vscode-colors.sh <<'EOF'
#!/bin/sh
jq --indent 4 'del(.["workbench.colorCustomizations"])' 2>/dev/null || cat
EOF
chmod +x ~/.config/git/strip-vscode-colors.sh
# 2. Register the filter
git config --global filter.vscode-settings.clean "$HOME/.config/git/strip-vscode-colors.sh"
# 3. Apply it to .vscode/settings.json in every repo via global gitattributes
echo '.vscode/settings.json filter=vscode-settings' >> ~/.config/git/attributes
git config --global core.attributesfile "$HOME/.config/git/attributes"
Per repo (only if colors were already committed)
A clean filter only runs on files as they're staged, so a settings.json that
already has committed colors needs a one-time re-stage to drop them:
git add --renormalize .vscode/settings.json
git commit -m "Stop tracking IDE chrome colors in settings.json"
--renormalize re-runs the filter over already-tracked files without editing
them, producing a commit that removes the existing color block. After that,
git status stays clean even as the extension keeps rewriting the colors
locally.
Notes
- Prefer a committed
.gitattributes (with the .vscode/settings.json filter=vscode-settings line) if you want teammates covered — git just no-ops
the filter for anyone who hasn't defined it.
- If a
settings.json contains // comments (JSONC), jq can't parse it; the
|| cat fallback passes it through unchanged so a commit never breaks (colors
aren't stripped that time).
Contributing
See CONTRIBUTING.md for build, test, and release instructions.