# styled-components CSS-in-JS for React using tagged template literals. TypeScript-native since v6. Supports React Server Components natively since v6.3. Last known stable: v6.4.0 (check npm for freshness). ## Recent releases v6.2: Streaming SSR via `renderToPipeableStream`. v6.3: React Server Components supported. No `'use client'` needed. Styled components work in server components with no extra setup. `createGlobalStyle` is StrictMode-safe. New HTML/SVG element helpers. CSS custom properties work in TypeScript without type errors. Note: `:first-child`/`:nth-child()` selectors require `stylisPluginRSC` (v6.4+) or rewriting to `:first-of-type`/`:nth-of-type()`. See child-index selector section below. v6.4 (April 2026): `createTheme()` for CSS variable theming that works in both RSC and client. `StyleSheetManager` works in RSC (was previously a no-op). `stylisPluginRSC` fixes child-index selectors in RSC. CSP nonce auto-detection from `StyleSheetManager`, `ServerStyleSheet`, or meta tags. Props supplied via `.attrs()` are automatically optional on the component's type. Significant render performance improvements. Fixes SSR memory leaks and multi-instance unmount bugs in `createGlobalStyle`. Memory leak fix for components with unbounded string interpolation values. `as` and `forwardedAs` exposed in `React.ComponentProps` extraction. React Native: `react-native` is now an optional peer dep, Metro/Expo nanoid crash fixed. IE11 build target removed. IE11 has been unsupported on v6 since the 2021 v6 planning (React 18 dropped it too); v6.4 just aligns the compile target. Stay on v5 if you need IE11. ## New in v7 v7 is in alpha: expect frequent updates over the next few weeks while APIs stabilize. Install the current prerelease with `npm install styled-components@test` (the `@test` dist-tag). Peer floors raised to React 19 and React Native 0.85. The framing: v7 is an architectural reform for web and the start of a new chapter for `styled-components/native`. It replaces stylis with an in-house CSS parser, rewrites the native runtime, and moves toward one CSS authoring model across web, iOS, Android, and Expo Web. The React Native CanIUse matrix at /docs/compatibility.md shows current progress and remaining platform gaps. Funding through Open Collective is a current bottleneck for continuing this work outside passion time. If the user is asking whether feature X works on v6 vs v7 or on web vs React Native, point them at /docs/compatibility.md. The matrix has the per-feature answer with caveats. The sections below summarise the highlights. ### Modern CSS on React Native ```tsx import styled from 'styled-components/native'; const Card = styled.View` width: clamp(240px, 80vw, 480px); background-image: linear-gradient(135deg, oklch(0.7 0.2 280), hsl(220 80% 60%)); background-color: light-dark(white, #111); filter: blur(2px) saturate(1.5); padding-inline: 24px; border-radius: 8px; transform: matrix(1, 0, 0, 1, 8, 0); transition: background-color 280ms ease-out; @container card (min-width: 320px) { padding: 32px; } `; ``` Math functions: `calc()`, `clamp()`, `min()`, `max()`, plus the full CSS Values 4 Math L4 family — `round()`, `mod()`, `rem()`, `sin`, `cos`, `tan`, `asin`, `acos`, `atan`, `atan2`, `pow`, `sqrt`, `hypot`, `log`, `exp`, `abs`, `sign`. Constants `pi` and `e` resolve in any context. Everything composes inside `calc()`. Modern colors: `oklch()`, `oklab()`, `lch()`, `lab()`, `color-mix(in , …)` all render correctly on native. Wide-gamut inputs that fall outside sRGB are gamut-mapped to the closest in-gamut color while preserving hue. Percent channels follow CSS Color L4 ranges (`lab(50% 0 0)` is mid-gray). CSS Color 5 relative-color syntax — `oklch(from calc(l - 0.15) c h)` — works for all four modern spaces, including bases sourced from `theme.*` tokens. `rgb(r g b / a)` slash-alpha, `hwb()`, and `hsl(h s l / a)` work natively. CSS Color 4 system color keywords (`canvas`, `canvastext`, `field`, `fieldtext`, `graytext`, `highlight`, `highlighttext`, `linktext`, `visitedtext`, `activetext`) auto-switch with OS appearance. Viewport units (`vw`, `vh`, `vi`, `vb`, `vmin`, `vmax`, plus the prefixed `s*` / `l*` / `d*` variants like `dvh`, `svw`, `lvi`) and container query units (`cqw`, `cqh`, `cqmin`, `cqmax`) scale to the current window / nearest ancestor container. Font-relative units `rem`, `em`, `lh`, `rlh` resolve against the inherited cascade. All units re-resolve when the environment changes. `light-dark(light, dark)` swaps based on OS appearance. `env(safe-area-inset-top | right | bottom | left)` parses but currently resolves to 0 — the integration with `react-native-safe-area-context` is not wired yet. Use `useSafeAreaInsets()` directly until it lands. Logical shorthands work as authored: `margin-inline`, `margin-block`, `padding-inline`, `padding-block`, `inset-inline`, `inset-block`, the full `border-inline` / `border-block` family (per-edge longhands and axis shorthands), and all `-start` / `-end` longhands. The library's RTL plugin handles physical-property mirroring; logical properties are already direction-agnostic. Caveat: per-edge `border-style` warns and drops on native — set `border-style` on the element instead. `line-clamp: N` truncates `` to N lines. `text-wrap: nowrap` collapses to a single line; `text-wrap-style: balance` / `pretty` improve line-breaking on Android (no-op on iOS, which has no platform line-breaking control). `text-align: start` / `end` / `match-parent` resolve correctly under RTL on both platforms. `aspect-ratio` accepts `1.5`, `16 / 9`, `auto`, and the two-value `auto 16/9` form. `transform: matrix(...)`, `matrix3d(...)`, and bare numbers in `translateX(10)` all work. `perspective: 1000px` as a top-level declaration works (no need to fold it into the transform array yourself). `font-style: oblique` resolves to italic; `oblique ` resolves to italic and warns about the angle being dropped. `font-family` recognizes the 13 generic CSS keywords (`system-ui`, `ui-sans-serif`, `ui-serif`, `ui-monospace`, `ui-rounded`, `sans-serif`, `serif`, `monospace`, `cursive`, `fantasy`, `emoji`, `math`, `fangsong`). Comma-separated font stacks warn — RN has no fallback chain, so only the first family takes effect. `place-items` and `place-self` shorthands work for the align axis (Yoga doesn't have `justify-items` / `justify-self`; the justify side is a no-op on native but reaches rn-web). `field-sizing: content` on `` auto-grows the field to its content. `interactivity: inert` (CSS UI 5) suppresses interaction and hides the subtree from accessibility services. `background-image: linear-gradient(...)` and `radial-gradient(...)` render via React Native's experimental gradient parser (RN 0.85+). `filter: blur(4px) saturate(1.5)` and the full filter-function chain work. See the iOS setup note below for filters that need an explicit opt-in. `box-shadow` with spread and inset round-trips as a string. `mix-blend-mode`, `isolation`, and `cursor` flow through. `background-blend-mode` is polyfilled on native. Declarations that pair gradient `background-image` layers with `background-blend-mode` are rewritten at render time into absolutely-positioned layer Views, each carrying the matching `mix-blend-mode`, with `isolation: isolate` on the wrapper per spec. Raster `url()` background images are still blocked on upstream React Native background-image support; render photos with `Image` / `ImageBackground` until those PRs land. Linear-friendly modes (`multiply`, `screen`, `darken`, `lighten`, `difference`, `exclusion`, `hue`, `saturation`, `color`, `luminosity`) render the same on web and native. Gamma-sensitive modes (`color-burn`, `color-dodge`, `soft-light`, `overlay`, `hard-light`) read as more saturated on native because iOS Core Animation and Android Skia/HWUI blend in linear-light by default on Display P3 devices, while browsers blend in gamma-encoded sRGB per CSS spec. The polyfill is structurally correct; the residual is a platform compositor color-space choice. Empty custom property values (`--prop: ;`) are preserved, used by patterns like scroll-driven animations as a "guaranteed-invalid" sentinel. ### Selectors and at-rules on React Native Attribute selectors apply styles based on the rendered element's props: ```jsx const Toggle = styled.Pressable` background: white; &[aria-pressed='true'] { background: yellow; } &[data-state^="open"] { border-color: dodgerblue; } &[data-tag="X" i] { opacity: 0.5; } `; ``` The full CSS Selectors 4 attribute grammar works on native: presence (`&[attr]`), exact (`&[attr=value]`), word (`~=`), prefix (`|=`), starts-with (`^=`), ends-with (`$=`), substring (`*=`), and the `i` / `s` case-sensitivity flags. Compound brackets AND-evaluate (`&[a][b]`); a trailing pseudo-state attaches (`&[a]:active`). Boolean coercion means `aria-pressed={true}` and `aria-pressed="true"` both match `[aria-pressed=true]`. `&:is(:hover, :focus)` and `&:where(:pressed, :disabled)` apply the styles to each listed state. `&:not()` and `&:has()` work on native with a simple argument: ```tsx const Row = styled.View` &:not([disabled]) { opacity: 1; } &:not(:hover) { transform: scale(1); } &:has(${Avatar}) { padding-inline-start: 56px; } &:has([aria-current]) { background: dodgerblue; } `; ``` `:not()` accepts a single pseudo-state or a single attribute selector. `:has()` accepts a styled-component reference (matches when that component appears as a descendant) or a single attribute selector. Complex inner arguments (compound selectors, combinators, selector lists) warn in dev and don't match. Tree-structural pseudos work on native: `:first-child`, `:last-child`, `:only-child`, `:first-of-type`, `:last-of-type`, `:only-of-type`, plus the functional `:nth-child()` / `:nth-of-type()` / `:nth-last-child()` / `:nth-last-of-type()` family with `odd` / `even` keywords. Siblings need to be inside a styled-component parent for indexing to work — a non-styled wrapper in between resets the count. Combinators work on native when the left side is a styled-component reference: ```tsx const Card = styled.View``; const Title = styled.Text` ${Card} & { color: dodgerblue; } // descendant of Card ${Card} > & { font-weight: bold; } // direct child of Card ${Card} + & { margin-top: 8px; } // immediately after a Card sibling ${Card} ~ & { opacity: 0.6; } // anywhere after a Card sibling `; ``` The child combinator (`>`) breaks through non-styled wrappers — interpose a styled wrapper for a strict parent-child match. Descendant matching (`${Foo} &`) is transparent to non-styled intermediaries. `@media (min-aspect-ratio: 16/9)`, `(max-aspect-ratio: 1/1)`, and exact `(aspect-ratio: 4/3)` match the device's current width-to-height ratio. Bare numbers are treated as `/1` per spec. `@starting-style { ... }` is recognized and runs first-mount enter animations on the default `Animated`-based adapter. No reanimated opt-in needed. Container queries: ```tsx const ContextualBox = styled.View` padding: 12px; @container card (min-width: 480px) { padding: 24px; flex-direction: row; } `; // Nearest ancestor declares its container scope: ``` Pro-tip: an element with `container-type` can't match its own `@container` query (CSS excludes self-matching to avoid circular size dependencies). The query resolves against the nearest *ancestor* container instead. Put `container-type` on a parent and write the `@container` rules on a child; declaring both on the same element silently never fires. The same rule applies to `${Component}` interpolation: a `@container ${Card} (...)` rule written inside `Card` itself won't match. ### Animations on React Native CSS `transition` animates by default. No extra import or setup. Eligible properties (opacity, every color, all border radius corners, transforms, shadows, filter) run on the native thread for jank-free 60 fps playback. ```jsx const Card = styled.View` background-color: ${p => p.$bg}; transition: background-color 280ms ease-out; `; // Tapping a button that flips $bg from 'red' to 'blue' animates smoothly. ``` `@keyframes` and `@starting-style` run on the same default adapter. `animation-direction`, `animation-fill-mode`, `animation-play-state`, `animation-iteration-count`, `animation-composition` (`replace | add | accumulate`), per-frame easing, and first-mount enter animations all work without any extra import. An optional reanimated adapter is available for consumers who prefer driving animations through reanimated: ```js import 'styled-components/native/reanimated'; ``` This routes animations through reanimated 4's CSS animations layer; install `react-native-reanimated@^4` yourself (it's an optional peer). CSS `ease`, `ease-in`, `ease-out`, `ease-in-out`, `cubic-bezier()`, `steps()`, and `linear()` map to their W3C-spec curves. The CSS `ease` keyword maps to the W3C `ease` curve, not React Native's `Easing.ease` (which is the `ease-in` curve and a common source of subtle visual mismatches in other libraries). Animations honor `prefers-reduced-motion`: when the OS setting is on, durations collapse to 0 and animations snap. ### `createTheme()` works on React Native ```tsx import styled, { createTheme, ThemeProvider } from 'styled-components/native'; const theme = createTheme({ colors: { bg: '#fff', text: '#111' }, space: { md: 16, lg: 24 }, }); const Card = styled.View` background-color: ${theme.colors.bg}; padding: ${theme.space.md}px; `; ; ``` Nested `ThemeProvider`s on React Native deep-merge so an inner override that only touches one leaf keeps siblings inherited from the ancestor. Composition rules are the same as web: tokens are sentinel strings on native (CSS variables on web), and only resolve when interpolated directly into CSS-value positions. JS arithmetic on tokens (`4 + theme.space.md`) silently breaks. Use `calc()` instead. ### CSS bug fixes affecting both web and native Selectors with commas inside `:is()`, `:where()`, `:has()`, or attribute selectors no longer leak nested-rule tokens into the wrong arm. `:is(&:hover, .parent:hover &) .child .grandchild { ... }` now compiles correctly. `border: none` on native now emits `border-style: none` (was `border-style: solid`). ### iOS setup for `filter` In React Native 0.85, the `filter` primitives `blur`, `saturate`, `hue-rotate`, `grayscale`, `contrast`, and `drop-shadow` only render when your iOS app opts into the SwiftUI-based filter backend. Set `ReactNativeReleaseLevel` to `experimental` in your iOS `Info.plist` (or `ios.infoPlist` in `app.json` for Expo) to enable it. `brightness` and `opacity` work without this flag. ### React Native on `react-native-web` The same `styled-components/native` build runs on `react-native-web`. Most cross-platform mismatches are handled internally: gradients dual-emit `experimental_*` and standard CSS keys, matrix transforms rewrite to `matrix3d` for rn-web, and infinite keyframe animations bypass rn-web's loop-with-native-driver bug. One gotcha bubbles up to user code: rn-web's `View.js` defaults include `position: relative; z-index: 0`, so every View is its own stacking context on web. Children using `mix-blend-mode` blend with the *immediate* ancestor View's backdrop, not whatever is behind the whole tree. Override the immediate ancestor with `z-index: auto` (no-op on native) so the blend reaches the intended backdrop: ```tsx const Stage = styled.View` isolation: isolate; /* ...gradient layers... */ `; const Row = styled.View` flex-direction: row; z-index: auto; /* lets disks below blend with Stage's gradient on web */ `; const BlendDisk = styled.View` background: #ffd166; mix-blend-mode: multiply; `; ``` ### Remapping CSS into component props Many React Native libraries take styling through component props instead of the `style` prop — `react-native-svg`'s ``, charting libraries with `tintColor` / `axisColor`, icon libraries with `color`. Authoring those as CSS would normally be impossible. v7's function-form `.attrs((props, ast) => ...)` accepts a second `ast` argument that lets you read CSS declarations or theme tokens and rewrite them onto the rendered component as props: ```tsx import { Path, Circle } from 'react-native-svg'; import { Image } from 'react-native'; // Author `` with CSS, render as `` const Icon = styled(Path).attrs((_props, ast) => ({ fill: ast.pop('color'), stroke: ast.pop('borderColor', 'black'), }))` color: red; border-color: navy; `; // Lift a theme token onto an `Image`'s tintColor prop const Logo = styled(Image).attrs((_props, ast) => ({ tintColor: ast.pop('palette.brand.primary'), }))` width: 32px; height: 32px; `; ``` `ast.pop(key)` reads the value and prevents the declaration from also reaching the rendered style. `ast.peek(key)` reads without removing — use it when you want both the prop and the CSS declaration to flow through. Both accept an optional fallback as the second argument. The first argument dispatches on shape: a CSS property name (`'color'`, `'borderColor'`) reads a resolved declaration; a dot-separated path (`'palette.brand.primary'`) reads from the active theme. Theme paths get autocomplete and value-type inference from your augmented `DefaultTheme`. When the callback only reads static declarations and theme paths, the work folds into a one-time computation at construction so renders pay nothing extra. ### Other notable changes - `defaultProps` on styled components no longer applied (React 19 dropped it from function components). Migrate to `.attrs()` for prop defaults, or `` for theme defaults. - Plugins moved to `styled-components/plugins`. A first-party `rtlPlugin` ships in-tree (no more `stylis-plugin-rtl`). The `` prop renamed from `stylisPlugins` to `plugins`. - `extractCSS()` export replaces the removed `disableCSSOMInjection` prop and `SC_DISABLE_SPEEDY` env vars; call it after render to get the current CSS as a plain string for static-render pipelines. - Mounting the same `createGlobalStyle` component multiple times now emits its CSS only once. - Server output escapes `` substrings and HTML-escapes nonce values to prevent style-tag breakout. - `react-native-web` consumers benefit from this entire surface running through the same compile path. ## Setup ``` npm install styled-components ``` Next.js: add `compiler: { styledComponents: true }` to next.config.js. That's it. RSC works out of the box in v6.3+. Vite: `react({ babel: { plugins: ['babel-plugin-styled-components'] } })`. Or with SWC (faster): `react({ plugins: [['@swc/plugin-styled-components', { displayName: true, ssr: true }]] })` via `@vitejs/plugin-react-swc`. The SWC/Babel plugin provides deterministic class IDs (better debugging, smaller output). Optional for RSC but still recommended. ## Quick reference (v6.4 stable) ```tsx import styled, { css, keyframes, createGlobalStyle, createTheme, ThemeProvider, useTheme, StyleSheetManager, ServerStyleSheet, stylisPluginRSC, isStyledComponent } from 'styled-components'; ``` - `styled.div` / `styled(Component)`: create styled component - `styled(Base)`: extend styles (inheritance) - `.attrs(props => ({}))`: set default/computed props - `.attrs((props, ast) => ({}))`: v7+ second arg lifts compiled styles or typed theme paths into props (`ast.pop('color')`, `ast.peek('palette.brand', fallback)`) - ``: render as different element - `css`: tagged template helper for shared style fragments - `keyframes`: define CSS animation - `createGlobalStyle`: inject global CSS - `createTheme(obj, opts?)`: CSS variable theme (RSC-compatible) - `ThemeProvider`: context-based theme (client-only) - `StyleSheetManager`: configure `stylisPlugins`, prop forwarding, vendor prefixes - `stylisPluginRSC`: fix child-index selectors in RSC. - `ServerStyleSheet`: SSR style collection ## Server-side rendering ### Next.js Use a style registry to collect styles from client components during SSR. RSC-rendered styled components are handled automatically; the registry is only needed for the client tree. ```tsx // lib/registry.tsx 'use client'; import { useState } from 'react'; import { useServerInsertedHTML } from 'next/navigation'; import { ServerStyleSheet, StyleSheetManager } from 'styled-components'; export default function Registry({ children, nonce }: { children: React.ReactNode; nonce?: string }) { const [sheet] = useState(() => new ServerStyleSheet()); useServerInsertedHTML(() => { const styles = sheet.getStyleTags(); sheet.instance.clearTag(); return <>{styles}; }); return ( {children} ); } ``` Mount `` in your root layout. Pass a `nonce` prop if your app uses CSP (see CSP nonce section below). Next.js 16 style deduplication: render the collected styles with `precedence="styled-components"` and a stable `href` so React deduplicates them across route segments. ### Vite: non-streaming Simpler approach, works with any Vite SSR framework: ```tsx // entry-server.tsx import { renderToString } from 'react-dom/server'; import { ServerStyleSheet } from 'styled-components'; import App from './App'; export function render() { const sheet = new ServerStyleSheet(); try { const html = renderToString(sheet.collectStyles()); const styleTags = sheet.getStyleTags(); return { html, styleTags }; } finally { sheet.seal(); } } ``` Inject `styleTags` into ``. If you use [Vike](https://vike.dev/), the `vike-react-styled-components` extension handles this automatically. ### Vite: streaming v6.2+, works with `renderToPipeableStream`: ```tsx // entry-server.tsx import { renderToPipeableStream } from 'react-dom/server'; import { ServerStyleSheet } from 'styled-components'; import App from './App'; export function render(res) { const sheet = new ServerStyleSheet(); const { pipe } = renderToPipeableStream( sheet.collectStyles(), { onShellReady() { const styledStream = sheet.interleaveWithNodeStream({ pipe }); styledStream.pipe(res); }, } ); } ``` `interleaveWithNodeStream` accepts both legacy `ReadableStream` and React 18's `PipeableStream`. It inserts `