Appearance
Appearance & Theming
The Hive desktop and Android apps support full theming of the UI chrome, embedded terminals, and both CodeMirror-based editors via CSS custom properties.
Where to configure
Open Settings → Appearance. The card contains:
- Preset picker - pick a built-in theme. Each theme is a complete light or dark look; selecting one also sets the app's light/dark mode, so there is no separate light/dark toggle. Built-ins: Default Dark/Light, Solarized Dark/Light, Dracula Dark/Light, Catppuccin Mocha (dark), Catppuccin Latte (light), Nord (dark), Tokyo Night (dark), and Custom.
- Mode selector - shown only for the Custom theme; chooses whether your custom CSS renders as a light or dark theme.
- CSS Variables editor - a CodeMirror editor showing the active theme. Read-only while a built-in preset is selected; pick Edit as Custom to fork the active CSS into the Custom slot and start editing.
- Reset - drops back to the Default theme (matching your OS light/dark preference) and clears any custom CSS.
Edits in Custom mode apply live to the running app and are persisted in the device's localStorage. Each theme is a single :root { ... } block; the Custom theme's Mode selector decides whether it renders light or dark.
Available variables
UI chrome:
| Variable | Purpose |
|---|---|
--background / --foreground | App body |
--card / --card-foreground | Card surfaces |
--popover / --popover-foreground | Popovers, dropdowns |
--primary / --primary-foreground | Primary action buttons |
--secondary / --secondary-foreground | Secondary surfaces |
--muted / --muted-foreground | Muted text and surfaces |
--accent / --accent-foreground | Hover / selected states |
--destructive | Destructive actions |
--border / --input / --ring | Borders, inputs, focus rings |
--sidebar* | Sidebar surface, foreground, primary, accent, border, ring |
Terminal:
| Variable | Purpose |
|---|---|
--terminal-bg / --terminal-fg | Terminal background / default text |
--terminal-cursor | Cursor color |
--terminal-selection-bg | Mouse selection background |
--terminal-black / --terminal-bright-black | ANSI 0 / 8 |
--terminal-white / --terminal-bright-white | ANSI 7 / 15 |
--terminal-font-family | Terminal font stack |
ANSI colors 1-6 (red, green, yellow, blue, magenta, cyan) are not driven by CSS variables; they come from a fixed light/dark palette in useTerminalManager.ts.
Editor:
| Variable | Purpose |
|---|---|
--editor-bg / --editor-foreground | Editor canvas and default text |
--editor-gutter-bg / --editor-gutter-fg | Line-number gutter colors |
--editor-gutter-active-fg | Active line number |
--editor-border | Gutter / panel borders |
--editor-selection-bg | Text selection |
--editor-active-line-bg / --editor-active-line-gutter-bg | Active line highlight |
--editor-cursor | Caret color |
--editor-search-match-* | Search result highlight fill / border / selected match |
--editor-bracket-match-* | Matching bracket highlight fill / border |
Example: minimal custom theme
A custom theme is a single :root block. Set the Mode selector to light or dark to tell the app which ANSI palette and dark: utilities to use.
css
:root {
--background: #1c1b18;
--foreground: #e6e1d6;
--primary: #d98a52;
--editor-bg: #24221f;
--terminal-bg: #1c1b18;
--terminal-fg: #e6e1d6;
}Notes
- Themes are device-local by default. Use Settings → Sync preferences with cluster → Save to cluster to push the current theme (and every other
hive-*localStorage preference) to the connected daemon; any client that later connects to that cluster can pull the same blob with Pull from cluster. Sync is opt-in per action: editing locally never touches the cluster value. - The base styles in
crates/hive-app/src/style.cssdefine the defaults; your overrides cascade on top of those, so you only need to declare the variables you want to change. - Already-open terminals and editors refresh their colors live when the theme changes; no reopen / reattach needed.
Resizable panels
Every side panel can be resized by dragging the thin handle on its border, the same gesture used between terminal panes. On desktop (md+) only - on phone widths these panels are full-width swappable panes or sheets, so the handle is hidden. Each panel remembers its width across restarts in localStorage:
| Panel | Page | Range | Key |
|---|---|---|---|
| Main navigation (left) | all pages | 180-420 px | hive-nav-width |
| Right sidebar (Files/Git/Run/Commands) | Workspace | 220-560 px | hive-right-sidebar-width |
| Notes file tree (left) | Notes | 180-480 px | hive-notes-tree-width |
| Notes agent chat (right) | Notes | 280-720 px | hive-notes-agent-width |
| Notes editor split divider | Notes (split mode) | 20-80% | hive-notes-editor-split |
The shared drag logic lives in src/composables/useResizablePanel.ts; the handle visual is src/components/common/ResizeHandle.vue. The collapsed main nav keeps its fixed w-12 rail and is not resizable until re-expanded.
Cluster-shared preferences
The daemon persists the synced blob in cluster_preferences.json next to its config.toml and broadcasts updates to its cluster peers via PeerMessage::SyncClusterPreferences. Conflict resolution is last-writer-wins on the whole blob, ordered by a server-side updated_at timestamp - so a slow peer broadcast can never clobber a fresher local write. Wire protocol: ClientMessage::GetClusterPreferences / SetClusterPreferences → ServerMessage::ClusterPreferences { preferences }.