From 9cdfa98a331b315180938464baaa38d2f7ea4fab Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 13 May 2026 14:34:21 +0000 Subject: [PATCH 1/3] chore(v7): trim docs duplication, fix stale gradient/filter/@supports claims - whats-new: tighten Highlights bullets, link out to native + plugins sections instead of restating their content; drop the disableCSSOMInjection mention that appeared in both Highlights and Removed. - migration: collapse the dedicated rscPlugin / rtlPlugin / SCPlugin sections into a single pointer at the plugins page; defer native specifics to native.mdx and the compatibility matrix. - native: qualify hover with the RN 0.85 feature-flag default, flag the conic-gradient gap and platform-conditional filter / blend-mode / skew rows in Limitations. - cssCompat: add @supports row. The native parser routes its condition through the same evaluator as @media / @container, which only understands media features, so feature-test conditions never gate anything on native and the inner block always applies. - Bump styled-components prerelease to 20260513034901. --- package.json | 2 +- pnpm-lock.yaml | 44 ++++++++++++++++----------------- sections/v7/migration.mdx | 51 ++++++--------------------------------- sections/v7/native.mdx | 11 +++++++-- sections/v7/whats-new.mdx | 40 +++++++++++++++--------------- utils/cssCompat.ts | 13 ++++++++++ 6 files changed, 74 insertions(+), 87 deletions(-) diff --git a/package.json b/package.json index 55da855a..0340c9fa 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "react-live-runner": "^1.0.7", "react-transition-group": "^4.4.5", "server-only": "^0.0.1", - "styled-components": "7.0.0-prerelease-20260511181437", + "styled-components": "7.0.0-prerelease-20260513034901", "styled-theming": "^2.2.0", "stylis": "^4.2.0", "stylis-plugin-rtl": "^2.1.1" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 50843008..2db25d00 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -28,13 +28,13 @@ importers: version: 16.1.1(@mdx-js/loader@3.1.1) '@styled-icons/boxicons-regular': specifier: ^10.47.0 - version: 10.47.0(react@19.2.6)(styled-components@7.0.0-prerelease-20260511181437(react-dom@19.2.6(react@19.2.6))(react@19.2.6)) + version: 10.47.0(react@19.2.6)(styled-components@7.0.0-prerelease-20260513034901(react-dom@19.2.6(react@19.2.6))(react@19.2.6)) '@styled-icons/fa-brands': specifier: ^10.47.0 - version: 10.47.0(react@19.2.6)(styled-components@7.0.0-prerelease-20260511181437(react-dom@19.2.6(react@19.2.6))(react@19.2.6)) + version: 10.47.0(react@19.2.6)(styled-components@7.0.0-prerelease-20260513034901(react-dom@19.2.6(react@19.2.6))(react@19.2.6)) '@styled-icons/material': specifier: ^10.47.0 - version: 10.47.0(react@19.2.6)(styled-components@7.0.0-prerelease-20260511181437(react-dom@19.2.6(react@19.2.6))(react@19.2.6)) + version: 10.47.0(react@19.2.6)(styled-components@7.0.0-prerelease-20260513034901(react-dom@19.2.6(react@19.2.6))(react@19.2.6)) bluesky-comments: specifier: ^0.13.1 version: 0.13.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6) @@ -72,8 +72,8 @@ importers: specifier: ^0.0.1 version: 0.0.1 styled-components: - specifier: 7.0.0-prerelease-20260511181437 - version: 7.0.0-prerelease-20260511181437(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + specifier: 7.0.0-prerelease-20260513034901 + version: 7.0.0-prerelease-20260513034901(react-dom@19.2.6(react@19.2.6))(react@19.2.6) styled-theming: specifier: ^2.2.0 version: 2.2.0 @@ -134,7 +134,7 @@ importers: version: 30.2.0 jest-styled-components: specifier: ^7.1.1 - version: 7.2.0(styled-components@7.0.0-prerelease-20260511181437(react-dom@19.2.6(react@19.2.6))(react@19.2.6)) + version: 7.2.0(styled-components@7.0.0-prerelease-20260513034901(react-dom@19.2.6(react@19.2.6))(react@19.2.6)) lint-staged: specifier: ^16.4.0 version: 16.4.0 @@ -2881,8 +2881,8 @@ packages: style-to-object@1.0.14: resolution: {integrity: sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==} - styled-components@7.0.0-prerelease-20260511181437: - resolution: {integrity: sha512-n6vFC6k1cHLMB4BvaUSNwTvoL7QcN2UZiDKuoaZYLZ3MXB46Rx81ec0vu7ICn6YCoKZZBA8fnYsJR6lci3XYJw==} + styled-components@7.0.0-prerelease-20260513034901: + resolution: {integrity: sha512-+GCUw6l6Dg1ygwIdYEfFVPDWl8cXeUxvqRPqDkpGb6gs50ktsuPvruKCydZcm50IsUUdTRIfgJf8ucqfL2/MbA==} engines: {node: '>= 16'} peerDependencies: react: '>= 19.0.0' @@ -4201,32 +4201,32 @@ snapshots: dependencies: '@sinonjs/commons': 3.0.1 - '@styled-icons/boxicons-regular@10.47.0(react@19.2.6)(styled-components@7.0.0-prerelease-20260511181437(react-dom@19.2.6(react@19.2.6))(react@19.2.6))': + '@styled-icons/boxicons-regular@10.47.0(react@19.2.6)(styled-components@7.0.0-prerelease-20260513034901(react-dom@19.2.6(react@19.2.6))(react@19.2.6))': dependencies: '@babel/runtime': 7.28.4 - '@styled-icons/styled-icon': 10.7.1(react@19.2.6)(styled-components@7.0.0-prerelease-20260511181437(react-dom@19.2.6(react@19.2.6))(react@19.2.6)) + '@styled-icons/styled-icon': 10.7.1(react@19.2.6)(styled-components@7.0.0-prerelease-20260513034901(react-dom@19.2.6(react@19.2.6))(react@19.2.6)) react: 19.2.6 - styled-components: 7.0.0-prerelease-20260511181437(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + styled-components: 7.0.0-prerelease-20260513034901(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@styled-icons/fa-brands@10.47.0(react@19.2.6)(styled-components@7.0.0-prerelease-20260511181437(react-dom@19.2.6(react@19.2.6))(react@19.2.6))': + '@styled-icons/fa-brands@10.47.0(react@19.2.6)(styled-components@7.0.0-prerelease-20260513034901(react-dom@19.2.6(react@19.2.6))(react@19.2.6))': dependencies: '@babel/runtime': 7.28.4 - '@styled-icons/styled-icon': 10.7.1(react@19.2.6)(styled-components@7.0.0-prerelease-20260511181437(react-dom@19.2.6(react@19.2.6))(react@19.2.6)) + '@styled-icons/styled-icon': 10.7.1(react@19.2.6)(styled-components@7.0.0-prerelease-20260513034901(react-dom@19.2.6(react@19.2.6))(react@19.2.6)) react: 19.2.6 - styled-components: 7.0.0-prerelease-20260511181437(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + styled-components: 7.0.0-prerelease-20260513034901(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@styled-icons/material@10.47.0(react@19.2.6)(styled-components@7.0.0-prerelease-20260511181437(react-dom@19.2.6(react@19.2.6))(react@19.2.6))': + '@styled-icons/material@10.47.0(react@19.2.6)(styled-components@7.0.0-prerelease-20260513034901(react-dom@19.2.6(react@19.2.6))(react@19.2.6))': dependencies: '@babel/runtime': 7.28.4 - '@styled-icons/styled-icon': 10.7.1(react@19.2.6)(styled-components@7.0.0-prerelease-20260511181437(react-dom@19.2.6(react@19.2.6))(react@19.2.6)) + '@styled-icons/styled-icon': 10.7.1(react@19.2.6)(styled-components@7.0.0-prerelease-20260513034901(react-dom@19.2.6(react@19.2.6))(react@19.2.6)) react: 19.2.6 - styled-components: 7.0.0-prerelease-20260511181437(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + styled-components: 7.0.0-prerelease-20260513034901(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@styled-icons/styled-icon@10.7.1(react@19.2.6)(styled-components@7.0.0-prerelease-20260511181437(react-dom@19.2.6(react@19.2.6))(react@19.2.6))': + '@styled-icons/styled-icon@10.7.1(react@19.2.6)(styled-components@7.0.0-prerelease-20260513034901(react-dom@19.2.6(react@19.2.6))(react@19.2.6))': dependencies: '@babel/runtime': 7.28.4 react: 19.2.6 - styled-components: 7.0.0-prerelease-20260511181437(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + styled-components: 7.0.0-prerelease-20260513034901(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@swc/helpers@0.5.15': dependencies: @@ -5354,10 +5354,10 @@ snapshots: transitivePeerDependencies: - supports-color - jest-styled-components@7.2.0(styled-components@7.0.0-prerelease-20260511181437(react-dom@19.2.6(react@19.2.6))(react@19.2.6)): + jest-styled-components@7.2.0(styled-components@7.0.0-prerelease-20260513034901(react-dom@19.2.6(react@19.2.6))(react@19.2.6)): dependencies: '@adobe/css-tools': 4.4.4 - styled-components: 7.0.0-prerelease-20260511181437(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + styled-components: 7.0.0-prerelease-20260513034901(react-dom@19.2.6(react@19.2.6))(react@19.2.6) jest-util@30.2.0: dependencies: @@ -6487,7 +6487,7 @@ snapshots: dependencies: inline-style-parser: 0.2.7 - styled-components@7.0.0-prerelease-20260511181437(react-dom@19.2.6(react@19.2.6))(react@19.2.6): + styled-components@7.0.0-prerelease-20260513034901(react-dom@19.2.6(react@19.2.6))(react@19.2.6): dependencies: csstype: 3.2.3 react: 19.2.6 diff --git a/sections/v7/migration.mdx b/sections/v7/migration.mdx index 21fb53f6..f1d2edac 100644 --- a/sections/v7/migration.mdx +++ b/sections/v7/migration.mdx @@ -46,51 +46,19 @@ The result is plain CSS, safe to inject into another document via `innerHTML`. T The runtime vendor prefixer has been removed. For the properties that still need prefixing on older Safari (`backdrop-filter`, `mask`, `user-select`), declare both the prefixed and unprefixed forms in your CSS, or run a build-time PostCSS transform. -### `stylisPluginRSC` moved to a subpath +### Plugins moved to a subpath -The top-level export has been removed. Import from `styled-components/plugins` instead, and pass via the renamed `plugins` prop: +`stylisPluginRSC` and the v6 `stylisPlugins` prop are gone. Import the first-party plugins from `styled-components/plugins` and pass them via the renamed `plugins` prop: ```diff -import { stylisPluginRSC } from 'styled-components'; -+import { rscPlugin } from 'styled-components/plugins'; ++import { rscPlugin, rtlPlugin } from 'styled-components/plugins'; - + ``` -### RTL is now first-party - -`stylis-plugin-rtl` is no longer needed: - -```diff --import rtl from 'stylis-plugin-rtl'; -+import { rtlPlugin } from 'styled-components/plugins'; - -- -+ -``` - -`rtlPlugin` swaps physical side properties (`padding-left` ↔ `padding-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. - -### Custom stylis plugins need to port - -Custom plugins authored against the v6 stylis contract no longer load. Implement the v7 plugin shape: - -```ts -import type { SCPlugin } from 'styled-components/plugins'; - -const scopePlugin: SCPlugin = { - name: 'scope', - // `rw` runs on every fully-resolved selector after `&` substitution and - // namespace prepending. Return a new selector string. - rw: selector => `.app ${selector}`, - // `decl` runs on every emitted `prop: value` pair. Return `{ prop, value }` - // to rewrite, or `undefined` to leave the pair unchanged. - decl: (prop, value) => ({ prop, value }), -}; -``` - -Legacy stylis function plugins don't run in v7 because they don't expose `rw` or `decl`. Production builds ignore them; development builds warn once per unrecognized plugin name. +`stylis-plugin-rtl` is replaced by the first-party `rtlPlugin`. Custom stylis plugins authored against the v6 middleware contract no longer load and must port to the new `SCPlugin` shape. See [Plugins moved to a dedicated subpath](#plugins-moved-to-a-dedicated-subpath) for details and authoring guidance. ### Global styles emit once per component @@ -98,16 +66,13 @@ Legacy stylis function plugins don't run in v7 because they don't expose `rw` or ### React Native bumps and changes -The [CSS Compatibility matrix](/docs/compatibility) is the per-feature source of truth for what changed between v6 and v7 on web and native; the list below covers the most common cases. - -If you're on React Native: +The [CSS Compatibility matrix](/docs/compatibility) is the per-feature source of truth for what changed between v6 and v7 on web and native. The most common gotchas: - The peer floor moves to RN ≥ 0.85. -- The `lab()`, `lch()`, `oklab()`, `oklch()`, and `color-mix()` notations now resolve to displayable colors with hue-preserving gamut mapping. Wide-gamut inputs that fall outside sRGB land at the closest in-gamut color. -- `transform: matrix(...)` / `matrix3d(...)` and bare-number `translateX(N)` work on native. -- `linear-gradient(...)`, `radial-gradient(...)`, and the full `filter` chain (`blur`, `saturate`, `hue-rotate`, ...) work on native. iOS apps need `ReactNativeReleaseLevel: experimental` in `Info.plist` to enable the SwiftUI filter backend for `blur` / `saturate` / `hue-rotate` / `grayscale` / `contrast` / `drop-shadow`. `brightness` and `opacity` work without it. - `border: none` now emits `border-style: none` (previously `solid`, which produced surprising hairlines). -- `transition`, `@keyframes`, and `@starting-style` all animate on the native thread by default — no extra import, peer dependency, or configuration. If you prefer to drive animations through reanimated 4's CSS layer, opt in once at your app entry with `import 'styled-components/native/reanimated'`; `react-native-reanimated@^4` becomes a peer dependency only in that case. +- The optional reanimated adapter is opt-in at app entry: `import 'styled-components/native/reanimated'`. `react-native-reanimated@^4` becomes a peer dependency only in that case. The adapter is experimental and not heavily tested yet — the default `Animated`-based adapter runs without any setup. + +[React Native gets modern CSS](#react-native-gets-modern-css) walks through the new surface (modern color spaces, gradients, filters, selectors, animations, `createTheme()`) and calls out the platform-level caveats — iOS SwiftUI filter opt-in, hover feature flag, conic-gradient gap, and the rest. The compatibility matrix has the per-feature breakdown. ### TypeScript diff --git a/sections/v7/native.mdx b/sections/v7/native.mdx index 1d746828..28453b4d 100644 --- a/sections/v7/native.mdx +++ b/sections/v7/native.mdx @@ -62,7 +62,7 @@ const Toggle = styled.Pressable<{ 'aria-pressed'?: boolean }>` The same CSS works on web and native. Anchor pseudo-states with `&` (bare `:hover` parses as a descendant selector, not a state). -**Pseudo-states.** `&:hover`, `&:focus`, `&:focus-visible`, `&:pressed`, `&:disabled`. Hover on native uses Pressable's pointer events; React Native ≥ 0.85. +**Pseudo-states.** `&:hover`, `&:focus`, `&:focus-visible`, `&:pressed`, `&:disabled`. Hover on native uses Pressable's pointer events on RN ≥ 0.85, but stock RN gates them behind `ReactNativeFeatureFlags.shouldPressibilityUseW3CPointerEventsForHover`, which defaults to FALSE in 0.85 — enable it at startup to get `&:hover` to fire. **Attribute selectors.** The full CSS Selectors 4 grammar works on native: @@ -189,11 +189,18 @@ These spec features pass through to react-native-web but do not render on React - `::before` / `::after` / `::placeholder` / `::marker` / `::selection` - `backdrop-filter`, `mask` / `mask-image` (community packages cover both) -- `background-image: url()` for raster images (gradient `background-image` works) +- `background-image: url()` for raster images (linear / radial gradients work; conic-gradient parses but does not paint on native) - `clip-path` - `position: fixed`, `position: sticky`, `position: anchor()` / `anchor-name` - `text-indent`, `word-spacing`, `text-box-trim`, `text-spacing-trim` - `transform-style: preserve-3d` (basic `rotateX` / `rotateY` / `translateZ` / `perspective` do work) - CSS Grid (`display: grid`, `grid-template-*`) - Form-state pseudos (`:invalid`, `:required`, `:read-only`, `:checked`) +- `@supports` is parsed but its condition is never evaluated against the runtime — declarations inside always apply on native. Branch with `Platform.OS` instead. - Scroll-snap, view transitions, scroll-driven animations + +Platform-conditional features (the matrix has the full story): + +- `filter` — only `brightness` and `opacity` apply on iOS without the SwiftUI opt-in (set `ReactNativeReleaseLevel: experimental` in `Info.plist` / `ios.infoPlist`); Android `blur` and `drop-shadow` require API 31+. +- `mix-blend-mode` — all 16 non-Normal modes on iOS; Android needs API 29+. +- `transform: skewX/skewY` — iOS renders correctly; Android silently drops the shear pending an upstream fix. diff --git a/sections/v7/whats-new.mdx b/sections/v7/whats-new.mdx index 2980d86b..686895bb 100644 --- a/sections/v7/whats-new.mdx +++ b/sections/v7/whats-new.mdx @@ -2,7 +2,7 @@ > **v7 is in alpha.** This release is under active development, and the docs will receive **frequent updates over the next few weeks** as internals are refined. > -> Install the prerelease from npm’s `@test` dist-tag: +> Install the prerelease from npm's `@test` dist-tag: > > ```bash > npm install styled-components@test @@ -18,24 +18,26 @@ The bottleneck now is funding. styled-components can help provide a universal Re ### Highlights -- **In-house CSS parser**: replaces stylis as the runtime engine. No more `:is()` / `:where()` / `:has()` recursion bugs, no more silent breakage on modern at-rules. -- **Modern CSS on React Native**: `@media`, `@container`, `@supports`, container queries, viewport units (`vw`, `dvh`, `cqw`), font-relative units (`rem`, `em`, `lh`), `oklch` / `oklab` / `lab` / `lch` / `color-mix` / `light-dark()` / relative-color syntax, `clamp` / `min` / `max` / `round` / `sin` / `cos` and the full Math L4 family, logical shorthands (`margin-inline`, `border-inline`, …), gradients, and the full filter chain all work in `styled.View\`...\``. -- **Selectors and combinators on React Native**: attribute selectors with every operator (`~=`, `^=`, `$=`, `*=`, `|=`, `i` flag), `:not()` and `:has()`, tree-structural pseudos (`:first-child`, `:nth-child(an+b)`, `:nth-of-type`), and the four combinators (`Foo &`, `Foo > &`, `Foo + &`, `Foo ~ &`) between styled-component references. Pseudo-state selectors (`:hover`, `:focus`, `:pressed`, `:disabled`) work via React Native's existing event surfaces. The [CSS Compatibility matrix](/docs/compatibility) breaks down every feature by version and raw platform support. -- **Native animations**: `transition`, `@keyframes`, and `@starting-style` all run on React Native out of the box via the default `Animated`-based adapter. An optional reanimated adapter is available for consumers who prefer it (experimental, probably doesn't work yet). -- **`createTheme()` works on native**: the same theme contract from web. `` deep-merges nested themes on native. -- **Plugins subpath**: `import { rtlPlugin, rscPlugin } from 'styled-components/plugins'`. First-party RTL replaces `stylis-plugin-rtl`. Custom plugins move to a narrower `SCPlugin` interface (`name`, `rw`, `decl`). -- **`extractCSS()`**: read the current stylesheet as plain text. Replaces the legacy `disableCSSOMInjection` toggle. You asked, we listened. -- **Global styles emit once**: mounting the same `createGlobalStyle` component multiple times now emits its CSS only once. -- **Faster SSR at scale**. -- **Remapping CSS into native props**: function-form `.attrs((props, ast) => ...)` accepts a second argument that lifts declared style values or theme tokens onto the rendered component as props. Most useful for React Native libraries that style via props (e.g. `react-native-svg`'s ``, `Image`'s `tintColor`, icon libraries). [Details →](/docs/basics#bridging-styles-into-props) - -### Removed from v7 - -- `defaultProps` no longer flows through styled components (React 19 mostly removed it). -- `disableCSSOMInjection` prop on `StyleSheetManager` and the `SC_DISABLE_SPEEDY` env vars have been removed; reach for `extractCSS()` instead. -- `enableVendorPrefixes` has been removed. Modern browser targets handle prefixing themselves; for the few properties that still need them (e.g. `-webkit-backdrop-filter` on Safari), declare both forms in your CSS or run a build-time PostCSS transform. -- `css-to-react-native` is no longer a peer dependency, the functionality has been folded into core. -- The top-level `stylisPluginRSC` export moved to `styled-components/plugins` as `rscPlugin`. The `stylisPlugins` prop on `` is now `plugins`. +- **In-house CSS parser** replaces stylis as the runtime engine. No more `:is()` / `:where()` / `:has()` recursion bugs, no more silent breakage on modern at-rules. +- **Modern CSS on React Native.** `@media` and `@container` queries, viewport and container-query units, font-relative units, modern color spaces, relative-color syntax, the full Math L4 family, logical shorthands, gradients, and filters all work in `styled.View\`...\``. See [React Native gets modern CSS](#react-native-gets-modern-css) for the full surface and the [compatibility matrix](/docs/compatibility) for per-feature status. +- **Selectors and combinators on React Native.** Attribute selectors with every operator, `:not()`, `:has()`, tree-structural pseudos, the four combinators between styled-component references, and the `:hover` / `:focus` / `:pressed` / `:disabled` pseudo-states. +- **Native animations** by default. `transition`, `@keyframes`, and `@starting-style` run on React Native through the built-in `Animated`-based adapter — no setup, no peer dependency. An optional reanimated adapter is available behind one import (experimental; not heavily tested yet). +- **`createTheme()` on native**, with the same contract as web. `` deep-merges nested themes so an inner override that touches one leaf keeps the siblings it inherited. +- **Plugins subpath.** `import { rtlPlugin, rscPlugin } from 'styled-components/plugins'`. First-party RTL replaces `stylis-plugin-rtl`; custom plugins move to a narrower `SCPlugin` interface. See [Plugins moved to a dedicated subpath](#plugins-moved-to-a-dedicated-subpath). +- **`extractCSS()`** reads the current stylesheet as plain text. Replaces the legacy `disableCSSOMInjection` toggle. +- **Global styles emit once.** Mounting the same `createGlobalStyle` component multiple times now emits its CSS only once. +- **Remapping CSS into native props** via the function form of `.attrs((props, ast) => ...)`. Most useful for React Native libraries that style via props (e.g. `react-native-svg`'s ``, `Image`'s `tintColor`, icon libraries). [Details →](/docs/basics#bridging-styles-into-props) +- **Faster SSR at scale.** + +### Removed in v7 + +The [migration guide](#migrating-from-v6) has the full porting steps. At a glance: + +- `defaultProps` on function components — React 19 dropped it. Use `.attrs()` or ``. +- `disableCSSOMInjection` prop and the `SC_DISABLE_SPEEDY` env var — reach for `extractCSS()` instead. +- `enableVendorPrefixes` and the runtime vendor prefixer — declare both forms in CSS or run a build-time PostCSS transform for the few properties that still need them. +- `css-to-react-native` as a peer dependency — folded into core. +- Top-level `stylisPluginRSC` export — now `rscPlugin` from `styled-components/plugins`. ### Peer dependency floors diff --git a/utils/cssCompat.ts b/utils/cssCompat.ts index 54f5c2ee..34ca3269 100644 --- a/utils/cssCompat.ts +++ b/utils/cssCompat.ts @@ -125,6 +125,19 @@ export const COMPAT_ENTRIES: CompatEntry[] = [ "v6 stylis sometimes swallowed @scope or flattened its donut shape. v7's in-house parser keeps @scope intact and lets the browser handle the cascade.", caveats: ['React Native does not implement @scope on either version.'], }, + { + id: 'supports', + title: '@supports', + category: 'at-rules', + caniuseId: 'css-supports-api', + nativeV6: 'no', + nativeV7: 'partial', + summary: + 'Web pass-through in both versions. On React Native, v7 recognises the at-rule but routes the condition through the same evaluator as `@media` / `@container`, which only understands media-query features. Feature-test conditions like `(display: grid)`, `selector(:has(...))`, `font-tech()`, and `not` are not parsed, so the inner block applies unconditionally on native rather than feature-testing the runtime.', + caveats: [ + 'Effectively a no-op wrapper on native today — declarations inside an `@supports` block always apply. Use `Platform.OS` checks instead of `@supports` to branch native behaviour.', + ], + }, { id: 'property', title: '@property (registered custom props)', From c99db0eb39b961acebf4ddb1958f59c913c32fe5 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 13 May 2026 14:44:56 +0000 Subject: [PATCH 2/3] docs(v7): break long hover feature-flag id out of inline code ReactNativeFeatureFlags.shouldPressibilityUseW3CPointerEventsForHover was overflowing the docs column. Move it into a small fenced code block that also shows users how to actually flip the flag. --- sections/v7/native.mdx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/sections/v7/native.mdx b/sections/v7/native.mdx index 28453b4d..1c20a044 100644 --- a/sections/v7/native.mdx +++ b/sections/v7/native.mdx @@ -62,7 +62,15 @@ const Toggle = styled.Pressable<{ 'aria-pressed'?: boolean }>` The same CSS works on web and native. Anchor pseudo-states with `&` (bare `:hover` parses as a descendant selector, not a state). -**Pseudo-states.** `&:hover`, `&:focus`, `&:focus-visible`, `&:pressed`, `&:disabled`. Hover on native uses Pressable's pointer events on RN ≥ 0.85, but stock RN gates them behind `ReactNativeFeatureFlags.shouldPressibilityUseW3CPointerEventsForHover`, which defaults to FALSE in 0.85 — enable it at startup to get `&:hover` to fire. +**Pseudo-states.** `&:hover`, `&:focus`, `&:focus-visible`, `&:pressed`, `&:disabled`. Hover on native uses Pressable's pointer events on RN ≥ 0.85, but stock RN gates them behind a feature flag that defaults to FALSE in 0.85 — flip it at app startup to get `&:hover` to fire: + +```js +import { ReactNativeFeatureFlags } from 'react-native'; + +ReactNativeFeatureFlags.override({ + shouldPressibilityUseW3CPointerEventsForHover: () => true, +}); +``` **Attribute selectors.** The full CSS Selectors 4 grammar works on native: From 61d0f851dd8283d7b30df2a56048b88228d54ada Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 13 May 2026 14:50:22 +0000 Subject: [PATCH 3/3] docs(v7): drop the duplicated Removed-in-v7 list and sweep em-dashes Each Removed bullet in whats-new.mdx restated the matching section in the migration guide, so the section was pure duplication. Removed it; readers land on Highlights and then the migration guide directly below. Em-dash sweep: kept one in migration.mdx where it punctuates naturally, swapped the rest to colons (in name: description bullets) or semicolons / periods elsewhere. --- sections/v7/migration.mdx | 2 +- sections/v7/native.mdx | 24 ++++++++++++------------ sections/v7/whats-new.mdx | 12 +----------- 3 files changed, 14 insertions(+), 24 deletions(-) diff --git a/sections/v7/migration.mdx b/sections/v7/migration.mdx index f1d2edac..7f971adc 100644 --- a/sections/v7/migration.mdx +++ b/sections/v7/migration.mdx @@ -70,7 +70,7 @@ The [CSS Compatibility matrix](/docs/compatibility) is the per-feature source of - The peer floor moves to RN ≥ 0.85. - `border: none` now emits `border-style: none` (previously `solid`, which produced surprising hairlines). -- The optional reanimated adapter is opt-in at app entry: `import 'styled-components/native/reanimated'`. `react-native-reanimated@^4` becomes a peer dependency only in that case. The adapter is experimental and not heavily tested yet — the default `Animated`-based adapter runs without any setup. +- The optional reanimated adapter is opt-in at app entry: `import 'styled-components/native/reanimated'`. `react-native-reanimated@^4` becomes a peer dependency only in that case. The adapter is experimental and not heavily tested yet; the default `Animated`-based adapter runs without any setup. [React Native gets modern CSS](#react-native-gets-modern-css) walks through the new surface (modern color spaces, gradients, filters, selectors, animations, `createTheme()`) and calls out the platform-level caveats — iOS SwiftUI filter opt-in, hover feature flag, conic-gradient gap, and the rest. The compatibility matrix has the per-feature breakdown. diff --git a/sections/v7/native.mdx b/sections/v7/native.mdx index 1c20a044..6b1ac55f 100644 --- a/sections/v7/native.mdx +++ b/sections/v7/native.mdx @@ -19,7 +19,7 @@ const Card = styled.View` `; ``` -- **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. +- **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. - **Modern color spaces**: `oklch(...)`, `oklab(...)`, `lch(...)`, `lab(...)`, `color-mix(in , ...)`. 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). - **Relative-color syntax** (CSS Color 5) for the four modern spaces: `oklch(from #ff0000 calc(l - 0.15) c h)`. Theme-token bases work too: `oklch(from ${theme.colors.brand} calc(l - 0.15) c h)`. - **`color-mix(in , …)`** through `srgb`, `oklab`, `oklch`, `lab`, or `lch`. @@ -38,7 +38,7 @@ const Card = styled.View` - **`field-sizing: content`** auto-grows a `` to its content. - **`interactivity: inert`** suppresses interaction and hides the subtree from screen readers. -**`env(safe-area-inset-*)` caveat.** The function parses correctly but currently resolves to 0 — the integration with `react-native-safe-area-context` is not wired yet. Use `useSafeAreaInsets()` directly until that lands. +**`env(safe-area-inset-*)` caveat.** The function parses correctly but currently resolves to 0; the integration with `react-native-safe-area-context` is not wired yet. Use `useSafeAreaInsets()` directly until that lands. ### Selectors on React Native @@ -62,7 +62,7 @@ const Toggle = styled.Pressable<{ 'aria-pressed'?: boolean }>` The same CSS works on web and native. Anchor pseudo-states with `&` (bare `:hover` parses as a descendant selector, not a state). -**Pseudo-states.** `&:hover`, `&:focus`, `&:focus-visible`, `&:pressed`, `&:disabled`. Hover on native uses Pressable's pointer events on RN ≥ 0.85, but stock RN gates them behind a feature flag that defaults to FALSE in 0.85 — flip it at app startup to get `&:hover` to fire: +**Pseudo-states.** `&:hover`, `&:focus`, `&:focus-visible`, `&:pressed`, `&:disabled`. Hover on native uses Pressable's pointer events on RN ≥ 0.85, but stock RN gates them behind a feature flag that defaults to FALSE in 0.85. Flip it at app startup to get `&:hover` to fire: ```js import { ReactNativeFeatureFlags } from 'react-native'; @@ -98,7 +98,7 @@ const Row = styled.View` **`:is()` and `:where()`.** Apply the rule to each listed state: `&:is(:hover, :focus)`. -**Tree-structural pseudos.** `:first-child`, `:last-child`, `:only-child`, `:first-of-type`, `:last-of-type`, `:only-of-type`, and the functional `:nth-*()` 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. +**Tree-structural pseudos.** `:first-child`, `:last-child`, `:only-child`, `:first-of-type`, `:last-of-type`, `:only-of-type`, and the functional `:nth-*()` 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 between styled-component references.** @@ -112,7 +112,7 @@ const Title = styled.Text` `; ``` -Descendant matching (`${Foo} &`) is transparent to non-styled wrappers. The child combinator (`>`) requires the matched element to be a direct child of a published parent — a non-styled wrapper in between will break the match. +Descendant matching (`${Foo} &`) is transparent to non-styled wrappers. The child combinator (`>`) requires the matched element to be a direct child of a published parent; a non-styled wrapper in between will break the match. ### Animations @@ -129,7 +129,7 @@ const Card = styled.View` Eligible properties (opacity, every color, all border radius corners, transforms, shadows, filter) run on the native thread. `@keyframes` and `@starting-style` (first-mount enter animations) work the same way. `animation-direction`, `animation-fill-mode`, `animation-play-state`, `animation-iteration-count`, `animation-composition` (`replace | add | accumulate`), and per-frame easing all work without extra imports. -CSS easing matches the W3C spec curves: `ease`, `ease-in`, `ease-out`, `ease-in-out`, `cubic-bezier()`, `steps()`, `linear()`. Note: the CSS `ease` keyword is the W3C `ease` curve, not React Native's `Easing.ease` (which is `ease-in`). `prefers-reduced-motion` is honored — durations collapse to 0 when the OS setting is on. +CSS easing matches the W3C spec curves: `ease`, `ease-in`, `ease-out`, `ease-in-out`, `cubic-bezier()`, `steps()`, `linear()`. Note: the CSS `ease` keyword is the W3C `ease` curve, not React Native's `Easing.ease` (which is `ease-in`). `prefers-reduced-motion` is honored; durations collapse to 0 when the OS setting is on. If you'd rather drive animations through reanimated, the optional adapter is one import: @@ -141,7 +141,7 @@ import 'styled-components/native/reanimated'; ### Remapping CSS into native props -Many React Native libraries take styling through component props rather than the `style` prop — `react-native-svg`'s ``, charting libraries with `tintColor`, icon libraries with `color`. The function form of `.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: +Many React Native libraries take styling through component props rather than the `style` prop: `react-native-svg`'s ``, charting libraries with `tintColor`, icon libraries with `color`. The function form of `.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 styled from 'styled-components/native'; @@ -166,7 +166,7 @@ const Logo = styled(Image).attrs((_props, ast) => ({ `; ``` -`ast.pop(key)` reads the value and prevents the declaration from reaching the rendered style. `ast.peek(key)` reads without removing — use it when both the prop and the CSS declaration should flow through. Both accept an optional fallback. +`ast.pop(key)` reads the value and prevents the declaration from reaching the rendered style. `ast.peek(key)` reads without removing; use it when both the prop and the CSS declaration should flow through. Both accept an optional fallback. 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 with autocomplete and type inference from your augmented `DefaultTheme`. @@ -204,11 +204,11 @@ These spec features pass through to react-native-web but do not render on React - `transform-style: preserve-3d` (basic `rotateX` / `rotateY` / `translateZ` / `perspective` do work) - CSS Grid (`display: grid`, `grid-template-*`) - Form-state pseudos (`:invalid`, `:required`, `:read-only`, `:checked`) -- `@supports` is parsed but its condition is never evaluated against the runtime — declarations inside always apply on native. Branch with `Platform.OS` instead. +- `@supports` is parsed but its condition is never evaluated against the runtime, so declarations inside always apply on native. Branch with `Platform.OS` instead. - Scroll-snap, view transitions, scroll-driven animations Platform-conditional features (the matrix has the full story): -- `filter` — only `brightness` and `opacity` apply on iOS without the SwiftUI opt-in (set `ReactNativeReleaseLevel: experimental` in `Info.plist` / `ios.infoPlist`); Android `blur` and `drop-shadow` require API 31+. -- `mix-blend-mode` — all 16 non-Normal modes on iOS; Android needs API 29+. -- `transform: skewX/skewY` — iOS renders correctly; Android silently drops the shear pending an upstream fix. +- `filter`: only `brightness` and `opacity` apply on iOS without the SwiftUI opt-in (set `ReactNativeReleaseLevel: experimental` in `Info.plist` / `ios.infoPlist`); Android `blur` and `drop-shadow` require API 31+. +- `mix-blend-mode`: all 16 non-Normal modes on iOS; Android needs API 29+. +- `transform: skewX/skewY`: iOS renders correctly; Android silently drops the shear pending an upstream fix. diff --git a/sections/v7/whats-new.mdx b/sections/v7/whats-new.mdx index 686895bb..fc767d72 100644 --- a/sections/v7/whats-new.mdx +++ b/sections/v7/whats-new.mdx @@ -21,7 +21,7 @@ The bottleneck now is funding. styled-components can help provide a universal Re - **In-house CSS parser** replaces stylis as the runtime engine. No more `:is()` / `:where()` / `:has()` recursion bugs, no more silent breakage on modern at-rules. - **Modern CSS on React Native.** `@media` and `@container` queries, viewport and container-query units, font-relative units, modern color spaces, relative-color syntax, the full Math L4 family, logical shorthands, gradients, and filters all work in `styled.View\`...\``. See [React Native gets modern CSS](#react-native-gets-modern-css) for the full surface and the [compatibility matrix](/docs/compatibility) for per-feature status. - **Selectors and combinators on React Native.** Attribute selectors with every operator, `:not()`, `:has()`, tree-structural pseudos, the four combinators between styled-component references, and the `:hover` / `:focus` / `:pressed` / `:disabled` pseudo-states. -- **Native animations** by default. `transition`, `@keyframes`, and `@starting-style` run on React Native through the built-in `Animated`-based adapter — no setup, no peer dependency. An optional reanimated adapter is available behind one import (experimental; not heavily tested yet). +- **Native animations** by default. `transition`, `@keyframes`, and `@starting-style` run on React Native through the built-in `Animated`-based adapter, with no setup or peer dependency. An optional reanimated adapter is available behind one import (experimental; not heavily tested yet). - **`createTheme()` on native**, with the same contract as web. `` deep-merges nested themes so an inner override that touches one leaf keeps the siblings it inherited. - **Plugins subpath.** `import { rtlPlugin, rscPlugin } from 'styled-components/plugins'`. First-party RTL replaces `stylis-plugin-rtl`; custom plugins move to a narrower `SCPlugin` interface. See [Plugins moved to a dedicated subpath](#plugins-moved-to-a-dedicated-subpath). - **`extractCSS()`** reads the current stylesheet as plain text. Replaces the legacy `disableCSSOMInjection` toggle. @@ -29,16 +29,6 @@ The bottleneck now is funding. styled-components can help provide a universal Re - **Remapping CSS into native props** via the function form of `.attrs((props, ast) => ...)`. Most useful for React Native libraries that style via props (e.g. `react-native-svg`'s ``, `Image`'s `tintColor`, icon libraries). [Details →](/docs/basics#bridging-styles-into-props) - **Faster SSR at scale.** -### Removed in v7 - -The [migration guide](#migrating-from-v6) has the full porting steps. At a glance: - -- `defaultProps` on function components — React 19 dropped it. Use `.attrs()` or ``. -- `disableCSSOMInjection` prop and the `SC_DISABLE_SPEEDY` env var — reach for `extractCSS()` instead. -- `enableVendorPrefixes` and the runtime vendor prefixer — declare both forms in CSS or run a build-time PostCSS transform for the few properties that still need them. -- `css-to-react-native` as a peer dependency — folded into core. -- Top-level `stylisPluginRSC` export — now `rscPlugin` from `styled-components/plugins`. - ### Peer dependency floors - `react` and `react-dom` ≥ 19.0.0