xiaohongshu
xiaohongshu
dark-mode
Implements theme switching with semantic tokens and prefers-color-scheme detection. Use when adding dark mode, light/dark themes, color scheme toggles, or converting hardcoded colors to theme-aware tokens.
Full skill instructions
Implement robust dark mode using semantic color tokens, CSS custom properties, and system preference detection. Creates a theme system that's maintainable, accessible, and works across frameworks.
| Layer | Purpose | Example |
|---|---|---|
| Primitive tokens | Raw color values | --color-blue-500: #3b82f6 |
| Semantic tokens | Purpose-based aliases | --color-primary: var(--color-blue-500) |
| Theme tokens | Mode-specific values | --color-bg: var(--color-gray-50) in light |
| Component tokens | Component-specific | --button-bg: var(--color-primary) |
prefers-color-schemeCopy this checklist and track progress:
Dark Mode Implementation:
- [ ] Audit colors in codebase (grep for hex, rgb, hsl, color names)
- [ ] Create semantic token layer (bg, text, border, surface, interactive, status)
- [ ] Define light and dark theme values for all semantic tokens
- [ ] Implement switching mechanism (data-theme attribute recommended)
- [ ] Add system preference detection (prefers-color-scheme)
- [ ] Add inline script in <head> to prevent flash of wrong theme
- [ ] Build toggle component with localStorage persistence
- [ ] Handle edge cases (images, shadows, third-party components, form inputs)
- [ ] Test contrast ratios meet WCAG AA in both themes
:root {
/* Gray scale */
--color-gray-50: #f9fafb;
--color-gray-100: #f3f4f6;
--color-gray-200: #e5e7eb;
--color-gray-300: #d1d5db;
--color-gray-400: #9ca3af;
--color-gray-500: #6b7280;
--color-gray-600: #4b5563;
--color-gray-700: #374151;
--color-gray-800: #1f2937;
--color-gray-900: #111827;
--color-gray-950: #030712;
/* Brand colors */
--color-blue-500: #3b82f6;
--color-blue-600: #2563eb;
--color-blue-400: #60a5fa;
/* Semantic colors */
--color-green-500: #22c55e;
--color-red-500: #ef4444;
--color-amber-500: #f59e0b;
}
/* Light theme (default) */
:root,
[data-theme="light"] {
/* Backgrounds */
--color-bg-primary: var(--color-gray-50);
--color-bg-secondary: var(--color-gray-100);
--color-bg-tertiary: var(--color-gray-200);
--color-bg-inverse: var(--color-gray-900);
/* Surfaces (cards, modals, dropdowns) */
--color-surface-primary: #ffffff;
--color-surface-secondary: var(--color-gray-50);
--color-surface-elevated: #ffffff;
/* Text */
--color-text-primary: var(--color-gray-900);
--color-text-secondary: var(--color-gray-600);
--color-text-tertiary: var(--color-gray-500);
--color-text-inverse: var(--color-gray-50);
--color-text-disabled: var(--color-gray-400);
/* Borders */
--color-border-primary: var(--color-gray-200);
--color-border-secondary: var(--color-gray-300);
--color-border-focus: var(--color-blue-500);
/* Interactive */
--color-interactive-primary: var(--color-blue-500);
--color-interactive-primary-hover: var(--color-blue-600);
--color-interactive-secondary: var(--color-gray-100);
--color-interactive-secondary-hover: var(--color-gray-200);
/* Status */
--color-status-success: var(--color-green-500);
--color-status-warning: var(--color-amber-500);
--color-status-error: var(--color-red-500);
--color-status-info: var(--color-blue-500);
/* Shadows - more visible in light mode */
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
/* Overlay */
--color-overlay: rgb(0 0 0 / 0.5);
}
/* Dark theme */
[data-theme="dark"] {
/* Backgrounds */
--color-bg-primary: var(--color-gray-950);
--color-bg-secondary: var(--color-gray-900);
--color-bg-tertiary: var(--color-gray-800);
--color-bg-inverse: var(--color-gray-50);
/* Surfaces - slightly elevated from background */
--color-surface-primary: var(--color-gray-900);
--color-surface-secondary: var(--color-gray-800);
--color-surface-elevated: var(--color-gray-800);
/* Text */
--color-text-primary: var(--color-gray-50);
--color-text-secondary: var(--color-gray-400);
--color-text-tertiary: var(--color-gray-500);
--color-text-inverse: var(--color-gray-900);
--color-text-disabled: var(--color-gray-600);
/* Borders - less contrast in dark mode */
--color-border-primary: var(--color-gray-800);
--color-border-secondary: var(--color-gray-700);
--color-border-focus: var(--color-blue-400);
/* Interactive - lighter shades for dark bg */
--color-interactive-primary: var(--color-blue-500);
--color-interactive-primary-hover: var(--color-blue-400);
--color-interactive-secondary: var(--color-gray-800);
--color-interactive-secondary-hover: var(--color-gray-700);
/* Status - same or slightly adjusted */
--color-status-success: var(--color-green-500);
--color-status-warning: var(--color-amber-500);
--color-status-error: var(--color-red-500);
--color-status-info: var(--color-blue-400);
/* Shadows - less visible, add subtle glow */
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.3);
--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.4), 0 2px 4px -2px rgb(0 0 0 / 0.3);
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.5), 0 4px 6px -4px rgb(0 0 0 / 0.4);
/* Overlay */
--color-overlay: rgb(0 0 0 / 0.7);
}
<html data-theme="light">
[data-theme="light"] { /* light tokens */ }
[data-theme="dark"] { /* dark tokens */ }
Pros: Explicit, debuggable, works with SSR Cons: Requires JavaScript to toggle
<html class="dark">
:root { /* light tokens */ }
.dark { /* dark tokens */ }
Pros: Simple, Tailwind-compatible Cons: Can conflict with other classes
:root { /* light tokens */ }
@media (prefers-color-scheme: dark) {
:root { /* dark tokens */ }
}
Pros: Zero JavaScript, respects system Cons: No user override
/* Default: follow system */
:root { /* light tokens */ }
@media (prefers-color-scheme: dark) {
:root:not([data-theme="light"]) { /* dark tokens */ }
}
/* Manual overrides */
[data-theme="light"] { /* light tokens */ }
[data-theme="dark"] { /* dark tokens */ }
// theme.js
const STORAGE_KEY = 'theme';
function getSystemTheme() {
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
}
function getStoredTheme() {
return localStorage.getItem(STORAGE_KEY);
}
function setTheme(theme) {
const resolvedTheme = theme === 'system' ? getSystemTheme() : theme;
document.documentElement.dataset.theme = resolvedTheme;
localStorage.setItem(STORAGE_KEY, theme);
}
function initTheme() {
const stored = getStoredTheme();
const theme = stored || 'system';
setTheme(theme);
// Listen for system changes
window.matchMedia('(prefers-color-scheme: dark)')
.addEventListener('change', (e) => {
if (getStoredTheme() === 'system' || !getStoredTheme()) {
document.documentElement.dataset.theme = e.matches ? 'dark' : 'light';
}
});
}
// Initialize on load
initTheme();
// Export for toggle button
window.toggleTheme = () => {
const current = document.documentElement.dataset.theme;
setTheme(current === 'dark' ? 'light' : 'dark');
};
Add inline script in <head> before CSS:
<head>
<script>
(function() {
const stored = localStorage.getItem('theme');
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
const theme = stored || (prefersDark ? 'dark' : 'light');
document.documentElement.dataset.theme = theme;
})();
</script>
<link rel="stylesheet" href="styles.css">
</head>
// ThemeProvider.tsx
import { createContext, useContext, useEffect, useState } from 'react';
type Theme = 'light' | 'dark' | 'system';
interface ThemeContextValue {
theme: Theme;
resolvedTheme: 'light' | 'dark';
setTheme: (theme: Theme) => void;
}
const ThemeContext = createContext<ThemeContextValue | null>(null);
export function ThemeProvider({ children }: { children: React.ReactNode }) {
const [theme, setThemeState] = useState<Theme>('system');
const [resolvedTheme, setResolvedTheme] = useState<'light' | 'dark'>('light');
useEffect(() => {
const stored = localStorage.getItem('theme') as Theme | null;
if (stored) setThemeState(stored);
}, []);
useEffect(() => {
const root = document.documentElement;
const systemDark = window.matchMedia('(prefers-color-scheme: dark)');
const updateTheme = () => {
const resolved = theme === 'system'
? (systemDark.matches ? 'dark' : 'light')
: theme;
setResolvedTheme(resolved);
root.dataset.theme = resolved;
};
updateTheme();
systemDark.addEventListener('change', updateTheme);
return () => systemDark.removeEventListener('change', updateTheme);
}, [theme]);
const setTheme = (newTheme: Theme) => {
setThemeState(newTheme);
localStorage.setItem('theme', newTheme);
};
return (
<ThemeContext.Provider value={{ theme, resolvedTheme, setTheme }}>
{children}
</ThemeContext.Provider>
);
}
export const useTheme = () => {
const context = useContext(ThemeContext);
if (!context) throw new Error('useTheme must be used within ThemeProvider');
return context;
};
// ThemeToggle.tsx
import { useTheme } from './ThemeProvider';
export function ThemeToggle() {
const { theme, setTheme } = useTheme();
return (
<button
onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}
aria-label={`Switch to ${theme === 'dark' ? 'light' : 'dark'} mode`}
>
{theme === 'dark' ? '☀️' : '🌙'}
</button>
);
}
tailwind.config.js:
module.exports = {
darkMode: ['selector', '[data-theme="dark"]'],
// or: darkMode: 'class', // uses .dark class
theme: {
extend: {
colors: {
// Map to CSS variables
bg: {
primary: 'var(--color-bg-primary)',
secondary: 'var(--color-bg-secondary)',
},
text: {
primary: 'var(--color-text-primary)',
secondary: 'var(--color-text-secondary)',
},
},
},
},
};
Usage:
<!-- Using CSS variable colors -->
<div class="bg-bg-primary text-text-primary">
Content
</div>
<!-- Or using Tailwind's dark: variant -->
<div class="bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-50">
Content
</div>
/* Dim images in dark mode */
[data-theme="dark"] img:not([data-theme-safe]) {
filter: brightness(0.9);
}
/* Invert diagrams/illustrations */
[data-theme="dark"] img[data-invert] {
filter: invert(1) hue-rotate(180deg);
}
/* Provide alternate images */
<picture>
<source srcset="dark-logo.svg" media="(prefers-color-scheme: dark)">
<img src="light-logo.svg" alt="Logo">
</picture>
/* Icons that use currentColor work automatically */
.icon {
color: var(--color-text-primary);
}
/* Filled icons may need explicit colors */
[data-theme="dark"] .icon-filled {
fill: var(--color-text-primary);
}
/* Override third-party styles */
[data-theme="dark"] .third-party-component {
--third-party-bg: var(--color-surface-primary);
--third-party-text: var(--color-text-primary);
}
/* Light theme syntax */
:root {
--code-bg: var(--color-gray-100);
--code-text: var(--color-gray-800);
--code-keyword: #d73a49;
--code-string: #22863a;
}
/* Dark theme syntax */
[data-theme="dark"] {
--code-bg: var(--color-gray-900);
--code-text: var(--color-gray-100);
--code-keyword: #ff7b72;
--code-string: #a5d6ff;
}
/* Ensure form elements follow theme */
input, textarea, select {
background-color: var(--color-surface-primary);
color: var(--color-text-primary);
border-color: var(--color-border-primary);
}
/* Autofill override */
input:-webkit-autofill {
-webkit-box-shadow: 0 0 0 1000px var(--color-surface-primary) inset;
-webkit-text-fill-color: var(--color-text-primary);
}
prefers-color-scheme by default/* Smooth theme transition (respect motion preferences) */
@media (prefers-reduced-motion: no-preference) {
:root {
transition: background-color 200ms ease, color 200ms ease;
}
}
| Mistake | Fix |
|---|---|
| Hardcoding colors | Use CSS variables everywhere |
| Only theming backgrounds | Theme text, borders, shadows too |
| Forgetting scrollbars | Add color-scheme: light dark |
| Ignoring system preference | Use prefers-color-scheme as default |
| Flash on load | Inline script in <head> |
| Not testing contrast | Run accessibility audit on both themes |
| Over-transitioning | Only transition color properties |
xiaohongshu
technical spec
product ux expert
database patterns
Conduct multi-agent task orchestration and workflow coordination.
Initialize project with Conductor artifacts (product definition,
Expert in web animations, transitions, and motion design using Framer Motion and CSS
Creates Mermaid and ASCII diagrams for flowcharts, architecture, ERDs, state machines, mindmaps, and more. Use when user mentions diagram, flowchart, mermaid, ASCII diagram, text diagram, terminal diagram, visualize, C4, mindmap, architecture diagram, sequence diagram, ERD, or needs visual docume...
PostgreSQL bindings for H3 hexagonal grid system. Use when working with H3 cells in Postgres, including spatial indexing, geometry/geography integration, and raster analysis.
Context-Driven Development skill for projects using Conductor. Use this skill when you detect a `conductor/` directory in the project, when working on tasks defined in a `plan.md` file, or when the user asks about tracks, specs, or plans. Automatically applies TDD workflow, tracks task completion...
Display project status, active tracks, and next actions
Official Stakpak application containerization standard operating procedure, a step-by-step guidline to properly dockerize applications. This is a rule book curated by the Stakpak Team.
Generate, edit, and beat-sync AI video with leading models in one workspace.
The world's fastest calendar for remote work
Transform Your Design with AI Designer by ImgCreator.ai
Revolutionizing Video Production with AI-Powered Creativity
Extend an image past the frame and let AI fill the new aspect ratio.
Discover your celebrity doppelgänger with StarByFace!
ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.
Opus.ai: Revolutionize Your Web Experience