This document is the reference checklist for the NativeWind to Uniwind transition in NEO. It is intended for developers who need to review this branch or manually reproduce the migration in a production branch.
react-native-css-interop with uniwind in the Expo app.withNativeWind Metro wrapper.withUniwindConfig in apps/expo/metro.config.js with cssEntryFile: './global.combined.css' and polyfills: { rem: 16 }.apps/expo/global.combined.css using Tailwind CSS 4 @theme variables and @source entries for apps/expo/app and packages/app.@tailwindcss/postcss) and replaced key React Native Web styling paths with lean DOM primitives in packages/app/design/view.js, view.web.js, and typography.js.onLayout support via packages/app/design/view.web.js, using ResizeObserver only when a component passes onLayout.packages/app/design/controls/inputs.web.tsx so common RN input props are translated to DOM props instead of depending on native styling interop.Remove NativeWind dependencies:
yarn remove nativewind react-native-css-interop
Add Uniwind and Tailwind CSS 4 to apps/expo:
cd apps/expo
yarn add uniwind tailwindcss
Remove NativeWind config files and patches:
apps/expo/nativewind-env.d.ts.packages/app/nativewind.d.ts.patches/react-native-css-interop+*.patch.nativewind/babel from apps/expo/babel.config.js.withNativeWind from apps/expo/metro.config.js.Configure Expo Metro:
const { getDefaultConfig } = require('expo/metro-config')
const { withUniwindConfig } = require('uniwind/metro')
const config = getDefaultConfig(__dirname)
module.exports = withUniwindConfig(config, {
cssEntryFile: './global.combined.css',
polyfills: { rem: 16 },
})
Keep withUniwindConfig as the outermost wrapper if the app also customizes SVG transformers or resolver settings.
Create or update apps/expo/global.combined.css:
@import 'tailwindcss';
@import 'uniwind';
@source './app';
@source '../../packages/app';
@theme inline {
/* map NEO semantic tokens here */
}
Import theme switching in the Expo root layout:
import { Uniwind } from 'uniwind'
Uniwind.setTheme(themeName !== 'auto' ? themeName : 'system')
Do not wrap core React Native components with withUniwind. Core View, Text, Pressable, Image, TextInput, and related primitives already support className through Uniwind.
Keep web separate from native where performance benefits are clear:
View, Text, Pressable, and inputs, plus a small compatibility layer for RN props that the app actually uses.Use CSS layout first. Measure only when behavior depends on rendered dimensions.
onLayout.useWebLayout wrapper behavior from packages/app/design/view.web.js.ResizeObserver code unless the feature cannot be expressed through primitive onLayout.onLayout.onLayout.Preferred app code:
<View onLayout={({ nativeEvent: { layout } }) => setWidth(layout.width)} />
Avoid feature-local DOM measurement unless you need popup positioning or viewport-relative coordinates.
Expected gains:
Do not assume the transition is complete based only on visual parity. Validate with production builds, native smoke tests, and profiler data for high-traffic screens.
The main architectural win is a cleaner platform split: Uniwind owns native styling, while the web app uses lean DOM primitives where that is faster and easier to debug. This better matches NEO's shared packages/app model because common components can keep the same API while each platform gets a more natural implementation underneath.
Runtime benefits:
View, Text, Pressable, and inputs avoid unnecessary React Native Web styling interop in high-volume UI.ResizeObserver only runs for components that pass onLayout.useNativeDriver warnings, /api/icon static-generation warnings, missing panel resize-handle warnings, and route debug logs.nativewind, react-native-css-interop, and the css-interop patch are gone.Development benefits:
onPress, onLayout, pointerEvents, inputs) lives in the design layer.The strongest NEO-specific areas to measure for hard proof are native startup/style resolution, logged-in /home, post pages, messenger, tab navigation, dropdown-heavy menus, and animated icon usage.
Run these after migrating:
yarn list --pattern "nativewind|react-native-css-interop|uniwind" --depth=2
cd apps/next && npx tsc --noEmit
cd apps/expo && npx tsc --noEmit
yarn build
Expected dependency result: uniwind is present, nativewind and react-native-css-interop are absent.
Also verify:
/home three-column layout resizes left and right panels.Text, not inherited from parent View or Pressable.box-shadow; use static native-safe color values or platform-specific classes.calc() with mixed % and length units are not present in files scanned by Uniwind; move those to web-only styles or web-only modules that native does not scan.