Checklist for adding or changing a field on ThemeConfig (a new design token, per-mode setting, or export option) end to end — schema, defaults, store + history, CSS apply, export, editor UI, AI, and tests. Use whenever ThemeConfig's shape changes.
Installs into .claude/skills of the current project.
Are you the author of Add Theme Property?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/mattycraig-add-theme-property)
---
name: add-theme-property
description: Checklist for adding or changing a field on ThemeConfig (a new design token, per-mode setting, or export option) end to end — schema, defaults, store + history, CSS apply, export, editor UI, AI, and tests. Use whenever ThemeConfig's shape changes.
argument-hint: "<property name and purpose>"
---
# Add a theme property: $ARGUMENTS
`ThemeConfig` is persisted in users' cookies, stored in saved presets, exported as JSON, generated by AI, and synced into the preview iframe. A change must work for all of them, including **old data that lacks the new field**.
Work through every step. Say explicitly when a step doesn't apply.
1. **Shared constants**: if the property has a fixed value set (palettes, shades, fonts, ranges), add it to `shared/constants/theme.ts` so app, server, and AI prompt share it.
2. **Types + schema** (`app/types/theme.ts`): add the field to `ThemeConfig`. In `rawThemeSchema`, make it `.optional()` and fill it in `ThemeConfigSchema.transform()` (usually mirroring the light-mode value for `dark*` fields). Existing cookies and presets must still parse.
3. **Defaults** (`app/utils/defaults.ts`): add the value to `DEFAULT_THEME` (and to the light/dark override defaults if it's a token override).
4. **Presets** (`app/utils/presets.ts`): built-in presets are full configs, so either add the field to each preset or rely on the schema transform, and make sure `loadPreset` still validates.
5. **Store** (`app/stores/theme.ts`): add a `setXForMode(mode, value)` setter that mutates `config.value` then calls `_pushHistory()`. Continuous inputs (sliders, drags) also get a visual setter without history, committed once at the end. Add it to the returned API.
6. **Apply** (`app/composables/useThemeApply.ts` → `app/utils/cssGenerator.ts`): semantic palette names go to `appConfig.ui.colors`; everything else is a CSS variable in `:root` / `.dark`. Output must still pass `isCleanCSS`; sanitize free-form values with `sanitizeCSSValue`.
7. **Export** (`app/composables/useThemeExport.ts`, `generateExportCSS`): include the property in the CSS and app-config output where relevant (JSON export is automatic).
8. **Editor UI** (`app/components/editor/`): a leaf control taking `modelValue` and emitting updates, wired in `EditorPanel` via `useThemeModeAccessors` so it edits the current color mode.
9. **AI**: if the model should choose it, extend `aiThemeSchema` in `server/api/ai/generate.post.ts`, `buildThemeConfig` in `server/utils/aiResponseBuilder.ts`, and describe it in `server/utils/aiSystemPrompt.ts` (value lists come from shared constants).
10. **Docs**: update `app/data/help.md` and any learn article describing the editor's controls.
11. **Tests**:
- `tests/unit/types/theme.test.ts`: the schema accepts old configs without the field and fills the default.
- `tests/unit/stores/theme.test.ts`: the setter updates the right mode, pushes history, and undo/redo work.
- `tests/unit/utils/cssGenerator.test.ts` / `useThemeExport.test.ts`: the variable/export output.
12. **Verify**: `/dev-workflow full`, then check the preview updates live and survives a reload (`/run-app`).