Choose and Customize a Theme
Picking and tuning a Daintree theme: switching with live preview, the theme palette and the theme browser, overriding the accent color and its contrast warning, matching your system appearance, and the accessibility modes.
Changing Your Theme
The theme picker lives under Settings → Appearance → App. It is an inline listbox: a palette-strip hero at the top showing the active theme's colors, a search input and a Dark/Light type toggle below it, and a scrollable list of theme rows underneath, with the active theme highlighted. Click any row and it applies immediately across the whole app.
A shuffle button sits above the list for jumping to a random theme. Export app theme... and Import app theme... sit below it. The accent swatch and its Reset to theme default button sit below the list, covered in Accent Color Override. A Change theme… button opens the fuller Theme Browser.
The first-run welcome dialog offers a choice between Daintree and Bondi Beach, auto-selected from your OS appearance, with a note pointing at Settings → Appearance for the other twelve.
Live preview
Hover a theme row and its colors preview across the whole app after a 300ms debounce: sidebar, toolbar, panels, dock, everything. Moving to another row replaces the preview; moving off the list reverts to your saved theme. Keyboard focus, from tabbing or arrowing into the list, previews immediately with no debounce so keyboard navigation stays instant.
Previews are transient. The saved theme does not change until you click a row or press Enter in the theme palette. Escape while a preview is active also reverts.
Applying a theme without a restart
Every theme change re-applies the full token set to the document root in one pass, and this is the mechanism that makes switching reliable rather than something you have to restart for. Applying a theme writes every CSS variable, sets data-theme, data-colorMode, the CSS color-scheme property and the .dark / .light classes. Critically, it also clears stale variables left behind by the previous theme before writing the new ones.
That clearing step matters because component-level overrides are optional. A theme can ship extensions that restyle the toolbar, sidebar, settings dialog or panel grid, and a theme with fewer extensions than its predecessor would otherwise inherit the old theme's component chrome and end up half in one theme and half in the other. Clearing first means what you see after a switch is exactly the new theme, with nothing carried over.
Reveal animation and crossfade
When you pick a theme (clicking a row, shuffling, committing from the browser), the UI plays a short directional sweep as the new theme swaps in. The direction follows your click position: clicks on the left half of the window sweep the old theme out to the right, clicks on the right half sweep it left. The wipe runs 400ms and is the signal that encodes where the change came from.
When the app changes the theme on your behalf, it crossfades instead of cutting. An OS appearance switch under match system appearance, or the first-run wizard auto-selecting a theme after the window has already painted, both fade the outgoing theme out rather than snapping. The directional wipe is deliberately not used there: a wipe encodes a gesture's origin, and these paths have no gesture. Startup hydration is not animated at all, because there is no prior theme to fade from.
Rapid changes are handled by a monotonic "latest theme intent" counter. A crossfade defers its DOM write by a frame, so a newer change claimed after it (a second crossfade, a hover preview, an accent tweak) outranks it and the stale callback bails rather than stranding the DOM out of sync with the store.
Both animations are built on document.startViewTransition with a WAAPI animation on the transition pseudo-element, so they composite on the GPU. Both turn off entirely when prefers-reduced-motion: reduce is set, when reduced-animation or performance mode is on, when the document is hidden, or when View Transitions are unavailable, in which case the switch happens instantly.
The Theme Palette
To switch themes from the keyboard without opening Settings, press Cmd+K Cmd+T. It is a chord: hold Cmd, press K then T. A floating palette opens seeded to your current theme, with fuzzy search across every available theme, custom ones included.
Arrow keys walk the list with instant live preview on each step, no debounce. Enter commits the focused theme and closes; Escape reverts to whatever was active when you opened it. Clicking a row commits immediately. Each row shows a palette strip of the theme's colors, its name and its location caption, a Dark or Light label, and an "Active" badge on the current one.
Toggling dark and light
A Toggle Dark/Light Theme command flips between your preferred dark and light themes, set under Match System Appearance. It ships with no default shortcut: run it from the Action Palette (Cmd+Shift+P, type "toggle theme") or bind it yourself. See Keyboard Shortcuts.
The Theme Browser
The Theme Browser is a drawer that slides in from the right edge. It is the most spacious way to shop for a theme: a sticky hero at the top showing the previewed theme's image and its location caption, a filter input beside a Dark/Light segmented toggle, a scrollable list of rows with thumbnails and color strips, and a sticky Cancel / Set theme bar at the bottom. Each row shows the theme's name, its location and its type; any theme with validation warnings carries a small warning pill with the warning count.
It has no default keyboard shortcut. Open it from the Change theme… button in Settings → Appearance → App, or from the Action Palette (Cmd+Shift+P → "Change Theme…"). Cmd+K Cmd+T does not open the browser: that is the lighter-weight theme palette.
Clicking a row previews it immediately across the whole app with no debounce, which is the difference from the Settings picker's 300ms hover delay. Arrow keys (including Page Up and Page Down) walk the list with the same instant preview, and work whether focus is in the list or the filter input. The screen-reader announcement of the focused theme is debounced by 300ms so a fast scroll does not fire on every step. Switching the Dark/Light filter reverts any active preview first, so a theme you can no longer see cannot be committed by accident.
Set theme (or Enter) commits: it turns off match-system-appearance if it was on, plays the reveal sweep from the button's position, closes the drawer and saves. Cancel, Escape, the close button, or clicking the dimmed area all revert and close without saving, except that the first Escape clears a non-empty filter query rather than closing.
While the Theme Browser is open, the rest of the app is dimmed, non-interactive and removed from the accessibility tree. If the Portal browser is open it hides temporarily so it cannot draw over the preview. Closing the drawer restores everything. Opening the browser also closes the Settings dialog so you can see the preview across the full app; Settings re-opens on the Appearance tab afterwards only if that is where you opened it from.
Accent Color Override
The Accent color section under the theme picker overrides the active theme's accent with any CSS color. Use it when a theme's surfaces are right but you want a different accent: a team brand color, a personal preference, or more separation for color-vision reasons.
Click the swatch to open your OS color picker. Changes apply live as you drag, with no debounce. Reset to theme default clears the override and restores the theme's own accent. The override persists across restarts and rides along with whatever theme you switch to, and live previews show the override combined with the previewed theme, so what you see is what you get.
The low-contrast warning
An accent is not a free choice: it is the color that carries primary buttons, focus rings, switch tracks and selection state. Daintree checks the accent you pick and shows an inline warning when it will not hold up, in one of two forms:
- Foreground failure: the button text sitting on the accent no longer reads. The warning names the measured ratio: "Low contrast: button text scores only 2.31:1 on the accent color. Pick a lighter or darker accent."
- Surface failure: the accent no longer separates from the theme's own surfaces. "Low contrast: the accent scores only 1.84:1 on Fiordland surfaces. Pick a more distinct accent."
Foreground is checked first, because unreadable text on a primary button is the more severe failure and the one you should fix before discovering the surface problem. The warning is advisory (the override still applies), but it is worth acting on, because a failing accent degrades every primary action in the app at once.
Exporting a theme while an accent override is active bakes the override into the exported file as the accent-primary token. It is the quickest way to share a branded variant without editing JSON by hand.
Match System Appearance
The Match system appearance toggle tells Daintree to follow your operating system's dark/light mode. Two sub-pickers appear below it, Preferred dark theme and Preferred light theme, defaulting to Daintree and Bondi Beach. When the OS flips, the app crossfades to the matching preference.
Selecting a theme from the main picker while follow-system is on turns the toggle off. That is deliberate: an OS change should not overrule a choice you just made by hand.
Accessibility
Color vision deficiency modes
Under Settings > Appearance, the Color Vision section offers three modes:
- Default: the theme's own palette, no adjustments.
- Red-Green (deuteranopia and protanopia).
- Blue-Yellow (tritanopia).
Each mode replaces a hand-authored set of tokens chosen for distinguishability under that deficiency, layered on top of the active theme as inline overrides. Across the two modes that is 39 explicitly overridden tokens, plus the status-surface washes and the light-mode diff fills and gutters re-derived from them, for around 50 in total. The set covers status colors (success, warning, danger, info), agent activity states, pull-request states, terminal ANSI slots, syntax highlighting colors and the categorical hue set.
The derived values matter as much as the explicit ones. Status washes and diff fills are baked from the base status colors at theme-build time, so patching only the base color would leave banners, pills, diff backgrounds and diff gutters on the original hue while the text on them moved. They are re-derived alongside the override.
CVD modes do not touch the theme's accent or its surface palette, so your chosen theme still looks like itself. Switching back to Default restores each token to the active theme's value rather than simply unsetting it: the theme tokens have no stylesheet fallback, so unsetting would leave them undefined.
Forced colors and increased contrast
Daintree also responds to the operating system's own contrast settings, independently of which theme you have chosen.
Under Windows High Contrast (forced-colors: active), the UA overrides color, background and border, so Daintree stops relying on theme tokens for the surfaces that need to stay distinguishable and re-expresses them in system colors: sidebar worktree items and their selection state, Project Pulse heat levels (which would otherwise all collapse to the same canvas color and lose their four-level ramp) and the diff viewer, which carries its own forced-colors rules.
macOS "Increase Contrast" fires prefers-contrast: more but not forced-colors: active, so there is a separate block for it, and it is the sole high-contrast surface for Mac users. Panel focus and selection chrome stay hardcoded to system colors in both blocks and deliberately override any theme's own focus-chrome extension, so a theme cannot make focus invisible under high contrast.