Comprehensive Rules for implementing accessible, token-driven theming in TypeScript/React projects using CSS-in-JS or utility-first workflows.
Ever find yourself hunting through hundreds of components to update a single color? Or discovering your "dark mode" fails accessibility standards after launch? You're not alone. Most React apps suffer from scattered styling decisions that make theming a nightmare.
Here's what happens without proper theming architecture:
#3B82F6 appears in 47 different files)These Cursor Rules implement a production-ready theming system that scales from startups to enterprise. Instead of managing colors, spacing, and typography in dozens of files, you maintain a single source of truth through design tokens.
Here's what changes:
Before:
const Button = styled.button`
background: #3b82f6;
color: #ffffff;
padding: 12px 24px;
border-radius: 8px;
`;
After:
const Button = styled.button`
background: ${({ theme }) => theme.color.primary[500]};
color: ${({ theme }) => theme.color.surface[50]};
padding: ${({ theme }) => theme.spacing[3]} ${({ theme }) => theme.spacing[6]};
border-radius: ${({ theme }) => theme.radius[2]};
`;
theme.color.primary. shows all available scalesprefers-color-scheme and prefers-contrast automaticallyYou're building a white-label SaaS with different color schemes per client:
// tokens/brands/client-a.json
{
"color": {
"primary": { "500": "#3b82f6" },
"surface": { "50": "#f8fafc" }
}
}
// Auto-generated theme switching
const theme = useBrandTheme(clientId); // Handles token resolution
Result: Deploy client themes in minutes, not days.
Your design team creates a new color palette, but you need WCAG AA compliance:
# Automated in CI
npm run validate-contrast
✅ color.primary.500 on surface.50: 4.8:1 (AA compliant)
❌ color.warning.300 on surface.100: 2.1:1 (Fails AA)
Result: Catch accessibility issues before they reach production.
You're maintaining React web app, React Native mobile, and Flutter admin panel:
// Same tokens, different outputs
// tokens/core.json → web/theme.ts
// tokens/core.json → mobile/theme.dart
// tokens/core.json → admin/theme.scss
Result: Perfect visual consistency across all platforms.
# Install dependencies
npm install @tokens-studio/sd-transforms style-dictionary
# Create token structure
mkdir -p tokens/core
mkdir -p src/theme/generated
// scripts/build-tokens.js
const StyleDictionary = require('style-dictionary');
StyleDictionary.registerTransform({
name: 'typescript/design-tokens',
type: 'value',
matcher: () => true,
transformer: (token) => `'${token.value}'`
});
module.exports = {
source: ['tokens/**/*.json'],
platforms: {
ts: {
transformGroup: 'js',
buildPath: 'src/theme/generated/',
files: [{
destination: 'tokens.ts',
format: 'typescript/es6-declarations'
}]
}
}
};
// src/theme/light.ts
import { tokens } from './generated/tokens';
export const lightTheme = {
color: {
primary: tokens.color.blue,
surface: tokens.color.gray,
text: tokens.color.gray[900]
},
spacing: tokens.spacing,
radius: tokens.radius
} as const;
// src/hooks/useThemeMode.ts
export const useThemeMode = () => {
const [mode, setMode] = useState<ThemeMode>(() => {
const systemPreference = window.matchMedia('(prefers-color-scheme: dark)').matches
? 'dark' : 'light';
return (localStorage.getItem('theme-mode') as ThemeMode) || systemPreference;
});
useEffect(() => {
document.documentElement.setAttribute('data-theme', mode);
localStorage.setItem('theme-mode', mode);
}, [mode]);
return { mode, setMode };
};
// Components automatically adapt to theme changes
const ProductCard = styled.div`
background: ${({ theme }) => theme.color.surface[50]};
border: 1px solid ${({ theme }) => theme.color.surface[200]};
border-radius: ${({ theme }) => theme.radius[2]};
padding: ${({ theme }) => theme.spacing[4]};
`;
These Cursor Rules don't just organize your styles—they transform how your team thinks about design consistency, accessibility, and maintainability. You'll wonder how you ever shipped themes without them.
Ready to implement bulletproof theming in your React app? These rules handle the complexity so you can focus on building great user experiences.
You are an expert in React, TypeScript, CSS Variables, Design Tokens, styled-components, Emotion, Material UI (MUI), Chakra UI, Tailwind CSS, and Stitches.
Technology Stack Declaration
- Primary language: TypeScript (strict mode).
- Styling: CSS Custom Properties, CSS-in-JS (styled-components/Emotion), or utility-first (Tailwind).
- Design-token pipeline: Style Dictionary → generated .ts + .css files.
- Tooling: Figma Tokens, ESLint, Stylelint, Prettier, Jest, Testing Library, Axe-core.
Key Principles
- Single source of truth: maintain **design tokens** (`color.primary.100`, `spacing.4`) in platform-agnostic JSON.
- All visual values must resolve from tokens or CSS variables—never hard-code hex, px, or rem.
- Themes must be **accessible by default** (WCAG 2.1 AA contrast) and respect `prefers-color-scheme` & `prefers-contrast` media queries.
- Architect for **multiple color modes** (light, dark, high-contrast) and **density/spacing modes** (comfortable, compact).
- Stateless components: accept `className` / `style` props; never call `window.matchMedia` inside components (use hook).
- Always export tokens, helpers, and themes as typed modules to enable IntelliSense and avoid typos.
Language-Specific Rules (TypeScript)
- Token naming: `category.type.level` (e.g., `color.surface.900`). Lower-snake-case JSON, `camelCase` JS identifiers.
- Store type-safe token map:
```ts
export interface ColorScale { 50:string;100:string;200:string; ... }
export interface ThemeTokens { color:{ primary:ColorScale; surface:ColorScale }; spacing:number[]; radius:number[] }
```
- Provide `as const` assertions when generating token objects to preserve literal types.
- Use `enum ThemeMode { light = 'light', dark = 'dark', highContrast = 'high-contrast' }`.
- Avoid `any`; use utility types (`keyof`, `Record<K,V>`) to map token keys to CSS variables.
- Co-locate per-component style helpers in `*.theme.ts` next to component.
Error Handling and Validation
- Validate contrast programmatically during CI:
- Run `@wcag/contrast` on every token pair referenced in themes.
- Fail build if contrast < 4.5:1 for normal text or <3:1 for large text.
- Inside theme switch hook (`useThemeMode`), guard against unsupported mode, throw descriptive `ThemeModeError`.
- During runtime, fallback to `defaultTheme` if token lookup fails; emit `console.error('[Theme] Missing token:', tokenPath)` only in development.
Framework-Specific Rules
React + styled-components / Emotion
- Wrap app with `<ThemeProvider theme={activeTheme}>` at root only.
- Compose themes via deep merge: `merge(baseTokens, lightOverrides)`.
- Use helper: `const surface = ({ theme }) => theme.color.surface[100];`
- Prefer **function interpolations** over template-string escapes for better tree-shaking.
- Never nest >3 styled components; extract new component.
Material UI (MUI)
- Create theme with `createTheme({ palette:{ mode:'dark', ... }, spacing: token.spacing })`.
- Extend `PaletteOptions` via module augmentation for custom scales.
- Keep `ThemeProvider` precedence: MUI outermost, styled-components inner so that `sx` prop wins.
Chakra UI
- Store tokens in `extendTheme({ colors:{ brand: token.color.primary }, config:{ initialColorMode:'system' }})`.
- Use `useColorModeValue(light, dark)` for inline overrides; avoid duplication by mapping to tokens.
Tailwind CSS (+ Twin.macro)
- Generate `tailwind.config.js` from token JSON via script; do **not** edit by hand.
- Theme switching: compile only one Tailwind build; change scheme by toggling `data-theme` attr and rely on `theme('colors.primary.500')` in config.
Additional Sections
Testing
- Unit test token integrity with Jest snapshot: `expect(tokens).toMatchSnapshot()`.
- Component tests: render both light & dark modes; use `axe` to assert zero violations.
Performance Optimization
- Use CSS variables to switch themes; avoid DOM repaint by grouping changes in a single `documentElement.style.cssText = ...`.
- Pre-render theme attribute on `<html>` server-side to prevent FOUC.
- Lazy-load heavy theme assets (e.g., charts color palettes) with dynamic `import()`.
Accessibility
- Expose theme toggle with keyboard-navigable button (`role="switch"`, `aria-checked`).
- Provide high-contrast mode meeting WCAG AAA (7:1) for critical text.
Documentation
- Autogenerate `THEME.md` from token JSON describing each token with usage examples.
- Include Figma link + CSS variable reference table.
Directory Conventions
- `tokens/` — raw JSON design tokens.
- `scripts/build-tokens.ts` — transforms → `src/theme/generated/`.
- `src/theme/{light,dark,high-contrast}.ts` — merge tokens into theme objects.
- `src/components/<feature>/` — UI using hooks `useToken('color.surface.100')`.
- `__tests__/theme/` — contrast & regression tests.
Hook Example (happy path last)
```ts
export const useThemeMode = () => {
const [mode, setMode] = useState<ThemeMode>(() => {
const pref = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
return (localStorage.getItem('theme-mode') as ThemeMode) || pref;
});
const toggle = () => setMode(prev => (prev === 'dark' ? 'light' : 'dark'));
// side-effects last (happy path)
useEffect(() => {
if (!Object.values(ThemeMode).includes(mode)) throw new ThemeModeError(mode);
document.documentElement.setAttribute('data-theme', mode);
localStorage.setItem('theme-mode', mode);
}, [mode]);
return { mode, toggle };
};
```