Customize the theme

Repaint Bandura one colour token at a time with ui.themeOverrides: the full token list, a worked example, and the rules that keep a palette readable.

5 min read

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

TokenDark defaultRole
bg#0b101aApp background, the editor well, the graph canvas
bg-panel#111726Panels, cards, the sidebar
bg-raised#1b2333Raised buttons, hover fills, selected rows
border#232e42Hairlines
border-strong#35435eEmphasized and hover borders
fg#e3e9f2Body text
fg-dim#93a1b8Secondary text, placeholders, empty states

Status

TokenDark defaultRole
ok#3ecf8ePassed runs and passed nodes, plus the true edge out of a condition
err#f87171Failures, plus the false edge out of a condition
warn#fbbf24Warnings, and the animated edge carrying data during a run
info#38bdf8Informational 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.

TokenDark defaultNode type
node-request#4da3ffrequest
node-grpc#3cb3d9grpc
node-websocket#22d3eewebsocket
node-assertion#a78bfaassertion
node-condition#fb923ccondition
node-loop#2dd4bfloop
node-parallel#818cf8parallel
node-ai-action#f472b6ai-action
node-subflow#94a3b8subflow
node-script#a3e635script
edge#3d4a61The 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(), or hsl(). These values go straight into CSS custom properties, the editor theme, and the terminal palette, so anything more exotic (a var() reference, a gradient, a named colour like rebeccapurple) is rejected; that entry alone is ignored and the theme’s own value stands.
  • ui.accent is 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() and hsl() are refused there on purpose.
  • Overrides are theme-agnostic. They sit on top of whichever theme ui.theme selects, 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 two settings.json files 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 against bg.
  • 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.

Last updated

Looking for something else? All 37 articles are on one page in the Help Center.