Sitemap

Styled Components: CSS-in-JS for React

9 min readApr 16, 2026

--

Press enter or click to view image in full size

When CSS Feels Messy

You write CSS. Separate file. Classes everywhere. .button, .button-primary, .button-large. Name collisions. .button in one component conflicts with .button in another. BEM naming: .block__element--modifier. Ugly. Hard to maintain.

You add CSS. Never delete. File grows. 5000 lines. Which classes are used? No idea. Delete anything, break something. Dead CSS accumulates. Bundle size grows. Performance suffers.

You need dynamic styles. Button color from props. Add class names. className={active ? 'button-active' : 'button'}. Combine classes. Template strings. Messy. Hard to read. Logic scattered.

You want component-scoped styles. CSS Modules help but still awkward. Import styles. Use styles.button. Better but not ideal. Want to write CSS directly with components.

Styled Components solves this. Write CSS in JavaScript. Styles scoped to component. No class name collisions. Dynamic styles from props. Automatic critical CSS. Delete component, delete styles. Modern styling for React.

QUICK SUMMARY

What You’ll Learn: Styled Components is CSS-in-JS library for React. Create styled components with tagged template literals. Styles scoped to component automatically. No class name conflicts. Dynamic styles using props. ThemeProvider shares values across components. Extend styled components to reuse styles. Media queries for responsive design. Keyframes for animations. attrs helper sets default attributes. Global styles with createGlobalStyle. Server-side rendering support. TypeScript support with types. Automatic vendor prefixing. Critical CSS extraction. Bundle only used styles. Better than inline styles (pseudo-classes, media queries work).

Why It Matters:

  • Scoped styles prevent conflicts
  • Colocate styles with components
  • Dynamic styling from props
  • Delete component, delete styles
  • Better developer experience
  • Type-safe with TypeScript
  • Industry-standard approach

Key Concepts:

  • Tagged template literals
  • Component-scoped styles
  • Props-based styling
  • Theme provider
  • Component extension
  • Media queries
  • Keyframe animations
  • Global styles

What You’ll Build:

  • Styled components
  • Dynamic themed components
  • Responsive layouts
  • Reusable styled elements
  • Animated components
  • Complete styled system
  • Production-ready styling

Time Investment: Basic styled components take 1–2 hours to learn. Mastering patterns takes practice.

Prerequisites:

  • React fundamentals (Post 11)
  • CSS basics
  • JavaScript template literals
  • Component composition

FULL DETAILED ARTICLE

Installing Styled Components

npm install styled-components
# For TypeScript
npm install @types/styled-components

Basic babel plugin (optional, better performance):

npm install --save-dev babel-plugin-styled-components

Your First Styled Component

Basic styled component:

import styled from 'styled-components';
// Create styled component
const Button = styled.button`
background-color: #007bff;
color: white;
padding: 10px 20px;
border: none;
border-radius: 4px;
font-size: 16px;
cursor: pointer;

&:hover {
background-color: #0056b3;
}

&:active {
transform: scale(0.98);
}
`;
// Use like regular component
function App() {
return (
<div>
<Button>Click me</Button>
<Button>Another button</Button>
</div>
);
}

Generated output:

<!-- Styled Components generates unique class names -->
<button class="sc-AxjAm fZKjlD">Click me</button>
<button class="sc-AxjAm fZKjlD">Another button</button>
<!-- Styles injected in <head> -->
<style>
.sc-AxjAm {} /* Component id */
.fZKjlD {
background-color: #007bff;
color: white;
/* ... rest of styles */
}
</style>

Dynamic Styling with Props

Style based on props:

const Button = styled.button`
background-color: ${props => props.$primary ? '#007bff' : '#6c757d'};
color: white;
padding: ${props => props.$size === 'large' ? '15px 30px' : '10px 20px'};
border: none;
border-radius: 4px;
font-size: ${props => props.$size === 'large' ? '18px' : '16px'};
cursor: pointer;
opacity: ${props => props.disabled ? 0.5 : 1};

&:hover {
background-color: ${props => props.$primary ? '#0056b3' : '#5a6268'};
}
`;
// Usage
function App() {
return (
<div>
<Button $primary>Primary</Button>
<Button>Secondary</Button>
<Button $primary $size="large">Large Primary</Button>
<Button disabled>Disabled</Button>
</div>
);
}

Note: Use $ prefix for props that shouldn't be passed to DOM:

// Bad - warns about non-standard attribute
const Button = styled.button`
color: ${props => props.textColor};
`;
<Button textColor="red">Click</Button>
// Good - $ prefix prevents DOM attribute
const Button = styled.button`
color: ${props => props.$textColor};
`;
<Button $textColor="red">Click</Button>

Complex prop logic:

const Button = styled.button`
${props => {
switch (props.$variant) {
case 'primary':
return `
background-color: #007bff;
color: white;
`;
case 'secondary':
return `
background-color: #6c757d;
color: white;
`;
case 'outline':
return `
background-color: transparent;
color: #007bff;
border: 2px solid #007bff;
`;
default:
return `
background-color: #e9ecef;
color: #495057;
`;
}
}}

padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
`;
// Usage
<Button $variant="primary">Primary</Button>
<Button $variant="outline">Outline</Button>

Theming

Global theme with ThemeProvider:

import { ThemeProvider } from 'styled-components';
// Define theme
const theme = {
colors: {
primary: '#007bff',
secondary: '#6c757d',
success: '#28a745',
danger: '#dc3545',
text: '#212529',
background: '#ffffff'
},
fonts: {
main: '"Helvetica Neue", sans-serif',
code: '"Fira Code", monospace'
},
spacing: {
small: '8px',
medium: '16px',
large: '24px'
},
breakpoints: {
mobile: '576px',
tablet: '768px',
desktop: '992px'
}
};
// Wrap app with provider
function App() {
return (
<ThemeProvider theme={theme}>
<YourApp />
</ThemeProvider>
);
}
// Access theme in styled components
const Button = styled.button`
background-color: ${props => props.theme.colors.primary};
color: white;
padding: ${props => props.theme.spacing.medium};
font-family: ${props => props.theme.fonts.main};
border-radius: 4px;
`;
const Container = styled.div`
max-width: ${props => props.theme.breakpoints.desktop};
margin: 0 auto;
padding: ${props => props.theme.spacing.large};
`;

Dark mode theme:

const lightTheme = {
colors: {
background: '#ffffff',
text: '#000000',
primary: '#007bff'
}
};
const darkTheme = {
colors: {
background: '#1a1a1a',
text: '#ffffff',
primary: '#0d6efd'
}
};
function App() {
const [isDark, setIsDark] = useState(false);

return (
<ThemeProvider theme={isDark ? darkTheme : lightTheme}>
<GlobalStyle />
<button onClick={() => setIsDark(!isDark)}>
Toggle Theme
</button>
<YourApp />
</ThemeProvider>
);
}
// Use theme
const Page = styled.div`
background-color: ${props => props.theme.colors.background};
color: ${props => props.theme.colors.text};
min-height: 100vh;
`;

Extending Styled Components

Extend existing styled components:

const Button = styled.button`
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
`;
// Extend Button
const PrimaryButton = styled(Button)`
background-color: #007bff;
color: white;

&:hover {
background-color: #0056b3;
}
`;
const OutlineButton = styled(Button)`
background-color: transparent;
color: #007bff;
border: 2px solid #007bff;

&:hover {
background-color: #007bff;
color: white;
}
`;
// Usage
<Button>Default</Button>
<PrimaryButton>Primary</PrimaryButton>
<OutlineButton>Outline</OutlineButton>

Style any component:

// Regular React component
function MyButton({ className, children }) {
return <button className={className}>{children}</button>;
}
// Style it
const StyledMyButton = styled(MyButton)`
background-color: #007bff;
color: white;
padding: 10px 20px;
`;
// Works with Link from react-router
import { Link } from 'react-router-dom';
const StyledLink = styled(Link)`
color: #007bff;
text-decoration: none;

&:hover {
text-decoration: underline;
}
`;

Pseudo-classes and Nesting

Get Yakhilesh’s stories in your inbox

Join Medium for free to get updates from this writer.

CSS-like nesting:

const Card = styled.div`
background: white;
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);

/* Nested element */
h2 {
margin-top: 0;
color: #333;
}

p {
color: #666;
line-height: 1.6;
}

/* Pseudo-classes */
&:hover {
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
}

/* Pseudo-elements */
&::before {
content: '';
display: block;
width: 100%;
height: 4px;
background: ${props => props.theme.colors.primary};
margin-bottom: 20px;
}

/* Nested styled component */
${Button} {
margin-top: 10px;
}

/* Media queries */
@media (max-width: 768px) {
padding: 15px;
}
`;

Responsive Design

Media queries:

const Container = styled.div`
padding: 20px;

@media (min-width: 768px) {
padding: 40px;
}

@media (min-width: 992px) {
padding: 60px;
max-width: 1200px;
margin: 0 auto;
}
`;
// Reusable media queries
const breakpoints = {
mobile: '576px',
tablet: '768px',
desktop: '992px',
wide: '1200px'
};
const media = {
mobile: `@media (min-width: ${breakpoints.mobile})`,
tablet: `@media (min-width: ${breakpoints.tablet})`,
desktop: `@media (min-width: ${breakpoints.desktop})`,
wide: `@media (min-width: ${breakpoints.wide})`
};
const Title = styled.h1`
font-size: 24px;

${media.tablet} {
font-size: 32px;
}

${media.desktop} {
font-size: 48px;
}
`;

Helper function for media queries:

const sizes = {
mobile: 576,
tablet: 768,
desktop: 992,
wide: 1200
};
const media = Object.keys(sizes).reduce((acc, label) => {
acc[label] = (...args) => css`
@media (min-width: ${sizes[label]}px) {
${css(...args)}
}
`;
return acc;
}, {});
// Usage
const Container = styled.div`
padding: 10px;

${media.tablet`
padding: 20px;
`}

${media.desktop`
padding: 30px;
max-width: 1200px;
`}
`;

Animations

Keyframes:

import styled, { keyframes } from 'styled-components';
const fadeIn = keyframes`
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
`;
const Card = styled.div`
animation: ${fadeIn} 0.3s ease-in;
`;
const spin = keyframes`
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
`;
const Spinner = styled.div`
width: 40px;
height: 40px;
border: 4px solid #f3f3f3;
border-top: 4px solid #007bff;
border-radius: 50%;
animation: ${spin} 1s linear infinite;
`;

Global Styles

Apply global styles:

import { createGlobalStyle } from 'styled-components';
const GlobalStyle = createGlobalStyle`
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}

body {
font-family: ${props => props.theme.fonts.main};
color: ${props => props.theme.colors.text};
background-color: ${props => props.theme.colors.background};
line-height: 1.6;
}

h1, h2, h3, h4, h5, h6 {
margin-bottom: 1rem;
font-weight: 600;
}

a {
color: ${props => props.theme.colors.primary};
text-decoration: none;

&:hover {
text-decoration: underline;
}
}
`;
// Use in app
function App() {
return (
<ThemeProvider theme={theme}>
<GlobalStyle />
<YourApp />
</ThemeProvider>
);
}

attrs Helper

Set default attributes:

const Input = styled.input.attrs(props => ({
type: props.type || 'text',
size: props.$size || '1em'
}))`
padding: 10px;
font-size: ${props => props.size};
border: 1px solid #ccc;
border-radius: 4px;
`;
// Usage
<Input placeholder="Name" /> {/* type="text" */}
<Input type="email" placeholder="Email" />
<Input $size="1.2em" />

Set dynamic attrs:

const ProgressBar = styled.div.attrs(props => ({
style: {
width: `${props.$progress}%`
}
}))`
height: 20px;
background-color: #007bff;
border-radius: 10px;
transition: width 0.3s ease;
`;
// Usage
<ProgressBar $progress={75} />

Component Composition

Build complex components:

const CardContainer = styled.div`
background: white;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
overflow: hidden;
`;
const CardHeader = styled.div`
padding: 20px;
background: ${props => props.theme.colors.primary};
color: white;
`;
const CardTitle = styled.h2`
margin: 0;
font-size: 24px;
`;
const CardBody = styled.div`
padding: 20px;
`;
const CardFooter = styled.div`
padding: 20px;
background: #f8f9fa;
border-top: 1px solid #dee2e6;
`;
// Use together
function Card({ title, children, footer }) {
return (
<CardContainer>
<CardHeader>
<CardTitle>{title}</CardTitle>
</CardHeader>
<CardBody>{children}</CardBody>
{footer && <CardFooter>{footer}</CardFooter>}
</CardContainer>
);
}

CSS Helper

Reuse CSS snippets:

import styled, { css } from 'styled-components';
const flexCenter = css`
display: flex;
align-items: center;
justify-content: center;
`;
const Button = styled.button`
${flexCenter}
padding: 10px 20px;
background: #007bff;
color: white;
`;
const Card = styled.div`
${flexCenter}
flex-direction: column;
padding: 20px;
`;

TypeScript Support

Type-safe styled components:

import styled from 'styled-components';
// Define theme type
interface Theme {
colors: {
primary: string;
secondary: string;
};
spacing: {
small: string;
medium: string;
large: string;
};
}
// Extend DefaultTheme
declare module 'styled-components' {
export interface DefaultTheme extends Theme {}
}
// Type-safe props
interface ButtonProps {
$primary?: boolean;
$size?: 'small' | 'medium' | 'large';
}
const Button = styled.button<ButtonProps>`
background-color: ${props =>
props.$primary ? props.theme.colors.primary : props.theme.colors.secondary
};
padding: ${props => {
switch (props.$size) {
case 'small': return props.theme.spacing.small;
case 'large': return props.theme.spacing.large;
default: return props.theme.spacing.medium;
}
}};
`;

Common Patterns

Pattern 1: Flexbox Grid

const Row = styled.div`
display: flex;
flex-wrap: wrap;
margin: -10px;
`;
const Col = styled.div`
flex: ${props => props.$size || 1};
padding: 10px;

@media (max-width: 768px) {
flex: 0 0 100%;
}
`;
// Usage
<Row>
<Col $size={2}>Main Content</Col>
<Col $size={1}>Sidebar</Col>
</Row>

Pattern 2: Conditional Styling

const Alert = styled.div`
padding: 15px;
border-radius: 4px;

${props => {
switch (props.$variant) {
case 'success':
return css`
background-color: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
`;
case 'error':
return css`
background-color: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
`;
case 'warning':
return css`
background-color: #fff3cd;
color: #856404;
border: 1px solid #ffeaa7;
`;
default:
return css`
background-color: #d1ecf1;
color: #0c5460;
border: 1px solid #bee5eb;
`;
}
}}
`;

Summary: Your Styled Components Checklist

Basic Usage:

  • styled.element`` creates styled component
  • Write CSS in template literal
  • Use like regular React component
  • Styles scoped automatically

Props:

  • Access props in template literal
  • Use $ prefix for transient props
  • Dynamic styles based on props
  • Conditional styling with props

Theme:

  • ThemeProvider for global theme
  • Access theme in any styled component
  • Multiple themes for dark mode
  • Type-safe themes with TypeScript

Advanced:

  • Extend components with styled()
  • Keyframes for animations
  • Global styles with createGlobalStyle
  • Media queries for responsive
  • attrs for default attributes

Practice Challenges

Build styled components:

Challenge 1: Button Library Create reusable button variants.

Challenge 2: Card Components Build composable card system.

Challenge 3: Form Elements Style all form inputs consistently.

Challenge 4: Layout System Create grid and flex utilities.

Challenge 5: Dark Mode Implement theme switching.

You are at post 55 of the 75-post series. Styled Components are now in your toolkit.

--

--

Yakhilesh
Yakhilesh

Written by Yakhilesh

I write about work, learning, and the things we overcomplicate. Clear thinking, honest notes from real experience. No hype. akhilesh.pages.dev