Advanced Usage
Theming
styled-components has full theming support by exporting a <ThemeProvider> wrapper component.
This component provides a theme to all React components underneath itself via the context API. In the render
tree all styled-components will have access to the provided theme, even when they are multiple levels deep.
React Server Components (v6.3.0+): ThemeProvider is a no-op in RSC environments since React context is unavailable. Use CSS custom properties for theming in RSC instead. See the RSC section for best practices.
To illustrate this, let's create our Button component, but this time we'll pass some variables down as a theme.
Function themes
You can also pass a function for the theme prop. This function will receive the parent theme, that is from
another <ThemeProvider> higher up the tree. This way themes themselves can be made contextual.
This example renders our above themed Button and a second one that uses a second ThemeProvider to invert the
background and foreground colors. The function invertTheme receives the upper theme and creates a new one.
Getting the theme without styled components
via withTheme higher-order component
If you ever need to use the current theme outside styled components (e.g. inside big components), you can use
the withTheme higher order component.
import { withTheme } from 'styled-components' function MyComponent({ theme }) { console.log('Current theme: ', theme) // ... } export default withTheme(MyComponent)
via useTheme hook
You can also use useTheme to access the current theme outside of styled components when working with React Hooks.
import { useTheme } from 'styled-components' const MyComponent = () => { const theme = useTheme() console.log('Current theme: ', theme) // ... }
The theme prop
A theme can also be passed down to a component using the theme prop.
This is useful to circumvent a missing ThemeProvider or to override it.
Server Side Rendering v2+
styled-components supports concurrent server side rendering, with stylesheet rehydration.
The basic idea is that every time you render your app on the server, you can create
a ServerStyleSheet and add a provider to your React tree, that accepts styles
via a context API.
This doesn't interfere with global styles, such as keyframes or createGlobalStyle and
allows you to use styled-components with React DOM's various SSR APIs.
React Server Components v6.3.0+
styled-components now natively supports React Server Components (RSC) with zero configuration. In RSC environments, styled components automatically emit inline <style> tags that React 19 hoists and deduplicates.
Key behaviors in RSC:
- No
'use client'directive required ThemeProviderandStyleSheetManagerbecome pass-through components (no-ops)- CSS is automatically injected inline
Best practices for RSC:
| Do | Don't |
|---|---|
| Use static styles | Use dynamic interpolations (serialization overhead) |
Use data attributes for variants (&[data-size='lg']) | Use props for discrete style variants |
Use CSS custom properties via inline style prop | Rely on ThemeProvider (no-op in RSC) |
| Use build-time CSS variable generation for theming | Use runtime theme context |
Theming with CSS custom properties:
Since ThemeProvider is a no-op in RSC, use CSS custom properties for theming instead. Variables set on a parent element cascade to all DOM children:
const Container = styled.div``; const Button = styled.button` background: var(--color-primary, blue); `; <Container style={{ '--color-primary': 'orchid' }}> <Button>Inherits orchid background</Button> </Container>
Tooling setup
To ensure deterministic component IDs for server/client consistency, use our Babel plugin or SWC plugin. Both provide equivalent functionality including SSR support, minification, and better debugging.
If you use React Server Components (v6.3.0+), the plugins are optional for SSR since styled-components generates deterministic IDs automatically in RSC mode. They still provide benefits like minification and better debugging.
For Next.js users, the SWC plugin is built-in — just add styledComponents: true to your next.config.js compiler options.
Example
The basic API goes as follows:
import { renderToString } from 'react-dom/server'; import { ServerStyleSheet } from 'styled-components'; const sheet = new ServerStyleSheet(); try { const html = renderToString(sheet.collectStyles(<YourApp />)); const styleTags = sheet.getStyleTags(); // or sheet.getStyleElement(); } catch (error) { // handle error console.error(error); } finally { sheet.seal(); }
The collectStyles method wraps your element in a provider. Optionally you can use
the StyleSheetManager provider directly, instead of this method. Just make sure not to
use it on the client-side.
import { renderToString } from 'react-dom/server'; import { ServerStyleSheet, StyleSheetManager } from 'styled-components'; const sheet = new ServerStyleSheet(); try { const html = renderToString( <StyleSheetManager sheet={sheet.instance}> <YourApp /> </StyleSheetManager> ); const styleTags = sheet.getStyleTags(); // or sheet.getStyleElement(); } catch (error) { // handle error console.error(error); } finally { sheet.seal(); }
The sheet.getStyleTags() returns a string of multiple <style> tags.
You need to take this into account when adding the CSS string to your HTML output.
Alternatively the ServerStyleSheet instance also has a getStyleElement() method
that returns an array of React elements.
If rendering fails for any reason it's a good idea to use try...catch...finally to ensure that the sheet object will always be available for garbage collection. Make sure sheet.seal() is only called after sheet.getStyleTags() or sheet.getStyleElement() have been called otherwise a different error will be thrown.
sheet.getStyleTags() and sheet.getStyleElement() can only be called after your element is rendered. As a result, components from sheet.getStyleElement() cannot be combined with <YourApp /> into a larger component.
Next.js
App Router (recommended)
With styled-components v6.3.0+, RSC is natively supported with zero configuration. Styled components work in Server Components without a 'use client' directive or registry setup. See the React Server Components section above for best practices.
With styled-components v6.0-6.2, you need to put a styled-components registry in one of your layout files, as described in Next.js docs. The 'use client' directive is required, so styled-components will appear in your client bundle.
Pages Router
Add styledComponents: true to the compiler options in next.config.js, then modify _document with getInitialProps to support SSR. See the Next.js styled-components example for reference.
If you're using Babel instead of SWC, refer to the Babel-based example.
Gatsby
Gatsby has an official plugin that enables server-side rendering for styled-components.
Refer to the plugin's page for setup and usage instructions.
Streaming Rendering
styled-components offers streaming APIs for use with React's server rendering methods.
renderToPipeableStream (React 18+) v6.2.0+
The recommended approach for streaming SSR uses React 18's renderToPipeableStream:
On the server:
import { renderToPipeableStream } from 'react-dom/server'; import { ServerStyleSheet } from 'styled-components'; const sheet = new ServerStyleSheet(); const { pipe } = renderToPipeableStream( sheet.collectStyles(<YourApp />), { onShellReady() { // Set headers and pipe res.setHeader('content-type', 'text/html'); pipe(res); }, onAllReady() { sheet.seal(); }, } );
On the client:
import { hydrateRoot } from 'react-dom/client'; hydrateRoot(document.getElementById('root'), <YourApp />);
renderToNodeStream (legacy)
renderToNodeStream is deprecated in React 18. Use renderToPipeableStream instead.
For legacy codebases still using renderToNodeStream, styled-components wraps the readable stream to interleave style blocks as HTML chunks are emitted:
import { renderToNodeStream } from 'react-dom/server'; import styled, { ServerStyleSheet } from 'styled-components'; const sheet = new ServerStyleSheet(); const jsx = sheet.collectStyles(<Heading>Hello SSR!</Heading>); const stream = sheet.interleaveWithNodeStream(renderToNodeStream(jsx)); stream.pipe(res, { end: false }); stream.on('end', () => res.end('</body></html>'));
After client-side rehydration is complete, styled-components will take over as usual and inject any further dynamic styles after the relocated streaming ones.
Performance
styled-components generates CSS classes dynamically. Understanding when and why new classes are created helps you write faster components.
Avoid declaring styled components in render
Defining a styled component inside a render function creates a new component identity on every render. React discards and remounts the entire subtree, losing DOM state (focus, scroll position, input values) and injecting new style rules each time.
// Bad: new component identity every render const Header = () => { const Title = styled.h1`font-size: 10px;`; return <Title>Hello</Title>; }; // Good: stable identity, defined at module scope const Title = styled.h1`font-size: 10px;`; const Header = () => <Title>Hello</Title>;
CSS custom properties vs prop interpolation
Every unique interpolation result generates a new CSS class. For values that change frequently (colors from a picker, positions from mouse events, animation progress), this can produce hundreds of classes. styled-components warns at 200+.
CSS custom properties avoid this entirely — the CSS rule stays the same and the browser updates the variable natively:
// Bad: new CSS class for every distinct color const Box = styled.div<{ $color: string }>` background: ${props => props.$color}; `; // Good: one CSS class, variable updated via inline style const Box = styled.div` background: var(--box-color, blue); `; <Box style={{ '--box-color': `hsl(${hue}, 70%, 50%)` } as React.CSSProperties} />
Use attrs for rapidly-changing values
When a value changes on every frame (mouse position, scroll offset), use attrs with a style object. This applies values as inline styles — no class generation at all:
// Bad: generates a new CSS class on every mouse move const Cursor = styled.div<{ $x: number; $y: number }>` position: fixed; left: ${props => props.$x}px; top: ${props => props.$y}px; `; // Good: inline style, zero class generation const Cursor = styled.div.attrs<{ $x: number; $y: number }>(props => ({ style: { left: `${props.$x}px`, top: `${props.$y}px`, }, }))` position: fixed; width: 20px; height: 20px; border-radius: 50%; background: red; `;
Data attributes for discrete variants
For finite variant sets (size, color scheme, state), data attributes keep styles fully static — no interpolation functions, one cached class:
// Dynamic: interpolation function runs every render const Button = styled.button<{ $variant: 'primary' | 'secondary' }>` background: ${props => props.$variant === 'primary' ? '#BF4F74' : '#ccc'}; `; // Static: CSS attribute selectors, no JS in the template const Button = styled.button` background: #ccc; color: #333; &[data-variant='primary'] { background: #BF4F74; color: white; } `; <Button data-variant="primary">Click</Button>
This pattern is especially valuable in RSC environments, where dynamic interpolations incur serialization overhead.
Static vs dynamic components
A styled component is static if its template contains no function interpolations (references to other styled components are fine). Static components generate their class once and cache it — subsequent renders skip style generation entirely.
// Static: cached after first render const Card = styled.div` padding: 16px; border: 1px solid #eee; `; // Dynamic: hash recomputed every render const Card = styled.div<{ $elevated: boolean }>` box-shadow: ${props => props.$elevated ? '0 4px 12px rgba(0,0,0,0.1)' : 'none'}; `; // Static alternative using data attribute const Card = styled.div` &[data-elevated] { box-shadow: 0 4px 12px rgba(0,0,0,0.1); } `;
SSR payload considerations
In SSR (and especially RSC), every unique dynamic class is serialized into the HTML. A list of 500 items with unique interpolated colors produces 500 CSS rules in the server output. Prefer static styles with inline style for per-item uniqueness:
// Bad: 500 CSS rules in SSR output const Item = styled.li<{ $hue: number }>` background: hsl(${props => props.$hue}, 70%, 90%); `; // Good: 1 CSS rule + compact inline styles const Item = styled.li` padding: 8px; `; <Item style={{ background: `hsl(${hue}, 70%, 90%)` }}>...</Item>
Existing CSS
There are a couple of implementation details that you should be aware of, if you choose to use styled-components together with existing CSS.
styled-components generates an actual stylesheet with classes, and attaches those classes to
the DOM nodes of styled components via the className prop.
It injects the generated stylesheet at the end of the head of the document during runtime.
Styling normal React components
If you use the styled(MyComponent) notation and MyComponent does not
render the passed-in className prop, then no styles will be applied.
To avoid this issue, make sure your component attaches the passed-in className to a DOM node:
function MyComponent({ className }) { return <div className={className} />; }
If you have pre-existing styles with a class, you can combine the global class with the passed-in one:
function MyComponent({ className }) { return <div className={`some-global-class ${className}`} />; }
Issues with specificity
If you apply a global class together with a styled component class, the result might not be what you're expecting. If a property is defined in both classes with the same specificity, the last one will win.
// MyComponent.js const MyComponent = styled.div`background-color: green;`; // my-component.css .red-bg { background-color: red; } // For some reason this component still has a green background, // even though you're trying to override it with the "red-bg" class! <MyComponent className="red-bg" />
In the above example the styled component class takes precedence over the global class, since
styled-components injects its styles during runtime at the end of the <head> by default.
Thus its styles win over other single classname selectors.
One solution is to bump up the specificity of the selectors in your stylesheet:
/* my-component.css */ .red-bg.red-bg { background-color: red; }
Avoiding conflicts with third-party styles and scripts
If you deploy styled-components on a page you don't fully control, you may need to take precautions to ensure that your component styles don't conflict with those of the host page.
The most common problem is insufficient specificity. For example, consider a host page with this style rule:
body.my-body button { padding: 24px; }
Since the rule contains a classname and two tag names, it has higher specificity than the single classname selector generated by this styled component:
styled.button` padding: 16px; `
There's no way to give your components complete immunity from the host page's styles, but you can
at least boost the specificity of their style rules with
babel-plugin-styled-components-css-namespace,
which allows you to specify a CSS namespace for all of your styled components. A good namespace
would be something like #my-widget, if all of your styled-components render in a container
with id="my-widget", since ID selectors have more specificity than any number of classnames.
A rarer problem is conflicts between two instances of styled-components on the page. You can avoid
this by defining process.env.SC_ATTR in the code bundle with your styled-components instance.
This value overrides the default <style> tag attribute, data-styled, allowing
each styled-components instance to recognize its own tags.
Referring to other components
This is a web-specific API. It is not available in React Native.
There are many ways to apply contextual overrides to a component's styling. That being said, it rarely is easy without rigging up a well-known targeting CSS selector paradigm and then making them accessible for use in interpolations.
styled-components solves this use case cleanly via the "component selector" pattern. Whenever
a component is created or wrapped by the styled() factory function, it is also assigned a
stable CSS class for use in targeting. This allows for extremely powerful composition patterns
without having to fuss around with naming and avoiding selector collisions.
A practical example: here, our Icon component defines its response to the parent Link being hovered:
We could have nested the color-changing rule within our Link component, but then we'd have to consider both sets of rules to understand why Icon behaves as it does.
Caveat
This behaviour is only supported within the context of Styled Components:
attempting to mount B in the following example will fail because component
A is not a Styled Component.
const A = () => <div />; const B = styled.div` ${A} { } `
The error occurs because the styled component is attempting to call the component as an interpolation function, but it is not a Styled Component.
However, wrapping A in a styled() factory makes it eligible for interpolation -- just
make sure the wrapped component passes along className.
const A = ({ className }) => <div className={className} />; const StyledA = styled(A)`` const B = styled.div` ${StyledA} { } `
Style Objects
styled-components optionally supports writing CSS as JavaScript objects instead of strings. This is particularly useful when you have existing style objects and want to gradually move to styled-components.
Accessibility
styled-components generates CSS classes dynamically, which means standard JSX accessibility linting tools may not detect issues. Here's how to maintain accessible components.
Start with semantic elements
Use the correct HTML element as the base for your styled component. This gives you built-in keyboard handling, focus management, and screen reader semantics for free:
// Good: semantic base elements const Nav = styled.nav`...`; const Button = styled.button`...`; const Article = styled.article`...`; // Avoid: div soup const Nav = styled.div`...`; const Button = styled.div`...`;
Use the as prop when the same visual component needs different semantics depending on context:
const Text = styled.p` font-size: 1rem; line-height: 1.5; `; <Text>Body paragraph</Text> <Text as="label" htmlFor="name">Name</Text> <Text as="span">Inline text</Text>
Default accessible attributes with attrs
Use .attrs() to set accessible defaults that apply to every instance:
const IconButton = styled.button.attrs<{ $label: string }>(props => ({ 'aria-label': props.$label, type: 'button', }))` background: none; border: none; cursor: pointer; `; <IconButton $label="Close dialog"> <CloseIcon /> </IconButton>
Linting
Standard eslint-plugin-jsx-a11y does not recognize styled-components — it only inspects JSX element names and cannot determine that <StyledButton> is a <button>. Use eslint-plugin-styled-components-a11y instead, which parses styled-components' tagged template literals to identify the underlying HTML element.
npm install --save-dev eslint-plugin-styled-components-a11y
ESLint flat config:
import styledA11y from 'eslint-plugin-styled-components-a11y'; export default [ styledA11y.flatConfigs.recommended, ];
The plugin provides 36 rules mirroring eslint-plugin-jsx-a11y, including alt-text, aria-role, click-events-have-key-events, label-has-associated-control, and more.
Runtime auditing
For runtime accessibility checks, use @axe-core/react in development. It audits the rendered DOM and logs violations to the browser console — it works with any styling approach since it inspects the final HTML:
if (process.env.NODE_ENV === 'development') { import('@axe-core/react').then(({ default: axe }) => { axe(React, ReactDOM, 1000); }); }
For test-time auditing, jest-axe (or vitest-axe) runs axe-core against rendered components:
import { render } from '@testing-library/react'; import { axe, toHaveNoViolations } from 'jest-axe'; expect.extend(toHaveNoViolations); test('button is accessible', async () => { const { container } = render(<StyledButton>Click me</StyledButton>); expect(await axe(container)).toHaveNoViolations(); });
Refs
Passing a ref prop to a styled component will give you one of two things depending on the styled target:
- the underlying DOM node (if targeting a basic element, e.g.
styled.div) - a React component instance (if targeting a custom component)
Security
Since styled-components allows you to use arbitrary input as interpolations, you must be careful to sanitize that input. Using user input as styles can lead to any CSS being evaluated in the user's browser that an attacker can place in your application.
This example shows how bad user input can even lead to API endpoints being called on a user's behalf.
// Oh no! The user has given us a bad URL! const userInput = '/api/withdraw-funds' const ArbitraryComponent = styled.div` background: url(${userInput}); /* More styles here... */ `
Be very careful! This is obviously a made-up example, but CSS injection can be unobvious and have bad repercussions.
You can use CSS.escape to sanitize CSS from JavaScript. It is well-supported in all modern browsers.
Tagged Template Literals
Tagged Template Literals are an ES6 feature. They let you define custom string interpolation rules, which is how we're able to create styled components.
If you pass no interpolations, the first argument your function receives is an array with a string in it.
// These are equivalent: fn`some string here`; fn(['some string here']);
Once you pass interpolations, the array contains the passed string, split at the positions of the interpolations. The rest of the arguments will be the interpolations, in order.
const aVar = 'good'; // These are equivalent: fn`this is a ${aVar} day`; fn(['this is a ', ' day'], aVar);
This is a bit cumbersome to work with, but it means that we can receive variables, functions, or mixins
(css helper) in styled components and can flatten that into pure CSS.
Speaking of which, during flattening, styled-components ignores interpolations that evaluate to undefined, null,
false, or an empty string (""), which means you're free to use
short-circuit evaluation
to conditionally add CSS rules.
const Title = styled.h1<{ $upsideDown?: boolean; }>` /* Text centering won't break if props.$upsideDown is falsy */ ${props => props.$upsideDown && 'transform: rotate(180deg);'} text-align: center; `;
If you want to learn more about tagged template literals, check out Max Stoiber's article: The magic behind 💅🏾 styled-components