Description
Stellar Explain is currently hardcoded dark. This issue adds a proper theme system with a toggle button in the AppShell header, respecting the user's system preference by default and persisting their choice in localStorage.
This is non-trivial — it requires converting all hardcoded color values to CSS custom properties and ensuring both themes look polished.
Design Tokens
Define all colors as CSS custom properties on :root and [data-theme="light"]:
/* Dark theme (default) */
:root {
--bg-base: #080c12;
--bg-card: rgba(255,255,255,0.04);
--bg-card-hover: rgba(255,255,255,0.06);
--border-subtle: rgba(255,255,255,0.08);
--border-accent: rgba(56,189,248,0.3);
--text-primary: rgba(255,255,255,0.90);
--text-secondary: rgba(255,255,255,0.50);
--text-muted: rgba(255,255,255,0.25);
--text-mono: rgba(255,255,255,0.40);
--accent-sky: #38bdf8;
--accent-sky-dim: rgba(56,189,248,0.1);
--accent-purple: #a78bfa;
--accent-purple-dim: rgba(167,139,250,0.1);
--pill-success-bg: rgba(34,197,94,0.12);
--pill-success-text: #86efac;
--pill-fail-bg: rgba(239,68,68,0.12);
--pill-fail-text: #fca5a5;
}
/* Light theme */
[data-theme="light"] {
--bg-base: #f0f4f8;
--bg-card: rgba(0,0,0,0.03);
--bg-card-hover: rgba(0,0,0,0.05);
--border-subtle: rgba(0,0,0,0.08);
--border-accent: rgba(2,132,199,0.3);
--text-primary: rgba(0,0,0,0.90);
--text-secondary: rgba(0,0,0,0.55);
--text-muted: rgba(0,0,0,0.30);
--text-mono: rgba(0,0,0,0.45);
--accent-sky: #0284c7;
--accent-sky-dim: rgba(2,132,199,0.08);
--accent-purple: #7c3aed;
--accent-purple-dim: rgba(124,58,237,0.08);
--pill-success-bg: rgba(22,163,74,0.10);
--pill-success-text: #15803d;
--pill-fail-bg: rgba(220,38,38,0.10);
--pill-fail-text: #b91c1c;
}
What To Build
src/hooks/useTheme.ts
export function useTheme(): {
theme: 'dark' | 'light';
toggleTheme: () => void;
setTheme: (theme: 'dark' | 'light') => void;
}
- Reads
localStorage.getItem('stellar-explain-theme') on mount
- Falls back to
window.matchMedia('(prefers-color-scheme: dark)')
- Sets
document.documentElement.setAttribute('data-theme', theme) on change
- Persists to localStorage on change
src/components/ThemeToggle.tsx
A small icon button in the AppShell header:
- 🌙 moon icon in dark mode → click to switch to light
- ☀️ sun icon in light mode → click to switch to dark
- Smooth transition on switch (CSS
transition: background 0.2s ease)
Update src/app/globals.css
Define all CSS custom properties as specified above.
Update all components
Replace all hardcoded color values with CSS variables:
src/components/Card.tsx
src/components/Pill.tsx
src/components/Label.tsx
src/components/AddressChip.tsx
src/components/AppShell.tsx
src/components/TransactionResult.tsx
src/components/AccountResult.tsx
src/components/SearchBar.tsx
src/components/TabSwitcher.tsx
Integration
Add useTheme to AppShell.tsx and render <ThemeToggle /> in the header between the logo and the other action buttons.
Add to src/app/layout.tsx:
<script
dangerouslySetInnerHTML={{
__html: `
(function() {
var t = localStorage.getItem('stellar-explain-theme');
if (!t) t = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
document.documentElement.setAttribute('data-theme', t);
})();
`
}}
/>
This prevents flash of wrong theme on page load.
Key Files
New files:
src/hooks/useTheme.ts
src/components/ThemeToggle.tsx
Modified files:
src/app/globals.css
src/app/layout.tsx
src/components/AppShell.tsx
src/components/Card.tsx
src/components/Pill.tsx
src/components/Label.tsx
src/components/AddressChip.tsx
src/components/TransactionResult.tsx
src/components/AccountResult.tsx
src/components/SearchBar.tsx
src/components/TabSwitcher.tsx
Acceptance Criteria
Complexity: High · 200 pts
Description
Stellar Explain is currently hardcoded dark. This issue adds a proper theme system with a toggle button in the AppShell header, respecting the user's system preference by default and persisting their choice in localStorage.
This is non-trivial — it requires converting all hardcoded color values to CSS custom properties and ensuring both themes look polished.
Design Tokens
Define all colors as CSS custom properties on
:rootand[data-theme="light"]:What To Build
src/hooks/useTheme.tslocalStorage.getItem('stellar-explain-theme')on mountwindow.matchMedia('(prefers-color-scheme: dark)')document.documentElement.setAttribute('data-theme', theme)on changesrc/components/ThemeToggle.tsxA small icon button in the AppShell header:
transition: background 0.2s ease)Update
src/app/globals.cssDefine all CSS custom properties as specified above.
Update all components
Replace all hardcoded color values with CSS variables:
src/components/Card.tsxsrc/components/Pill.tsxsrc/components/Label.tsxsrc/components/AddressChip.tsxsrc/components/AppShell.tsxsrc/components/TransactionResult.tsxsrc/components/AccountResult.tsxsrc/components/SearchBar.tsxsrc/components/TabSwitcher.tsxIntegration
Add
useThemetoAppShell.tsxand render<ThemeToggle />in the header between the logo and the other action buttons.Add to
src/app/layout.tsx:This prevents flash of wrong theme on page load.
Key Files
New files:
src/hooks/useTheme.tssrc/components/ThemeToggle.tsxModified files:
src/app/globals.csssrc/app/layout.tsxsrc/components/AppShell.tsxsrc/components/Card.tsxsrc/components/Pill.tsxsrc/components/Label.tsxsrc/components/AddressChip.tsxsrc/components/TransactionResult.tsxsrc/components/AccountResult.tsxsrc/components/SearchBar.tsxsrc/components/TabSwitcher.tsxAcceptance Criteria
Complexity: High · 200 pts