What is styled-components?
styled-components is a CSS-in-JS library. Instead of writing a stylesheet and referencing classes, you write CSS inside a JavaScript file and attach it to a component. The library generates a unique class name, injects the rule into the page and hands you back a normal React component.
// Button.jsx
import styled from "styled-components";
const Button = styled.button`
padding: 0.5rem 1rem;
border: none;
border-radius: 0.5rem;
background: #2563eb;
color: white;
`;
export default function App() {
return <Button>Save</Button>;
}
The styled.button call is a tagged template: the CSS string is parsed by the library, not by the browser. The result is a component you can render, style, extend and theme like any other.
Writing CSS in a template
Inside the template you write ordinary CSS, including nesting, media queries and pseudo-selectors. The ampersand refers to the generated class.
// Card.jsx
const Card = styled.article`
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 1rem;
padding: 1.5rem;
transition: border-color 0.2s;
&:hover {
border-color: rgba(255, 255, 255, 0.2);
}
@media (min-width: 768px) {
padding: 2rem;
}
`;
Because the styles are scoped to the generated class, you can write the natural selector without worrying about the rest of the app.
Dynamic styles with props
The real advantage appears when styles depend on data. Any function inside the template receives the component’s props and can return a value.
// Badge.jsx
const Badge = styled.span`
display: inline-flex;
align-items: center;
padding: 0.125rem 0.5rem;
border-radius: 999px;
font-size: 0.75rem;
background: ${(props) => (props.$tone === "success" ? "#16a34a" : "#dc2626")};
color: white;
`;
<Badge $tone="success">Active</Badge>;
Prefix transient props with $ so they are consumed by the style function and not forwarded to the DOM element, which avoids React warnings about unknown attributes.
Extending and composing
You can build on an existing styled component, which keeps a shared base in one place.
// Buttons.jsx
const Button = styled.button`
padding: 0.5rem 1rem;
border: none;
border-radius: 0.5rem;
font-weight: 500;
`;
const Primary = styled(Button)`
background: #2563eb;
color: white;
`;
const Ghost = styled(Button)`
background: transparent;
color: #e5e7eb;
border: 1px solid rgba(255, 255, 255, 0.15);
`;
The as prop lets a component render a different element while keeping its styles, which is useful for semantic HTML.
// as.jsx
<Button as="a" href="/docs">Read the docs</Button>
Theming
The ThemeProvider shares design tokens with every styled component below it. Components read them from props.theme.
// theme.jsx
import { ThemeProvider } from "styled-components";
const theme = {
colors: { primary: "#2563eb", text: "#e5e7eb" },
space: { sm: "0.5rem", md: "1rem", lg: "2rem" },
};
function App() {
return (
<ThemeProvider theme={theme}>
<Page />
</ThemeProvider>
);
}
// Page.jsx
const Wrapper = styled.main`
color: ${(props) => props.theme.colors.text};
padding: ${(props) => props.theme.space.lg};
`;
To support multiple themes, swap the object passed to ThemeProvider. Every component re-renders with the new tokens, which makes light and dark modes straightforward.
Global styles and animations
createGlobalStyle injects document-level CSS such as resets, fonts and base typography.
// Global.jsx
import { createGlobalStyle } from "styled-components";
export const Global = createGlobalStyle`
*, *::before, *::after { box-sizing: border-box; }
body {
margin: 0;
font-family: system-ui, sans-serif;
background: #0b0d0c;
color: #e5e7eb;
}
`;
keyframes defines reusable animations that you reference inside a styled component.
// spinner.jsx
import { keyframes } from "styled-components";
const spin = keyframes`
to { transform: rotate(360deg); }
`;
const Spinner = styled.div`
width: 2rem;
height: 2rem;
border: 2px solid rgba(255, 255, 255, 0.2);
border-top-color: #2563eb;
border-radius: 50%;
animation: ${spin} 0.8s linear infinite;
`;
Attrs and defaults
attrs attaches default props or attributes once, so every instance shares them.
// input.jsx
const Input = styled.input.attrs({
type: "text",
autoComplete: "off",
})`
padding: 0.5rem 0.75rem;
border-radius: 0.5rem;
`;
Trade-offs to understand
styled-components is ergonomic, but it is not free.
- Runtime cost. Styles are generated and injected while the app runs, which adds work on the main thread.
- Bundle size. The library itself ships to the client.
- Server rendering. You must collect styles during SSR to avoid a flash of unstyled content.
- React Server Components. The library relies on client context and is not compatible with RSC, which is a significant limitation for modern React apps.
- Debugging. Generated class names are less readable in DevTools than hand-written ones.
None of these are fatal, but they are why many teams now prefer zero-runtime options. CSS Modules scope plain CSS with no runtime, Tailwind keeps styles in the markup, and vanilla-extract or StyleX extract static CSS from JavaScript at build time.
When to use styled-components
Reach for it when dynamic, prop-driven styles are central to your components, when you value colocating styles with logic, and when your app is a client-rendered React application without strict runtime budgets. It is a pleasure to use and makes theming clean.
Consider alternatives when you need the smallest possible runtime, when you are building with React Server Components, or when your team prefers writing CSS in .css files. The best choice depends on your constraints, not on which library is trending.
Best practices
- Use transient props (
$prop) so styling props never reach the DOM. - Define a small, explicit set of variants instead of many nested conditionals.
- Keep shared values in the theme and read them from
props.theme. - Use
createGlobalStylefor resets and fonts, and styled components for everything else. - Split components into their own files as they grow.
- Add a ServerStyleSheet when you render on the server.
- Measure the runtime cost if your app is large or performance-sensitive.
Common mistakes
- Passing styling props without
$and triggering DOM attribute warnings. - Recreating a styled component inside the render of another component, which remounts it every time.
- Building every style from props and losing readability.
- Forgetting SSR style collection and shipping a flash of unstyled content.
- Assuming it works with React Server Components when it does not.
- Choosing it by default without weighing the runtime cost.
Where to go next
styled-components is one answer to component styling. Compare it with CSS Modules for zero-runtime scoping and Tailwind CSS for utilities in markup, and lean on React fundamentals to get the most from the component model.