About react-native-unistyles-v3
react-native-unistyles-v3 is a styling skill for React Native developers using Unistyles v3 to build themed, responsive interfaces. It focuses on style updates without re-renders through a C++ core and build-time processing.
Key features
- 01Static and theme-reactive styles
- 02Theme and breakpoint configuration
- 03Property-based style variants
- 04Map theme values to component props
- 05Babel, Metro, and Re.Pack integrations
Use cases
- 01Add light and dark themes to an app
- 02Adapt layouts to screen sizes
- 03Create component size variants
- 04Pass theme colors to third-party components
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ jpudysz/ react-native-unistyles --skill react-native-unistyles-v3After installing, type/react-native-unistyles-v3in Claude Code to confirm it loaded.
Bundled files
References and scripts installed along with SKILL.md.
references/api-reference.mdcommon-issues.mdsetup-guide.mdstyling-patterns.mdthird-party-integration.mdDetails
From the project READMEReact Native Unistyles v3 Skill
You are assisting with React Native Unistyles v3 styling. v3 is a zero-re-render styling library with a C++ core (Nitro Modules) and a Babel plugin that processes StyleSheets at build time (since 3.6.0, Metro apps can use the experimental zero-config Metro integration instead). It replaces React Native's StyleSheet with a reactive, theme-aware, responsive system.
Prerequisites
- React Native 0.81+ with New Architecture mandatory (enabled by default since RN 0.76, the only architecture since RN 0.82)
- React 19+ (enforced at runtime)
react-native-nitro-modules(native bridge dependency; pin an exact version — Unistyles 3.4.0+ needs>= 0.37.1)- iOS 15.1+, Android 7+
react-native-edge-to-edgeis not needed (optional since v3.1.0, now deprecated) — setedgeToEdgeEnabled=trueinandroid/gradle.propertiesinstead (Expo SDK 54+ enables it automatically)- Expo SDK 54+ (if using Expo; not compatible with Expo Go — requires dev client or prebuild)
- Xcode 16.4+ (iOS, required by Nitro Modules)
Workflow
- Read user's code first — understand current setup, imports, and styling approach
- Identify intent — new setup, add theming, responsive design, fix an issue, etc.
- Apply v3 patterns — use the correct API from this skill; consult reference files for details
- Verify correctness — check for critical rule violations (spreading, barrel re-exports, etc.)
Quick Reference
StyleSheet.create — 3 overloads
import { StyleSheet } from 'react-native-unistyles'
// 1. Static object compatible with React Native StyleSheet.create
const styles = StyleSheet.create({
container: { flex: 1, padding: 16 }
})
// 2. Theme function (reactive to theme changes, zero re-renders)
const styles = StyleSheet.create(theme => ({
container: { backgroundColor: theme.colors.background }
}))
// 3. Theme + miniRuntime function (reactive to theme AND device state)
const styles = StyleSheet.create((theme, rt) => ({
container: {
backgroundColor: theme.colors.background,
paddingTop: rt.insets.top,
width: rt.screen.width > 768 ? 500 : rt.screen.width - 32
}
}))
StyleSheet.configure — one-time setup
StyleSheet.configure({
themes: { light: lightTheme, dark: darkTheme },
breakpoints: { xs: 0, sm: 576, md: 768, lg: 992, xl: 1200 },
settings: {
initialTheme: 'light', // or: () => storage.getString('theme') ?? 'light'
// adaptiveThemes: true, // auto-switch light/dark based on OS (mutually exclusive with initialTheme)
// CSSVars: true, // web: themes as CSS variables (default: true)
// nativeBreakpointsMode: 'pixels' | 'points' // default 'pixels' = compare against rt.screen (pt/dp)
}
})
Variants
const styles = StyleSheet.create(theme => ({
button: {
variants: {
size: {
small: { padding: 4 },
medium: { padding: 8 },
large: { padding: 16 },
},
color: {
primary: { backgroundColor: theme.colors.primary },
secondary: { backgroundColor: theme.colors.secondary },
}
},
compoundVariants: [
{ size: 'large', color: 'primary', styles: { borderWidth: 2 } }
],
}
}))
// In component — call useVariants BEFORE accessing styles (always pass an object, `{}` for defaults)
styles.useVariants({ size: 'large', color: 'primary' })
return <View style={styles.button} />
withUnistyles — map theme/runtime to non-style props, or wrap views Unistyles can't bind to
React Native / Reanimated views that only take style need no wrapping. Use withUnistyles for props like color/trackColor, for contentContainerStyle, or for third-party views that don't expose a native ref.
import { withUnistyles } from 'react-native-unistyles'
import { Button } from 'some-library'
const UniButton = withUnistyles(Button, (theme, rt) => ({
color: theme.colors.primary,
size: rt.screen.width > 400 ? 'large' : 'small'
}))
// Usage: <UniButton title="Press me" />
// Per-instance mapping: <UniButton uniProps={theme => ({ color: isOn ? theme.colors.on : theme.colors.off })} />
Critical Rules
-
NEVER spread styles —
{...styles.x}breaks C++ proxy binding. ALWAYS use array syntax:[styles.x, styles.y]or[styles.x, { marginTop: 10 }] -
NEVER re-export StyleSheet from barrel files — import it directly from
react-native-unistyles. Outsideroot, the Babel plugin only recognizesStyleSheet.createvia that import (orautoProcessImports), so a re-export silently breaks detection. (The Metro integration also detects re-exports from your own modules, but direct imports work everywhere.) -
A build-time integration is REQUIRED — one of:
- Babel plugin (default):
['react-native-unistyles/plugin', { root: 'src' }]in babel.config.js (rootis required; every file under it is processed) - Metro integration (3.6.0+, experimental, Metro only):
withUnistylesConfig(config)fromreact-native-unistyles/metroin metro.config.js, no options, and remove the Babel plugin (having both throws). Bare RN apps that listbabel-plugin-react-compilerinbabel.config.jspluginsmust keep the Babel plugin — those plugins run before Metro's, so React Compiler would seeuseVariantsfirst - Re.Pack:
RepackUnistylePluginfromreact-native-unistyles/repack-plugin
Without one of them, styles won't be reactive.
- Babel plugin (default):
-
Import StyleSheet from
react-native-unistylesonly — it polyfills all RN StyleSheet APIs (hairlineWidth,compose,flatten,absoluteFill). Replace allimport { StyleSheet } from 'react-native'. -
styles.useVariants()must be called before accessing variant-dependent styles — treat it like a React hook (top of component). Always pass an object:useVariants(undefined)throws on native, use{}to selectdefaultvariants. -
Prefer
StyleSheet.create(theme => ...)overuseUnistyles()— theme functions cause zero re-renders;useUnistyles()re-renders whenever atheme/rtvalue you read changes (never readrt.insets.imethere — it re-renders every keyboard frame). -
StyleSheet.configure()must be called before anyStyleSheet.create()— typically in your app entry point, before any component renders. -
Dynamic function arguments must be serializable — Unistyles stores them in C++ and re-runs the function on theme/runtime changes. Functions are dropped;
Date/Map/Set/class instances become plain objects. Pass primitives / plain objects (e.g.date.getTime()).
Decision Trees
Choosing a styling approach
Need theme/runtime in styles?
├─ YES → StyleSheet.create((theme, rt) => ...) [zero re-renders]
└─ NO → StyleSheet.create({ ... }) [static styles]
Need theme values as non-style props?
├─ YES → withUnistyles(Component, (theme, rt) => ({ propName: theme.value }))
└─ NO → Pass Unistyles styles via style prop directly
Need theme/runtime in component logic (not just styles)?
├─ YES → useUnistyles() hook [re-renders when read values change]
└─ NO → Use StyleSheet.create with theme function
Third-party component integration
Passing a Unistyles style to `contentContainerStyle` (ScrollView/FlatList)?
├─ YES → withUnistyles(ScrollView) — auto-maps style + contentContainerStyle
└─ NO → react-native / react-native-reanimated view with a `style` prop?
├─ YES → Pass Unistyles styles directly (Babel swaps in ref-borrowing factories)
└─ NO → Third-party view built on RN components (in node_modules)?
├─ YES → Metro integration: works as-is (node_modules are covered)
│ Babel plugin: add its path to autoProcessPaths (e.g. 'some-lib/src/components')
└─ Still not updating / needs theme-derived props (color, size, ...)?
├─ YES → withUnistyles(Component, (theme, rt) => ({ prop: theme.value }))
└─ Last resort → useUnistyles() (re-renders the component)
Your own files outside `root` (monorepo package) not updating?
→ autoProcessImports: ['@my-org/styles'] (force-processes any file importing it; not needed with the Metro integration)
Reference Files
- Setup, installation, Babel plugin, Metro integration, Re.Pack, TypeScript, testing, Expo Router: read references/setup-guide.md
- Complete API for every export: read references/api-reference.md
- Code patterns: themes, variants, breakpoints, web, SSR, Reanimated: read references/styling-patterns.md
- withUnistyles, autoProcessPaths/Imports, React Compiler, Reanimated, edge-to-edge, frozen screens, FlatList: read references/third-party-integration.md
- Troubleshooting from 150+ GitHub issues with solutions: read references/common-issues.md