2026-02-01 18:29:34 +02:00
---
name : theme-system
2026-07-14 00:45:44 +03:00
description : Use when creating or modifying OpenChamber UI components, styling, colors, buttons, visual states, themes, or icons.
2026-02-01 18:29:34 +02:00
---
2026-07-14 00:45:44 +03:00
# Theme System
2026-02-01 18:29:34 +02:00
2026-07-14 00:45:44 +03:00
## Core Rules
2026-02-01 18:29:34 +02:00
2026-07-14 00:45:44 +03:00
- 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` .
2026-07-18 00:11:05 +03:00
- Every dropdown-style value-picker trigger (shows current value, opens a picker) takes its chrome from `dropdownTriggerVariants` in `packages/ui/src/components/ui/dropdown-trigger.ts` (sizes: `sm` dense h-6, `default` forms h-8; native `SelectTrigger` consumes it). Call sites add layout classes only (width/truncation) — never re-declare border/radius/bg/hover. Deliberately chrome-less pickers (chat composer, headers) are the only exception.
2026-07-14 00:45:44 +03:00
- 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.
2026-02-01 18:29:34 +02:00
2026-07-14 00:45:44 +03:00
## Load References By Task
2026-02-01 18:29:34 +02:00
2026-07-14 00:45:44 +03:00
| 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` |
2026-02-01 18:29:34 +02:00
2026-07-14 00:45:44 +03:00
Load every matching reference before editing. Settings work must also load `settings-ui-patterns` ; user-facing or accessible text must load `locale-ui-patterns` .
2026-02-01 18:29:34 +02:00
2026-07-14 00:45:44 +03:00
## Token Decision
2026-02-01 18:29:34 +02:00
2026-07-14 00:45:44 +03:00
1. Code display -> `syntax.*`
2. Error/warning/success/info -> `status.*`
3. Primary CTA -> `primary.*`
4. Hover/pressed/focus -> `interactive.*`
5. Selected/active state -> `interactive.selection*`
6. Background/text/border layer -> `surface.*` and semantic utility classes
2026-02-01 18:29:34 +02:00
2026-07-14 00:45:44 +03:00
Prefer CSS variables/classes for component styling. Use `useThemeSystem()` only when an API requires resolved color values.
2026-02-01 18:29:34 +02:00
2026-07-14 00:45:44 +03:00
## Button Contract
2026-03-20 01:01:03 +02:00
2026-07-14 00:45:44 +03:00
Use `Button` from `packages/ui/src/components/ui/button.tsx` .
2026-03-20 01:01:03 +02:00
2026-07-14 00:45:44 +03:00
| 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 |
2026-03-20 01:01:03 +02:00
2026-07-14 00:45:44 +03:00
| Size | Use |
|---|---|
| `xs` | Dense row/list control |
| `sm` | Compact action |
| `default` | Standard action |
| `lg` | Prominent action |
| `icon` | Icon-only square action |
2026-03-20 01:01:03 +02:00
2026-07-14 00:45:44 +03:00
Do not hardcode button height/padding when a size variant exists. Do not recreate selection/destructive styling with ad-hoc classes.
2026-03-20 01:01:03 +02:00
2026-07-14 00:45:44 +03:00
## Icon Contract
2026-03-20 01:01:03 +02:00
2026-02-01 18:29:34 +02:00
```tsx
2026-07-14 00:45:44 +03:00
import { Icon } from '@/components/icon/Icon' ;
2026-02-01 18:29:34 +02:00
2026-07-14 00:45:44 +03:00
< Icon name = "check" className = "size-4" />
2026-02-01 18:29:34 +02:00
```
2026-07-14 00:45:44 +03:00
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.
2026-02-01 18:29:34 +02:00
2026-08-03 18:38:58 +03:00
## Animation Contract
Animate only `transform` and `opacity` . The compositor drives those; every other
property recalculates style on each frame for as long as the animation runs, and
geometry properties add layout on top. Measured on this repository's fixture,
identical at any element count from 1 to 32:
| Animated property | Style recalculations/sec | Layouts/sec |
|---|---|---|
| `transform` , `opacity` , `filter` | 0 | 0 |
| `rotate` (the individual property) | 60 | 0 |
| `background-position` , `border-color` , `box-shadow` | 60 | 0 |
| `width` and other geometry | 60 | 60 |
- `rotate: 360deg` is not a cheap synonym for `transform: rotate(360deg)` .
Prefer the `transform` form.
- Cost applies for the entire time an animation runs, so an indicator tied to a
long-running operation pays it continuously. An indicator that is not
conveying anything should not be animating.
- `will-change` , wrapper elements, `contain` , and `steps()` timing do not make a
non-composited property cheap. Only changing the property does.
- Verify with `bun run profile:animation` rather than reasoning about it; add a
variant to `scripts/perf/animation-fixture.html` for a technique not covered.
See `scripts/perf/DOCUMENTATION.md` .
2026-07-14 00:45:44 +03:00
## Verification
2026-02-01 18:29:34 +02:00
2026-08-03 18:38:58 +03:00
- Animations are limited to `transform` and `opacity` , or their cost was measured and accepted.
2026-07-14 00:45:44 +03:00
- No hardcoded/palette colors were introduced.
- Buttons use shared variants and sizes.
- 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.
- Relevant type-check, visual/runtime validation, and generated-asset checks ran.