Babel plugin
Unistyles 3.0 relies heavily on the Babel plugin, which helps convert your code in a way that allows binding the ShadowNode with Unistyles. Before reading this guide, make sure to check the Look under the hood guide.
Our golden rule is to never introduce any component that could pollute your native view hierarchy. In other words, if you use a View, it will be rendered as-is in the native view hierarchy.
Let’s discuss the responsibilities of the Babel plugin:
1. Detecting StyleSheet dependencies
Section titled “1. Detecting StyleSheet dependencies”Each StyleSheet is different. One might rely on a theme, another on miniRuntime, and so on.
The same applies to styles. Each style depends on different things. For example, you can wrap your app in a View that safeguards your app from rendering behind the notch or navigation bar.
Another style might be used in your Typography component and provides text color based on the apps’ theme.
Should the Typography style re-calculate on an insets change? Or should the View that relies on insets re-render on a theme change?
We don’t think that’s a good idea. The first responsibility of the Babel plugin is to detect all dependencies in your StyleSheet. This ensures that only the relevant styles are recalculated when necessary.
// Babel: depends on themeconst stylesheet = StyleSheet.create(theme => ({ container: { // Babel: depends on theme backgroundColor: theme.colors.background }, text: { // Babel: static (no dependencies) fontSize: 12 }}))// Babel: depends on theme and miniRuntimeconst stylesheet = StyleSheet.create((theme, rt) => ({ container: { // Babel: depends on theme and insets paddingTop: rt.insets.top, paddingBottom: rt.insets.bottom, backgroundColor: theme.colors.background }, text: (fontSize: number) => ({ // Babel: depends on theme color: theme.colors.text, // Babel: depends on fontScale fontSize: rt.fontScale >= 3 ? fontSize * 1.5 : fontSize * 0.8 })}))2. Component factory (borrowing ref)
Section titled “2. Component factory (borrowing ref)”This is the most crucial part—without it, Unistyles won’t be able to update your views from C++.
In the early versions of Unistyles 3.0, we tried solving this problem by using the ref prop, but it wasn’t reliable enough.
Many developers use different style syntaxes, making it impossible to support all of them.
Instead, we decided to leave the user’s ref as is and transfer the implementation from Babel to our component factory.
This way we have more control and we have an unified way of registering your ShadowNodes.
The component factory is a function that takes your component and renders it with an overridden ref prop:
const factory = Component => <Component ref={someMagic✨} {...props} />;Let’s go through some examples so you can better understand how this works:
import { View } from 'react-native'
const ref = useRef()
<View ref={ref} />import { View } from 'react-native-unistyles/components/native/View'
const ref = useRef()
// no changes<View ref={ref} />We also support other components to extract ShadowNode from them:
import { Pressable, Image } from 'react-native'
<Pressable ref={ref => { doSomething(ref) }} onPress={() => {}}/><Image source={require('./image.png')} style={styles.image} ref={ref2} />import { Pressable } from 'react-native-unistyles/components/native/Pressable'import { Image } from 'react-native-unistyles/components/native/Image'
// no changes<Pressable ref={ref => { doSomething(ref) }} onPress={() => {}}/><Image source={require('./image.png')} style={styles.image} ref={ref2} />The following react-native components are processed by the component factory: ActivityIndicator, View, Text, Image, ImageBackground, KeyboardAvoidingView, Pressable, ScrollView, FlatList, SectionList, Switch, TextInput, RefreshControl, TouchableHighlight, TouchableOpacity, VirtualizedList, Animated and SafeAreaView.
Modal (no exposed native handle) and TouchableWithoutFeedback (can’t accept a ref) are not supported.
3. Creating scopes for stateless variants
Section titled “3. Creating scopes for stateless variants”When you use variants, each time you call useVariants, a new scope is created. This scope contains a local copy of stylesheet that won’t affect other components.
This feature is similar to time travel, allowing you to explore different states of your styles with different calls to useVariants.
From your perspective, using variants is simple: you just need to call the useVariants hook:
styles.useVariants({ size: 'small'})Behind the scenes, we create a scoped constant that can be accessed anywhere within the same block:
const _styles = styles{ const styles = _styles.useVariants({ size: 'small' })
// Your code here}This approach also works seamlessly with console.log, allowing you to inspect styles at any point:
// Styles without variantsconsole.log(styles.container)
styles.useVariants({ size: 'small'})
// Styles with variants: smallconsole.log(styles.container)
styles.useVariants({ size: 'large'})
// Styles with variants: largeconsole.log(styles.container)By leveraging such scopes, we ensure support for any level of nesting!
Extra configuration
Section titled “Extra configuration”The Babel plugin comes with a few additional options to extend its usage.
root (required)
Section titled “root (required)”All files within the specified root folder will be processed by the Babel plugin.
Files outside of root are processed only if they create a StyleSheet from Unistyles or import one of the autoProcessImports paths.
If you need to process extra folders, use autoProcessImports or autoProcessPaths options.
{ root: 'src' // or 'app', or any name of your root folder}The folder is resolved relative to Babel’s root (your project directory by default).
It can’t point to the project root itself (e.g. '.'), as that would include the node_modules folder.
autoProcessImports
Section titled “autoProcessImports”This configuration should be used when you want to process files containing specific imports.
It can be useful for monorepos that use Unistyles with absolute paths, such as @my-org/styles.
{ autoProcessImports: ['@my-org/styles'] // whenever Babel encounters this import, it will process your file}autoRemapImports
Section titled “autoRemapImports”This is the most powerful option, but most likely, you won’t need to use it. It allows you to remap uncommon imports to Unistyles components.
This may happen if a 3rd library does not import react-native components directly, but instead uses its own factory or a relative path.
Unistyles uses it internally to support the following imports from react-native internals:
// React Native 0.83+import { unstable_NativeText as NativeText, unstable_NativeView as NativeView } from "react-native"
// deep imports, deprecated by React Nativeimport { NativeText } from "react-native/Libraries/Text/TextNativeComponent"import View from "react-native/Libraries/Components/View/ViewNativeComponent"Let’s say you have a library called custom-library that imports react-native raw components directly:
import { NativeText } from "react-native/Libraries/Text/TextNativeComponent"import View from "react-native/Libraries/Components/View/ViewNativeComponent"To convert it to Unistyles, you can use the following configuration:
{ autoRemapImports: [ { path: 'node_modules/custom-library/components', // <- must be path from node_modules imports: [ { isDefault: false, // <- is default import? name: 'NativeText', // <- if not, what's the import name? path: 'react-native/Libraries/Text/TextNativeComponent', // <- what's the import source? mapTo: 'NativeText' // <- which Unistyles component should be used? Check react-native-unistyles/src/components/native }, { isDefault: true, path: 'react-native/Libraries/Components/View/ViewNativeComponent', mapTo: 'NativeView' } ] } ]}autoProcessPaths
Section titled “autoProcessPaths”This configuration is unrelated to the root, autoProcessImports, and autoRemapImports options and can be used alongside them.
By default, the Babel plugin ignores node_modules. However, you can extend these paths to attempt converting 3rd components into Unistyles compatible ones.
Within these paths, we will replace react-native imports with react-native-unistyles factories that borrow component refs. Read more.
The following paths are always processed, and any paths you pass are added to this list:
[ 'react-native-reanimated/src/component', 'react-native-reanimated/lib/module/component']In order to list detected dependencies by the Babel plugin you can enable the debug flag.
It will console.log name of the file and component with Unistyles dependencies.
Usage with React Compiler
Section titled “Usage with React Compiler”Check this guide for more details.
Usage in babel.config.js
Section titled “Usage in babel.config.js”You can apply any of the options above as follows:
/** @type {import('react-native-unistyles/plugin').UnistylesPluginOptions} */const unistylesPluginOptions = { // any component in this folder will be processed root: 'src', // also files with these imports will be processed (in any non-root folder) autoProcessImports: ['@react-native-ui-kit', '@my-org/styles'], // additionally process components from this `node_modules` package autoProcessPaths: ['external-library/components'], // log what you've found debug: true,}
module.exports = function (api) { api.cache(true)
return { // other config plugins: [ ['react-native-unistyles/plugin', unistylesPluginOptions] // other plugins ] }}Usage with Re.Pack
Section titled “Usage with Re.Pack”If your app is bundled with Re.Pack, use the RepackUnistylePlugin instead of adding Unistyles to your babel.config.js.
It registers an Rspack loader that runs the Unistyles Babel plugin on your files.
import * as Repack from '@callstack/repack'import { RepackUnistylePlugin } from 'react-native-unistyles/repack-plugin'
export default { // other config plugins: [ new Repack.RepackPlugin(), new RepackUnistylePlugin({ // the same options as for the Babel plugin, `root` is required unistylesPluginOptions: { root: 'src' } }) ]}RepackUnistylePlugin accepts the following options:
unistylesPluginOptions– options passed to the Unistyles Babel plugin (see Extra configuration). The Babel plugin requires therootoption, so you need to provide it hereruleExcludePaths– an array ofRegExpwith paths that should be skipped by the loader. Defaults toBASE_REPACK_EXCLUDE_PATHS(exported fromreact-native-unistyles/repack-plugin), which coversreact,react-native,react-native-unistyles,react-native-nitro-modules,@callstack/repackand other core packages