Theme System
Core Rules
- Use semantic OpenChamber theme tokens; never hardcode hex colors or generic Tailwind palette colors.
- Use shared UI primitives before introducing feature-local controls.
- Use the shared
Button; do not create button wrappers such as ButtonSmall or ButtonLarge.
- Every dropdown-style value-picker trigger takes its chrome from
dropdownTriggerVariants in packages/ui/src/components/ui/dropdown-trigger.ts; call sites add layout classes only. Deliberately chrome-less pickers in composers or headers are the exception.
- Use the sprite-based
Icon; never import icons directly from @remixicon/react.
- Apply hover tokens only to interactive elements.
- Use status colors only for actual status/feedback.
- Use selection tokens for selected state and primary tokens for primary actions.
Load References By Task
| Task |
Required reference |
| Choosing colors/tokens or reviewing styled examples |
references/tokens-and-examples.md |
| Adding, converting, storing, or generating icons |
references/icons.md |
| Adding built-in or custom themes |
references/adding-themes.md |
Load every matching reference before editing. User-facing or accessible text must load locale-ui-patterns. Settings composition is owned by settings-ui-patterns, which declares theme-system as its one-way companion.
Token Decision
- Code display ->
syntax.*
- Error/warning/success/info ->
status.*
- Primary CTA ->
primary.*
- Hover/pressed/focus ->
interactive.*
- Selected/active state ->
interactive.selection*
- Background/text/border layer ->
surface.* and semantic utility classes
Prefer CSS variables/classes for component styling. Use useThemeSystem() only when an API requires resolved color values.
Button Contract
Use Button from packages/ui/src/components/ui/button.tsx.
| Variant |
Use |
default |
Primary local action |
outline |
Visible secondary action |
secondary |
Soft secondary action |
ghost |
Quiet row/toolbar action |
destructive |
Destructive action |
chip |
Compact selectable option with aria-pressed |
link |
Rare inline text action |
| Size |
Use |
xs |
Dense row/list control |
sm |
Compact action |
default |
Standard action |
lg |
Prominent action |
icon |
Icon-only square action |
Do not hardcode button height/padding when a size variant exists. Do not recreate selection/destructive styling with ad-hoc classes.
Keyboard Navigation Contract
- Menus, selects, and autocomplete pickers with ArrowDown/ArrowUp navigation must also support Ctrl+N/Ctrl+P, including submenus and searchable lists.
- Keep this behavior in shared components so callers inherit it. Use the keyboard mapping in
packages/ui/src/components/ui/dropdown-navigation.ts; feature code must not duplicate key detection.
- Lists that own their active option or stop keyboard propagation must call the shared navigation helper at their own event boundary. Wrapping a custom list in a dropdown does not guarantee that its navigation events reach the wrapper.
- Route both key pairs through the same selection logic, preserving disabled-item skipping, boundary or wrap behavior, highlight, and scroll visibility. Consume each navigation event once, only while the menu or picker is active; preserve IME text entry and other modifier chords.
- Verify Ctrl+N/P alongside arrow keys in the real component, including search-input focus, submenus, and closed state. A key-mapping unit test alone does not verify event propagation or focus behavior.
Icon Contract
import { Icon } from '@/components/icon/Icon';
<Icon name="check" className="size-4" />
Use IconName for icon values stored in arrays, objects, state, or config. Icon has no size prop. Run bun run icons:generate when introducing a sprite name, and never edit sprite.ts manually. Load references/icons.md for the complete workflow.
Animation Contract
Animate only transform and opacity. Use transform: rotate(...), not the individual rotate property. Non-composited properties recalculate style continuously; geometry also triggers layout, and wrappers, will-change, contain, or stepped timing do not remove that cost. Animate only while conveying live information.
For any other technique, load performance-engineering and scripts/perf/DOCUMENTATION.md, measure it with bun run profile:animation, and add a fixture variant when needed. This skill owns animation styling; performance-engineering owns performance evidence.
Completion Criteria
- Animations are limited to
transform and opacity, or their cost was measured and accepted.
- No hardcoded/palette colors were introduced.
- Buttons use shared variants and sizes.
- Menus and pickers satisfy the keyboard navigation contract without caller-specific key handling for standard shared components.
- Icons use
Icon/IconName, and generated sprite changes are intentional.
- Hover, selection, primary, and status semantics are distinct.
- Light/dark/high-contrast and long-text states remain legible.
- Every applicable contract and loaded task reference was verified with relevant type-check, visual/runtime validation, and generated-asset checks.
1---2name: theme-system3description: Use when creating or modifying OpenChamber UI components, styling, colors, buttons, visual states, themes, or icons.4---5
6# Theme System
7
8## Core Rules
9
10- Use semantic OpenChamber theme tokens; never hardcode hex colors or generic Tailwind palette colors.
11- Use shared UI primitives before introducing feature-local controls.
12- Use the shared `Button`; do not create button wrappers such as `ButtonSmall` or `ButtonLarge`.
13- Every dropdown-style value-picker trigger takes its chrome from `dropdownTriggerVariants` in `packages/ui/src/components/ui/dropdown-trigger.ts`; call sites add layout classes only. Deliberately chrome-less pickers in composers or headers are the exception.
14- Use the sprite-based `Icon`; never import icons directly from `@remixicon/react`.
15- Apply hover tokens only to interactive elements.
16- Use status colors only for actual status/feedback.
17- Use selection tokens for selected state and primary tokens for primary actions.
18
19## Load References By Task
20
21| Task | Required reference |
22|---|---|
23| Choosing colors/tokens or reviewing styled examples | `references/tokens-and-examples.md` |
24| Adding, converting, storing, or generating icons | `references/icons.md` |
25| Adding built-in or custom themes | `references/adding-themes.md` |
26
27Load every matching reference before editing. User-facing or accessible text must load `locale-ui-patterns`. Settings composition is owned by `settings-ui-patterns`, which declares `theme-system` as its one-way companion.
28
29## Token Decision
30
311. Code display -> `syntax.*`
322. Error/warning/success/info -> `status.*`
333. Primary CTA -> `primary.*`
344. Hover/pressed/focus -> `interactive.*`
355. Selected/active state -> `interactive.selection*`
366. Background/text/border layer -> `surface.*` and semantic utility classes
37
38Prefer CSS variables/classes for component styling. Use `useThemeSystem()` only when an API requires resolved color values.
39
40## Button Contract
41
42Use `Button` from `packages/ui/src/components/ui/button.tsx`.
43
44| Variant | Use |
45|---|---|
46| `default` | Primary local action |
47| `outline` | Visible secondary action |
48| `secondary` | Soft secondary action |
49| `ghost` | Quiet row/toolbar action |
50| `destructive` | Destructive action |
51| `chip` | Compact selectable option with `aria-pressed` |
52| `link` | Rare inline text action |
53
54| Size | Use |
55|---|---|
56| `xs` | Dense row/list control |
57| `sm` | Compact action |
58| `default` | Standard action |
59| `lg` | Prominent action |
60| `icon` | Icon-only square action |
61
62Do not hardcode button height/padding when a size variant exists. Do not recreate selection/destructive styling with ad-hoc classes.
63
64## Keyboard Navigation Contract
65
66- Menus, selects, and autocomplete pickers with ArrowDown/ArrowUp navigation must also support Ctrl+N/Ctrl+P, including submenus and searchable lists.
67- Keep this behavior in shared components so callers inherit it. Use the keyboard mapping in `packages/ui/src/components/ui/dropdown-navigation.ts`; feature code must not duplicate key detection.
68- Lists that own their active option or stop keyboard propagation must call the shared navigation helper at their own event boundary. Wrapping a custom list in a dropdown does not guarantee that its navigation events reach the wrapper.
69- Route both key pairs through the same selection logic, preserving disabled-item skipping, boundary or wrap behavior, highlight, and scroll visibility. Consume each navigation event once, only while the menu or picker is active; preserve IME text entry and other modifier chords.
70- Verify Ctrl+N/P alongside arrow keys in the real component, including search-input focus, submenus, and closed state. A key-mapping unit test alone does not verify event propagation or focus behavior.
71
72## Icon Contract
73
74```tsx
75import { Icon } from '@/components/icon/Icon';
76
77<Icon name="check" className="size-4" />
78```
79
80Use `IconName` for icon values stored in arrays, objects, state, or config. `Icon` has no `size` prop. Run `bun run icons:generate` when introducing a sprite name, and never edit `sprite.ts` manually. Load `references/icons.md` for the complete workflow.
81
82## Animation Contract
83
84Animate only `transform` and `opacity`. Use `transform: rotate(...)`, not the individual `rotate` property. Non-composited properties recalculate style continuously; geometry also triggers layout, and wrappers, `will-change`, `contain`, or stepped timing do not remove that cost. Animate only while conveying live information.
85
86For any other technique, load `performance-engineering` and `scripts/perf/DOCUMENTATION.md`, measure it with `bun run profile:animation`, and add a fixture variant when needed. This skill owns animation styling; `performance-engineering` owns performance evidence.
87
88## Completion Criteria
89
90- Animations are limited to `transform` and `opacity`, or their cost was measured and accepted.
91- No hardcoded/palette colors were introduced.
92- Buttons use shared variants and sizes.
93- Menus and pickers satisfy the keyboard navigation contract without caller-specific key handling for standard shared components.
94- Icons use `Icon`/`IconName`, and generated sprite changes are intentional.
95- Hover, selection, primary, and status semantics are distinct.
96- Light/dark/high-contrast and long-text states remain legible.
97- Every applicable contract and loaded task reference was verified with relevant type-check, visual/runtime validation, and generated-asset checks.