Appearance
Theming System Reference
Quick reference for understanding and adjusting the 5-theme CSS custom property system.
Token Architecture
3 layers:
- Primitives (
:root) — absolute colors, never change per theme (e.g.,--primitive-grey-500) - Semantic tokens (
[data-theme="..."]) — what colors mean (e.g.,--surface-raised,--text-primary) - SCSS bridge (
$alpha-*) — mapsvar(--accent-*)for use in SCSS expressions
Core File Map
| File | Purpose |
|---|---|
src/styles/theme.css | All 5 themes' semantic tokens — the single source of truth |
src/composables/useTheme.ts | Theme switcher composable (cycle, init, persist) |
src/components/layout/PageBase.vue | Theme picker UI in sidebar |
src/styles/_vuetify-reset.scss | Overrides Vuetify's hardcoded colors with semantic tokens |
src/styles/_components-builder.scss | Builder-panel-specific overrides |
src/styles/_components-forms.scss | Form element overrides (dropdowns, checkboxes) |
src/styles/_components-misc.scss | Tooltip (CTooltip) styles, loading spinners |
Surface Hierarchy
Surfaces are layered for depth. In light themes, higher = lighter. In dark themes, higher = brighter.
sunken → base → raised → overlay
(deepest) (page) (cards) (menus/modals)| Token | Light | Dark | Used By |
|---|---|---|---|
--surface-sunken | #e2e0dd | #2a2a30 | Flow editor gradient edge |
--surface-base | #eceae8 | #333338 | Page background |
--surface-raised | #f6f5f3 | #494950 | Cards, panels |
--surface-overlay | #fdfcfb | #55555c | Menus, modals, tooltips |
--surface-hover | #e0dedb | #606068 | Hover states on surfaces |
--surface-active | #d5d3cf | #73737c | Active/pressed states |
--input-bg | #e7e5e2 | #3e3e44 | Input fields, textareas, unselected tab buttons |
--surface-chrome | varies | varies | Top navigation bar (always dark) |
Input Background Token (--input-bg)
Subtle inset look for form elements sitting on raised cards. Less contrast than --surface-hover (which is for interactive hover states).
Used by:
InputField.vue—::v-deep inputand.v-input__append-innerbackgroundsTextArea.vue—.text-area-inputbackgroundOptionButton.vue— unselected button background (e.g., None/Color/Gradient tabs)
Per-theme values:
| Theme | --input-bg | --surface-raised (card) | Delta |
|---|---|---|---|
| Dark | #3e3e44 | #494950 | ~11 darker |
| Light | #f0f0f0 | #f5f5f5 | ~5 darker |
| Cavai | #efe9e2 | #f5f1ec | ~6 darker |
| Nord | #353d4b | #3b4252 | ~6 darker |
| Mocha | #2e2f40 | #363749 | ~8 darker |
Text Tokens
| Token | Purpose | Example Components |
|---|---|---|
--text-primary | Main content text | Headings, body text, input values |
--text-secondary | Supporting text | Labels, descriptions, tooltip text |
--text-muted | De-emphasized text | Placeholders, disabled labels |
Accent Scale (--accent-05 to --accent-95)
Full 20-step ramp from deep to light for the theme's accent color.
SCSS bridge: $alpha-05 → $alpha-95 maps to var(--accent-05) → var(--accent-95)
| Accent Range | Usage |
|---|---|
| 05–20 | Deep backgrounds (selected items in dark mode) |
| 25–40 | Interactive elements (buttons, links) |
| 45–60 | Prominent UI (selected block text, icon tints) |
| 65–80 | Subtle highlights (selected indicators, focus rings) |
| 85–95 | Very light tints (hover backgrounds, badge fills) |
Per-theme accent colors:
| Theme | Accent | Color Family |
|---|---|---|
| Dark | #a5b4fc | Indigo |
| Light | #0b84c2 | Blue |
| Cavai | #e8606a | Coral |
| Nord | #88c0d0 | Frost/Cyan |
| Mocha | #cba6f7 | Mauve/Purple |
Component Style Sources
Builder Configuration Panel
| Element | Token | Component |
|---|---|---|
| Card body | --surface-raised | Card.vue |
| Section dividers | --border-subtle | Card.vue (> * + * rule) |
| Section dividers (within sections) | --border-subtle | CardSection.vue |
| Input fields | --input-bg | InputField.vue |
| Textareas | --input-bg | TextArea.vue |
| Tab buttons (unselected) | --input-bg | OptionButton.vue |
| Tab buttons (selected) | $alpha-95 | OptionButton.vue |
| Toggle switches | Vuetify overrides | _vuetify-reset.scss |
| Option row title | --text-primary | OptionRow.vue |
| Info tooltip icon | $alpha-50 | InfoTooltip.vue |
Tooltips
Two tooltip systems exist:
- TooltipWrapper.vue (used by InfoTooltip) — gradient background using
--surface-overlay→--surface-base, text uses--text-secondary - CTooltip.vue — uses global
.tooltip-contentfrom_components-misc.scss, always dark bg (--primitive-grey-100) with light text (--primitive-grey-900)
Flow Editor
| Element | Token | Location |
|---|---|---|
| Radial gradient center | --surface-raised | CavaiFlow.vue :before pseudo |
| Radial gradient middle | --surface-base | Same (70% stop) |
| Radial gradient edge | --surface-sunken | Same (100% stop) |
| Grid pattern opacity | --preview-grid-opacity | CavaiFlow.vue |
| Operator cards (ShowHide) | Various | ShowHideOp.vue |
Show/Hide Operator
| Element | Token |
|---|---|
| Target card background | --border-subtle |
| Target name | --text-primary |
| Active button (Hide/Show) | --accent-primary |
| Inactive button | --surface-raised + --text-secondary |
| Remove button | --accent-danger-* |
Adding a New Theme
- Add a
[data-theme="name"]block intheme.csswith ALL semantic tokens - Add to
useTheme.ts:Themetype,THEME_CYCLE,THEME_BG - Add to
PageBase.vue:themeOptions()array with id, name, icon, accent - Ensure flow-invert palette is defined (for change operators in flow editor)
- Test: surfaces, text, accents, shadows, chrome, input backgrounds
Common Pitfalls
- Don't use primitives in components — always use semantic tokens. Primitives don't adapt to themes.
--surface-hoveris for hover states, not resting backgrounds. Use--input-bgfor form element backgrounds.--border-subtleis a border token, not a text color. Use--text-secondaryor--text-mutedfor text. Known places that had this bug:TooltipWrapper.vue,AddBlockTool.vue.- Scoped tooltip styles: TooltipWrapper.vue has scoped
<style>but Vuetify 2 renders tooltip content inline (not teleported), so scoped styles DO apply. - The (i) icon SVG (
info.svg) is stroke-only with transparent fill — any visible "background" comes from the container.