The three built-in themes (Bandura Dark, Bandura Light, High Contrast) are each a complete set
of colour tokens: 22 named colours that everything on screen reads from. ui.themeOverrides
in settings.json lets you replace any of them individually, on top of
whichever theme is active.
There’s no theme file format to learn and nothing to install. It’s one object of
token: colour pairs.
One token, three surfaces
A token isn’t a per-widget colour. Each one is a role, and every surface that plays that role reads it: the app chrome, the code editor, and the built-in terminal all take their colours from the same map.
So overriding bg moves the app background, the editor well, and the terminal ground together.
All three repaint the moment you save. You don’t reopen the editor, restart the
terminal, or relaunch the app.
The tokens
Values are the Bandura Dark defaults, for reference; Light and High Contrast use their own.
Grounds and text
| Token | Dark default | Role |
|---|---|---|
bg | #0b101a | App background, the editor well, the graph canvas |
bg-panel | #111726 | Panels, cards, the sidebar |
bg-raised | #1b2333 | Raised buttons, hover fills, selected rows |
border | #232e42 | Hairlines |
border-strong | #35435e | Emphasized and hover borders |
fg | #e3e9f2 | Body text |
fg-dim | #93a1b8 | Secondary text, placeholders, empty states |
Status
| Token | Dark default | Role |
|---|---|---|
ok | #3ecf8e | Passed runs and passed nodes, plus the true edge out of a condition |
err | #f87171 | Failures, plus the false edge out of a condition |
warn | #fbbf24 | Warnings, and the animated edge carrying data during a run |
info | #38bdf8 | Informational text and data-flow links |
The node ramp
Every node card wears its type as a coloured left edge; these are those colours. They also tint that type’s handles, glyph, and label, so a change here shows up in several places at once.
| Token | Dark default | Node type |
|---|---|---|
node-request | #4da3ff | request |
node-grpc | #3cb3d9 | grpc |
node-websocket | #22d3ee | websocket |
node-assertion | #a78bfa | assertion |
node-condition | #fb923c | condition |
node-loop | #2dd4bf | loop |
node-parallel | #818cf8 | parallel |
node-ai-action | #f472b6 | ai-action |
node-subflow | #94a3b8 | subflow |
node-script | #a3e635 | script |
edge | #3d4a61 | The default edge line between nodes |
That’s the complete list of 22 tokens. A name that isn’t one of them is ignored (the rest of your overrides still apply), which is what stops one typo from discarding a hand-tuned palette.
A worked example
Say Bandura Dark is close but you want a flatter, warmer editor and a node ramp that matches the rest of your terminal setup. Open the file with the command palette (⌘⇧P) → Preferences: Open Settings (JSON), then add:
{
"ui.theme": "dark",
"ui.accent": "#ff7a59",
"ui.themeOverrides": {
// Flatter grounds: one step of contrast between the canvas and the panels,
// instead of three.
"bg": "#12100f",
"bg-panel": "#1a1715",
"bg-raised": "#241f1c",
"border": "#332c28",
"border-strong": "#4a403a",
// Warmer text, same contrast ratio.
"fg": "#f2e9e4",
"fg-dim": "#a9998f",
// Keep pass/fail unmistakable: these two are the ones you read fastest.
"ok": "#4ade80",
"err": "#ff6b6b",
// A three-hue node ramp instead of ten: transport, logic, everything else.
"node-request": "#8ab4f8",
"node-grpc": "#8ab4f8",
"node-websocket": "#8ab4f8",
"node-condition": "#ffb454",
"node-loop": "#ffb454",
"node-parallel": "#ffb454",
"edge": "#463c36"
}
}
(Comments are fine in this file; trailing commas are not.)
Save. The chrome, the code editor’s syntax colours, and the terminal all repaint at once. No restart, no reopening a tab.
To undo any of it, delete the entry. To undo all of it, delete ui.themeOverrides. An absent
key is the default, so there’s no “reset to defaults” button to hunt for.
The rules
- Colours can be hex,
rgb(), orhsl(). These values go straight into CSS custom properties, the editor theme, and the terminal palette, so anything more exotic (avar()reference, a gradient, a named colour likerebeccapurple) is rejected; that entry alone is ignored and the theme’s own value stands. ui.accentis not a theme token, and it is hex-only. It’s a live overlay applied on top of any theme, and the hover shade and the ink colour on filled accent surfaces are derived from its channels, which only works if the value is hex or one of the presets (blue,emerald,violet,teal,neutral).rgb()andhsl()are refused there on purpose.- Overrides are theme-agnostic. They sit on top of whichever theme
ui.themeselects, so a palette tuned against Dark will still be applied if you switch to Light. That’s usually not what you want. If you flip between themes, keep the override set small, or keep twosettings.jsonfiles and swap them. - Node colours double as text and borders. They aren’t only a stripe on a card, so a node
colour that’s beautiful as a 3px edge can be unreadable as a label. Aim for something that
holds up against
bg-panel, not just againstbg. - Bad values fail small. One unparseable colour or unknown token name is dropped on its own; the rest of the object is applied. A malformed file is a different matter. See what happens when the file doesn’t parse.
Starting from a palette you already like
The fastest route to a port of a familiar editor theme is to fill the grounds and text first
(bg, bg-panel, bg-raised, border, fg, fg-dim), check that the app still reads, and
only then touch the node ramp. The grounds are 80% of the impression; the ramp is detail work,
and getting it wrong first makes the grounds harder to judge.
If the result is unreadable and you’d rather start over, delete ui.themeOverrides and save.
You’re back on a stock theme immediately.
Related
- settings.json: every key, and how the file behaves.
- Settings & appearance: the theme, accent, font and density controls the UI does have.