Theme Factory
Use this skill when you need to add a new theme to AiderDesk.
AiderDesk themes are implemented as SCSS files that define a .theme-<name> class with a full set of CSS custom properties (variables). The UI uses Tailwind utilities mapped to these CSS variables.
Where themes live
- Theme files:
src/renderer/src/themes/theme-<name>.scss - Theme aggregator (imports all themes):
src/renderer/src/themes/themes.scss - Theme type registry:
packages/common/src/types/common.ts(THEMES) - Theme selector UI:
src/renderer/src/components/settings/GeneralSettings.tsx - Theme application:
src/renderer/src/App.tsx(appliestheme-<name>class todocument.body) - Theme display names (i18n):
packages/common/src/locales/en.json(themeOptions.<name>)packages/common/src/locales/zh.json(themeOptions.<name>)
Definition format
Each theme is a class:
- Class name:
.theme-<name> - Contents: a complete set of
--color-*variables.
Best workflow: copy an existing theme (e.g. theme-dark.scss) and adjust values.
Checklist: add a new theme
1) Choose a theme name
Pick a kebab-case name, e.g. sunset, nord, paper.
You will reference it consistently in:
- CSS class:
.theme-<name> - filename:
theme-<name>.scss THEMESarray value:'<name>'- i18n key:
themeOptions.<name>
2) Create the theme SCSS file
Create:
src/renderer/src/themes/theme-<name>.scss
Start by copying a similar theme (dark -> dark-ish, light -> light-ish), then update the hex colors.
Minimum requirement: define all variables expected by the app.
Practical way to ensure completeness:
- Compare with
src/renderer/src/themes/theme-dark.scss(or another full theme) - Keep variable names identical; only change values.
3) Register the theme in the theme aggregator
Edit:
src/renderer/src/themes/themes.scss
Add:
@use 'theme-<name>.scss';
If the file is not imported here, it won’t be included in the built CSS.
4) Register the theme in TypeScript types
Edit:
packages/common/src/types/common.ts
Add '<name>' to the exported THEMES array.
This makes the theme selectable and type-safe.
5) Add i18n display names
Edit:
packages/common/src/locales/en.jsonpackages/common/src/locales/zh.json
Add entries under themeOptions:
{
"themeOptions": {
"<name>": "Your Theme Name"
}
}
6) Verify in the UI
- Open Settings → General → Theme
- Confirm the new theme appears in the dropdown
- Switch to it and confirm the whole UI updates (no restart)
7) Quality checks
- Contrast: confirm text is readable on all backgrounds (aim for WCAG AA)
- Verify key surfaces:
- main background panels
- inputs
- buttons
- borders/dividers
- diff viewer colors
- code blocks
- muted/secondary text
- Check both states:
- normal
- hover/active
Color Variable Reference
Each variable maps from --color-<group>-<variant> in SCSS to a Tailwind utility like bg-<group>-<variant>, text-<group>-<variant>, or border-<group>-<variant>. The mapping is defined in tailwind.config.js.
Background layer system (--color-bg-*)
The app uses a 5-tier surface hierarchy from darkest to lightest (for dark themes; reversed for light):
| Variable | Usage | Where visible |
|---|---|---|
| bg-primary | Deepest background — app body, outer containers | body, outer page wrapper, main content areas, inline edit panels |
| bg-primary-light | Primary raised surface — task bars, sidebar items, file viewers, content panels | TaskBar, TaskItem (idle), file viewer scrollable area, top-bar gradient end |
| bg-primary-light-strong | Semi-transparent overlay — selected items, diff/file headers, notifications, tooltip arrows, reflected messages | TaskItem (selected), PierreDiffViewer header, toast notification bg |
| bg-secondary | Card/panel surface — input fields, dialog content, chips, selected/hovered task items | Model dialog, chip items, settings cards, hover on menu items |
| bg-secondary-light | Elevated input container — search/dropdown wrappers, dropdown menus, merge button popover | Tag input containers, settings dropdown focus wrappers |
| bg-secondary-light-strongest | Opaque elevated surface — dialogs (BaseDialog), thinking blocks, inactive tab hover | BaseDialog bg, ThinkingAnswerBlock, inactive project tab hover |
| bg-tertiary | Hover/highlight surface — icon button hover, menu item hover, scrollbar thumb, diff gutter omit, CodeMirror autocomplete border | All icon button hovers, menu item hovers, scrollbar thumbs |
| bg-tertiary-emphasis | Accent-tinted hover — uses the theme's accent color at ~25% opacity for tinted hover states | Header icon button hover, delete button hover backgrounds, task badges |
| bg-tertiary-strong | Stronger tinted hover — accent color at ~50% opacity | Active project tab hover |
| bg-fourth | Separator / small control surface — vertical dividers in TaskBar, checkbox checked state, close button bg, tab hover for active tab | TaskBar dividers, Checkbox checked bg, BaseDialog close button |
| bg-fourth-muted | Accent-tinted subtle bg — accent color at ~20% opacity | Decorative/special accent backgrounds |
| bg-fourth-emphasis | Accent-tinted medium bg — accent color at ~30% opacity | Decorative/special accent backgrounds |
| bg-fifth | Highest hover state — used for "close" button hover in dialogs | BaseDialog close button hover |
| bg-selection | Text selection highlight — used in PromptField for text selection color | PromptField ::selection color |
| bg-code-block | Code block background — standalone code blocks, diff file items, log viewer | CodeBlock component, DiffFileItem, LogsPage pre blocks |
Diff viewer backgrounds (--color-bg-diff-viewer-*)
| Variable | Usage |
|---|---|
| diff-viewer-old-primary | Deleted line background (used in DiffViewer.scss, CompactDiffViewer) |
| diff-viewer-old-secondary | Deleted line character-level edit highlight |
| diff-viewer-new-primary | Inserted line background |
| diff-viewer-new-secondary | Inserted line character-level edit highlight |
Text hierarchy (--color-text-*)
| Variable | Usage | Visible on |
|---|---|---|
| text-primary | Primary text — labels, headings, button text, body text | Most text throughout the app |
| text-secondary | Secondary text — icons in header, model subtitles, status text | Header icons (notebook, chart, settings), model provider text |
| text-tertiary | Tertiary text — hover state for muted items, diff modified markers, toolbar button hover | Hover state text, diff line numbers, expanded toolbar buttons |
| text-muted-light | Dimmed text — reflected messages, placeholder labels | ReflectedMessageBlock, disabled-state labels |
| text-muted | Muted text — description paragraphs, log viewer text, empty states | Settings descriptions, log output, chip empty labels |
| text-muted-dark | Dark muted — input placeholders, section dividers | PromptField placeholder, TaskSectionHeader |
| text-dark | Darkest text — very deep background text, decorative | Rarely used, deepest layer text |
Border hierarchy (--color-border-*)
| Variable | Usage | Visible on |
|---|---|---|
| border-dark | Subtlest border — outer container edges, sticky headers | Home page outer border, UpdatedFilesDiffModal header, bash blocks |
| border-dark-light | Light subtle border — code blocks, sidebar section separators, task item borders | CodeBlock border, TaskSectionHeader top border, TaskItem border |
| border-dark-light-strong | Semi-transparent subtle border — reflected messages, code block <hr> | ReflectedMessageBlock, CodeBlock horizontal rules |
| border-default-dark | Medium border — prompt input borders (unfocused), diff comment panel | PromptField unfocused border |
| border-default | Standard border — inputs, cards, dividers, containers (most common) | Settings inputs, Home container, inline edit panels, TaskItem |
| border-accent | Accent border — focused inputs, checked checkboxes/radios, diff headers, badge borders | PromptField focus, Checkbox checked, PierreDiffViewer header |
| border-light | Lightest border — selected/focused inputs, active tab indicators | Settings active option border, input focus state |
Accent colors (--color-accent-*)
| Variable | Usage |
|---|---|
| accent-primary | Brand accent — AI sparkle icon, welcome message bullets, commit badges, voice recording indicator |
| accent-secondary | Secondary accent — selected answer highlights, decorative accents |
| accent-light | Highlight accent — token usage bar fill, hover text for commit links |
Status colors (--color-success-*, --color-warning-*, --color-error-*, --color-info-*)
Each has up to 7 variants with consistent suffix semantics:
- (base) — solid color for icons, text, badges
- -light — lighter shade for hover states
- -lighter / -lightest — progressively lighter for gradient effects (error, info only)
- -subtle — ~10% opacity for very faint backgrounds
- -muted — ~20% opacity for muted backgrounds
- -emphasis — ~30% opacity for medium-strength backgrounds
- -strong — ~50% opacity for strong backgrounds (error only)
- -dark — darker variant for darkened states (error only)
- -text — text color to use on top of the base color (warning, buttons)
Button colors (--color-button-*)
Three button palettes (primary, secondary, danger) each with 5 variants. See Button.tsx for the full mapping:
| Variant | Usage in contained | Usage in text | Usage in outline |
|---|---|---|---|
| (base) | Background | — | Border color |
| -light | Hover background | — | — |
| -subtle | — | Hover background | Hover background |
| -emphasis | Hover background (danger) | — | — |
| -text | Text color | Text color | Text color |
The tertiary button color uses bg-primary/bg-secondary + text-primary instead of dedicated button tokens.
Disabled buttons use: bg-bg-tertiary-strong background + text-text-muted text.
Input colors (--color-input-*)
Currently not directly used in TSX components — inputs use bg-bg-secondary + border-border-default + text-text-primary instead. These are defined for potential future use or custom components.
Agent colors (--color-agent-*)
Semantic colors for tool/badge indicators in the AgentSelector and related UI:
agent-auto-approve— auto-approve toggle indicatoragent-aider-tools— aider tools iconagent-power-tools— power tools iconagent-todo-tools— todo tool badgeagent-tasks-tools— tasks tool badgeagent-memory-tools— memory tool badgeagent-skills-tools— skills tool badgeagent-subagents-tools— subagents tool badgeagent-context-files— context file indicatoragent-repo-map— repo map indicatoragent-ai-request— AI request indicatoragent-sub-agent— sub-agent indicator
Dark theme registration
Dark themes (those that need a dark code editor) must also be added to the isCodeEditorDarkTheme array in packages/common/src/types/common.ts.
Color opacity suffix convention
Many variables include an inline hex alpha suffix (e.g. #D4A05440 = accent at ~25% opacity). The convention is:
1a≈ 10% — subtle19≈ 10% — subtle (alternate)26≈ 15% — muted33≈ 20% — muted4c≈ 30% — emphasis4d≈ 30% — emphasis (alternate)50≈ 31% — selection60≈ 38% — strong7f≈ 50% — strong (alternate)80≈ 50% — semi-transparentf2≈ 95% — almost opaque
Global color applications (non-Tailwind)
Some components use CSS variables directly via var(--color-*) instead of Tailwind utilities:
main.css: body background/text, CodeMirror editor styling, resize handleDiffViewer.scss/PierreDiffViewer.scss: diff line backgrounds, guttersnotifications.ts: toast background/text stylingPromptField.tsx: text selection color
Troubleshooting
-
Theme not showing up:
- missing
@useimport insrc/renderer/src/themes/themes.scss - missing entry in
THEMESarray inpackages/common/src/types/common.ts - typo mismatch between
.theme-<name>and the<name>stored in settings
- missing
-
Some UI areas look "unstyled":
- you likely missed one or more
--color-*variables; compare against a known-good theme and fill in the missing ones.
- you likely missed one or more
-
Input fields don't match theme:
- Components use
bg-bg-secondary+border-border-defaultfor inputs, not theinput-*tokens. Focus on the bg/border/text hierarchies instead.
- Components use