원클릭으로
expo-tailwind-setup
Set up Tailwind CSS v4 in Expo with react-native-css and NativeWind v5 for universal styling
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Set up Tailwind CSS v4 in Expo with react-native-css and NativeWind v5 for universal styling
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
You MUST use this before any creative work - creating features, building components, adding functionality, or modifying behavior. Explores user intent, requirements and design before implementation.
Set up high-performance development servers with Hot Module Replacement. Use when creating dev servers for web apps, setting up React Fast Refresh, or configuring API servers with live reload.
Modern JavaScript/TypeScript development with Bun runtime. Covers package management, bundling, testing, and migration from Node.js. Use when working with Bun, optimizing JS/TS development speed, o...
Initialize a new Bun project with TypeScript and optimal configuration. Use when starting a new Bun project or converting a directory to a Bun project.
Configure Bun's built-in test runner with Jest-compatible APIs. Use when setting up testing infrastructure, writing unit/integration/snapshot tests, migrating from Jest, or configuring test coverage. 3-10x faster than Jest.
Guides the agent through upgrading a Capacitor app project to a newer major version. Supports upgrades from Capacitor 4 through 8, including multi-version jumps. Covers automated upgrade via the Capacitor CLI and manual step-by-step fallback for each version. Do not use for plugin library upgrade or non-Capacitor mobile frameworks.
| name | expo-tailwind-setup |
| description | Set up Tailwind CSS v4 in Expo with react-native-css and NativeWind v5 for universal styling |
| version | 1.0.0 |
| license | MIT |
This guide covers setting up Tailwind CSS v4 in Expo using react-native-css and NativeWind v5 for universal styling across iOS, Android, and Web.
This setup uses:
# Install dependencies
npx expo install tailwindcss@^4 nativewind@5.0.0-preview.2 react-native-css@0.0.0-nightly.5ce6396 @tailwindcss/postcss tailwind-merge clsx
Add resolutions for lightningcss compatibility:
// package.json
{
"resolutions": {
"lightningcss": "1.30.1"
}
}
Create or update metro.config.js:
// metro.config.js
const {getDefaultConfig} = require('expo/metro-config')
const {withNativewind} = require('nativewind/metro')
/** @type {import('expo/metro-config').MetroConfig} */
const config = getDefaultConfig(__dirname)
module.exports = withNativewind(config, {
// inline variables break PlatformColor in CSS variables
inlineVariables: false,
// We add className support manually
globalClassNamePolyfill: false,
})
Create postcss.config.mjs:
// postcss.config.mjs
export default {
plugins: {
'@tailwindcss/postcss': {},
},
}
Create src/global.css:
@import 'tailwindcss/theme.css' layer(theme);
@import 'tailwindcss/preflight.css' layer(base);
@import 'tailwindcss/utilities.css';
/* Platform-specific font families */
@media android {
:root {
--font-mono: monospace;
--font-rounded: normal;
--font-serif: serif;
--font-sans: normal;
}
}
@media ios {
:root {
--font-mono: ui-monospace;
--font-serif: ui-serif;
--font-sans: system-ui;
--font-rounded: ui-rounded;
}
}
With Tailwind v4 and NativeWind v5, you do NOT need a babel.config.js for Tailwind. Remove any NativeWind babel presets if present:
// DELETE babel.config.js if it only contains NativeWind config
// The following is NO LONGER needed:
// module.exports = function (api) {
// api.cache(true);
// return {
// presets: [
// ["babel-preset-expo", { jsxImportSource: "nativewind" }],
// "nativewind/babel",
// ],
// };
// };
Since react-native-css requires explicit CSS element wrapping, create reusable components:
src/tw/index.tsx)import {
useCssElement,
useNativeVariable as useFunctionalVariable,
} from 'react-native-css'
import {
View as RNView,
Text as RNText,
Pressable as RNPressable,
ScrollView as RNScrollView,
TouchableHighlight as RNTouchableHighlight,
TextInput as RNTextInput,
StyleSheet,
} from 'react-native'
import Animated from 'react-native-reanimated'
import {Link as RouterLink} from 'expo-router'
import React from 'react'
// CSS-enabled Link
export const Link = (
props: React.ComponentProps<typeof RouterLink> & {className?: string},
) => {
return useCssElement(RouterLink, props, {className: 'style'})
}
Link.Trigger = RouterLink.Trigger
Link.Menu = RouterLink.Menu
Link.MenuAction = RouterLink.MenuAction
Link.Preview = RouterLink.Preview
// CSS Variable hook
export const useCSSVariable =
process.env.EXPO_OS !== 'web'
? useFunctionalVariable
: (variable: string) => `var(${variable})`
// View
export type ViewProps = React.ComponentProps<typeof RNView> & {
className?: string
}
export const View = (props: ViewProps) => {
return useCssElement(RNView, props, {className: 'style'})
}
View.displayName = 'CSS(View)'
// Text
export const Text = (
props: React.ComponentProps<typeof RNText> & {className?: string},
) => {
return useCssElement(RNText, props, {className: 'style'})
}
Text.displayName = 'CSS(Text)'
// ScrollView
export const ScrollView = (
props: React.ComponentProps<typeof RNScrollView> & {
className?: string
contentContainerClassName?: string
},
) => {
return useCssElement(RNScrollView, props, {
className: 'style',
contentContainerClassName: 'contentContainerStyle',
})
}
ScrollView.displayName = 'CSS(ScrollView)'
// Pressable
export const Pressable = (
props: React.ComponentProps<typeof RNPressable> & {className?: string},
) => {
return useCssElement(RNPressable, props, {className: 'style'})
}
Pressable.displayName = 'CSS(Pressable)'
// TextInput
export const TextInput = (
props: React.ComponentProps<typeof RNTextInput> & {className?: string},
) => {
return useCssElement(RNTextInput, props, {className: 'style'})
}
TextInput.displayName = 'CSS(TextInput)'
// AnimatedScrollView
export const AnimatedScrollView = (
props: React.ComponentProps<typeof Animated.ScrollView> & {
className?: string
contentClassName?: string
contentContainerClassName?: string
},
) => {
return useCssElement(Animated.ScrollView, props, {
className: 'style',
contentClassName: 'contentContainerStyle',
contentContainerClassName: 'contentContainerStyle',
})
}
// TouchableHighlight with underlayColor extraction
function XXTouchableHighlight(
props: React.ComponentProps<typeof RNTouchableHighlight>,
) {
const {underlayColor, ...style} = StyleSheet.flatten(props.style) || {}
return (
<RNTouchableHighlight
underlayColor={underlayColor}
{...props}
style={style}
/>
)
}
export const TouchableHighlight = (
props: React.ComponentProps<typeof RNTouchableHighlight>,
) => {
return useCssElement(XXTouchableHighlight, props, {className: 'style'})
}
TouchableHighlight.displayName = 'CSS(TouchableHighlight)'
src/tw/image.tsx)import Animated from 'react-native-reanimated'
import {useCssElement} from 'react-native-css'
import {Image as RNImage} from 'expo-image'
import {StyleSheet} from 'react-native'
import React from 'react'
const AnimatedExpoImage = Animated.createAnimatedComponent(RNImage)
export type ImageProps = React.ComponentProps<typeof Image>
function CSSImage(props: React.ComponentProps<typeof AnimatedExpoImage>) {
// @ts-expect-error: Remap objectFit style to contentFit property
const {objectFit, objectPosition, ...style} =
StyleSheet.flatten(props.style) || {}
return (
<AnimatedExpoImage
contentFit={objectFit}
contentPosition={objectPosition}
{...props}
source={
typeof props.source === 'string' ? {uri: props.source} : props.source
}
// @ts-expect-error: Style is remapped above
style={style}
/>
)
}
export const Image = (
props: React.ComponentProps<typeof CSSImage> & {className?: string},
) => {
return useCssElement(CSSImage, props, {className: 'style'})
}
Image.displayName = 'CSS(Image)'
src/tw/animated.tsx)import RNAnimated from 'react-native-reanimated'
import * as TW from './index'
export const Animated = {
...RNAnimated,
View: RNAnimated.createAnimatedComponent(TW.View),
}
Import CSS-wrapped components from your tw directory:
import {View, Text, ScrollView, Image} from '@/tw'
export default function MyScreen() {
return (
<ScrollView className="flex-1 bg-white">
<View className="p-4 gap-4">
<Text className="text-xl font-bold text-gray-900">Hello Tailwind!</Text>
<Image
className="w-full h-48 rounded-lg object-cover"
source={{uri: 'https://example.com/image.jpg'}}
/>
</View>
</ScrollView>
)
}
Add custom theme variables in your global.css using @theme:
@layer theme {
@theme {
/* Custom fonts */
--font-rounded: 'SF Pro Rounded', sans-serif;
/* Custom line heights */
--text-xs--line-height: calc(1em / 0.75);
--text-sm--line-height: calc(1.25em / 0.875);
--text-base--line-height: calc(1.5em / 1);
/* Custom leading scales */
--leading-tight: 1.25em;
--leading-snug: 1.375em;
--leading-normal: 1.5em;
}
}
Use platform media queries for platform-specific styling:
@media ios {
:root {
--font-sans: system-ui;
--font-rounded: ui-rounded;
}
}
@media android {
:root {
--font-sans: normal;
--font-rounded: normal;
}
}
Create a CSS file for Apple semantic colors:
/* src/css/sf.css */
@layer base {
html {
color-scheme: light;
}
}
:root {
/* Accent colors with light/dark mode */
--sf-blue: light-dark(rgb(0 122 255), rgb(10 132 255));
--sf-green: light-dark(rgb(52 199 89), rgb(48 209 89));
--sf-red: light-dark(rgb(255 59 48), rgb(255 69 58));
/* Gray scales */
--sf-gray: light-dark(rgb(142 142 147), rgb(142 142 147));
--sf-gray-2: light-dark(rgb(174 174 178), rgb(99 99 102));
/* Text colors */
--sf-text: light-dark(rgb(0 0 0), rgb(255 255 255));
--sf-text-2: light-dark(rgb(60 60 67 / 0.6), rgb(235 235 245 / 0.6));
/* Background colors */
--sf-bg: light-dark(rgb(255 255 255), rgb(0 0 0));
--sf-bg-2: light-dark(rgb(242 242 247), rgb(28 28 30));
}
/* iOS native colors via platformColor */
@media ios {
:root {
--sf-blue: platformColor(systemBlue);
--sf-green: platformColor(systemGreen);
--sf-red: platformColor(systemRed);
--sf-gray: platformColor(systemGray);
--sf-text: platformColor(label);
--sf-text-2: platformColor(secondaryLabel);
--sf-bg: platformColor(systemBackground);
--sf-bg-2: platformColor(secondarySystemBackground);
}
}
/* Register as Tailwind theme colors */
@layer theme {
@theme {
--color-sf-blue: var(--sf-blue);
--color-sf-green: var(--sf-green);
--color-sf-red: var(--sf-red);
--color-sf-gray: var(--sf-gray);
--color-sf-text: var(--sf-text);
--color-sf-text-2: var(--sf-text-2);
--color-sf-bg: var(--sf-bg);
--color-sf-bg-2: var(--sf-bg-2);
}
}
Then use in components:
<Text className="text-sf-text">Primary text</Text>
<Text className="text-sf-text-2">Secondary text</Text>
<View className="bg-sf-bg">...</View>
Use the useCSSVariable hook:
import {useCSSVariable} from '@/tw'
function MyComponent() {
const blue = useCSSVariable('--sf-blue')
return <View style={{borderColor: blue}} />
}
@tailwindcss/postcss instead of tailwindcss@import "tailwindcss/..." instead of @tailwind directives@theme in CSS instead of tailwind.config.jsuseCssElement for className supportwithNativewind with different options (inlineVariables: false)useCssElementwithNativewind appliedplatformColor() in @media ios blockslight-dark() for web/AndroidAdd className to component props:
type Props = React.ComponentProps<typeof RNView> & {className?: string}