Table of Contents
- What is CSS-in-JS?
- Why Use CSS-in-JS in React?
- Popular CSS-in-JS Libraries
- Getting Started with styled-components
- Advanced Customization Techniques
- Performance Considerations
- Comparison with Other Styling Methods
- Conclusion
- References
What is CSS-in-JS?
CSS-in-JS is a styling approach where CSS is written directly in JavaScript files (typically alongside React components). Instead of maintaining separate .css or .scss files, styles are defined using JavaScript syntax, often via tagged template literals or objects. These styles are then injected into the DOM at runtime (or compiled to static CSS at build time, depending on the library) and are automatically scoped to prevent global conflicts.
At its core, CSS-in-JS bridges the gap between component logic and styling, enabling dynamic, props-driven styles while keeping code organized. It’s not a single library but a concept—with multiple implementations tailored to different needs (e.g., runtime flexibility, performance, or zero-overhead).
Why Use CSS-in-JS in React?
React’s component-based architecture thrives on encapsulation, and CSS-in-JS aligns perfectly with this philosophy. Here are key reasons to adopt it:
1. Scoped Styles by Default
Traditional CSS suffers from global scope, where styles leak across components and cause unintended side effects (e.g., a .button class in one component overriding another). CSS-in-JS generates unique class names for each component, ensuring styles are scoped and isolated. No more worrying about naming collisions!
2. Dynamic Styling with Props
React components often need styles that adapt to props (e.g., a button changing color when disabled or primary). CSS-in-JS makes this trivial: you can directly reference component props in your styles, enabling logic-driven design without clunky class toggling.
3. Colocation of Concerns
With CSS-in-JS, styles live alongside the components they style. This makes your codebase easier to navigate: when you edit a component, you don’t need to switch between .jsx and .css files. It also improves maintainability—removing a component automatically removes its styles.
4. Theming and Dark Modes
Theming (e.g., light/dark modes, brand-specific color schemes) is simplified with CSS-in-JS. Most libraries include built-in theming APIs (e.g., ThemeProvider in styled-components) that let you pass a theme object and access it across all styled components.
5. Server-Side Rendering (SSR) Compatibility
Many CSS-in-JS libraries (e.g., styled-components, Emotion) support SSR out of the box, preventing Flash of Unstyled Content (FOUC). Styles are injected into the server-rendered HTML, ensuring consistent rendering on initial load.
Popular CSS-in-JS Libraries
The CSS-in-JS ecosystem is rich, with libraries optimized for different use cases. Here are the most popular options:
1. styled-components
The most widely adopted CSS-in-JS library, styled-components uses tagged template literals to create reusable, styled components. It’s known for its developer experience, robust theming, and seamless React integration.
- Use Case: General-purpose apps, component libraries, dynamic UIs.
- Key Features: Props-driven styling, nesting, theme support, SSR.
2. Emotion
A flexible, high-performance library that supports both template literals and object styles. Emotion is lighter than styled-components and offers granular control over runtime behavior.
- Use Case: Performance-sensitive apps, teams preferring object syntax.
- Key Features: Zero-runtime mode (via
@emotion/css), CSS-in-JS-in-CSS (with@emotion/styled), fast hydration.
3. Styled JSX
Developed by Vercel, Styled JSX is the default styling solution for Next.js. It uses a <style jsx> tag inside components, with scoped styles by default.
- Use Case: Next.js apps, developers familiar with HTML-style
<style>tags. - Key Features: Scoped by default, global styles with
globalmodifier, no runtime overhead in production.
4. Linaria
A zero-runtime CSS-in-JS library that extracts styles to static CSS files at build time. It avoids runtime overhead by leveraging Babel to compile styles into standard CSS.
- Use Case: Performance-critical apps, static sites, teams avoiding runtime JS.
- Key Features: Zero runtime, TypeScript support, works with CSS preprocessors.
5. Glamorous (Deprecated)
A predecessor to styled-components, Glamorous popularized the “styled component” pattern but is no longer maintained. It’s mentioned here for historical context.
Getting Started with styled-components
Let’s walk through a hands-on example with styled-components (the most popular library) to see CSS-in-JS in action.
Step 1: Install styled-components
First, install the library and its types (for TypeScript users):
# npm
npm install styled-components
# yarn
yarn add styled-components
# TypeScript types (optional)
npm install @types/styled-components --save-dev
Step 2: Basic Styling
Create a styled component by calling styled.tagname (e.g., styled.button) and passing CSS via a tagged template literal:
// Button.jsx
import styled from 'styled-components';
// Define a styled button
const StyledButton = styled.button`
padding: 12px 24px;
border-radius: 4px;
border: none;
font-size: 16px;
cursor: pointer;
transition: background-color 0.3s ease;
/* Hover state */
&:hover {
background-color: #0056b3;
}
`;
// Use the styled component in your app
const Button = ({ label }) => {
return <StyledButton>{label}</StyledButton>;
};
export default Button;
Here, StyledButton is a React component with built-in styles. The & syntax mimics SASS for nesting pseudo-classes like :hover.
Step 3: Dynamic Styles with Props
Use props to conditionally style components. For example, add a primary variant:
const StyledButton = styled.button`
padding: 12px 24px;
border-radius: 4px;
border: none;
font-size: 16px;
cursor: pointer;
transition: background-color 0.3s ease;
/* Base color */
background-color: ${props => props.primary ? '#007bff' : '#6c757d'};
color: white;
/* Hover state (dynamic) */
&:hover {
background-color: ${props => props.primary ? '#0056b3' : '#5a6268'};
}
/* Disabled state */
&:disabled {
background-color: #ccc;
cursor: not-allowed;
}
`;
// Usage: <StyledButton primary disabled={isDisabled}>Click Me</StyledButton>
Props like primary or disabled dynamically adjust the background color, making it easy to build flexible UI components.
Step 4: Theming with ThemeProvider
For consistent theming (e.g., brand colors, spacing), use ThemeProvider to pass a theme object to all styled components:
// theme.js
export const theme = {
colors: {
primary: '#007bff',
secondary: '#6c757d',
light: '#f8f9fa',
dark: '#343a40',
},
spacing: {
sm: '8px',
md: '16px',
lg: '24px',
},
};
// App.jsx
import { ThemeProvider } from 'styled-components';
import { theme } from './theme';
import Button from './Button';
function App() {
return (
<ThemeProvider theme={theme}>
<div>
<Button label="Primary Button" primary />
</div>
</ThemeProvider>
);
}
Now update StyledButton to use the theme:
const StyledButton = styled.button`
padding: ${props => props.theme.spacing.md};
background-color: ${props => props.primary ? props.theme.colors.primary : props.theme.colors.secondary};
color: white;
/* ... rest of styles */
`;
Themes ensure consistency across your app and make it easy to switch between light/dark modes (just pass a different theme object to ThemeProvider).
Advanced Customization Techniques
Once you’re comfortable with the basics, explore these advanced techniques to unlock the full power of CSS-in-JS.
1. Conditional Styling with Objects
For complex conditional logic, use objects instead of template literals. Emotion and styled-components support this:
// Using Emotion's object syntax
import { css } from '@emotion/react';
const buttonStyles = css({
padding: '12px 24px',
borderRadius: '4px',
backgroundColor: props => props.primary ? 'blue' : 'gray',
});
// With styled-components (template literal + object)
const StyledButton = styled.button`
${props => ({
padding: props.large ? '16px 32px' : '12px 24px',
fontSize: props.large ? '18px' : '16px',
})}
`;
2. Animations with keyframes
styled-components and Emotion let you define animations using keyframes:
import styled, { keyframes } from 'styled-components';
// Define a fade-in animation
const fadeIn = keyframes`
from { opacity: 0; }
to { opacity: 1; }
`;
// Apply the animation to a component
const FadeInDiv = styled.div`
animation: ${fadeIn} 0.5s ease-in-out;
`;
3. Global Styles with createGlobalStyle
For global resets (e.g., box-sizing: border-box) or fonts, use createGlobalStyle from styled-components:
import { createGlobalStyle } from 'styled-components';
const GlobalStyle = createGlobalStyle`
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Arial', sans-serif;
background-color: ${props => props.theme.colors.light};
}
`;
// Use in App.jsx: <GlobalStyle />
4. Composing Components
Build reusable UI primitives by composing styled components:
const BaseButton = styled.button`
padding: 12px 24px;
border-radius: 4px;
border: none;
`;
// Extend BaseButton to create a PrimaryButton
const PrimaryButton = styled(BaseButton)`
background-color: ${props => props.theme.colors.primary};
color: white;
`;
// Extend again for a DangerButton
const DangerButton = styled(BaseButton)`
background-color: red;
color: white;
`;
Performance Considerations
While CSS-in-JS offers flexibility, it can introduce runtime overhead. Optimize your app with these tips:
1. Avoid Runtime Overhead
- Zero-runtime libraries: Use Linaria or
@emotion/css(withbabel-plugin-emotion) to extract styles to static CSS at build time, eliminating runtime JS. - Memoize styled components: If a styled component re-renders frequently, wrap it in
React.memoor useuseMemoto prevent unnecessary style recalculations:
import React from 'react';
const MemoizedButton = React.memo(styled.button`
/* styles */
`);
2. Minimize Style Recalculations
- Avoid dynamic styles in hot paths: If a component re-renders often (e.g., a scroll listener), avoid using props to drive styles. Use CSS transitions/animations instead.
- Use
shouldForwardProp: Filter out unused props to prevent unnecessary re-renders:
const StyledButton = styled.button.withConfig({
shouldForwardProp: (prop) => prop !== 'isActive', // Ignore 'isActive' prop
})`
background: ${props => props.isActive ? 'blue' : 'gray'};
`;
3. Server-Side Rendering (SSR)
- Use SSR to prevent FOUC: Libraries like styled-components and Emotion support SSR, which injects styles into the initial HTML. This avoids the “Flash of Unstyled Content” (FOUC) common with client-side-only injection.
- Hydrate efficiently: Use
StyleSheetManager(styled-components) orextractCritical(Emotion) to server-render and hydrate styles correctly.
Comparison with Other Styling Methods
CSS-in-JS isn’t the only option. Here’s how it stacks up against alternatives:
| Method | Pros | Cons | Best For |
|---|---|---|---|
| CSS-in-JS | Dynamic props, scoped, colocated | Runtime overhead, steeper learning curve | Dynamic UIs, component libraries, theming |
| CSS Modules | Static scoping, simple, no runtime | No dynamic props, separate files | Static UIs, apps with minimal dynamic styles |
| SASS/SCSS | Preprocessor features (mixins, variables) | Global scope, no dynamic props | Large apps with complex static styles |
| Tailwind CSS | Utility-first, fast development | Verbose HTML, learning curve for utilities | Prototyping, apps prioritizing speed |
| Inline Styles | No class names, dynamic | No pseudo-classes, poor performance | Simple dynamic styles (e.g., style={{ color }}) |
When to Choose CSS-in-JS: Use it for apps with dynamic UIs (e.g., dashboards, interactive components), component libraries, or when theming is critical. For static sites or performance-critical apps, consider CSS Modules or zero-runtime CSS-in-JS (Linaria).
Conclusion
CSS-in-JS revolutionizes styling in React by combining the power of JavaScript with the expressiveness of CSS. It solves common pain points like global scope, dynamic styling, and colocation, making it a top choice for modern React apps.
Whether you choose styled-components for its developer experience, Emotion for flexibility, or Linaria for zero runtime, CSS-in-JS empowers you to build beautiful, maintainable UIs. Start small—convert a few components to CSS-in-JS—and gradually scale up as you see the benefits.