Using Styled Components in React

Master CSS-in-JS styling for modern React and Next.js applications with dynamic props, theming, and performance optimization

What Are Styled Components?

Styled-components represents one of the most influential CSS-in-JS libraries in the React ecosystem, enabling developers to write actual CSS within JavaScript files using tagged template literals. This approach fundamentally changes how developers think about component styling by encapsulating styles directly with the components they define, creating a more cohesive development experience that aligns styling logic with component logic. The library has played a significant role in popularizing the CSS-in-JS paradigm and remains widely used despite newer alternatives emerging in the ecosystem. Understanding styled-components provides valuable insights into component-based styling patterns that extend beyond the library itself.

For modern React applications built with our web development services, styled-components offers a compelling option when teams prioritize dynamic styling capabilities, theming systems, and component-scoped styles without the need for external CSS file management. The library automatically generates unique class names, eliminating the common headache of class name collisions across large codebases, and provides excellent developer experience through features like syntax error highlighting and source map support. While the library's maintenance status has become a consideration for long-term projects, its mature ecosystem and proven patterns continue to make it relevant for teams evaluating styling solutions.

When combined with TypeScript generics for reusable components, styled-components enables teams to build maintainable, type-safe design systems that scale across large applications.

Key Styled Components Capabilities

Everything you need to build styled, maintainable React components

Prop-Based Styling

Dynamic styles based on component props, enabling flexible reusable components without CSS class management

Hover & Pseudo-Selectors

Full CSS pseudo-class support with &:hover, &:focus, &:active, and nested selector patterns

Theming System

Built-in ThemeProvider for design tokens, color palettes, and consistent styling across applications

Critical CSS Extraction

Automatic style extraction during SSR prevents flash of unstyled content in Next.js

Scoped Styles

Unique class names generated automatically, eliminating class name collisions across codebases

TypeScript Support

Full type safety for props and theme access in typed React applications

Installation and Configuration

Getting started with styled-components requires only a single npm package installation, though Next.js projects benefit from additional configuration to ensure proper server-side rendering support. The library integrates seamlessly with React's component model, allowing developers to create styled versions of any HTML element or React component with familiar CSS syntax.

Basic Installation

npm install styled-components
# or
yarn add styled-components
# or
pnpm add styled-components

Next.js App Router Setup

For Next.js App Router projects, create a registry component to handle SSR style extraction. This setup is essential for building performant React applications that avoid the flash of unstyled content:

// lib/registry.tsx
'use client';

import React, { useState } from 'react';
import { useServerInsertedHTML } from 'next/navigation';
import { ServerStyleSheet, StyleSheetManager } from 'styled-components';

export default function StyledComponentsRegistry({ children }) {
 const [styledComponentsStyleSheet] = useState(() => new ServerStyleSheet());

 useServerInsertedHTML(() => {
 const styles = styledComponentsStyleSheet.getStyleElement();
 styledComponentsStyleSheet.instance.clearTag();
 return <>{styles}</>;
 });

 if (typeof window !== 'undefined') return <>{children}</>;

 return (
 <StyleSheetManager sheet={styledComponentsStyleSheet.instance}>
 {children}
 </StyleSheetManager>
 );
}

This registry pattern ensures that all styled-components are collected during server-side rendering and injected into the HTML before JavaScript loads, preventing hydration mismatches and improving perceived performance.

Creating Your First Styled Component

The fundamental building block of styled-components is the styled function, which takes an HTML element or React component as its base and returns a new component with attached styles defined in a template literal. This pattern allows developers to think in terms of component composition rather than separate stylesheets, keeping related concerns together within the same file or module. The syntax feels natural to developers familiar with CSS, as it uses standard CSS property names and values while enabling JavaScript expressions for dynamic values.

import styled from 'styled-components';

const Button = styled.button`
 background-color: #007bff;
 color: white;
 padding: 12px 24px;
 border: none;
 border-radius: 4px;
 font-size: 16px;
 font-weight: 600;
 cursor: pointer;
 transition: background-color 0.2s ease-in-out;
 
 &:hover {
 background-color: #0056b3;
 }
 
 &:active {
 background-color: #004494;
 }
 
 &:focus {
 outline: 2px solid #007bff;
 outline-offset: 2px;
 }
`;

function App() {
 return (
 <div>
 <Button>Click Me</Button>
 </div>
 );
}

Styled Elements Reference

ElementStyled FunctionOutput Component
HTMLstyled.div<div> with styles
Buttonstyled.button<button> with styles
Inputstyled.input<input> with styles
Imagestyled.img<img> with styles
Linkstyled.a<a> with styles
Customstyled(Component)Wrapped React component

This approach works seamlessly with responsive navbar patterns using CSS and other styling techniques to create cohesive UI experiences.

Dynamic Prop-Based Styling

One of styled-components' most powerful features is the ability to dynamically adjust styles based on props passed to the component. This eliminates the need for separate component variants and allows for flexible, reusable components that adapt their appearance based on context or user interaction. The prop function receives the component's props as its argument, enabling conditional styling logic that would require CSS classes or inline styles in traditional approaches.

import styled from 'styled-components';

const StyledButton = styled.button`
 padding: 12px 24px;
 border-radius: 6px;
 font-size: 14px;
 font-weight: 600;
 cursor: pointer;
 transition: all 0.2s ease;
 
 background-color: ${props => {
 if (props.$variant === 'primary') return '#007bff';
 if (props.$variant === 'secondary') return '#6c757d';
 if (props.$variant === 'danger') return '#dc3545';
 if (props.$variant === 'success') return '#28a745';
 return '#007bff';
 }};
 
 color: ${props => props.$variant === 'outline' ? '#007bff' : 'white'};
 
 border: ${props => props.$variant === 'outline' ? '2px solid #007bff' : 'none'};
 
 opacity: ${props => props.$disabled ? 0.6 : 1};
 pointer-events: ${props => props.$disabled ? 'none' : 'auto'};
 
 &:hover {
 transform: ${props => props.$disabled ? 'none' : 'translateY(-1px)'};
 }
`;

// Usage with transient props (prefixed with $ to avoid DOM warnings)
<StyledButton $variant="primary" $disabled>Primary Disabled</StyledButton>
<StyledButton $variant="outline">Outline Button</StyledButton>

Transient Props Pattern

Use $ prefix on props to prevent them from being passed to the underlying DOM element, avoiding React warnings:

// Good - $disabled won't appear in DOM
const Button = styled.button`
 opacity: ${props => props.$disabled ? 0.6 : 1};
`;

// Avoid - disabled will appear as DOM attribute
const BadButton = styled.button`
 opacity: ${props => props.disabled ? 0.6 : 1};
`;

This pattern is especially valuable when combined with TypeScript generics to create fully typed, reusable component libraries.

Hover States and Pseudo-Selectors

Styled-components provides full support for CSS pseudo-classes through the & symbol, which serves as a reference to the current component. This enables interactive states without external CSS:

import styled from 'styled-components';

const Card = styled.div`
 background: white;
 border-radius: 12px;
 padding: 24px;
 box-shadow: 0 2px 8px rgba(0,0,0,0.08);
 transition: all 0.3s ease;
 
 &:hover {
 transform: translateY(-4px);
 box-shadow: 0 12px 24px rgba(0,0,0,0.12);
 }
 
 &:hover ${Image} {
 transform: scale(1.05);
 }
 
 &:focus-within {
 outline: 2px solid #007bff;
 outline-offset: 2px;
 }
 
 ${props => props.$highlighted && `
 border: 2px solid #007bff;
 background: linear-gradient(to bottom right, #f8f9ff, #ffffff);
 `}
`;

const Image = styled.img`
 width: 100%;
 height: 200px;
 object-fit: cover;
 border-radius: 8px;
 transition: transform 0.3s ease;
`;

Supported Pseudo-Selectors

  • &:hover - Mouse over element
  • &:focus - Element has focus
  • &:active - Element is being clicked
  • &:disabled - Element is disabled
  • &:first-child - First child element
  • &:last-child - Last child element
  • &:nth-child(n) - Specific child position
  • &::before - Before pseudo-element
  • &::after - After pseudo-element
  • &:focus-visible - Visible focus state

These patterns integrate seamlessly with typing animation implementations to create engaging user interactions.

Theming Architecture

Styled-components includes a native theming system through ThemeProvider, which makes design tokens available to all styled components within the provider's scope. This theming approach enables consistent styling across large applications by centralizing color palettes, spacing scales, typography systems, and other design decisions in a single configuration object.

import { ThemeProvider } from 'styled-components';

const theme = {
 colors: {
 primary: '#007bff',
 secondary: '#6c757d',
 success: '#28a745',
 danger: '#dc3545',
 background: '#ffffff',
 surface: '#ffffff',
 text: '#212529',
 textMuted: '#6c757d',
 border: '#dee2e6',
 },
 spacing: {
 xs: '4px',
 sm: '8px',
 md: '16px',
 lg: '24px',
 xl: '32px',
 },
 fonts: {
 body: "'Inter', -apple-system, sans-serif",
 heading: "'Inter', -apple-system, sans-serif",
 mono: "'Fira Code', monospace",
 },
 shadows: {
 sm: '0 1px 3px rgba(0,0,0,0.08)',
 md: '0 4px 12px rgba(0,0,0,0.1)',
 lg: '0 8px 24px rgba(0,0,0,0.12)',
 },
};

const ThemedButton = styled.button`
 background-color: ${props => props.theme.colors.primary};
 color: white;
 padding: ${props => `${props.theme.spacing.sm} ${props.theme.spacing.lg}`};
 border-radius: 6px;
 font-family: ${props => props.theme.fonts.body};
 box-shadow: ${props => props.theme.shadows.md};
 
 &:hover {
 filter: brightness(1.1);
 }
`;

function App() {
 return (
 <ThemeProvider theme={theme}>
 <ThemedButton>Themed Button</ThemedButton>
 </ThemeProvider>
 );
}

Theme Object Structure

Design tokens can include any values components need consistently:

const theme = {
 // Semantic colors
 colors: { primary, secondary, success, danger, warning, info, light, dark },
 // Spacing scale
 spacing: { xs, sm, md, lg, xl, xxl },
 // Typography
 fonts: { body, heading, mono },
 fontSizes: { sm, base, lg, xl, xxl, display },
 // Breakpoints for responsive design
 breakpoints: { sm, md, lg, xl },
 // Shadow tokens
 shadows: { sm, md, lg, xl },
 // Border radius values
 radii: { sm, md, lg, full },
 // Z-index scale
 zIndices: { base, dropdown, sticky, modal, tooltip },
};

Performance Considerations

Optimizing styled-components for performance requires understanding how the library generates and injects CSS during rendering, particularly in server-side rendered applications. The library automatically performs critical CSS extraction during server renders, collecting all styled-component definitions and embedding them into a style tag within the HTML response. According to Telerik's analysis of critical CSS extraction, this approach eliminates render-blocking stylesheets while ensuring styles are available immediately upon page load. For large applications, the extraction process can impact server render times, making caching strategies and component organization important considerations, as noted in LogRocket's performance guide.

import styled, { css } from 'styled-components';

// Use css helper for shared style fragments
const buttonBaseStyles = css`
 display: inline-flex;
 align-items: center;
 justify-content: center;
 padding: 12px 24px;
 border-radius: 6px;
 font-weight: 600;
 cursor: pointer;
 transition: all 0.2s ease;
 border: none;
 
 &:disabled {
 opacity: 0.6;
 cursor: not-allowed;
 }
`;

const OptimizedButton = styled.button`
 ${buttonBaseStyles}
 background-color: ${props => props.$primary ? '#007bff' : '#6c757d'};
`;

// Use attrs for optimized attribute handling
const StyledInput = styled.input.attrs(props => ({
 type: props.type || 'text',
 autoComplete: props.autoComplete || 'off',
}))`
 padding: 12px 16px;
 border: 1px solid ${props => props.theme.colors.border};
 border-radius: 8px;
`;

Performance Best Practices

  1. Use css helper for shared style fragments to avoid duplication
  2. Use attrs for static attributes to reduce runtime overhead
  3. Use transient props ($ prefix) to prevent DOM prop warnings
  4. Memoize expensive components using React.memo
  5. Cache theme objects to prevent unnecessary re-renders
  6. Avoid deep prop chains in template expressions

These optimizations are crucial for building performant enterprise applications that deliver excellent user experiences.

Best Practices and Common Patterns

Organization Patterns

// Pattern 1: Co-located styles (recommended for small components)
import styled from 'styled-components';

export const Card = styled.div`
 background: white;
 border-radius: 12px;
 padding: 24px;
`;

export const Title = styled.h2`
 font-size: 20px;
 font-weight: 700;
`;

// Pattern 2: Extending styles from another styled component
const BaseCard = styled.div`
 background: white;
 border-radius: 12px;
`;

export const FeaturedCard = styled(BaseCard)`
 border-left: 4px solid ${props => props.theme.colors.primary};
 background: linear-gradient(to right, rgba(0,123,255,0.05), transparent);
`;

// Pattern 3: Polymorphic component with 'as' prop
const StyledLink = styled.a`
 color: ${props => props.theme.colors.primary};
 text-decoration: none;
 
 &:hover {
 text-decoration: underline;
 }
`;

// Renders as <a> or can be styled(Button) for button behavior
<StyledLink as="button">Clickable Link</StyledLink>

Error Boundaries for Styles

import { ErrorBoundary } from 'react-error-boundary';

function StyleErrorFallback({ error }) {
 return <div style={{ color: 'red' }}>Style error: {error.message}</div>;
}

function App() {
 return (
 <ErrorBoundary FallbackComponent={StyleErrorFallback}>
 <StyledComponent />
 </ErrorBoundary>
 );
}

TypeScript Type Definitions

import styled from 'styled-components';

interface ButtonProps {
 $variant: 'primary' | 'secondary' | 'danger';
 $disabled?: boolean;
 $size?: 'sm' | 'md' | 'lg';
}

const StyledButton = styled.button<ButtonProps>`
 padding: ${props => 
 props.$size === 'sm' ? '8px 16px' :
 props.$size === 'lg' ? '16px 32px' : '12px 24px'
 };
 
 background-color: ${props => {
 switch (props.$variant) {
 case 'danger': return props.theme.colors.danger;
 case 'secondary': return props.theme.colors.secondary;
 default: return props.theme.colors.primary;
 }
 }};
`;

Next.js Integration Complete Example

Registry Setup (lib/registry.tsx)

'use client';

import React, { useState } from 'react';
import { useServerInsertedHTML } from 'next/navigation';
import { ServerStyleSheet, StyleSheetManager } from 'styled-components';

export default function StyledComponentsRegistry({ children }) {
 const [styledComponentsStyleSheet] = useState(() => new ServerStyleSheet());

 useServerInsertedHTML(() => {
 const styles = styledComponentsStyleSheet.getStyleElement();
 styledComponentsStyleSheet.instance.clearTag();
 return <>{styles}</>;
 });

 if (typeof window !== 'undefined') return <>{children}</>;

 return (
 <StyleSheetManager sheet={styledComponentsStyleSheet.instance}>
 {children}
 </StyleSheetManager>
 );
}

Root Layout (app/layout.tsx)

import StyledComponentsRegistry from '@/lib/registry';
import { ThemeProvider } from 'styled-components';
import { theme } from '@/styles/theme';

export default function RootLayout({ children }) {
 return (
 <html lang="en">
 <body>
 <StyledComponentsRegistry>
 <ThemeProvider theme={theme}>
 {children}
 </ThemeProvider>
 </StyledComponentsRegistry>
 </body>
 </html>
 );
}

Client Component (components/ClientButton.tsx)

'use client';

import styled from 'styled-components';

const ClientButton = styled.button`
 background-color: ${props => props.theme.colors.primary};
 color: white;
 padding: 12px 24px;
 border: none;
 border-radius: 6px;
 cursor: pointer;
 transition: all 0.2s ease;
 
 &:hover {
 filter: brightness(1.1);
 }
`;

export default ClientButton;

For teams building scalable GraphQL APIs with Node.js, styled-components provides an excellent frontend styling solution that integrates seamlessly with modern full-stack architectures.

Frequently Asked Questions

Is styled-components still maintained?

Styled-components continues to be used in production applications. While development pace has slowed compared to its early days, the library remains stable and functional for existing projects. Teams should evaluate maintenance status against their long-term roadmap when starting new projects.

Should I use styled-components or Tailwind CSS?

Choose styled-components for complex interactive components with dynamic props, sophisticated theming needs, and teams comfortable with CSS-in-JS patterns. Choose Tailwind CSS for rapid UI development, smaller bundle sizes, and projects prioritizing utility-first workflows. Both are valid choices depending on project requirements.

Does styled-components work with Next.js App Router?

Yes, styled-components works with Next.js App Router through a registry pattern that collects styles during server-side rendering. This ensures proper SSR without flash of unstyled content. The setup involves creating a registry component that wraps the application.

How do styled-components affect bundle size?

Styled-components adds a runtime (~12KB minified) to the JavaScript bundle in exchange for automatic critical CSS extraction, scoped styles, and dynamic theming. For most applications, this tradeoff is acceptable given the developer experience and feature benefits.

Can I use styled-components with TypeScript?

Yes, styled-components has excellent TypeScript support. You can define prop types for styled components, access theme types, and get full type safety for your styling code. The @types/styled-components package provides type definitions.

How do I handle dark mode with styled-components?

Implement dark mode by creating a theme object for each mode and switching between them using ThemeProvider. Use CSS custom properties or theme-based color values to enable smooth theme switching across all styled components.

Build Better React Applications with Styled Components

Our team specializes in modern React development using styled-components and Next.js to create performant, maintainable applications.

Sources

  1. Telerik: The Ultimate Guide to Styling React Components - Comprehensive guide covering styled-components as a leading CSS-in-JS solution
  2. LogRocket: Using styled-components in React - Detailed tutorial covering basic syntax, theming, and practical patterns for component styling