CSS-in-JS (Styled Components & Emotion)
Build themed component libraries with CSS-in-JS: styled-components patterns, Emotion, dynamic styling, and SSR considerations.
CSS-in-JS libraries like styled-components and Emotion co-locate styles with components in JavaScript, enabling dynamic styling based on props and theming via context. They are popular in design systems but have runtime overhead that makes them unsuitable for some performance-critical contexts.
CSS-in-JS is best for design systems needing dynamic theming and prop-based styling. Use Emotion in RSC-compatible setups. For RSC/Next.js app router, prefer CSS Modules or Tailwind - CSS-in-JS with runtime injection doesn't work in React Server Components.
Workflow
- Create styled componentsStyled components co-locate styles and components, making it easy to see what styles apply to what.
- Implement theming with ThemeProviderThemeProvider makes design tokens available to all nested components via the theme prop.
- Use Emotion for RSC-compatible CSS-in-JSEmotion has more flexible extraction options and works in more environments than styled-components.
- Handle dynamic and computed stylesCSS-in-JS shines for styles that depend on runtime data (user preferences, component state).
- Optimize SSR with style extractionWithout SSR style extraction, the client sees unstyled content (FOUC) until JS hydrates.
Output Format
Styled components with prop-based styling, ThemeProvider with typed theme, Emotion for Next.js App Router, SSR style extraction, keyframe animations, CSS mixins as tagged template literals.
Examples
Typed theme: `declare module "styled-components" { export interface DefaultTheme { colors: { primary: string; secondary: string; background: string }; radii: { sm: string; md: string; lg: string } } }; const Button = styled.button`background: ${p => p.theme.colors.primary}; border-radius: ${p => p.theme.radii.md};`Responsive mixin: `const respondTo = (bp: "sm"|"md"|"lg") => { const sizes = { sm: "640px", md: "768px", lg: "1024px" }; return `@media (min-width: ${sizes[bp]})` }; const Hero = styled.div`font-size: 1.5rem; ${respondTo("md")} { font-size: 2rem; } ${respondTo("lg")} { font-size: 3rem; }`Conditional animation: `const pulse = keyframes`0%, 100% { opacity: 1; } 50% { opacity: 0.5; }`;; const StatusDot = styled.span`animation: ${props => props.active ? css`${pulse} 2s ease infinite` : "none"};`Edge Cases & Gotchas
- React Server Components: CSS-in-JS with runtime style injection (styled-components, Emotion with css prop) cannot run in RSCs - they require a client context. Use CSS Modules or `style` prop for RSC-compatible styling
- Test environments: configure `StyleSheetTestUtils.suppressStyleInjection()` in Jest setup to prevent styled-components from injecting styles in tests
- Specificity: CSS-in-JS generates unique class names with high specificity; this makes global overrides harder but eliminates most specificity bugs
When to Break These Rules
- For performance-critical apps (Core Web Vitals focus), CSS-in-JS runtime overhead (style parsing, CSS generation, class injection) impacts FCP/LCP. Use CSS Modules or Tailwind instead.
Reference Files
styled-components: https://styled-components.com/docsEmotion: https://emotion.sh/docs/introductionstyled-components SSR: https://styled-components.com/docs/advanced#server-side-renderingVanilla Extract (static CSS-in-JS): https://vanilla-extract.style/