Getting started
Installation
npm install styled-components
yarn add styled-components
pnpm add styled-components
v6+ includes TypeScript types by default.
Basic usage
import styled from "styled-components";
const Button = styled.button`
background: #bf4f74;
border-radius: 3px;
border: none;
color: white;
padding: 0.25em 1em;
`;
// Usage
<Button>Click me</Button>;
Creates a React component with scoped styles.
Quick example
const Container = styled.div`
text-align: center;
padding: 2rem;
`;
const Title = styled.h1`
font-size: 2.5em;
color: #bf4f74;
`;
function App() {
return (
<Container>
<Title>Hello World</Title>
</Container>
);
}
Dynamic styling
Props-based styles
const Button = styled.button<{ $primary?: boolean }>`
background: ${props =>
props.$primary ? '#BF4F74' : 'white'
};
color: ${props =>
props.$primary ? 'white' : '#BF4F74'
};
border: 2px solid #BF4F74;
`;
// Usage
<Button $primary>Primary</Button>
<Button>Normal</Button>
Use $ prefix for transient props (v5.1+).
Transient props
const Input = styled.input<{ $error?: boolean }>`
border-color: ${(p) => (p.$error ? "red" : "#ccc")};
`;
// $error won't be forwarded to DOM
<Input $error type="text" />;
Prevents invalid DOM attribute warnings.
Prop interpolation
const Box = styled.div<{
$size?: number;
$bg?: string;
}>`
width: ${(p) => p.$size || 100}px;
height: ${(p) => p.$size || 100}px;
background: ${(p) => p.$bg || "blue"};
`;
<Box $size={200} $bg="tomato" />;
Extending styles
Styled extension
const Button = styled.button`
color: #bf4f74;
border: 2px solid #bf4f74;
padding: 0.25em 1em;
`;
const TomatoButton = styled(Button)`
color: tomato;
border-color: tomato;
`;
Override styles in child component.
Polymorphic "as" prop
const Button = styled.button`
padding: 0.5em 1em;
border: 2px solid #BF4F74;
`;
// Render as different element
<Button as="a" href="/page">
Link Button
</Button>
<Button as="div">
Div Button
</Button>
Extending custom components
const Link = ({ className, children }) => (
<a className={className}>{children}</a>
);
const StyledLink = styled(Link)`
color: #bf4f74;
font-weight: bold;
`;
Component must accept className prop.
Pseudo-selectors & nesting
Pseudo-selectors
const Thing = styled.div`
color: blue;
&:hover {
color: red;
}
&::before {
content: "🎨";
}
&:first-child {
margin-top: 0;
}
`;
Always use & prefix (v6 requirement).
Nesting & combinators
const Article = styled.article`
& > p {
margin-bottom: 1em;
}
& + & {
margin-top: 2em; /* Adjacent sibling */
}
.child-class {
border: 1px solid #ccc;
}
`;
Targeting child elements
const Container = styled.div`
padding: 1rem;
h2 {
color: #bf4f74;
}
p {
line-height: 1.6;
}
`;
Media queries
Basic media queries
const Container = styled.div`
background: papayawhip;
@media (min-width: 768px) {
background: mediumseagreen;
}
@media (min-width: 1024px) {
background: rebeccapurple;
}
`;
Reusable breakpoints
const sizes = {
mobile: "320px",
tablet: "768px",
desktop: "1024px",
};
const media = {
mobile: `@media (min-width: ${sizes.mobile})`,
tablet: `@media (min-width: ${sizes.tablet})`,
desktop: `@media (min-width: ${sizes.desktop})`,
};
const Box = styled.div`
width: 100%;
${media.tablet} {
width: 50%;
}
${media.desktop} {
width: 33.33%;
}
`;
Container queries
const Card = styled.div`
container-type: inline-size;
h2 {
font-size: 1.5rem;
}
@container (min-width: 400px) {
h2 {
font-size: 2rem;
}
}
`;
Theming
ThemeProvider
import { ThemeProvider } from "styled-components";
const theme = {
colors: {
primary: "#BF4F74",
secondary: "mediumseagreen",
},
fonts: {
body: "Helvetica, sans-serif",
},
};
function App() {
return (
<ThemeProvider theme={theme}>
<Button>Themed Button</Button>
</ThemeProvider>
);
}
Using theme
const Button = styled.button`
color: ${(props) => props.theme.colors.primary};
font-family: ${(props) => props.theme.fonts.body};
background: ${({ theme }) => theme.colors.secondary};
`;
Function themes
// Merge with parent theme
<ThemeProvider theme={outerTheme => ({
...outerTheme,
colors: {
...outerTheme.colors,
primary: 'blue',
},
})}>
<Content />
</ThemeProvider>
// Invert theme
<ThemeProvider theme={({ fg, bg }) => ({ fg: bg, bg: fg })}>
<InvertedSection />
</ThemeProvider>
useTheme hook
import { useTheme } from "styled-components";
function MyComponent() {
const theme = useTheme();
return (
<div style={{ color: theme.colors.primary }}>
Current theme: {theme.name}
</div>
);
}
TypeScript
Declare theme type
// styled.d.ts
import "styled-components";
declare module "styled-components" {
export interface DefaultTheme {
borderRadius: string;
colors: {
primary: string;
secondary: string;
};
}
}
Place in project root or src/.
Type component props
interface ButtonProps {
$primary?: boolean;
$size?: "small" | "medium" | "large";
}
const Button = styled.button<ButtonProps>`
background: ${(p) => (p.$primary ? "blue" : "white")};
padding: ${(p) => {
switch (p.$size) {
case "small":
return "0.25em 0.5em";
case "large":
return "0.75em 1.5em";
default:
return "0.5em 1em";
}
}};
`;
Type theme access
const Heading = styled.h1`
color: ${({ theme }) => theme.colors.primary};
font-size: ${({ theme }) => theme.fontSizes.xl};
`;
// theme is fully typed via DefaultTheme
Animations
keyframes
import styled, { keyframes } from "styled-components";
const rotate = keyframes`
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
`;
const Spinner = styled.div`
animation: ${rotate} 2s linear infinite;
width: 50px;
height: 50px;
`;
Multiple animations
const fadeIn = keyframes`
from { opacity: 0; }
to { opacity: 1; }
`;
const slideUp = keyframes`
from { transform: translateY(20px); }
to { transform: translateY(0); }
`;
const Box = styled.div`
animation:
${fadeIn} 0.3s ease-out,
${slideUp} 0.3s ease-out;
`;
Conditional animations
const pulse = keyframes`
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
`;
const Button = styled.button<{ $loading?: boolean }>`
animation: ${(p) => (p.$loading ? pulse : "none")} 1s infinite;
`;
Global styles
createGlobalStyle
import { createGlobalStyle } from "styled-components";
const GlobalStyle = createGlobalStyle`
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: 'Helvetica Neue', sans-serif;
line-height: 1.6;
color: #333;
}
a {
text-decoration: none;
color: inherit;
}
`;
// Usage
function App() {
return (
<>
<GlobalStyle />
<Content />
</>
);
}
Global styles with props
const GlobalStyle = createGlobalStyle<{ $whiteColor?: boolean }>`
body {
color: ${(props) => (props.$whiteColor ? "white" : "black")};
background: ${(props) => (props.$whiteColor ? "black" : "white")};
}
`;
<GlobalStyle $whiteColor />;
Global styles with theme
const GlobalStyle = createGlobalStyle`
body {
font-family: ${({ theme }) => theme.fonts.body};
background: ${({ theme }) => theme.colors.background};
color: ${({ theme }) => theme.colors.text};
}
`;
<ThemeProvider theme={theme}>
<GlobalStyle />
<App />
</ThemeProvider>;
Advanced patterns
.attrs()
// Static attributes
const Input = styled.input.attrs({
type: "text",
placeholder: "Enter text...",
})`
border: 2px solid #bf4f74;
padding: 0.5em;
`;
// Dynamic attributes
const Input = styled.input.attrs<{ $size?: string }>((props) => ({
type: "text",
size: props.$size || "1em",
}))`
padding: ${(props) => props.size};
`;
Avoid re-rendering for static values.
css helper
import styled, { css } from "styled-components";
const sharedStyles = css`
background: papayawhip;
color: #bf4f74;
padding: 1rem;
`;
const Box = styled.div`
${sharedStyles}
border: 1px solid;
`;
const Card = styled.article`
${sharedStyles}
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
`;
css with props
const textStyles = css<{ $large?: boolean }>`
font-size: ${(p) => (p.$large ? "2rem" : "1rem")};
font-weight: ${(p) => (p.$large ? "bold" : "normal")};
`;
const Heading = styled.h1<{ $large?: boolean }>`
${textStyles}
color: #BF4F74;
`;
Referring to other components
const Icon = styled.svg`
width: 24px;
fill: #bf4f74;
transition: fill 0.2s;
`;
const Link = styled.a`
display: flex;
align-items: center;
gap: 0.5rem;
&:hover ${Icon} {
fill: rebeccapurple;
}
`;
// Usage
<Link href="/home">
<Icon />
Home
</Link>;
shouldForwardProp
const Comp = styled("div").withConfig({
shouldForwardProp: (prop) => !["hidden", "active"].includes(prop),
})<{ hidden?: boolean; active?: boolean }>`
display: ${(p) => (p.hidden ? "none" : "block")};
opacity: ${(p) => (p.active ? 1 : 0.5)};
`;
Prevent custom props from reaching DOM.
Style objects
// Object syntax alternative
const Box = styled.div({
background: "#BF4F74",
height: "50px",
width: "50px",
});
// With props
const PropsBox = styled.div<{ $bg?: string }>((props) => ({
background: props.$bg || "blue",
padding: "1rem",
}));
Server-side rendering
SSR setup
import { ServerStyleSheet } from "styled-components";
import { renderToString } from "react-dom/server";
const sheet = new ServerStyleSheet();
try {
const html = renderToString(sheet.collectStyles(<App />));
const styleTags = sheet.getStyleTags();
// Inject styleTags into <head>
const fullHtml = `
<!DOCTYPE html>
<html>
<head>${styleTags}</head>
<body><div id="root">${html}</div></body>
</html>
`;
} catch (error) {
console.error(error);
} finally {
sheet.seal();
}
Next.js integration
// next.config.js
module.exports = {
compiler: {
styledComponents: true,
},
};
Enables SWC transformation for styled-components.
Next.js App Router (v6.3.0+)
// Works in React Server Components
const ServerComponent = styled.div`
color: red;
padding: 1rem;
`;
// For theming, use CSS custom properties
const Button = styled.button`
background: var(--color-primary, blue);
color: white;
`;
// In RSC
<div style={{ "--color-primary": "orchid" }}>
<Button>Themed Button</Button>
</div>;
No 'use client' needed for basic styles.
StyleSheetManager
Vendor prefixes
import { StyleSheetManager } from "styled-components";
function App() {
return (
<StyleSheetManager enableVendorPrefixes>
<Content />
</StyleSheetManager>
);
}
Disabled by default in v6.
RTL support
import { StyleSheetManager } from "styled-components";
import stylisRTLPlugin from "stylis-plugin-rtl";
function App() {
return (
<StyleSheetManager stylisPlugins={[stylisRTLPlugin]}>
<Content />
</StyleSheetManager>
);
}
Custom stylis plugins
import { StyleSheetManager } from "styled-components";
const customPlugin = (context, content) => {
// Custom CSS transformation
};
<StyleSheetManager stylisPlugins={[customPlugin]}>
<App />
</StyleSheetManager>;
Common gotchas
Never define inside render
// ❌ BAD - Creates new component every render
function MyComponent() {
const Heading = styled.h1`
color: red;
`;
return <Heading>Title</Heading>;
}
// ✅ GOOD - Define outside
const Heading = styled.h1`
color: red;
`;
function MyComponent() {
return <Heading>Title</Heading>;
}
Breaks reconciliation and remounts component.
Use transient props
// ❌ BAD - Warning in console
const Div = styled.div<{ hidden: boolean }>`
display: ${(p) => (p.hidden ? "none" : "block")};
`;
<Div hidden={true} />; // hidden goes to DOM
// ✅ GOOD - Use $ prefix
const Div = styled.div<{ $hidden: boolean }>`
display: ${(p) => (p.$hidden ? "none" : "block")};
`;
<Div $hidden={true} />; // $hidden filtered out
v6 pseudo-selector changes
// ❌ BAD - Treated as child selector in v6
const Div = styled.div`
:hover {
color: red; /* Selects :hover children! */
}
`;
// ✅ GOOD - Always use &
const Div = styled.div`
&:hover {
color: red; /* Selects self on hover */
}
`;
Custom components need className
// ❌ BAD - Styles won't apply
const MyComponent = ({ children }) => <div>{children}</div>;
const Styled = styled(MyComponent)`
color: red;
`;
// ✅ GOOD - Forward className
const MyComponent = ({ className, children }) => (
<div className={className}>{children}</div>
);
const Styled = styled(MyComponent)`
color: red;
`;
keyframes in shared fragments
// ❌ BAD - Won't work in v4+
const rotate = keyframes`...`;
const styles = `animation: ${rotate} 2s;`;
// ✅ GOOD - Use css helper
import { css, keyframes } from "styled-components";
const rotate = keyframes`...`;
const styles = css`
animation: ${rotate} 2s;
`;
Also see
- Styled Components Documentation (styled-components.com)
- API Reference (styled-components.com)
- Advanced Usage Guide (styled-components.com)
- TypeScript Guide (styled-components.com)
- GitHub Repository (github.com)
- Migration Guide v5 → v6 (styled-components.com)