github jpudysz/react-native-unistyles v3.8.0
Release v3.8.0

3 hours ago

What's Changed

A minor release with one breaking change: Unistyles now requires React Native 0.85+ (Expo SDK 56+). The experimental Metro integration no longer runs any Babel transform, and on web the stylesheet is updated rule by rule instead of being rewritten, so adding and removing styles is 2–8× faster and re-applying unchanged ones on resize is 100× faster or more. Fixes cover Android layouts stuck in the previous orientation after a rotation, useUnistyles after Fast Refresh, nested Text colors next to Reanimated views, setRootViewBackgroundColor while a Modal is open, RTL detection and the components/native/* exports.

🔀 Switching from the Babel plugin to Metro

The Babel plugin will be deprecated in the next release (see below). If your app bundles with Metro, you can switch now in three steps:

  1. Wrap your Metro config:
// metro.config.js
const { getDefaultConfig } = require('expo/metro-config')
// or for bare React Native
// const { getDefaultConfig } = require('@react-native/metro-config')
const { withUnistylesConfig } = require('react-native-unistyles/metro')

module.exports = withUnistylesConfig(getDefaultConfig(__dirname))
  1. Remove the plugin from babel.config.js. Unistyles throws an error if both are set up:
 plugins: [
-    ['react-native-unistyles/plugin', { root: 'src' }]
 ]
  1. Use the styles that useVariants returns. The codemod converts your components:
npx jscodeshift --parser=tsx --extensions=tsx,ts,jsx,js -t node_modules/react-native-unistyles/codemods/use-variants.js src

Then restart Metro with a clear cache (npx expo start -c or npx react-native start --reset-cache). Re.Pack, Vite, Next.js and other web bundlers keep using the Babel plugin, and Jest needs no changes. Full guide

⚠️ Breaking change

  • The minimum React Native version goes from 0.81 to 0.85, and from Expo SDK 54 to 56 (the first SDK that ships 0.85). The react-native peer dependency is now >=0.85.0, so your package manager warns before the native build fails. The New Architecture is still required (#1362)

📣 Upcoming deprecation

  • The Babel plugin (react-native-unistyles/plugin) will be deprecated in the next release and removed in Unistyles 4.0. Nothing changes in 3.8.0. If you bundle with Metro, you can switch now, see "Switching from the Babel plugin to Metro" above

🚇 Experimental Metro integration

  • withUnistylesConfig no longer runs the Babel plugin's transforms. Unistyles now records what each StyleSheet reads from rt while it runs: through a HostObject on native and a Proxy on web. Dependencies read behind a condition, in a helper or in a dynamic function are picked up too. What a StyleSheet function reads counts for all of its styles, so Unistyles recomputes the whole StyleSheet and updates only the views whose styles changed. On the e2e runtime screen, an accent change now commits 3 views instead of 133. To keep a value that changes every frame (like rt.insets.ime) to the views that use it, read it inside a dynamic function (#1355, docs)
  • If you already use the Metro integration:
    • styles.useVariants() no longer changes styles. Use the styles it returns. This form works with the Babel plugin too, and a codemod converts your components:
// before
styles.useVariants({ size })
return <View style={styles.button} />

// 3.8.0
const variantStyles = styles.useVariants({ size })
return <View style={variantStyles.button} />
npx jscodeshift --parser=tsx --extensions=tsx,ts,jsx,js -t node_modules/react-native-unistyles/codemods/use-variants.js src
  • The debug option is gone, and withUnistylesConfig now wraps only resolveRequest, not babelTransformerPath
  • Unistyles reached through a symlink is handled the same way. A second copy of Unistyles (e.g. nested in a library's node_modules) now prints a Metro warning, because its styles won't update
  • With the Metro integration on native, withUnistyles components re-render for other changes only when their computed styles changed. Theme changes re-render them as before (#1355)

⚡️ Web performance

  • Web no longer rewrites the whole <style id="unistyles-web"> tag on every style add or remove. Unistyles now inserts, edits and deletes single rules through the CSSOM, skips rules that haven't changed, and updateTheme replaces only that theme's rules (#1354 by @whydidoo)
Chromium 3.7.0 Chromium 3.8.0 Safari 3.7.0 Safari 3.8.0
Add 200 styles 847 173 1783 867
Remove 200 styles 740 88 1674 679
Re-apply 500 unchanged (resize) 511 5 767 2
updateTheme ×20 110 69 181 91

ms, 3000 elements and 2000 styles, layout read after each operation

  • #unistyles-web no longer has text content. DevTools still shows its rules. If your code reads innerText or textContent from that tag, read sheet.cssRules instead

🎨 Themes and styles

  • After a theme change, a Text nested in another Text next to a Reanimated animated view (e.g. a swipeable row) no longer keeps its old color. Reanimated clones React's nodes before they mount, and Unistyles used to skip them, so React's next commit brought back the old children (#1353, closes #1329)
  • useUnistyles() and withUnistyles keep following theme changes after a Fast Refresh. Before, they stayed on the old theme until the app restarted (#1358, closes #1316)
  • Several updateTheme calls in a row no longer let React re-commit views with styles from before the first update. When a later change committed nothing, Unistyles dropped the handles of the earlier commit. This affected the Babel plugin too (#1355)
  • rt.rtl now matches React Native's I18nManager.isRTL. Before, Unistyles worked out RTL on its own and ignored I18nManager.allowRTL(false), Android's supportsRtl and iOS apps without an RTL localization, so rt.rtl could say RTL while React Native laid the screen out LTR. On Android, allowRTL and forceRTL changes now show up at runtime (#1356)
  • transformOrigin strings now accept signed and decimal values on native, e.g. '-12.5px 50.5%' (#1356)

🤖 Android

  • After a rotation, the app is no longer sometimes laid out for the previous orientation until the next rotation. Unistyles committed style updates on a copy of the root taken before the commit, so when the new screen size arrived in between, it put the old size back. It now clones from the root it's committing to. In the reporter's repro, 3.6.0 got stuck in 3 of 40 rotations and 3.8.0 in none (#1357, closes #1347)

🍏 iOS

  • UnistylesRuntime.setRootViewBackgroundColor called while a React Native Modal is open now colors the app's root view. Before, it colored the Modal and the root view kept its old color (#1359, closes #1333)

🌐 Web

Don't miss a new react-native-unistyles release

NewReleases is sending notifications on new releases.