Unistyles 3.0: Beyond React Native StyleSheet
Development•React Native••9 minutes read
Jacek Pudysz
Guest Author
Discover how C++ core and a focus on developer experience can revolutionize styling in your cross-platform Expo application.

This is a guest post from Jacek Pudysz - the creator of Unistyles and a talented software engineer with a passion for performance, architecture, and making developer tools delightful.
...
Great design is at the heart of every successful application. On the web, CSS gives us the power to create visually astonishing products. But in the native world, styling is a different beast. There are no stylesheets, classNames, or media queries - only APIs where every style is set explicitly.
Styling UIKit View in Swift is imperative and tedious:
let myView = UIView()// set sizemyView.frame = CGRect(x: 50, y: 100, width: 200, height: 100)// set stylesmyView.backgroundColor = UIColor.systemBluemyView.layer.cornerRadius = 12myView.layer.borderWidth = 2myView.layer.borderColor = UIColor.white.cgColor
To solve this, React Native provides a StyleSheet API - brilliant bridge between these two worlds. It gives us a familiar, CSS-like declarative syntax, allowing us to define and reuse styles in JavaScript.
Under the hood, React Native handles the heavy lifting, mapping style objects to their native Swift and Kotlin equivalents. The result is code that feels intuitive and clean:
const styles = StyleSheet.create({myView: {width: 200,height: 100,backgroundColor: 'blue',borderRadius: 12,borderWidth: 2,borderColor: 'white'}});
But as our apps become more complex, we can hit the ceiling of what StyleSheet was designed for. Building sophisticated, theme-able, and responsive apps often means writing significant boilerplate and compromising on performance.
What if we could have the best of both worlds? The power of CSS-like features with the raw speed of native code?
Meet Unistyles
Unistyles was built for ambitious, cross-platform applications on the principle of zero performance compromises. Its power comes from a C++ core that communicates directly with the Fabric via JSI, bypassing the React Native bridge entirely.
The recent rewrite of Unistyles introduces a paradigm shift for React Native styling: enabling dynamic style changes without forcing your components to re-render.
We believe your app shouldn't have to re-render just because a theme color or the screen orientation changes.
Using Unistyles is as simple as replacing the React Native StyleSheet import:
-import { StyleSheet } from 'react-native';+import { StyleSheet } from 'react-native-unistyles';
From that point on, you have access to a suite of features that feel like they belong in a modern CSS environment, including dynamic themes, responsive breakpoints, media queries, and web-only features like pseudo-classes and CSS variables.
const styles = StyleSheet.create(theme => ({myView: {width: 200,height: 100,// access your theme values/functions// there is no restriction about the theme shapebackgroundColor: theme.colors.foreground,borderRadius: theme.gap(2),borderColor: theme.colors.white,// convert any style key to object to use brakpoints// we will generate automatically necessary media queries for the web!borderWidth: {sm: 2,md: 4},// applied only for web_web: {// you can use any pseudo class_hover: {// and any web styles!transform: 'scale(1.1)'}}}}));
When should you use Unistyles?
Unistyles isn't just another styling library. It's a specialized tool designed for high-performance, scalable applications. While it can be used in any project, it truly shines when you face these common challenges:
When performance is critical
Are you building complex screens with frequent UI updates? Unistyles moves all style computations to a C++ layer via JSI, eliminating bridge traffic and preventing performance killing re-renders. If your app feels sluggish during style transitions, Unistyles is your solution.
When you need powerful, type-safe theming
Supporting light/dark mode is just the beginning. Unistyles allows you to define and switch between multiple themes at runtime - instantly, and without re-rendering your components. All theme properties are 100% type-safe, so you'll never reference a non-existent color or font size again.
import { UnistylesRuntime } from 'react-native-unistyles';import { Pressable, Text } from 'react-native';export function ThemeSwitcher() {function onSwitchTheme() {// access current themeconst nextTheme = UnistylesRuntime.themeName === 'dark_red'? 'dark_blue': 'dark_red'// your components will update styles with no re-renderUnistylesRuntime.setTheme(nextTheme)}return (<Pressable onPress={onSwitchTheme}><Text>Switch theme</Text></Pressable>);}
When you’re building for every screen
Your Expo app runs on mobile, web, and tablets. Your styling solution should, too. Unistyles has first-class support for responsive design using familiar syntax. Define different styles based on breakpoints and generate media queries.
import { StyleSheet, mq } from 'react-native-unistyles';const styles = StyleSheet.create(theme => ({header: {width: '100%',height: theme.headerHeight,// convert any style value to object, and then// use breakpoints or build media queries with mq util// we will generate automatically media queries for web tooflexDirection: {sm: 'row',[mq.only.width('sm', 1200)]: 'column'}}}));
When you're building a Universal App with Expo Router
Unistyles is built with Server-Side Rendering in mind. It fully supports Expo Router, ensuring your styles are rendered correctly on the server and hydrated on the client, providing a fast, consistent experience for your users.
On the web, Unistyles intelligently optimizes theming by converting all your theme colors into CSS variables.
What does this mean for performance? When a user switches themes, Unistyles doesn't need to recompute every style on the page. Instead, it simply swaps a single class name on the <body> element, and the browser handles the color changes instantly through native CSS.
The result is blazing-fast, flicker-free theme changes with zero JavaScript overhead. Here’s a look at the generated CSS variables:
The same principle applies to responsive design. Unistyles avoids entirely JavaScript based breakpoint calculations. Instead, it compiles your responsive styles directly into native CSS media queries.
This offloads all responsive logic to the browser, ensuring maximum performance and eliminating layout shifts caused by JavaScript execution. The generated output is exactly what you'd expect from a modern CSS styling framework:
When you're tired of component boilerplate
If your components are cluttered with conditional logic, hooks and complex prop drilling for themes, you'll love Unistyles API that offers variants and compound variants. Keep styles right inside StyleSheet and clean up your component code for good.
This approach often starts simple. A single if statement computes a button's background color. But what happens when you need to support a dark mode, or add a "disabled" state?
Your component quickly becomes a tangled web of if-else statements and complex conditional logic. This pattern makes the code hard to read, difficult to debug, and a nightmare to maintain.
Consider this common example of a cluttered component:
export function ClutteredComponent({ isDisabled, isLink, size }) {const buttonSize = size === 'small'? { width: 100, height: 40 }: size === 'medium'? { width: 120, height: 50 }: { width: 150, height: 60 };const backgroundColor = isDisabled? 'gray': isLink? 'blue': 'white';return (<Pressabledisabled={isDisabled}onPress={() => {}}style={{...buttonSize,backgroundColor}}><Text>Press me</Text></Pressable>);}
This is where Unistyles provides an elegant solution. Instead of embedding conditional logic in your component, you can extract it into declarative variants and compoundVariants right inside your stylesheet.
The component is no longer responsible for computing **styles - it simply describes its state through props. The ClutteredComponent from before can be transformed into this clean, maintainable version:
export function UnclutteredComponent({ isDisabled, isLink, size }) {styles.useVariants(size,isDisabled,isLink);return (<Pressabledisabled={isDisabled}onPress={() => {}}style={styles.button}><Text>Press me</Text></Pressable>);}const styles = StyleSheet.create(theme => ({button: {variants: {size: {small: {width: 100,height: 40},medium: {width: 120,height: 50},large: {width: 120,height: 60}},isDisabled: {true: {backgrondColor: theme.colors.gray}},isLink: {true: {backgroundColor: theme.colors.link}}},compoundVariants: [{isLink: true,isDisabled: true,styles: {backgroundColor: theme.colors.gray}}]}});
Selective updates
One of the biggest challenges in a styling library is preventing unnecessary re-renders. A small state change can often trigger a cascade of updates, leading to performance issues. Unistyles solves this with a powerful feature we call Selective Updates.
Imagine you have multiple components on a screen, all using the same stylesheet:
function MyScreen () {return (<View style={styles.container}><Header style={styles.header} /><Content style={styles.container} /><Footer style={styles.footer} /></View>);}// with rt (runtime) you can access many platform values// like screen dimensions, insets, status bar dimensions and much more!const styles = StyleSheet.create((theme, rt) => {(container: {flex: 1,backgroundColor: theme.colors.background},header: {height: 200,width: rt.screen.width,backgroundColor: theme.colors.foreground},footer: {height: 200,width: rt.screen.width,backgroundColor: theme.colors.foreground},)});
Unistyles targets your style updates with surgical precision, and it's made possible by a deep understanding of your styles.
Our Babel plugin analyzes your stylesheets at build time to determine exactly which styles are dynamic. So, if your Header and Footer styles depend on screen width, Unistyles knows this.
When a resize event occurs, the C++ core doesn't waste time on styles that haven't changed. It precisely targets and re-computes only the styles for Header and Footer, leaving the View and Content component's styles untouched.
This is possible because Unistyles maintains a complete representation of your styles in C++, allowing it to apply targeted updates directly to the Shadow tree and skip the expensive React render cycle.
Here’s how your component tree is re-rendered with other styling libraries:
And here’s how Unistyles selectively targets only the affected nodes, leaving other components intact:
It might look like magic. But our goal is to provide performance and a developer experience so seamless, you'll never want to go back. This is how styling in React Native should be.
Expo tutorial with Unistyles
If you want to get started with Unistyles 3.0, there’s no easier way than grabbing a mug of coffee and following the official tutorial. In just one hour, you’ll build a music player app using Expo Router, Reanimated, and of course, Unistyles.
I strongly encourage you to give it a try and see how to use all the StyleSheet APIs while building a cross-platform React Native application.
Don’t wait - get started today:
- Expo tutorial: https://www.unistyl.es/v3/tutorial/intro
- Check out our release blog post: https://www.reactnativecrossroads.com/posts/introducing-unistyles-3
- Leave us a star on Github: https://github.com/jpudysz/react-native-unistyles
- Get started with our beautiful documentation: https://www.unistyl.es/
- If you have any questions join the community on Discord: https://discord.com/invite/akGHf27P4C