Skip to content
Logo

Theming

Theme Structure

TerminalTheme (Boo/Models/Theme.swift) defines a complete color scheme from 21 base color fields. Everything else in the UI is derived from them.

Terminal colors

FieldRole
foreground / backgroundTerminal text and surface
cursorTerminal cursor
selectionTerminal selection highlight (carries its own alpha)
ansiColors[16]8 normal + 8 bright ANSI colors; also feed Monaco syntax mapping and the status tokens below

Chrome colors (the 5 UI base tokens)

FieldRole
chromeBgToolbar, status bar, settings surfaces
chromeTextPrimary text and icons in bars/panels
chromeMutedThe source hue for secondary text, separators, and hover fills — consume it via the semantic tokens below, not raw
sidebarBgSidebar and every floating panel (find bar, autocomplete, detail panel)
accentColorSelection, focus, activity — anything "active"

All 21 fields are user-editable in the custom theme editor (Settings → Theme → New Theme…).

Derived Semantic Tokens

Derived tokens are computed from the base fields — built-in and custom themes get them for free. Always reach for the token that names the role instead of hand-rolling chromeMuted.withAlphaComponent(…); the full table lives as a comment in Theme.swift.

TokenDerivationUsed for
textSecondarychromeMuted @ 0.7Secondary labels, close buttons, hovered glyphs
textTertiarychromeMuted @ 0.5Placeholders, idle glyphs, inactive icons
separatorchromeMuted @ 0.2 (alpha)Hairlines over varying backgrounds: status-bar dividers, unfocused field borders
chromeBorder / sidebarBordermuted @ 0.2 blended opaqueBorders that may overlap-draw (opaque → no double-darkening)
paneBordermuted @ 0.2 over backgroundEdge stroke of pane islands
islandBorder= chromeBorderEdge stroke of chrome-coloured islands. Every island border must blend over its own surface or it vanishes: panes use paneBorder, the sidebar uses sidebarBorder
hoverFillchromeMuted @ 0.10Row/segment/tab hover backgrounds
subtleFillchromeMuted @ 0.06Drag placeholder slots, idle button backgrounds
controlFillchromeMuted @ 0.15Small controls: close-button circle, toolbar button hover, field backgrounds
sidebarRowHovermuted @ 0.08 over sidebarBg (opaque)Sidebar list row hover
cardBgmuted @ 0.12 over sidebarBg (opaque)Card surfaces in sidebar/settings
windowBackdropchromeBg shifted ±0.06The gaps between chrome islands
accentEmphasisaccentColor @ 0.85Active/pressed accent fills, activity dots, drop insertion bars
focusRingaccentColor @ 0.7Focused text-field borders (URL field, find bar)
focusBorderaccentColor @ 0.45Focused pane island, active workspace pill
accentSelectionFillaccentColor @ 0.15Selected item backgrounds (sidebar tab, list rows)
dragGhostBgchromeBg @ 0.88Drag ghost backdrops (tabs, workspaces, panels)

Status colors

Theme-adaptive status hues are derived from the ANSI palette, so they always match the terminal colors:

TokenSourceUsed for
successANSI greenPositive states, git added/staged
warningANSI yellowWarnings, git modified
errorANSI redErrors, git deleted
infoANSI blueInformational badges

Plugin DSL tints (.success/.warning/.error) resolve to these tokens.

Theme-independent identity hues (a container is always "docker blue") live in Boo/Services/BooPaths.swift as NSColor.booRemote / .booDocker / .booLocal / .booDeleted / .booNeutral.

Alpha convention

If you must use a raw alpha (state matrices, animations), stay inside the established bands so the UI reads as one system:

  • 0.04–0.09 — subtle fills
  • 0.10–0.18 — hover fills
  • 0.20–0.25 — separators / borders
  • 0.40–0.60 — tertiary text
  • 0.70–0.90 — secondary text / active emphasis

SwiftUI

SwiftUI views read the same tokens through the Tokens environment value (Boo/Views/SettingsWindow.swift): t.text, t.textSecondary, t.textTertiary, t.separator, t.hoverFill, t.controlFill, t.accentSelectionFill, t.focusRing, t.cardBg, t.success … — mirrors of the TerminalTheme properties as Color. Any token without a named mirror is reachable generically: t.color(\.subtleFill). Prefer these over t.muted.opacity(…).

Built-in Themes

Boo ships with 32 themes (Boo/Models/TerminalTheme+BuiltIn.swift):

  • Default — Dark, Light
  • Tokyo Night
  • Catppuccin — Mocha, Macchiato, Frappé, Latte
  • Solarized — Dark, Light
  • Dracula
  • Nord
  • Gruvbox — Dark, Light
  • One — Dark, Light
  • Rosé Pine
  • Kanagawa
  • Everforest — Dark, Light
  • GitHub — Dark, Light
  • Ayu — Dark, Light
  • Monokai
  • Material — Dark, Light
  • Palenight
  • Horizon Dark
  • Cobalt2
  • Night Owl
  • Synthwave '84
  • Moonlight

Because every semantic token is derived, a theme only ever defines the 21 base fields — new tokens never require touching the 32 built-ins or migrating persisted custom themes.

How Theming Works

Theme colors are read at draw time from AppSettings.shared.theme. When a theme changes:

  1. Settings update is saved via AppSettings.shared
  2. NotificationCenter posts .settingsChanged
  3. SettingsObserver (ObservableObject) triggers SwiftUI re-renders
  4. AppKit views redraw with new theme colors

isDark is not stored — it is computed from the background luminance.

Color Utilities

Use the TerminalColor extensions for color conversion:

// Correct
let cg = myColor.cgColor
let ns = myColor.nsColor
 
// Avoid inline conversions like:
// CGFloat(r) / 255.0

For custom blends, use TerminalTheme.blend(_:over:alpha:) — it returns an opaque color, so overlapping draws don't double-darken the way withAlphaComponent fills do.