Styled Components
styled-components is a popular CSS-in-JS library that lets you write actual CSS inside your JavaScript files and attach it directly to a React component. Instead of juggling separate .css files and remembering to apply the right className, you create a component that already knows how it should look. Styles become scoped, reusable, and can react to props and a shared theme — all without leaving your component file.
Overview / How It Works
styled-components is not part of React itself; it is a separate npm package (npm install styled-components) that uses a JavaScript feature called tagged template literals. When you write styled.button`...`, you are calling the function styled.button and passing it a template literal containing raw CSS text. The library parses that CSS, generates a unique, hashed class name (something like sc-bdVaJa), and injects the real CSS rules into a <style> tag that it manages in the document head. The value returned by styled.button`...` is a brand-new React component. Rendering <Button>Click</Button> renders a real <button> element with that generated class name already applied.
Because the styles live next to the component that uses them, there is no risk of a class name colliding with styles defined elsewhere in a large app — every generated class name is unique to that styled component. You can also interpolate values inside the template literal, and those interpolations can be functions that receive the component’s props. That is what lets a styled component change its appearance based on state or props passed down from a parent, without writing any conditional className logic yourself.
A key mental-model point: a styled component is a normal component from React’s perspective. It re-renders whenever its props or context change, just like any function component. When a prop used in an interpolation changes, styled-components recomputes the CSS string, and if the result is new, it injects a new set of rules and swaps the class name on the underlying DOM node. React does not need to reconcile anything special here — it is just a class attribute changing on an element it already tracks, so the update is cheap.
Syntax
import styled from "styled-components";
const StyledTag = styled.htmlTag`
/* CSS rules here */
property: value;
&:hover { property: value; }
`;
| Part | Meaning |
|---|---|
styled.htmlTag |
Creates a styled version of a built-in HTML element, e.g. styled.div, styled.button, styled.input. |
styled(Component) |
Wraps an existing React component and adds styling to it, as long as that component forwards className to its root element. |
| Template literal | Contains real CSS. Nested selectors and pseudo-classes use the & character to refer to the component itself, e.g. &:hover. |
${props => ...} |
An interpolation function that receives the component’s props and returns a CSS value, making the style dynamic. |
ThemeProvider |
A context provider from styled-components that supplies a theme object to every styled component underneath it via props.theme. |
createGlobalStyle |
Creates a component that, when rendered once, injects application-wide CSS (resets, fonts, body styles). |
Examples
Example 1: A Basic Styled Button
import styled from "styled-components";
const Button = styled.button`
background-color: #6200ee;
color: white;
padding: 10px 20px;
border: none;
border-radius: 4px;
font-size: 16px;
cursor: pointer;
&:hover {
background-color: #3700b3;
}
`;
function App() {
return <Button>Click Me</Button>;
}
export default App;
This renders a purple button labeled "Click Me" that darkens on hover. Button is defined once, outside of any component function, at module scope. Every time <Button> is rendered, React reuses the same underlying styled component definition — only the generated class name is attached to a plain <button> in the actual DOM.
Example 2: Styling Based on Props
import styled from "styled-components";
const Alert = styled.div`
padding: 12px 16px;
border-radius: 6px;
color: white;
background-color: ${(props) => (props.type === "error" ? "#d32f2f" : "#388e3c")};
`;
function App() {
return (
<>
<Alert type="error">Something went wrong.</Alert>
<Alert type="success">Saved successfully.</Alert>
</>
);
}
export default App;
This renders two boxes: a red one reading "Something went wrong." and a green one reading "Saved successfully." The type prop is passed to Alert like any normal prop, and the interpolation function inside the template literal reads it to choose a background color. No manual className switching or separate CSS classes are needed.
Example 3: Theming and a Stateful Component
import { useState } from "react";
import styled, { ThemeProvider } from "styled-components";
const theme = {
primary: "#0070f3",
text: "#111",
};
const Box = styled.div`
padding: 20px;
border: 1px solid ${(props) => props.theme.primary};
border-radius: 8px;
color: ${(props) => props.theme.text};
text-align: center;
`;
const CountButton = styled.button`
background-color: ${(props) => props.theme.primary};
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
margin-top: 10px;
`;
function Counter() {
const [count, setCount] = useState(0);
return (
<Box>
<p>Count: {count}</p>
<CountButton onClick={() => setCount(count + 1)}>Increment</CountButton>
</Box>
);
}
function App() {
return (
<ThemeProvider theme={theme}>
<Counter />
</ThemeProvider>
);
}
export default App;
This renders a bordered box containing "Count: 0" and a blue "Increment" button; clicking the button increases the number shown. ThemeProvider wraps the tree and supplies the theme object to every styled component below it through props.theme, so Box and CountButton both pick up primary and text without those values being passed down as explicit props. Clicking the button calls setCount, React re-renders Counter, and the <p> text updates — the styled components themselves do not need to re-run any style calculation since their CSS does not depend on count.
How It Works Step by Step
On mount: React calls the styled component function. styled-components computes the CSS string (running any prop-interpolation functions), hashes it to a class name, and if that exact class name has not been injected yet, adds a new rule to its managed stylesheet in the document head. React then renders the underlying HTML tag with that class name attached, and the browser paints it with the injected styles already in place.
On a prop or theme update: The styled component re-renders like any function component. styled-components re-evaluates the interpolations with the new props. If the resulting CSS string is identical to one already generated, it reuses the existing class name (no new DOM work). If it differs, a new class is injected and swapped onto the element — the component itself is never unmounted, so its position in the tree, focus, and any local state are preserved.
On unmount: The DOM node is removed by React exactly as it would be for any element. styled-components does not need to do any special cleanup for the individual instance; the injected CSS rule for that class name stays in the stylesheet in case another instance needs it.
Common Mistakes
Mistake 1: Defining a styled component inside another component’s body.
function App() {
const Box = styled.div`
padding: 20px;
border: 1px solid #ccc;
`;
return <Box>Content</Box>;
}
Here styled.div is called on every render of App, which creates a brand-new component type each time. React sees a different component type on each render and unmounts the old DOM node before mounting a new one instead of updating it in place. This loses focus, resets CSS transitions, and hurts performance. Always define styled components at module scope (outside any component function) so the same component type is reused across renders:
const Box = styled.div`
padding: 20px;
border: 1px solid #ccc;
`;
function App() {
return <Box>Content</Box>;
}
Mistake 2: Forwarding style-only props straight onto the DOM.
const Card = styled.div`
background: ${(props) => (props.active ? "#e0f7fa" : "white")};
`;
function App() {
return <Card active>Hello</Card>;
}
If Card ever wraps a custom component instead of a plain HTML tag, a prop like active that exists purely for styling can be forwarded all the way down and end up spread onto a real DOM element, producing an invalid-attribute warning in the console. styled-components supports transient props, prefixed with $, that are used only for styling and are never passed down to the underlying element or component:
const Card = styled.div`
background: ${(props) => (props.$active ? "#e0f7fa" : "white")};
`;
function App() {
return <Card $active>Hello</Card>;
}
Mistake 3: Scattering createGlobalStyle across multiple components. Global styles should be defined once and rendered a single time near the root of the app (for example, inside App). Rendering multiple GlobalStyle components in different places makes it hard to reason about which rules are active and can cause them to be re-injected unnecessarily.
Best Practices
- Always define styled components outside of other component functions, at the top level of a module, so React can reuse the same component type across renders.
- Prefix props that exist only for styling with
$(transient props) so they never leak onto the DOM. - Use
ThemeProviderand a sharedthemeobject for colors, spacing, and fonts instead of hardcoding the same values in many styled components. - Extract shared style fragments with the
csshelper when the same block of CSS is reused across several styled components. - Name styled components clearly (
PrimaryButton,CardWrapper) so JSX stays readable, just like naming any other component. - Keep one
createGlobalStylecomponent and render it once near the root for resets and base typography. - Avoid overusing
styled(Component)on deeply nested custom components; make sure the wrapped component actually forwardsclassNameto its root element or the styling will silently do nothing.
Practice Exercises
- Create a styled
Cardcomponent with padding, a border-radius, and a box-shadow, then render three cards with different children. - Build a
Badgestyled component that accepts a$statustransient prop ("online","offline") and renders a green or gray background accordingly. - Set up a
themeobject withprimaryandsecondarycolors, wrap your app inThemeProvider, and useprops.themeinside at least two different styled components.
Summary
- styled-components lets you write real CSS inside JavaScript using tagged template literals, producing a React component with scoped, auto-generated class names.
- Interpolations inside the template literal are functions of props, which is how a styled component reacts to prop and theme changes.
ThemeProvidersupplies a sharedthemeobject to every styled component beneath it viaprops.theme.- Always define styled components at module scope, never inside another component’s render, to avoid unwanted remounts.
- Use
$-prefixed transient props for style-only data so it is never forwarded to the DOM. createGlobalStyleinjects app-wide CSS and should be rendered once near the root.
