Skip to main content

React CSS-in-JS

Introduction

CSS-in-JS is a styling approach where you write your CSS styles directly in your JavaScript code. This pattern has become increasingly popular in the React ecosystem as it solves many challenges that come with traditional CSS approaches. By using CSS-in-JS, you can create component-scoped styles, making your components more portable and preventing style leaks.

In this tutorial, we'll explore the concept of CSS-in-JS in React, learn about popular libraries, and see how to implement this approach in your projects.

Why CSS-in-JS?

Traditional CSS comes with certain limitations when working with component-based architectures like React:

  1. Global namespace: CSS selectors are global, which can lead to naming conflicts
  2. No dead code elimination: Unused CSS is hard to identify and remove
  3. No dependency management: CSS doesn't have a built-in way to manage dependencies
  4. No scoping: Styles can leak and affect other elements unintentionally
  5. Static styles: Dynamically changing styles based on props or state is difficult

CSS-in-JS solves these problems by:

  1. ✅ Generating unique class names automatically
  2. ✅ Only including styles that are actually being used
  3. ✅ Keeping styles along with the components that use them
  4. ✅ Providing component-level scoping for styles
  5. ✅ Enabling dynamic styling based on props and state

Several libraries implement the CSS-in-JS approach in React:

  1. styled-components: One of the most popular libraries that allows you to write actual CSS syntax in your JavaScript
  2. Emotion: A flexible library with powerful composition capabilities
  3. JSS: A more low-level implementation that compiles JavaScript objects to CSS
  4. Styled JSX: A CSS-in-JS library built into Next.js

For this tutorial, we'll focus on styled-components and Emotion as they are widely used and beginner-friendly.

Getting Started with styled-components

Installation

First, install styled-components in your React project:

bash
npm install styled-components
# or using yarn
yarn add styled-components

Basic Usage

The main concept behind styled-components is creating React components with styles attached to them:

jsx
import styled from 'styled-components';

// Create a styled button component
const Button = styled.button`
background-color: #4CAF50;
border: none;
color: white;
padding: 10px 20px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 16px;
margin: 4px 2px;
cursor: pointer;
border-radius: 4px;

&:hover {
background-color: #45a049;
}
`;

// Use it like a regular React component
function App() {
return (
<div>
<h1>My First Styled Component</h1>
<Button>Click Me</Button>
</div>
);
}

In this example, styled.button creates a button element with the CSS styles defined in the template literal. The & symbol is used to refer to the component itself, similar to how it works in SASS.

Dynamic Styling with Props

One of the major advantages of CSS-in-JS is the ability to create dynamic styles based on props:

jsx
import styled from 'styled-components';

const Button = styled.button`
background-color: ${props => props.primary ? '#4CAF50' : '#ffffff'};
color: ${props => props.primary ? 'white' : '#4CAF50'};
border: ${props => props.primary ? 'none' : '2px solid #4CAF50'};
padding: 10px 20px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 16px;
margin: 4px 2px;
cursor: pointer;
border-radius: 4px;

&:hover {
background-color: ${props => props.primary ? '#45a049' : '#e7e7e7'};
}
`;

function App() {
return (
<div>
<h1>Dynamic Styled Components</h1>
<Button>Normal Button</Button>
<Button primary>Primary Button</Button>
</div>
);
}

In this example, the button changes its styles based on whether the primary prop is passed to it.

Extending Styles

You can extend existing styled components to create new ones with additional styles:

jsx
import styled from 'styled-components';

const Button = styled.button`
background-color: #4CAF50;
color: white;
padding: 10px 20px;
border: none;
border-radius: 4px;
`;

const LargeButton = styled(Button)`
font-size: 20px;
padding: 15px 30px;
`;

const OutlinedButton = styled(Button)`
background-color: transparent;
color: #4CAF50;
border: 2px solid #4CAF50;
`;

function App() {
return (
<div>
<h1>Extended Styled Components</h1>
<Button>Regular Button</Button>
<LargeButton>Large Button</LargeButton>
<OutlinedButton>Outlined Button</OutlinedButton>
</div>
);
}

Using Emotion

Installation

Install Emotion with the following commands:

bash
npm install @emotion/react @emotion/styled
# or using yarn
yarn add @emotion/react @emotion/styled

Basic Usage with Emotion

Emotion offers two main styling approaches: the css prop and styled components.

Using the css prop:

jsx
/** @jsx jsx */
import { css, jsx } from '@emotion/react';

function App() {
return (
<div
css={css`
background-color: #f0f0f0;
padding: 20px;
border-radius: 8px;
`}
>
<h1
css={css`
color: #333;
`}
>
Hello Emotion
</h1>
<button
css={css`
background-color: #4CAF50;
color: white;
border: none;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;

&:hover {
background-color: #45a049;
}
`}
>
Click Me
</button>
</div>
);
}

Using Emotion's styled components:

jsx
import styled from '@emotion/styled';

const Container = styled.div`
background-color: #f0f0f0;
padding: 20px;
border-radius: 8px;
`;

const Heading = styled.h1`
color: #333;
`;

const Button = styled.button`
background-color: #4CAF50;
color: white;
border: none;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;

&:hover {
background-color: #45a049;
}
`;

function App() {
return (
<Container>
<Heading>Hello Emotion</Heading>
<Button>Click Me</Button>
</Container>
);
}

Dynamic Styling with Emotion

Like styled-components, Emotion also supports dynamic styling based on props:

jsx
import styled from '@emotion/styled';

const Button = styled.button`
background-color: ${props => props.primary ? '#4CAF50' : '#ffffff'};
color: ${props => props.primary ? 'white' : '#4CAF50'};
border: ${props => props.primary ? 'none' : '2px solid #4CAF50'};
padding: 10px 20px;
text-align: center;
font-size: 16px;
margin: 4px 2px;
cursor: pointer;
border-radius: 4px;

&:hover {
background-color: ${props => props.primary ? '#45a049' : '#e7e7e7'};
}
`;

function App() {
return (
<div>
<Button>Normal Button</Button>
<Button primary>Primary Button</Button>
</div>
);
}

Theming

Both styled-components and Emotion support theming, making it easy to apply consistent styling across your application.

Theming with styled-components:

jsx
import { ThemeProvider } from 'styled-components';
import styled from 'styled-components';

// Define your theme
const theme = {
colors: {
primary: '#4CAF50',
secondary: '#FFC107',
text: '#333333',
background: '#f9f9f9',
},
fontSizes: {
small: '0.8rem',
medium: '1rem',
large: '1.2rem',
},
spacing: {
small: '8px',
medium: '16px',
large: '24px',
}
};

// Create styled components that use the theme
const Button = styled.button`
background-color: ${props => props.theme.colors.primary};
color: white;
padding: ${props => props.theme.spacing.medium};
font-size: ${props => props.theme.fontSizes.medium};
border: none;
border-radius: 4px;

&:hover {
opacity: 0.9;
}
`;

const Card = styled.div`
background-color: ${props => props.theme.colors.background};
padding: ${props => props.theme.spacing.large};
border-radius: 8px;
color: ${props => props.theme.colors.text};
`;

function App() {
return (
<ThemeProvider theme={theme}>
<Card>
<h1>Themed Components</h1>
<p>This card and button use a consistent theme.</p>
<Button>Themed Button</Button>
</Card>
</ThemeProvider>
);
}

Theming with Emotion:

jsx
import { ThemeProvider } from '@emotion/react';
import styled from '@emotion/styled';

// Define your theme (same as above)
const theme = {
colors: {
primary: '#4CAF50',
secondary: '#FFC107',
text: '#333333',
background: '#f9f9f9',
},
fontSizes: {
small: '0.8rem',
medium: '1rem',
large: '1.2rem',
},
spacing: {
small: '8px',
medium: '16px',
large: '24px',
}
};

// Create styled components that use the theme
const Button = styled.button`
background-color: ${props => props.theme.colors.primary};
color: white;
padding: ${props => props.theme.spacing.medium};
font-size: ${props => props.theme.fontSizes.medium};
border: none;
border-radius: 4px;

&:hover {
opacity: 0.9;
}
`;

const Card = styled.div`
background-color: ${props => props.theme.colors.background};
padding: ${props => props.theme.spacing.large};
border-radius: 8px;
color: ${props => props.theme.colors.text};
`;

function App() {
return (
<ThemeProvider theme={theme}>
<Card>
<h1>Themed Components</h1>
<p>This card and button use a consistent theme.</p>
<Button>Themed Button</Button>
</Card>
</ThemeProvider>
);
}

Real-world Example: Building a Card Component

Let's create a reusable product card component using CSS-in-JS:

jsx
import React from 'react';
import styled from 'styled-components';

// Styled components for our card
const CardContainer = styled.div`
border-radius: 8px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
overflow: hidden;
width: 300px;
transition: transform 0.3s ease;

&:hover {
transform: translateY(-5px);
}
`;

const CardImage = styled.img`
width: 100%;
height: 200px;
object-fit: cover;
`;

const CardContent = styled.div`
padding: 16px;
`;

const ProductName = styled.h3`
margin: 0 0 8px 0;
color: #333;
`;

const ProductPrice = styled.div`
font-size: 18px;
font-weight: bold;
color: ${props => props.discounted ? '#e53935' : '#333'};
`;

const ProductDescription = styled.p`
color: #666;
font-size: 14px;
line-height: 1.4;
`;

const ProductRating = styled.div`
display: flex;
align-items: center;
margin: 8px 0;
`;

const StarContainer = styled.div`
color: ${props => props.filled ? '#FFC107' : '#e0e0e0'};
margin-right: 2px;
`;

const AddToCartButton = styled.button`
background-color: #4CAF50;
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
width: 100%;
margin-top: 16px;

&:hover {
background-color: #45a049;
}

&:disabled {
background-color: #cccccc;
cursor: not-allowed;
}
`;

// Star component for ratings
const Star = ({ filled }) => (
<StarContainer filled={filled}></StarContainer>
);

// Main ProductCard component
const ProductCard = ({ product }) => {
const { name, price, description, rating, image, inStock } = product;

// Generate star ratings
const stars = [];
for (let i = 1; i <= 5; i++) {
stars.push(<Star key={i} filled={i <= rating} />);
}

return (
<CardContainer>
<CardImage src={image} alt={name} />
<CardContent>
<ProductName>{name}</ProductName>
<ProductPrice discounted={price.original > price.current}>
${price.current.toFixed(2)}
{price.original > price.current && (
<span style={{ textDecoration: 'line-through', fontSize: '14px', color: '#999', marginLeft: '8px' }}>
${price.original.toFixed(2)}
</span>
)}
</ProductPrice>
<ProductRating>{stars}</ProductRating>
<ProductDescription>{description}</ProductDescription>
<AddToCartButton disabled={!inStock}>
{inStock ? 'Add to Cart' : 'Out of Stock'}
</AddToCartButton>
</CardContent>
</CardContainer>
);
};

// Example usage
function App() {
const productData = {
name: "Wireless Headphones",
price: {
current: 79.99,
original: 99.99
},
description: "Premium wireless headphones with noise cancellation and 20-hour battery life.",
rating: 4,
image: "https://example.com/headphones-image.jpg", // Replace with actual URL in real app
inStock: true
};

return (
<div style={{ padding: '20px', fontFamily: 'Arial, sans-serif' }}>
<h1>Featured Product</h1>
<ProductCard product={productData} />
</div>
);
}

Best Practices for CSS-in-JS

  1. Component-based structure: Keep your styled components close to the React components that use them.

  2. Reusable abstractions: Create a theme and reusable style components to maintain consistency.

jsx
// Define a theme
const theme = {
colors: {
primary: '#4CAF50',
secondary: '#FFC107',
danger: '#f44336',
}
};

// Create reusable style components
const flexCenter = css`
display: flex;
justify-content: center;
align-items: center;
`;

// Use these abstractions in your components
const Button = styled.button`
${flexCenter}
background-color: ${props => theme.colors[props.variant] || theme.colors.primary};
color: white;
// other button styles...
`;
  1. Name your components semantically: Use names that describe what the component represents, not how it looks.
jsx
// Good
const NavigationHeader = styled.header`...`;

// Not as good
const BlueBar = styled.header`...`;
  1. Keep styles focused: Each styled component should have a single responsibility.

  2. Use props for variations instead of creating many similar components.

Performance Considerations

While CSS-in-JS has many benefits, it can affect performance if not used correctly:

  1. Bundle size: CSS-in-JS libraries add to your bundle size. For small projects, this might not be an issue, but for larger ones, consider code splitting.

  2. Runtime overhead: Some CSS-in-JS solutions generate styles at runtime, which can impact performance. Consider using solutions that extract CSS at build time for production.

  3. Server-side rendering: When using server-side rendering, make sure your CSS-in-JS solution supports it properly to avoid flash of unstyled content.

Summary

CSS-in-JS provides a powerful approach to styling React components by:

  • Scoping styles to components
  • Supporting dynamic styling based on props
  • Enabling theming and consistent design systems
  • Avoiding global namespace conflicts
  • Improving component reusability

The two most popular libraries—styled-components and Emotion—offer similar features with slightly different APIs. Choose the one that best fits your project's needs.

CSS-in-JS is particularly valuable for component libraries and larger applications where style encapsulation and maintainability are important.

Exercises

  1. Create a theme with color scheme, typography, and spacing, then build a set of basic UI components (Button, Card, Input) that use this theme.

  2. Build a responsive navigation bar using CSS-in-JS that changes layout based on screen size.

  3. Create a form component with different input states (focus, error, disabled) using props to control the styling.

  4. Implement a dark mode toggle using CSS-in-JS and React context for state management.

  5. Refactor an existing component that uses traditional CSS or CSS Modules to use CSS-in-JS.

Additional Resources



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)