Skip to content

Theming System Reference

Quick reference for understanding and adjusting the 5-theme CSS custom property system.

Token Architecture

3 layers:

  1. Primitives (:root) — absolute colors, never change per theme (e.g., --primitive-grey-500)
  2. Semantic tokens ([data-theme="..."]) — what colors mean (e.g., --surface-raised, --text-primary)
  3. SCSS bridge ($alpha-*) — maps var(--accent-*) for use in SCSS expressions

Core File Map

FilePurpose
src/styles/theme.cssAll 5 themes' semantic tokens — the single source of truth
src/composables/useTheme.tsTheme switcher composable (cycle, init, persist)
src/components/layout/PageBase.vueTheme picker UI in sidebar
src/styles/_vuetify-reset.scssOverrides Vuetify's hardcoded colors with semantic tokens
src/styles/_components-builder.scssBuilder-panel-specific overrides
src/styles/_components-forms.scssForm element overrides (dropdowns, checkboxes)
src/styles/_components-misc.scssTooltip (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)
TokenLightDarkUsed By
--surface-sunken#e2e0dd#2a2a30Flow editor gradient edge
--surface-base#eceae8#333338Page background
--surface-raised#f6f5f3#494950Cards, panels
--surface-overlay#fdfcfb#55555cMenus, modals, tooltips
--surface-hover#e0dedb#606068Hover states on surfaces
--surface-active#d5d3cf#73737cActive/pressed states
--input-bg#e7e5e2#3e3e44Input fields, textareas, unselected tab buttons
--surface-chromevariesvariesTop 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 input and .v-input__append-inner backgrounds
  • TextArea.vue.text-area-input background
  • OptionButton.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

TokenPurposeExample Components
--text-primaryMain content textHeadings, body text, input values
--text-secondarySupporting textLabels, descriptions, tooltip text
--text-mutedDe-emphasized textPlaceholders, 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 RangeUsage
05–20Deep backgrounds (selected items in dark mode)
25–40Interactive elements (buttons, links)
45–60Prominent UI (selected block text, icon tints)
65–80Subtle highlights (selected indicators, focus rings)
85–95Very light tints (hover backgrounds, badge fills)

Per-theme accent colors:

ThemeAccentColor Family
Dark#a5b4fcIndigo
Light#0b84c2Blue
Cavai#e8606aCoral
Nord#88c0d0Frost/Cyan
Mocha#cba6f7Mauve/Purple

Component Style Sources

Builder Configuration Panel

ElementTokenComponent
Card body--surface-raisedCard.vue
Section dividers--border-subtleCard.vue (> * + * rule)
Section dividers (within sections)--border-subtleCardSection.vue
Input fields--input-bgInputField.vue
Textareas--input-bgTextArea.vue
Tab buttons (unselected)--input-bgOptionButton.vue
Tab buttons (selected)$alpha-95OptionButton.vue
Toggle switchesVuetify overrides_vuetify-reset.scss
Option row title--text-primaryOptionRow.vue
Info tooltip icon$alpha-50InfoTooltip.vue

Tooltips

Two tooltip systems exist:

  1. TooltipWrapper.vue (used by InfoTooltip) — gradient background using --surface-overlay--surface-base, text uses --text-secondary
  2. CTooltip.vue — uses global .tooltip-content from _components-misc.scss, always dark bg (--primitive-grey-100) with light text (--primitive-grey-900)

Flow Editor

ElementTokenLocation
Radial gradient center--surface-raisedCavaiFlow.vue :before pseudo
Radial gradient middle--surface-baseSame (70% stop)
Radial gradient edge--surface-sunkenSame (100% stop)
Grid pattern opacity--preview-grid-opacityCavaiFlow.vue
Operator cards (ShowHide)VariousShowHideOp.vue

Show/Hide Operator

ElementToken
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

  1. Add a [data-theme="name"] block in theme.css with ALL semantic tokens
  2. Add to useTheme.ts: Theme type, THEME_CYCLE, THEME_BG
  3. Add to PageBase.vue: themeOptions() array with id, name, icon, accent
  4. Ensure flow-invert palette is defined (for change operators in flow editor)
  5. 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-hover is for hover states, not resting backgrounds. Use --input-bg for form element backgrounds.
  • --border-subtle is a border token, not a text color. Use --text-secondary or --text-muted for 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.

Internal documentation