Skip to content

fix(theme): make all pages dark/light-mode compatible via design tokens#79

Merged
JayantDevkar merged 2 commits into
mainfrom
fix/theme-compat-all-pages
Jun 16, 2026
Merged

fix(theme): make all pages dark/light-mode compatible via design tokens#79
JayantDevkar merged 2 commits into
mainfrom
fix/theme-compat-all-pages

Conversation

@JayantDevkar

Copy link
Copy Markdown
Owner

Summary

App-wide audit + fix of dark/light theme compatibility across every route and component. Follow-up to #75 (which fixed /shells + /cron); this extends the same design-token discipline to the rest of the app.

The app themes via :root[data-theme='dark'] in app.css. Several pages bypassed the design tokens and therefore did not respond to the theme toggle:

  • Hardcoded light colors#fff/white/#f5f5f4, near-black text, light borders, rgba(0,0,0,…).
  • Tailwind dark: utilities (e.g. text-emerald-600 dark:text-emerald-400) — there is no dark: custom-variant configured, so these track prefers-color-scheme, not the app's data-theme toggle. They silently mismatch when the in-app theme disagrees with the OS preference.
  • color-mix(… white) — mixed toward a literal white instead of var(--bg-base), so it didn't flip.
  • Undefined "phantom" tokens--bg-surface, --bg-error, --border-error, --text-error, --red were referenced but never defined in app.css (rendered as invalid / light-only fallbacks).

What changed (30 files)

Converted surfaces/text/borders to var(--bg-*) / var(--text-*) / var(--border) and semantic accents to var(--success|accent|info|warning|error) (or rgba(var(--*-rgb), a) so they flip). Replaced phantom tokens with real ones. Areas touched: settings, about, archived, hooks (+ scripts/event), commands, skills, plugins, tools, conversation, timeline, and shared components (TokenSearchInput, Switch, SegmentedControl, EmptyState, LiveSessions*, PluginCard, Archived*, ToolCallDetail, …).

Intentionally left as literals: Chart.js data-series colors (charts can't read CSS vars at render), SVG brand/logo marks, and accent-on-accent button text (text-white on var(--accent) — valid contrast both themes).

Verification

  • Runtime audit in BOTH themes (Playwright contrast scanner) across /, /settings, /skills/[name], /archived, plus /shells + /cron: no light-surface-in-dark, no dark-surface-in-light, no new low-contrast text.
  • npm run check: 0 errors (15 pre-existing warnings).
  • Fixed one duplicate-style= attribute introduced during the swap (skills invocation bars).

Known / out of scope (pre-existing, not introduced here)

  • --text-faint on raised cards is ~2.2:1 in dark mode app-wide (also on /sessions) — a design-token a11y characteristic; candidate for a separate one-line token tweak.
  • dark:prose-invert (markdown prose) still tracks prefers-color-scheme rather than the toggle — would need a Tailwind @custom-variant dark mapped to data-theme to fully resolve.

🤖 Generated with Claude Code

Audited every route + component for theme compatibility. Many pages used
hardcoded light colors, Tailwind dark: utilities (which track
prefers-color-scheme, not the app's data-theme toggle), color-mix(… white),
and several UNDEFINED 'phantom' tokens (--bg-surface, --bg-error, --border-error,
--text-error, --red) — none of which respond to :root[data-theme='dark'].

Converted surfaces/text/borders to var(--bg-*)/var(--text-*)/var(--border) and
semantic accents to var(--success|accent|info|warning|error) (or
rgba(var(--*-rgb), a)) across 30 files: settings, about, archived, hooks,
commands, skills, plugins, tools, conversation, timeline, and shared components
(TokenSearchInput, Switch, SegmentedControl, EmptyState, LiveSessions*, etc.).
Replaced phantom tokens with real ones; merged a duplicate style= attribute on
the skills page. Chart.js data-series colors and SVG brand marks left as literals.

Verified at runtime in BOTH themes across 6+ representative pages: no
light-surface-in-dark, no dark-surface-in-light, no new low-contrast text.
npm run check: 0 errors.

Pre-existing (not changed): --text-faint on raised cards is ~2.2:1 in dark mode
app-wide (also on /sessions); candidate for a separate token a11y tweak.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@JayantDevkar JayantDevkar force-pushed the fix/theme-compat-all-pages branch from 0629683 to 3ec8308 Compare June 16, 2026 22:08
Review of the dark/light token pass surfaced two regressions:

- hooks/scripts/[filename]: the no-highlight fallback <pre> was changed
  to text-[var(--text-primary)] while its container stays a hardcoded
  dark #0d1117 (the Shiki block is force-dark in both themes). In light
  mode --text-primary is near-black -> invisible code. Restored the
  fixed light literal (#e6edf3) so the always-dark block reads in both
  themes (verified 16:1 contrast).

- skills/[skill_name]: the "Mentioned (not invoked)" bar segment and
  legend dots used var(--border-hover) (a 12-15% alpha border token) as
  a content fill, washing them out beside the solid category colors.
  Switched to var(--text-muted), a solid muted gray matching the
  original gray-500/50 intent, clearly visible in both themes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@JayantDevkar JayantDevkar merged commit 0e54db5 into main Jun 16, 2026
7 checks passed
@JayantDevkar JayantDevkar deleted the fix/theme-compat-all-pages branch June 16, 2026 23:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant