`, not chains of ``
+
+---
+
+## Testing Tools
+
+| Tool | Purpose |
+| ------------------------------------------------------- | ----------------------------------- |
+| Chrome DevTools → Accessibility tab | Inspect ARIA tree, contrast |
+| axe DevTools (browser extension) | Automated WCAG audit |
+| macOS VoiceOver (`Cmd+F5`) | Screen reader smoke test |
+| `prefers-reduced-motion: reduce` (DevTools → Rendering) | Verify animation suppression |
+| Keyboard-only navigation | Tab through entire UI without mouse |
diff --git a/ui/docs/design-system/color-tokens.md b/ui/docs/design-system/color-tokens.md
new file mode 100644
index 00000000000..f3cd990fc10
--- /dev/null
+++ b/ui/docs/design-system/color-tokens.md
@@ -0,0 +1,91 @@
+# Color Tokens
+
+All tokens are defined in `ui/src/styles/base.css` under `:root` (dark mode default) and `:root[data-theme-mode="light"]` (light override). Theme families may override accent tokens while keeping shared surface tokens.
+
+> Contrast ratios are measured against `--bg` (`#0e1015`) in dark mode using WCAG relative luminance formula. AA requires ≥4.5:1 for normal text, ≥3:1 for large text and UI components.
+
+---
+
+## Background Scale
+
+| Token | Dark Value | Light Value | Use | Don't |
+| --------------- | ---------- | ----------- | ----------------------------- | ------------------------------ |
+| `--bg` | `#0e1015` | `#f8f9fa` | Page root, deepest layer | Never use on elevated surfaces |
+| `--bg-accent` | `#13151b` | `#f1f3f5` | Sidebar, secondary panels | Not for interactive card hover |
+| `--bg-elevated` | `#191c24` | `#ffffff` | Raised panels, modals | Not for inline elements |
+| `--bg-hover` | `#1f2330` | `#eceef0` | List item hover state | Not for default state |
+| `--bg-muted` | `#1f2330` | `#eceef0` | Subtle fills, disabled states | Not for focus states |
+
+## Surface / Card
+
+| Token | Dark Value | Light Value | Use | Don't |
+| ---------------------- | ------------------------ | ------------------ | ----------------------------- | --------------- |
+| `--card` | `#161920` | `#ffffff` | Card backgrounds, composer | Avoid as border |
+| `--card-foreground` | `#f0f0f2` | `#1a1a1e` | Text on cards | — |
+| `--card-highlight` | `rgba(255,255,255,0.04)` | `rgba(0,0,0,0.02)` | Inner highlight on hover | Not for text |
+| `--popover` | `#191c24` | `#ffffff` | Dropdown, tooltip backgrounds | — |
+| `--popover-foreground` | `#f0f0f2` | `#1a1a1e` | Text inside popovers | — |
+
+## Text
+
+| Token | Dark Value | Contrast on `--bg` | Use |
+| ---------------- | ---------- | ------------------ | ---------------------------------------------------- |
+| `--text` | `#d4d4d8` | ~12.9:1 ✅ | Body copy, labels |
+| `--text-strong` | `#f4f4f5` | ~17.3:1 ✅ | Headings, emphasis |
+| `--muted` | `#838387` | ~5.0:1 ✅ | Placeholder, metadata |
+| `--muted-strong` | `#75757d` | ~4.2:1 | Secondary text, captions; avoid for normal body text |
+
+## Accent (Primary — Red)
+
+| Token | Value | Use | Don't |
+| ----------------- | --------------------- | ---------------------------------------------- | ---------------------------------------- |
+| `--accent` | `#ff5c5c` | Primary CTA, send button, active tab indicator | Don't use for large filled backgrounds |
+| `--accent-hover` | `#ff7070` | Hover state of accent elements | — |
+| `--accent-muted` | `#ff5c5c` | Same as accent (aliased) | — |
+| `--accent-subtle` | `rgba(255,92,92,0.1)` | Badge backgrounds, tinted fills | Not for text on dark bg (fails contrast) |
+| `--accent-glow` | `rgba(255,92,92,0.2)` | Focus rings, glow effects | Not as background |
+| `--primary` | `#ff5c5c` | Component library `primary` alias | — |
+
+## Accent 2 (Teal)
+
+| Token | Value | Use |
+| ------------------- | ---------------------- | ----------------------------------------- |
+| `--accent-2` | `#14b8a6` | Success-adjacent status, secondary badges |
+| `--accent-2-muted` | `rgba(20,184,166,0.7)` | Subtle teal fills |
+| `--accent-2-subtle` | `rgba(20,184,166,0.1)` | Tinted teal background |
+
+## Semantic
+
+| Token | Dark Value | Light Value | Contrast on `--bg` | Use |
+| --------------- | ---------- | ----------- | ------------------ | --------------------------------------------- |
+| `--ok` | `#22c55e` | `#15803d` | ~8.4:1 ✅ | Success states, token meter low |
+| `--warn` | `#f59e0b` | `#d97706` | ~8.9:1 ✅ | Warnings, degraded states |
+| `--danger` | `#ef4444` | `#dc2626` | ~5.1:1 ✅ | Errors, destructive actions, token meter high |
+| `--info` | `#3b82f6` | `#2563eb` | ~5.2:1 ✅ | Informational, token meter mid |
+| `--destructive` | `#ef4444` | — | ~5.1:1 ✅ | Destructive action labels |
+
+## Border
+
+| Token | Value | Use |
+| ----------------- | --------- | -------------------------------- |
+| `--border` | `#1e2028` | Default subtle borders, dividers |
+| `--border-strong` | `#2e3040` | Active/focused borders |
+| `--border-hover` | `#3e4050` | Hover-state borders |
+
+## Focus
+
+| Token | Value | Use |
+| -------------- | --------------------------------------------------------------------------------- | ------------------------------ |
+| `--ring` | `#ff5c5c` | Focus ring colour |
+| `--focus-ring` | `0 0 0 2px var(--bg), 0 0 0 3px color-mix(in srgb, var(--ring) 80%, transparent)` | Standard focus ring box-shadow |
+| `--focus-glow` | `0 0 0 2px var(--bg), 0 0 0 3px var(--ring), 0 0 16px var(--accent-glow)` | Elevated interactive elements |
+
+---
+
+## Anti-Patterns
+
+- ❌ Hardcoded hex colours in component CSS — always use tokens
+- ❌ `--accent-subtle` as text colour — fails contrast on dark backgrounds
+- ❌ Mixing `--ok` and `--accent-2` for "green success" — use `--ok` only
+- ❌ Using `--danger` for non-error states (e.g. "hot feature") — reserve for errors and destructive actions
+- ❌ `--muted-strong` for normal body text — below 4.5:1 on dark `--bg`; use `--text` instead
diff --git a/ui/docs/design-system/glass-surfaces.md b/ui/docs/design-system/glass-surfaces.md
new file mode 100644
index 00000000000..84e4a6ba8c0
--- /dev/null
+++ b/ui/docs/design-system/glass-surfaces.md
@@ -0,0 +1,94 @@
+# Glass Surfaces
+
+OpenClaw uses two glass tiers to establish visual hierarchy. **Never use flat, fully-opaque backgrounds** in the main chrome — glass creates the lived-in depth that defines the visual language.
+
+---
+
+## Tier 1 — Navigation Chrome
+
+Used for: top nav, sidebar, persistent chrome overlays.
+
+```css
+/* Exact values from ui/src/styles/layout.css — .topbar */
+background: color-mix(in srgb, var(--bg) 82%, transparent);
+backdrop-filter: blur(12px) saturate(1.6);
+-webkit-backdrop-filter: blur(12px) saturate(1.6);
+```
+
+**Why these values:** the topbar mixes the root background with transparency so it stays readable while still refracting content behind it. `saturate(1.6)` brings out colour from blurred layers.
+
+---
+
+## Tier 2 — Card / Input Surfaces
+
+Used for: chat composer input box, modals, popovers, settings cards, dropdowns.
+
+```css
+/* Exact values from ui/src/styles/chat/layout.css — .agent-chat__input */
+background: var(--card); /* #161920 */
+border: 1px solid var(--border); /* #1e2028 */
+border-radius: var(--radius-lg); /* 14px */
+
+@supports (backdrop-filter: blur(1px)) {
+ backdrop-filter: blur(12px) saturate(1.6);
+ -webkit-backdrop-filter: blur(12px) saturate(1.6);
+}
+```
+
+Popovers and floating overlays reduce blur slightly:
+
+```css
+/* Exact values from source */
+backdrop-filter: blur(
+ 8px
+); /* components.css action menu, cron-quick-create.css modal backdrop, workboard.css modal backdrop */
+backdrop-filter: blur(10px); /* skill-workshop.css revision dialog, dreams.css media lightbox */
+backdrop-filter: blur(14px); /* components.css markdown preview dialog backdrop */
+```
+
+---
+
+## The No-Solid-Panels Rule
+
+> **Do not** add `background: var(--bg)` or any fully-opaque fill to a surface that appears above other content.
+
+Use the `@supports` fallback pattern below instead. Browsers that lack `backdrop-filter` fall back gracefully to the semi-opaque base without breaking layout.
+
+### `@supports` Fallback Pattern
+
+```css
+.my-surface {
+ /* Fallback for older browsers / Firefox without backdrop-filter */
+ background: rgba(14, 16, 21, 0.92);
+}
+
+@supports (backdrop-filter: blur(1px)) {
+ .my-surface {
+ background: rgba(14, 16, 21, 0.75);
+ backdrop-filter: blur(12px) saturate(1.6);
+ -webkit-backdrop-filter: blur(12px) saturate(1.6);
+ }
+}
+```
+
+---
+
+## When to Use Each Tier
+
+| Context | Tier | Blur |
+| ---------------------- | ---- | ---- |
+| Top nav / sidebar rail | 1 | 12px |
+| Chat composer | 2 | 12px |
+| Modal / dialog | 2 | 10px |
+| Dropdown / popover | 2 | 8px |
+| Tooltip | 2 | 14px |
+| Workboard card | 2 | 10px |
+
+---
+
+## Anti-Patterns
+
+- ❌ `background: var(--bg)` on a surface element (use glass instead)
+- ❌ `backdrop-filter` without `-webkit-backdrop-filter` (Safari still requires prefix)
+- ❌ Nesting glass surfaces more than 2 levels (causes noticeable blur stacking on macOS)
+- ❌ Using `blur(>16px)` outside of modal overlays (hurts performance on mid-tier devices)
diff --git a/ui/docs/design-system/motion.md b/ui/docs/design-system/motion.md
new file mode 100644
index 00000000000..fba3983c22f
--- /dev/null
+++ b/ui/docs/design-system/motion.md
@@ -0,0 +1,91 @@
+# Motion
+
+OpenClaw uses a three-step duration scale with purpose-matched easing functions. Every animation should serve a functional goal — transitions that exist only for aesthetics add cognitive load without benefit.
+
+---
+
+## Duration Scale
+
+Defined in `ui/src/styles/base.css`:
+
+| Token | Value | Use |
+| ------------------- | ------- | --------------------------------------------------------- |
+| `--duration-fast` | `100ms` | Micro-interactions: hover colour, focus ring, icon swap |
+| `--duration-normal` | `180ms` | Standard transitions: menu open, tab switch, input expand |
+| `--duration-slow` | `300ms` | Page-level: sheet slide-in, modal fade, skeleton reveal |
+
+Non-token durations in use (document when adding new ones):
+
+| Context | Value | File |
+| ----------------------- | ------------------------------ | ----------------- |
+| Theme circle transition | `400ms` | `base.css` |
+| Shimmer animation | `1500ms` | `base.css` |
+| Composer border/shadow | `var(--duration-fast)` = 100ms | `chat/layout.css` |
+
+---
+
+## Easing Functions
+
+Defined in `ui/src/styles/base.css`:
+
+| Token | Curve | Use |
+| --------------- | ----------------------------------- | ------------------------------------------------------- |
+| `--ease-out` | `cubic-bezier(0.16, 1, 0.3, 1)` | Most enter/expand transitions — fast start, smooth land |
+| `--ease-in-out` | `cubic-bezier(0.4, 0, 0.2, 1)` | Elements that travel across screen (slides, drawers) |
+| `--ease-spring` | `cubic-bezier(0.34, 1.56, 0.64, 1)` | Playful/tactile: button press, badge pop, icon bounce |
+
+**Default rule of thumb:** Use `--ease-out` unless the element explicitly moves from point A to point B (use `--ease-in-out`) or needs a bouncy feel (use `--ease-spring`).
+
+---
+
+## `prefers-reduced-motion` Pattern
+
+Every animation or transition **must** be suppressed when the user has requested reduced motion. Use the global reset already present in `base.css` — do not add per-component overrides unless you need to preserve a non-animated state change (e.g. instant opacity change is acceptable, instant position snap is acceptable).
+
+```css
+/* Already in base.css — covers all transitions globally */
+@media (prefers-reduced-motion: reduce) {
+ *,
+ *::before,
+ *::after {
+ animation-duration: 0.01ms !important;
+ animation-iteration-count: 1 !important;
+ transition-duration: 0.01ms !important;
+ scroll-behavior: auto !important;
+ }
+}
+```
+
+For components with complex animation state (e.g. shimmer skeletons that use `animation-iteration-count: infinite`), add an explicit guard:
+
+```css
+@media (prefers-reduced-motion: reduce) {
+ .my-shimmer {
+ animation: none;
+ /* Show a static placeholder instead */
+ opacity: 0.5;
+ }
+}
+```
+
+---
+
+## Animation Inventory
+
+| Name | File | Duration | Purpose |
+| ------------------------- | ----------------- | ------------------------- | ----------------------------------------------- |
+| `shimmer` | `base.css` | 1500ms, infinite | Skeleton loading placeholders |
+| `theme-circle-transition` | `base.css` | 400ms, `--ease-out` | Dark/light mode circle wipe |
+| Composer border/shadow | `chat/layout.css` | 100ms (`--duration-fast`) | Focus ring on input area |
+| Workboard card glass | `workboard.css` | — | Static (no animation) |
+| Dreams diary reveal | `dreams.css` | 1.4s, cubic-bezier | Entry reveal keyframe with blur-to-clear effect |
+
+---
+
+## Anti-Patterns
+
+- ❌ Adding new keyframe animations without a `prefers-reduced-motion` suppression
+- ❌ Using `animation-iteration-count: infinite` outside of skeleton loaders
+- ❌ Duration > 500ms for UI chrome elements (feels sluggish)
+- ❌ `linear` easing for enter/exit — always use a curve from the token set
+- ❌ CSS transitions on `transform` and `opacity` simultaneously with `filter` — causes GPU layer explosion on mobile