From fdc8d472c79fd59c5d4535163d9d0c250b18a86a Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 25 Jul 2026 06:48:49 -0700 Subject: [PATCH] fix(ui): align shadow-DOM token fallbacks with real token values (#113684) Shadow-DOM components carried fallback colors that never matched the actual tokens: panel-tab-strip fell back to teal #4ec9a8 for the coral --accent, resizable-divider to Bootstrap blue #007bff, and --border to a generic #333. Align all fallbacks with the claw-family values in styles/base.css so token-less rendering matches the product palette. Also document the intentionally dark image-lightbox chrome and the terminal theme's hand-mirrored token values, and regenerate the drifted values in ui/docs/design-system/color-tokens.md (--muted, --muted-strong, --primary, --danger, --info, light status colors) from base.css truth. --- ui/docs/design-system/color-tokens.md | 36 +++++++++++--------- ui/src/components/image-lightbox.ts | 3 ++ ui/src/components/panel-tab-strip.ts | 6 ++-- ui/src/components/resizable-divider.ts | 6 ++-- ui/src/components/terminal/terminal-theme.ts | 4 +++ 5 files changed, 33 insertions(+), 22 deletions(-) diff --git a/ui/docs/design-system/color-tokens.md b/ui/docs/design-system/color-tokens.md index 84296085671c..4cfa8459be14 100644 --- a/ui/docs/design-system/color-tokens.md +++ b/ui/docs/design-system/color-tokens.md @@ -1,6 +1,6 @@ # 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. +All tokens are defined in `ui/src/styles/base.css` under `:root` (dark mode default) and `:root[data-theme-mode="light"]` (light override). Values in this doc are the default claw family; the knot (`data-theme="openknot"`/`openknot-light`) and dash (`dash`/`dash-light`) families override surface, accent, and status tokens in their own `base.css` blocks, each with its own WCAG audit comment. > 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. @@ -30,12 +30,12 @@ Light mode uses a warm paper palette: ivory backgrounds, warm gray borders (`#e8 ## 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 | +| Token | Dark Value | Contrast on `--bg` | Use | +| ---------------- | ---------- | ------------------ | -------------------------------------------------- | +| `--text` | `#d4d4d8` | ~12.9:1 ✅ | Body copy, labels | +| `--text-strong` | `#f4f4f5` | ~17.3:1 ✅ | Headings, emphasis | +| `--muted` | `#8b8b94` | ~5.6:1 ✅ | Placeholder, metadata | +| `--muted-strong` | `#898990` | ~5.5:1 ✅ | Secondary text, captions; prefer `--text` for body | ## Accent (Primary — Red) @@ -46,7 +46,7 @@ Light mode uses a warm paper palette: ivory backgrounds, warm gray borders (`#e8 | `--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 | — | +| `--primary` | `#d13c3c` | Filled primary buttons (white text, ~4.8:1 AA) | Not interchangeable with `--accent` | ## Accent 2 (Teal) @@ -58,13 +58,17 @@ Light mode uses a warm paper palette: ivory backgrounds, warm gray borders (`#e8 ## 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 | +| Token | Dark Value | Light Value | Contrast on dark `--bg` | Use | +| --------------- | ---------- | ----------- | ----------------------- | ---------------------------------------------- | +| `--ok` | `#22c55e` | `#166534` | ~8.4:1 ✅ | Success states, token meter low | +| `--warn` | `#f59e0b` | `#92400e` | ~8.9:1 ✅ | Warnings, degraded states | +| `--danger` | `#f87171` | `#b91c1c` | ~6.9:1 ✅ | Errors, destructive text, token meter high | +| `--info` | `#60a5fa` | `#1d4ed8` | ~7.5:1 ✅ | Informational, token meter mid | +| `--destructive` | `#d32f2f` | `#dc2626` | ~3.8:1 (fill only) | Destructive button fills (with `#fafafa` text) | + +Each `--x` has `-muted` (0.75 alpha) and `-subtle` (0.08 alpha) rgba siblings that must stay in sync with the base hex — the base doubles as label text on its own subtle tint, and re-tinting one without the other silently drops the pair below AA. Bases stay literal hex because `widget-theme.ts` publishes them to MCP app guest documents where `color-mix()` would not resolve. See the audit comments in `base.css` for the per-theme measurements. + +The dark `--destructive` value is the claw-family override (`:root[data-theme="dark"]`); the shared `:root` fallback is `#ef4444`. ## Border @@ -90,4 +94,4 @@ Light mode uses a warm paper palette: ivory backgrounds, warm gray borders (`#e8 - ❌ `--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 +- ❌ `--muted-strong` for normal body text — passes AA on `--bg` but not on every hover/input surface; use `--text` instead diff --git a/ui/src/components/image-lightbox.ts b/ui/src/components/image-lightbox.ts index cb599ff4ece2..fd5558c197de 100644 --- a/ui/src/components/image-lightbox.ts +++ b/ui/src/components/image-lightbox.ts @@ -57,6 +57,9 @@ class OpenClawImageLightbox extends OpenClawLitElement { overflow: hidden; border: 1px solid color-mix(in srgb, var(--border-strong) 80%, transparent); border-radius: var(--radius-lg); + /* Deliberately darker than any theme surface: the lightbox is a + photo-viewer chrome that stays near-black in light mode too, so the + white text and white-alpha borders below assume this literal. */ background: #07090f; box-shadow: 0 28px 90px rgba(0, 0, 0, 0.6); } diff --git a/ui/src/components/panel-tab-strip.ts b/ui/src/components/panel-tab-strip.ts index 87d8dac248bb..55e495db7aaa 100644 --- a/ui/src/components/panel-tab-strip.ts +++ b/ui/src/components/panel-tab-strip.ts @@ -144,7 +144,7 @@ export const panelTabStripStyles = css` } .tabstrip-tab__icon { display: inline-flex; - color: var(--accent, #4ec9a8); + color: var(--accent, #ff5c5c); } .tabstrip-tab.is-exited .tabstrip-tab__icon { color: var(--muted, #8a919e); @@ -160,9 +160,9 @@ export const panelTabStripStyles = css` color: var(--muted, #8a919e); } .tabstrip-tab__badge { - border: 1px solid color-mix(in srgb, var(--accent, #4ec9a8) 45%, transparent); + border: 1px solid color-mix(in srgb, var(--accent, #ff5c5c) 45%, transparent); border-radius: 999px; - color: var(--accent, #4ec9a8); + color: var(--accent, #ff5c5c); font-size: 9px; line-height: 14px; padding: 0 5px; diff --git a/ui/src/components/resizable-divider.ts b/ui/src/components/resizable-divider.ts index 54c2de15505d..ef47e18c7b47 100644 --- a/ui/src/components/resizable-divider.ts +++ b/ui/src/components/resizable-divider.ts @@ -47,7 +47,7 @@ class ResizableDivider extends OpenClawLitElement { left: 50%; width: 1px; transform: translateX(-50%); - background: var(--border, #333); + background: var(--border, #1e2028); transition: background 150ms ease-out, width 150ms ease-out; @@ -56,10 +56,10 @@ class ResizableDivider extends OpenClawLitElement { :host(.dragging)::after, :host(:focus-visible)::after { width: 2px; - background: var(--accent, #007bff); + background: var(--accent, #ff5c5c); } :host(:focus-visible) { - outline: 2px solid var(--accent, #007bff); + outline: 2px solid var(--accent, #ff5c5c); outline-offset: 2px; } :host([orientation="horizontal"]) { diff --git a/ui/src/components/terminal/terminal-theme.ts b/ui/src/components/terminal/terminal-theme.ts index 2d958e59b3ae..a756432658c3 100644 --- a/ui/src/components/terminal/terminal-theme.ts +++ b/ui/src/components/terminal/terminal-theme.ts @@ -29,6 +29,10 @@ const ANSI = { brightWhite: "#ffffff", } as const; +// Dark mirrors the claw tokens in styles/base.css (`--bg` #0e1015, +// `--accent` #ff5c5c) — keep them in sync when the tokens change. Light is a +// deliberate neutral cool white: the light theme families diverge (ivory, +// cool white, parchment) and the canvas gets only a binary mode. const DYNAMIC_COLORS = { dark: { background: "#0e1015", cursor: "#ff5c5c", foreground: "#d7dae0" }, light: { background: "#f7f8fa", cursor: "#1b1e26", foreground: "#1b1e26" },