Skip to content

Releases: styled-components/styled-components

styled-components@6.4.2-prerelease-20260516150535

16 May 15:06

Choose a tag to compare

d20b88e Fix a TypeScript error when wrapping a component whose props include an as prop with a non-string type (such as Next.js Link's as?: Url). The styled component now accepts either the styled-components polymorphism value or the wrapped component's own as type, so spreading the wrapped component's props onto the styled component is assignable again.

Full Changelog: styled-components@6.4.2-prerelease-20260514185513...styled-components@6.4.2-prerelease-20260516150535

styled-components@6.4.2-prerelease-20260514185513

14 May 18:56

Choose a tag to compare

styled-components@6.4.2-prerelease-20260514184932

14 May 18:50

Choose a tag to compare

No new changes since the previous release. (Debugging the changelog generation in the prerelease builds.)

Full Changelog: styled-components@6.4.2-prerelease-20260514182414...styled-components@6.4.2-prerelease-20260514184932

styled-components@6.4.2-prerelease-20260514182414

14 May 18:25
9945904

Choose a tag to compare

Patch Changes

  • 9945904: Restore TypeScript prop autocomplete inside the JSX of a styled component once the first attribute is typed.

  • 9945904: Apply all chain levels' styles when an extended styled component renders with the as prop under Preact's react-compat.

  • 9945904: Respect a custom toString on plain value objects (e.g. design tokens) when interpolated into a styled component, rather than walking the object's keys as CSS declarations.

  • 9945904: Fix a TypeScript error when wrapping a component whose props include an as prop with a non-string type (such as Next.js Link's as?: Url). The styled-components as and forwardedAs props now consistently override the wrapped component's same-named props instead of colliding with them.

  • 9945904: Restore reliable styling in production browser bundles built without a runtime process global.

Full Changelog: styled-components@6.4.1...styled-components@6.4.2-prerelease-20260514182414

styled-components@7.0.0-prerelease-20260513173704

13 May 17:39
8173410

Choose a tag to compare

styled-components@7.0.0-prerelease-20260513034901

13 May 03:51

Choose a tag to compare

Minor Changes

  • feacf77: React Native: :not(...) now works for simple selectors. Rules such as :not(:hover), :not(:focus), :not([disabled]), and :not([data-state='loading']) apply when the condition inside :not() is not true. More complex forms, including multiple selectors or nested descendant selectors, still show a development warning and are ignored on native.

  • feacf77: em, lh, and rlh length units now work on React Native. Values like padding: 1em, gap: 0.5lh, and min(10px, 5em) resolve against the current text size and line height, so typography-based spacing can be shared across web and native without rewriting everything to pixels.

    text-align: start, text-align: end, and text-align: match-parent now resolve correctly under both left-to-right and right-to-left writing directions on React Native. Authors get the same direction-aware behavior they get on the web; the previous fall-back-to-auto warning is removed.

    Components whose CSS declares font-size, line-height, or direction pass the resolved value to descendants, so one text size at the top of a card can drive the relative spacing inside it.

  • feacf77: React Native: descendant and child combinator selectors now work across styled components.

    const Card = styled.View`
      padding: 16px;
    `;
    const Title = styled.Text`
      font-size: 14px;
      ${Card} & {
        font-size: 18px;
      }
    `;
    
    <Card>
      <Title>Bigger inside a Card</Title>
    </Card>
    <Title>Default size when standalone</Title>

    The descendant form ${Card} & matches whenever the component is rendered anywhere inside Card. The child form ${Card} > & only matches when Card is the nearest styled parent. Regular React Native components can sit between styled components without breaking selector matching.

    The same selectors also work on web. This also fixes a web bug where ${Component} { ... } rules placed after another declaration could lose the component selector and target too broadly.

  • feacf77: React Native: field-sizing: content makes a TextInput autosize to its content.

    const Note = styled.TextInput`
      field-sizing: content;
      min-height: 44px;
      padding: 8px 12px;
      border: 1px solid #ddd;
    `;
    
    <Note placeholder="Start typing…" />

    The field grows in height as the user types, no controlled height state, no onContentSizeChange wiring. Pass multiline={false} explicitly to keep a fixed single-line field (a dev-time message points out that autosize is off in that case).

    On react-native-web the declaration is handed straight to the browser, which has supported field-sizing natively since Chrome 123.

  • feacf77: React Native: :has(<simple>) selector now works.

    const Card = styled.View`
      padding: 16px;
      &:has(${Icon}) {
        padding-left: 48px;
      }
      &:has([data-state='active']) {
        background-color: tomato;
      }
    `;
    
    <Card>
      <Icon />
    </Card>

    The rule checks the component's children at render time and applies when any descendant matches. Two forms are supported on native:

    • ${Component}: matches when the referenced styled component appears anywhere inside.
    • [attr] and [attr=value]: match when any descendant has the named prop. Value checks compare the rendered prop value as text, so aria-pressed={true} and aria-pressed="true" both match [aria-pressed='true'].

    More complex selectors inside :has(), such as descendant chains, sibling selectors, and nested :has() calls, are not supported on native yet.

  • feacf77: Relative color syntax now works in React Native styles. You can write values like oklch(from #f00 calc(l - 0.15) c h) to derive a new color from a base color, and styled-components converts the result to a color React Native can render consistently on iOS, Android, and the web. This works with oklch, oklab, lch, and lab.

    The base color can be a literal color, another modern color function, or a theme value such as oklch(from ${theme.colors.brand} calc(l - 0.15) c h). That makes it possible to build lighter, darker, or more transparent variants from one source color without maintaining a separate shade table.

  • feacf77: React Native: sibling combinator selectors and the :nth-child family now work.

    const Card = styled.View`
      padding: 16px;
    `;
    const Divider = styled.View`
      height: 1px;
      ${Card} + & {
        margin-top: 8px;
      }
    `;
    
    const ListItem = styled.View`
      padding: 8px;
      &:nth-child(odd) {
        background-color: gainsboro;
      }
      &:first-child {
        border-top-width: 0;
      }
    `;

    Supported selector forms include:

    • Adjacent sibling: ${Component} + &, which applies when the previous styled sibling is the referenced component.
    • General sibling: ${Component} ~ &, which applies when any earlier styled sibling is the referenced component.
    • :first-child, :last-child, :only-child.
    • :nth-child(N), :nth-child(an+b), :nth-child(odd), :nth-child(even).
    • :nth-last-child(...) (same syntax as :nth-child, counting from the end).
    • :first-of-type, :last-of-type, :only-of-type, :nth-of-type(...), and :nth-last-of-type(...), which count siblings of the same element type.

    These selectors follow the component's JSX position among its siblings. Regular React Native components can sit between styled components without breaking selector matching.

  • feacf77: System color keywords such as Canvas, CanvasText, Field, FieldText, GrayText, Highlight, and LinkText now work on React Native. Values like color: CanvasText and background-color: Canvas adapt to the user's appearance and platform color settings where React Native exposes them, with readable fallbacks for unsupported native semantics. The browser still handles these keywords directly on the web build.

    Keywords match regardless of casing. Values inside larger shorthands, such as border: 1px solid Canvas, are not covered in this release; use the matching standalone color property for the same result.

Patch Changes

  • feacf77: aspect-ratio on React Native now accepts the same common forms as CSS: 16 / 9, auto, auto 16 / 9, and 16 / 9 auto. When auto is combined with a ratio on a component that does not have its own natural dimensions, styled-components uses the ratio and shows a one-time development warning explaining that the auto part only applies to image-like elements.

  • feacf77: background-size: cover and background-size: contain on React Native no longer crash the app when applied to gradient backgrounds. Gradients now paint across the full element area as expected. react-native-web still receives the original keyword so the browser can handle it directly.

    background-position values like 0 0, 50% 50%, and top left also no longer trigger a react-native-web warning.

  • feacf77: The background shorthand now works on React Native. A single declaration can set image, position, size, repeat behavior, attachment, origin, clipping, and color. Multiple background layers, position / size, and a final background color are supported.

    When React Native does not expose a matching behavior, such as fixed background attachment or non-default background origin and clipping, styled-components shows a development warning instead of silently dropping the unsupported part. react-native-web continues to let the browser handle the full shorthand.

  • feacf77: direction: ltr and direction: rtl now work in React Native styles. Logical edges such as margin-inline-start and padding-inline-end follow that direction, so the same declaration can support left-to-right and right-to-left layouts on iOS, Android, and the web.

  • feacf77: font-style: oblique now maps to italic on React Native. If an angle is provided, such as oblique 14deg, styled-components shows a one-time development warning because React Native cannot control the exact slant. react-native-web still passes the declaration to the browser.

    line-height values React Native cannot apply, such as percentages, em, and rem, now show a one-time development warning with the specific value and a suggested replacement.

    letter-spacing values written with em, rem, or percentages also now warn on React Native and point to pixel or unitless values instead.

  • feacf77: Generic font-family keywords such as serif, sans-serif, monospace, system-ui, ui-rounded, emoji, and math now map to an appropriate platform font on iOS and Android. react-native-web still passes the keyword to the browser. When a React Native font list contains multiple comma-separated families, styled-components uses the first one and shows a one-time development warning because React Native accepts only one font family.

  • feacf77: interactivity: inert now applies on React Native: the styled component and its subtree stop responding to touch, no longer accept D-pad / keyboard focus, and are hidden from screen readers (VoiceOver on iOS, TalkBack on Android). One known gap surfaces via a one-time dev warning on Android: a focusable child rendered inside an inert subtree may still receive focus, because React Native does not propagate that flag down the tree.

    react-native-web lets the browser honor the property natively via the HTML inert attribute.

  • feacf77: Logical border shorthands now expand on React Native:

    • Per-edge color, style, and width declarations such as border-inline-start-color and border-block-end-width.
    • Axis shorthands such as border-inline-color, border-block-width, border-inline, and border-block.
    • Single-edge shorthands such as border-inline-start and border-block-end.

    Width and color apply to the matching logical edge. Per-edge border styles now show a one-time development warning on React Native, because the platfor...

Read more

styled-components@7.0.0-prerelease-20260511203830

11 May 20:40
a53bad8

Choose a tag to compare

Patch Changes

  • a53bad8: vertical-align: top | middle | bottom on a styled <Text> now positions text content within the Text's box on react-native-web, matching the visual semantic of React Native's verticalAlign on Android (textAlignVertical). Other vertical-align values pass through unchanged so the browser's native baseline-shifting semantics still apply.

Full Changelog: styled-components@7.0.0-prerelease-20260511181437...styled-components@7.0.0-prerelease-20260511203830

styled-components@7.0.0-prerelease-20260511181437

11 May 18:16

Choose a tag to compare

Major Changes

  • c5e1c54: React Native: createTheme() now works exactly the way it does on web. Pass the returned object to ThemeProvider, reference leaves in your styled components, and the current theme resolves automatically.

    import styled, { createTheme, ThemeProvider } from 'styled-components/native';
    
    const theme = createTheme({
      colors: { bg: '#ffffff', text: '#111111' },
    });
    
    const Card = styled.View`
      background-color: ${theme.colors.bg};
      border-color: ${theme.colors.text};
    `;
    
    <ThemeProvider theme={{ colors: { bg: '#111', text: '#eee' } }}>
      <Card />
    </ThemeProvider>

    Nested ThemeProviders on React Native deep-merge their theme objects so an inner override that only touches one leaf keeps the siblings it inherited; a child provider that sets colors.text keeps colors.bg from the ancestor. Web behavior is unchanged.

  • c5e1c54: Styled components no longer honor defaultProps. React 19 removed defaultProps support from function components, so styled components can no longer inherit a parent's defaultProps either.

    Migration: use .attrs() for prop defaults. The object form always wins over user-provided props (this is intentional, see the attrs FAQ). The function form lets user-provided props override the default:

    // Before (v6, no longer applies in v7)
    const Button = styled.button``;
    Button.defaultProps = { type: 'button' };
    
    // After (object form always wins)
    const Button = styled.button.attrs({ type: 'button' })``;
    
    // After (user-provided overrides allowed)
    const Button = styled.button.attrs<{ type?: string }>(p => ({
      type: p.type ?? 'button',
    }))``;

    For a default theme, wrap the tree in <ThemeProvider theme={...}> instead.

  • c5e1c54: Removed the disableCSSOMInjection prop on <StyleSheetManager> and the SC_DISABLE_SPEEDY / REACT_APP_SC_DISABLE_SPEEDY environment variables. Added a new extractCSS export.

    Browser builds now always use the same fast injection path that production has used by default for years. There's no longer a knob to switch into a slower text-based mode at runtime, and dev and production now behave identically.

    If you were using the toggle to make CSS visible as text (for static-render pipelines, micro-frontend cloning, embedding into iframes or Shadow DOM, or extraction tooling), call the new extractCSS() function after render to get the current CSS as a plain string:

    import { extractCSS } from 'styled-components';
    
    // after rendering
    const css = extractCSS();

    The result is plain CSS without the rehydration markers used by ServerStyleSheet, so it can be injected directly into another document, stamped into a cloned DOM tree, or written to disk.

  • c5e1c54: Mounting the same createGlobalStyle component multiple times now emits its CSS only once. Previously each mount produced its own copy of the stylesheet rules. Rendering output stays byte-stable across SSR, client, and Server Components.

  • c5e1c54: React Native: the CSS-to-style-object translation layer is now built in. Several long-standing limitations go away on the native path.

    • transform: matrix(...) / matrix3d(...) work.
    • transform: translateX(10) (bare number, no unit) works.
    • background-image: linear-gradient(...) / radial-gradient(...) work.
    • filter: blur(4px) saturate(1.5) and the full filter-function chain work.
    • Modern color notations pass through to React Native's color parser unchanged: rgb(r g b / a) slash-alpha, hwb(), hsl() all work.
    • box-shadow with spread and inset pass through as CSS strings.
    • mix-blend-mode, isolation, cursor flow through.
    import styled from 'styled-components/native';
    
    const Tile = styled.View`
      background-image: linear-gradient(135deg, hsl(220 80% 60%), hsl(280 70% 50%));
      filter: blur(2px) saturate(1.5);
      box-shadow: 0 4px 12px rgb(0 0 0 / 0.2);
      transform: matrix(1, 0, 0, 1, 8, 0);
    `;

    The transform layer also fixes border: none emitting border-style: solid on native; it now emits border-style: none to match the rest of the ecosystem.

    iOS setup note for filters: 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.

  • c5e1c54: Raised peer dependency floors:

    • react and react-dom now require >= 19.0.0 (was >= 16.8).
    • react-native now requires >= 0.85.0 (was >= 0.68).
    • css-to-react-native is no longer a peer dependency. Apps that listed it solely for styled-components can drop it from their package.json.
    • The enableVendorPrefixes prop on <StyleSheetManager> and the runtime vendor prefixer have been removed. Modern browser targets handle prefixing natively; for the few properties that still need them (e.g. -webkit-backdrop-filter on Safari), declare both the prefixed and unprefixed forms in your CSS, or run a build-time PostCSS transform.

    Older React / React Native projects should stay on styled-components v6.

  • c5e1c54: Plugins moved to a dedicated styled-components/plugins subpath, and a first-party RTL plugin ships with the library.

    import { StyleSheetManager } from 'styled-components';
    import { rtlPlugin, rscPlugin } from 'styled-components/plugins';
    
    <StyleSheetManager plugins={[rtlPlugin]}>
      <App />
    </StyleSheetManager>

    rtlPlugin replaces stylis-plugin-rtl for users coming from v6: it swaps physical side properties (padding-leftpadding-right), flips left/right keyword values on float / clear / text-align / caption-side, and mirrors 4-value shorthand positions. Logical properties like margin-inline-start pass through unchanged.

    The stylisPlugins prop on <StyleSheetManager> is now plugins, and the top-level stylisPluginRSC export has moved into the new subpath as rscPlugin.

    Migration:

    -import { rtl, stylisPluginRSC } from 'styled-components';
    +import { rtlPlugin, rscPlugin } from 'styled-components/plugins';
    
    -<StyleSheetManager stylisPlugins={[rtl, stylisPluginRSC]}>
    +<StyleSheetManager plugins={[rtlPlugin, rscPlugin]}>

    Custom plugins authored against the v6 stylis contract need to port to the narrower plugin interface, which exposes rw (selector rewrite) and decl (declaration rewrite) hooks; implement either or both. Plugins are tree-shaken out of any app that doesn't import them.

    import type { SCPlugin } from 'styled-components';
    
    // `rw` runs on every fully-resolved selector after `&` substitution and
    // namespace prepending. Return a new selector string.
    const scopePlugin: SCPlugin = {
      name: 'scope',
      rw: selector => `.app ${selector}`,
    };
    
    // `decl` runs on every emitted `prop: value` pair (top-level decls, decl-body
    // at-rules, keyframe frames). Return `{ prop, value }` to rewrite, or `void`
    // to leave the pair unchanged.
    const remToPxPlugin: SCPlugin = {
      name: 'rem-to-px',
      decl: (prop, value) => {
        const match = value.match(/^(-?\d*\.?\d+)rem$/);
        return match ? { prop, value: `${parseFloat(match[1]) * 16}px` } : undefined;
      },
    };

    The name field is required and identifies the plugin so different plugin sets across nested <StyleSheetManager> trees stay isolated.

Minor Changes

  • c5e1c54: Added a second argument to function-form attrs((props, ast) => ...) callbacks for bridging styles into props on third-party components. The ast accessor exposes peek (read a value) and pop (read and remove from the rendered style), and accepts either a CSS property name or a typed dot-separated theme path (e.g. 'color.red.500'). Path autocomplete and value-type inference flow from your augmented theme.

    import { Path } from 'react-native-svg';
    
    const Icon = styled(Path).attrs((_props, ast) => ({
      fill: ast.pop('color'),                 // lift CSS decl into a prop
      stroke: ast.peek('palette.brand'),       // read from theme via typed path
    }))`
      color: red;
    `;

    Both methods take an optional fallback as the second argument, returned when the value is missing. Works on web and native. When the callback's behavior is fully determined by static declarations the work folds into a one-time computation at construction so renders pay nothing extra.

  • c5e1c54: Modern CSS functions now work in React Native styles. Static expressions resolve up front; values that depend on the device environment (viewport, container, safe area, color scheme) re-resolve as the environment changes.

    import styled from 'styled-components/native';
    
    const Card = styled.View`
      width: clamp(240px, 80vw, 480px);
      background-color: light-dark(white, #111);
      padding-top: env(safe-area-inset-top);
      border-radius: 8px;
    
      @container card (min-width: 320px) {
        padding: 24px;
      }
    `;
    • clamp(10px, 50%, 400px) / min(100px, 50vw) / max(200px, 100vh) / calc(100vw - 40px) with any mix of static and runtime-resolvable arms.
    • oklch(...), oklab(...), lch(...), lab(...) resolve to a color React Native can render. Wide-gamut inputs that fall outside sRGB are mapped to the closest in-gamut color while preserving hue, so the rendered result stays as close as possible to what was written.
    • color-mix(in <space>, …) mixes through the requested space (srgb, oklab, oklch, lab, lch) and converts back to sRGB for display.
    • Viewport units vw / vh / vmin / vmax / dvh / svh / lvh scale to the current window dimensions.
    • Container query units cqw / cqh / `cqmi...
Read more

styled-components@7.0.0-prerelease-20260511181111

11 May 18:13

Choose a tag to compare

Major Changes

  • c5e1c54: React Native: createTheme() now works exactly the way it does on web. Pass the returned object to ThemeProvider, reference leaves in your styled components, and the current theme resolves automatically.

    import styled, { createTheme, ThemeProvider } from 'styled-components/native';
    
    const theme = createTheme({
      colors: { bg: '#ffffff', text: '#111111' },
    });
    
    const Card = styled.View`
      background-color: ${theme.colors.bg};
      border-color: ${theme.colors.text};
    `;
    
    <ThemeProvider theme={{ colors: { bg: '#111', text: '#eee' } }}>
      <Card />
    </ThemeProvider>

    Nested ThemeProviders on React Native deep-merge their theme objects so an inner override that only touches one leaf keeps the siblings it inherited; a child provider that sets colors.text keeps colors.bg from the ancestor. Web behavior is unchanged.

  • c5e1c54: Styled components no longer honor defaultProps. React 19 removed defaultProps support from function components, so styled components can no longer inherit a parent's defaultProps either.

    Migration: use .attrs() for prop defaults. The object form always wins over user-provided props (this is intentional, see the attrs FAQ). The function form lets user-provided props override the default:

    // Before (v6, no longer applies in v7)
    const Button = styled.button``;
    Button.defaultProps = { type: 'button' };
    
    // After (object form always wins)
    const Button = styled.button.attrs({ type: 'button' })``;
    
    // After (user-provided overrides allowed)
    const Button = styled.button.attrs<{ type?: string }>(p => ({
      type: p.type ?? 'button',
    }))``;

    For a default theme, wrap the tree in <ThemeProvider theme={...}> instead.

  • c5e1c54: Removed the disableCSSOMInjection prop on <StyleSheetManager> and the SC_DISABLE_SPEEDY / REACT_APP_SC_DISABLE_SPEEDY environment variables. Added a new extractCSS export.

    Browser builds now always use the same fast injection path that production has used by default for years. There's no longer a knob to switch into a slower text-based mode at runtime, and dev and production now behave identically.

    If you were using the toggle to make CSS visible as text (for static-render pipelines, micro-frontend cloning, embedding into iframes or Shadow DOM, or extraction tooling), call the new extractCSS() function after render to get the current CSS as a plain string:

    import { extractCSS } from 'styled-components';
    
    // after rendering
    const css = extractCSS();

    The result is plain CSS without the rehydration markers used by ServerStyleSheet, so it can be injected directly into another document, stamped into a cloned DOM tree, or written to disk.

  • c5e1c54: Mounting the same createGlobalStyle component multiple times now emits its CSS only once. Previously each mount produced its own copy of the stylesheet rules. Rendering output stays byte-stable across SSR, client, and Server Components.

  • c5e1c54: React Native: the CSS-to-style-object translation layer is now built in. Several long-standing limitations go away on the native path.

    • transform: matrix(...) / matrix3d(...) work.
    • transform: translateX(10) (bare number, no unit) works.
    • background-image: linear-gradient(...) / radial-gradient(...) work.
    • filter: blur(4px) saturate(1.5) and the full filter-function chain work.
    • Modern color notations pass through to React Native's color parser unchanged: rgb(r g b / a) slash-alpha, hwb(), hsl() all work.
    • box-shadow with spread and inset pass through as CSS strings.
    • mix-blend-mode, isolation, cursor flow through.
    import styled from 'styled-components/native';
    
    const Tile = styled.View`
      background-image: linear-gradient(135deg, hsl(220 80% 60%), hsl(280 70% 50%));
      filter: blur(2px) saturate(1.5);
      box-shadow: 0 4px 12px rgb(0 0 0 / 0.2);
      transform: matrix(1, 0, 0, 1, 8, 0);
    `;

    The transform layer also fixes border: none emitting border-style: solid on native; it now emits border-style: none to match the rest of the ecosystem.

    iOS setup note for filters: 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.

  • c5e1c54: Raised peer dependency floors:

    • react and react-dom now require >= 19.0.0 (was >= 16.8).
    • react-native now requires >= 0.85.0 (was >= 0.68).
    • css-to-react-native is no longer a peer dependency. Apps that listed it solely for styled-components can drop it from their package.json.
    • The enableVendorPrefixes prop on <StyleSheetManager> and the runtime vendor prefixer have been removed. Modern browser targets handle prefixing natively; for the few properties that still need them (e.g. -webkit-backdrop-filter on Safari), declare both the prefixed and unprefixed forms in your CSS, or run a build-time PostCSS transform.

    Older React / React Native projects should stay on styled-components v6.

  • c5e1c54: Plugins moved to a dedicated styled-components/plugins subpath, and a first-party RTL plugin ships with the library.

    import { StyleSheetManager } from 'styled-components';
    import { rtlPlugin, rscPlugin } from 'styled-components/plugins';
    
    <StyleSheetManager plugins={[rtlPlugin]}>
      <App />
    </StyleSheetManager>

    rtlPlugin replaces stylis-plugin-rtl for users coming from v6: it swaps physical side properties (padding-leftpadding-right), flips left/right keyword values on float / clear / text-align / caption-side, and mirrors 4-value shorthand positions. Logical properties like margin-inline-start pass through unchanged.

    The stylisPlugins prop on <StyleSheetManager> is now plugins, and the top-level stylisPluginRSC export has moved into the new subpath as rscPlugin.

    Migration:

    -import { rtl, stylisPluginRSC } from 'styled-components';
    +import { rtlPlugin, rscPlugin } from 'styled-components/plugins';
    
    -<StyleSheetManager stylisPlugins={[rtl, stylisPluginRSC]}>
    +<StyleSheetManager plugins={[rtlPlugin, rscPlugin]}>

    Custom plugins authored against the v6 stylis contract need to port to the narrower plugin interface, which exposes rw (selector rewrite) and decl (declaration rewrite) hooks; implement either or both. Plugins are tree-shaken out of any app that doesn't import them.

    import type { SCPlugin } from 'styled-components';
    
    // `rw` runs on every fully-resolved selector after `&` substitution and
    // namespace prepending. Return a new selector string.
    const scopePlugin: SCPlugin = {
      name: 'scope',
      rw: selector => `.app ${selector}`,
    };
    
    // `decl` runs on every emitted `prop: value` pair (top-level decls, decl-body
    // at-rules, keyframe frames). Return `{ prop, value }` to rewrite, or `void`
    // to leave the pair unchanged.
    const remToPxPlugin: SCPlugin = {
      name: 'rem-to-px',
      decl: (prop, value) => {
        const match = value.match(/^(-?\d*\.?\d+)rem$/);
        return match ? { prop, value: `${parseFloat(match[1]) * 16}px` } : undefined;
      },
    };

    The name field is required and identifies the plugin so different plugin sets across nested <StyleSheetManager> trees stay isolated.

Minor Changes

  • c5e1c54: Added a second argument to function-form attrs((props, ast) => ...) callbacks for bridging styles into props on third-party components. The ast accessor exposes peek (read a value) and pop (read and remove from the rendered style), and accepts either a CSS property name or a typed dot-separated theme path (e.g. 'color.red.500'). Path autocomplete and value-type inference flow from your augmented theme.

    import { Path } from 'react-native-svg';
    
    const Icon = styled(Path).attrs((_props, ast) => ({
      fill: ast.pop('color'),                 // lift CSS decl into a prop
      stroke: ast.peek('palette.brand'),       // read from theme via typed path
    }))`
      color: red;
    `;

    Both methods take an optional fallback as the second argument, returned when the value is missing. Works on web and native. When the callback's behavior is fully determined by static declarations the work folds into a one-time computation at construction so renders pay nothing extra.

  • c5e1c54: Modern CSS functions now work in React Native styles. Static expressions resolve up front; values that depend on the device environment (viewport, container, safe area, color scheme) re-resolve as the environment changes.

    import styled from 'styled-components/native';
    
    const Card = styled.View`
      width: clamp(240px, 80vw, 480px);
      background-color: light-dark(white, #111);
      padding-top: env(safe-area-inset-top);
      border-radius: 8px;
    
      @container card (min-width: 320px) {
        padding: 24px;
      }
    `;
    • clamp(10px, 50%, 400px) / min(100px, 50vw) / max(200px, 100vh) / calc(100vw - 40px) with any mix of static and runtime-resolvable arms.
    • oklch(...), oklab(...), lch(...), lab(...) resolve to a color React Native can render. Wide-gamut inputs that fall outside sRGB are mapped to the closest in-gamut color while preserving hue, so the rendered result stays as close as possible to what was written.
    • color-mix(in <space>, …) mixes through the requested space (srgb, oklab, oklch, lab, lch) and converts back to sRGB for display.
    • Viewport units vw / vh / vmin / vmax / dvh / svh / lvh scale to the current window dimensions.
    • Container query units cqw / cqh / `cqmi...
Read more

styled-components@7.0.0-prerelease-20260511073743

11 May 07:40

Choose a tag to compare

Major Changes

  • 26a3151: React Native: createTheme() now works exactly the way it does on web. Pass the returned object to ThemeProvider, reference leaves in your styled components, and the current theme resolves automatically.

    import styled, { createTheme, ThemeProvider } from 'styled-components/native';
    
    const theme = createTheme({
      colors: { bg: '#ffffff', text: '#111111' },
    });
    
    const Card = styled.View`
      background-color: ${theme.colors.bg};
      border-color: ${theme.colors.text};
    `;
    
    <ThemeProvider theme={{ colors: { bg: '#111', text: '#eee' } }}>
      <Card />
    </ThemeProvider>

    Nested ThemeProviders on React Native deep-merge their theme objects so an inner override that only touches one leaf keeps the siblings it inherited; a child provider that sets colors.text keeps colors.bg from the ancestor. Web behavior is unchanged.

  • 26a3151: Styled components no longer honor defaultProps. React 19 removed defaultProps support from function components, so styled components can no longer inherit a parent's defaultProps either.

    Migration: use .attrs() for prop defaults. The object form always wins over user-provided props (this is intentional, see the attrs FAQ). The function form lets user-provided props override the default:

    // Before (v6, no longer applies in v7)
    const Button = styled.button``;
    Button.defaultProps = { type: 'button' };
    
    // After (object form always wins)
    const Button = styled.button.attrs({ type: 'button' })``;
    
    // After (user-provided overrides allowed)
    const Button = styled.button.attrs<{ type?: string }>(p => ({
      type: p.type ?? 'button',
    }))``;

    For a default theme, wrap the tree in <ThemeProvider theme={...}> instead.

  • 26a3151: Removed the disableCSSOMInjection prop on <StyleSheetManager> and the SC_DISABLE_SPEEDY / REACT_APP_SC_DISABLE_SPEEDY environment variables. Added a new extractCSS export.

    Browser builds now always use the same fast injection path that production has used by default for years. There's no longer a knob to switch into a slower text-based mode at runtime, and dev and production now behave identically.

    If you were using the toggle to make CSS visible as text (for static-render pipelines, micro-frontend cloning, embedding into iframes or Shadow DOM, or extraction tooling), call the new extractCSS() function after render to get the current CSS as a plain string:

    import { extractCSS } from 'styled-components';
    
    // after rendering
    const css = extractCSS();

    The result is plain CSS without the rehydration markers used by ServerStyleSheet, so it can be injected directly into another document, stamped into a cloned DOM tree, or written to disk.

  • 26a3151: Mounting the same createGlobalStyle component multiple times now emits its CSS only once. Previously each mount produced its own copy of the stylesheet rules. Rendering output stays byte-stable across SSR, client, and Server Components.

  • 26a3151: React Native: the CSS-to-style-object translation layer is now built in. Several long-standing limitations go away on the native path.

    • transform: matrix(...) / matrix3d(...) work.
    • transform: translateX(10) (bare number, no unit) works.
    • background-image: linear-gradient(...) / radial-gradient(...) work.
    • filter: blur(4px) saturate(1.5) and the full filter-function chain work.
    • Modern color notations pass through to React Native's color parser unchanged: rgb(r g b / a) slash-alpha, hwb(), hsl() all work.
    • box-shadow with spread and inset pass through as CSS strings.
    • mix-blend-mode, isolation, cursor flow through.
    import styled from 'styled-components/native';
    
    const Tile = styled.View`
      background-image: linear-gradient(135deg, hsl(220 80% 60%), hsl(280 70% 50%));
      filter: blur(2px) saturate(1.5);
      box-shadow: 0 4px 12px rgb(0 0 0 / 0.2);
      transform: matrix(1, 0, 0, 1, 8, 0);
    `;

    The transform layer also fixes border: none emitting border-style: solid on native; it now emits border-style: none to match the rest of the ecosystem.

    iOS setup note for filters: 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.

  • 26a3151: Raised peer dependency floors:

    • react and react-dom now require >= 19.0.0 (was >= 16.8).
    • react-native now requires >= 0.85.0 (was >= 0.68).
    • css-to-react-native is no longer a peer dependency. Apps that listed it solely for styled-components can drop it from their package.json.
    • The enableVendorPrefixes prop on <StyleSheetManager> and the runtime vendor prefixer have been removed. Modern browser targets handle prefixing natively; for the few properties that still need them (e.g. -webkit-backdrop-filter on Safari), declare both the prefixed and unprefixed forms in your CSS, or run a build-time PostCSS transform.

    Older React / React Native projects should stay on styled-components v6.

  • 26a3151: Plugins moved to a dedicated styled-components/plugins subpath, and a first-party RTL plugin ships with the library.

    import { StyleSheetManager } from 'styled-components';
    import { rtlPlugin, rscPlugin } from 'styled-components/plugins';
    
    <StyleSheetManager plugins={[rtlPlugin]}>
      <App />
    </StyleSheetManager>

    rtlPlugin replaces stylis-plugin-rtl for users coming from v6: it swaps physical side properties (padding-leftpadding-right), flips left/right keyword values on float / clear / text-align / caption-side, and mirrors 4-value shorthand positions. Logical properties like margin-inline-start pass through unchanged.

    The stylisPlugins prop on <StyleSheetManager> is now plugins, and the top-level stylisPluginRSC export has moved into the new subpath as rscPlugin.

    Migration:

    -import { rtl, stylisPluginRSC } from 'styled-components';
    +import { rtlPlugin, rscPlugin } from 'styled-components/plugins';
    
    -<StyleSheetManager stylisPlugins={[rtl, stylisPluginRSC]}>
    +<StyleSheetManager plugins={[rtlPlugin, rscPlugin]}>

    Custom plugins authored against the v6 stylis contract need to port to the narrower plugin interface, which exposes rw (selector rewrite) and decl (declaration rewrite) hooks; implement either or both. Plugins are tree-shaken out of any app that doesn't import them.

    import type { SCPlugin } from 'styled-components';
    
    // `rw` runs on every fully-resolved selector after `&` substitution and
    // namespace prepending. Return a new selector string.
    const scopePlugin: SCPlugin = {
      name: 'scope',
      rw: selector => `.app ${selector}`,
    };
    
    // `decl` runs on every emitted `prop: value` pair (top-level decls, decl-body
    // at-rules, keyframe frames). Return `{ prop, value }` to rewrite, or `void`
    // to leave the pair unchanged.
    const remToPxPlugin: SCPlugin = {
      name: 'rem-to-px',
      decl: (prop, value) => {
        const match = value.match(/^(-?\d*\.?\d+)rem$/);
        return match ? { prop, value: `${parseFloat(match[1]) * 16}px` } : undefined;
      },
    };

    The name field is required and identifies the plugin so different plugin sets across nested <StyleSheetManager> trees stay isolated.

Minor Changes

  • 26a3151: Added a second argument to function-form attrs((props, ast) => ...) callbacks for bridging styles into props on third-party components. The ast accessor exposes peek (read a value) and pop (read and remove from the rendered style), and accepts either a CSS property name or a typed dot-separated theme path (e.g. 'color.red.500'). Path autocomplete and value-type inference flow from your augmented theme.

    import { Path } from 'react-native-svg';
    
    const Icon = styled(Path).attrs((_props, ast) => ({
      fill: ast.pop('color'),                 // lift CSS decl into a prop
      stroke: ast.peek('palette.brand'),       // read from theme via typed path
    }))`
      color: red;
    `;

    Both methods take an optional fallback as the second argument, returned when the value is missing. Works on web and native. When the callback's behavior is fully determined by static declarations the work folds into a one-time computation at construction so renders pay nothing extra.

  • 26a3151: Modern CSS functions now work in React Native styles. Static expressions resolve up front; values that depend on the device environment (viewport, container, safe area, color scheme) re-resolve as the environment changes.

    import styled from 'styled-components/native';
    
    const Card = styled.View`
      width: clamp(240px, 80vw, 480px);
      background-color: light-dark(white, #111);
      padding-top: env(safe-area-inset-top);
      border-radius: 8px;
    
      @container card (min-width: 320px) {
        padding: 24px;
      }
    `;
    • clamp(10px, 50%, 400px) / min(100px, 50vw) / max(200px, 100vh) / calc(100vw - 40px) with any mix of static and runtime-resolvable arms.
    • oklch(...), oklab(...), lch(...), lab(...) resolve to a color React Native can render. Wide-gamut inputs that fall outside sRGB are mapped to the closest in-gamut color while preserving hue, so the rendered result stays as close as possible to what was written.
    • color-mix(in <space>, …) mixes through the requested space (srgb, oklab, oklch, lab, lch) and converts back to sRGB for display.
    • Viewport units vw / vh / vmin / vmax / dvh / svh / lvh scale to the current window dimensions.
    • Container query units cqw / cqh / `cqmi...
Read more