Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Official Expo skill for setting up Tailwind CSS in Expo apps using react-native-css.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
SKILL.md
1---2name: expo-tailwind-setup3description: Set up Tailwind CSS v4 in Expo with react-native-css and NativeWind v5 for universal styling4version: 1.0.05license: MIT6---78# Tailwind CSS Setup for Expo with react-native-css910This guide covers setting up Tailwind CSS v4 in Expo using react-native-css and NativeWind v5 for universal styling across iOS, Android, and Web.1112## Overview1314This setup uses:1516- **Tailwind CSS v4** - Modern CSS-first configuration17- **react-native-css** - CSS runtime for React Native18- **NativeWind v5** - Metro transformer for Tailwind in React Native19- **@tailwindcss/postcss** - PostCSS plugin for Tailwind v42021## Installation2223```bash24# Install dependencies25npx expo install tailwindcss@^4 [email protected] [email protected] @tailwindcss/postcss tailwind-merge clsx26```2728Add resolutions for lightningcss compatibility:2930```json31// package.json32{33"resolutions": {34"lightningcss": "1.30.1"35}36}37```3839- autoprefixer is not needed in Expo because of lightningcss40- postcss is included in expo by default4142## Configuration Files4344### Metro Config4546Create or update `metro.config.js`:4748```js49// metro.config.js50const { getDefaultConfig } = require("expo/metro-config");51const { withNativewind } = require("nativewind/metro");5253/** @type {import('expo/metro-config').MetroConfig} */54const config = getDefaultConfig(__dirname);5556module.exports = withNativewind(config, {57// inline variables break PlatformColor in CSS variables58inlineVariables: false,59// We add className support manually60globalClassNamePolyfill: false,61});62```6364### PostCSS Config6566Create `postcss.config.mjs`:6768```js69// postcss.config.mjs70export default {71plugins: {72"@tailwindcss/postcss": {},73},74};75```7677### Global CSS7879Create `src/global.css`:8081```css82@import "tailwindcss/theme.css" layer(theme);83@import "tailwindcss/preflight.css" layer(base);84@import "tailwindcss/utilities.css";8586/* Platform-specific font families */87@media android {88:root {89--font-mono: monospace;90--font-rounded: normal;91--font-serif: serif;92--font-sans: normal;93}94}9596@media ios {97:root {98--font-mono: ui-monospace;99--font-serif: ui-serif;100--font-sans: system-ui;101--font-rounded: ui-rounded;102}103}104```105106## IMPORTANT: No Babel Config Needed107108With Tailwind v4 and NativeWind v5, you do NOT need a babel.config.js for Tailwind. Remove any NativeWind babel presets if present:109110```js111// DELETE babel.config.js if it only contains NativeWind config112// The following is NO LONGER needed:113// module.exports = function (api) {114// api.cache(true);115// return {116// presets: [117// ["babel-preset-expo", { jsxImportSource: "nativewind" }],118// "nativewind/babel",119// ],120// };121// };122```123124## CSS Component Wrappers125126Since react-native-css requires explicit CSS element wrapping, create reusable components:127128### Main Components (`src/tw/index.tsx`)129130```tsx131import {132useCssElement,133useNativeVariable as useFunctionalVariable,134} from "react-native-css";135136import { Link as RouterLink } from "expo-router";137import Animated from "react-native-reanimated";138import React from "react";139import {140View as RNView,141Text as RNText,142Pressable as RNPressable,143ScrollView as RNScrollView,144TouchableHighlight as RNTouchableHighlight,145TextInput as RNTextInput,146StyleSheet,147} from "react-native";148149// CSS-enabled Link150export const Link = (151props: React.ComponentProps<typeof RouterLink> & { className?: string }152) => {153return useCssElement(RouterLink, props, { className: "style" });154};155156Link.Trigger = RouterLink.Trigger;157Link.Menu = RouterLink.Menu;158Link.MenuAction = RouterLink.MenuAction;159Link.Preview = RouterLink.Preview;160161// CSS Variable hook162export const useCSSVariable =163process.env.EXPO_OS !== "web"164? useFunctionalVariable165: (variable: string) => `var(${variable})`;166167// View168export type ViewProps = React.ComponentProps<typeof RNView> & {169className?: string;170};171172export const View = (props: ViewProps) => {173return useCssElement(RNView, props, { className: "style" });174};175View.displayName = "CSS(View)";176177// Text178export const Text = (179props: React.ComponentProps<typeof RNText> & { className?: string }180) => {181return useCssElement(RNText, props, { className: "style" });182};183Text.displayName = "CSS(Text)";184185// ScrollView186export const ScrollView = (187props: React.ComponentProps<typeof RNScrollView> & {188className?: string;189contentContainerClassName?: string;190}191) => {192return useCssElement(RNScrollView, props, {193className: "style",194contentContainerClassName: "contentContainerStyle",195});196};197ScrollView.displayName = "CSS(ScrollView)";198199// Pressable200export const Pressable = (201props: React.ComponentProps<typeof RNPressable> & { className?: string }202) => {203return useCssElement(RNPressable, props, { className: "style" });204};205Pressable.displayName = "CSS(Pressable)";206207// TextInput208export const TextInput = (209props: React.ComponentProps<typeof RNTextInput> & { className?: string }210) => {211return useCssElement(RNTextInput, props, { className: "style" });212};213TextInput.displayName = "CSS(TextInput)";214215// AnimatedScrollView216export const AnimatedScrollView = (217props: React.ComponentProps<typeof Animated.ScrollView> & {218className?: string;219contentClassName?: string;220contentContainerClassName?: string;221}222) => {223return useCssElement(Animated.ScrollView, props, {224className: "style",225contentClassName: "contentContainerStyle",226contentContainerClassName: "contentContainerStyle",227});228};229230// TouchableHighlight with underlayColor extraction231function XXTouchableHighlight(232props: React.ComponentProps<typeof RNTouchableHighlight>233) {234const { underlayColor, ...style } = StyleSheet.flatten(props.style) || {};235return (236<RNTouchableHighlight237underlayColor={underlayColor}238{...props}239style={style}240/>241);242}243244export const TouchableHighlight = (245props: React.ComponentProps<typeof RNTouchableHighlight>246) => {247return useCssElement(XXTouchableHighlight, props, { className: "style" });248};249TouchableHighlight.displayName = "CSS(TouchableHighlight)";250```251252### Image Component (`src/tw/image.tsx`)253254```tsx255import { useCssElement } from "react-native-css";256import React from "react";257import { StyleSheet } from "react-native";258import Animated from "react-native-reanimated";259import { Image as RNImage } from "expo-image";260261const AnimatedExpoImage = Animated.createAnimatedComponent(RNImage);262263export type ImageProps = React.ComponentProps<typeof Image>;264265function CSSImage(props: React.ComponentProps<typeof AnimatedExpoImage>) {266// @ts-expect-error: Remap objectFit style to contentFit property267const { objectFit, objectPosition, ...style } =268StyleSheet.flatten(props.style) || {};269270return (271<AnimatedExpoImage272contentFit={objectFit}273contentPosition={objectPosition}274{...props}275source={276typeof props.source === "string" ? { uri: props.source } : props.source277}278// @ts-expect-error: Style is remapped above279style={style}280/>281);282}283284export const Image = (285props: React.ComponentProps<typeof CSSImage> & { className?: string }286) => {287return useCssElement(CSSImage, props, { className: "style" });288};289290Image.displayName = "CSS(Image)";291```292293### Animated Components (`src/tw/animated.tsx`)294295```tsx296import * as TW from "./index";297import RNAnimated from "react-native-reanimated";298299export const Animated = {300...RNAnimated,301View: RNAnimated.createAnimatedComponent(TW.View),302};303```304305## Usage306307Import CSS-wrapped components from your tw directory:308309```tsx310import { View, Text, ScrollView, Image } from "@/tw";311312export default function MyScreen() {313return (314<ScrollView className="flex-1 bg-white">315<View className="p-4 gap-4">316<Text className="text-xl font-bold text-gray-900">Hello Tailwind!</Text>317<Image318className="w-full h-48 rounded-lg object-cover"319source={{ uri: "https://example.com/image.jpg" }}320/>321</View>322</ScrollView>323);324}325```326327## Custom Theme Variables328329Add custom theme variables in your global.css using `@theme`:330331```css332@layer theme {333@theme {334/* Custom fonts */335--font-rounded: "SF Pro Rounded", sans-serif;336337/* Custom line heights */338--text-xs--line-height: calc(1em / 0.75);339--text-sm--line-height: calc(1.25em / 0.875);340--text-base--line-height: calc(1.5em / 1);341342/* Custom leading scales */343--leading-tight: 1.25em;344--leading-snug: 1.375em;345--leading-normal: 1.5em;346}347}348```349350## Platform-Specific Styles351352Use platform media queries for platform-specific styling:353354```css355@media ios {356:root {357--font-sans: system-ui;358--font-rounded: ui-rounded;359}360}361362@media android {363:root {364--font-sans: normal;365--font-rounded: normal;366}367}368```369370## Apple System Colors with CSS Variables371372Create a CSS file for Apple semantic colors:373374```css375/* src/css/sf.css */376@layer base {377html {378color-scheme: light;379}380}381382:root {383/* Accent colors with light/dark mode */384--sf-blue: light-dark(rgb(0 122 255), rgb(10 132 255));385--sf-green: light-dark(rgb(52 199 89), rgb(48 209 89));386--sf-red: light-dark(rgb(255 59 48), rgb(255 69 58));387388/* Gray scales */389--sf-gray: light-dark(rgb(142 142 147), rgb(142 142 147));390--sf-gray-2: light-dark(rgb(174 174 178), rgb(99 99 102));391392/* Text colors */393--sf-text: light-dark(rgb(0 0 0), rgb(255 255 255));394--sf-text-2: light-dark(rgb(60 60 67 / 0.6), rgb(235 235 245 / 0.6));395396/* Background colors */397--sf-bg: light-dark(rgb(255 255 255), rgb(0 0 0));398--sf-bg-2: light-dark(rgb(242 242 247), rgb(28 28 30));399}400401/* iOS native colors via platformColor */402@media ios {403:root {404--sf-blue: platformColor(systemBlue);405--sf-green: platformColor(systemGreen);406--sf-red: platformColor(systemRed);407--sf-gray: platformColor(systemGray);408--sf-text: platformColor(label);409--sf-text-2: platformColor(secondaryLabel);410--sf-bg: platformColor(systemBackground);411--sf-bg-2: platformColor(secondarySystemBackground);412}413}414415/* Register as Tailwind theme colors */416@layer theme {417@theme {418--color-sf-blue: var(--sf-blue);419--color-sf-green: var(--sf-green);420--color-sf-red: var(--sf-red);421--color-sf-gray: var(--sf-gray);422--color-sf-text: var(--sf-text);423--color-sf-text-2: var(--sf-text-2);424--color-sf-bg: var(--sf-bg);425--color-sf-bg-2: var(--sf-bg-2);426}427}428```429430Then use in components:431432```tsx433<Text className="text-sf-text">Primary text</Text>434<Text className="text-sf-text-2">Secondary text</Text>435<View className="bg-sf-bg">...</View>436```437438## Using CSS Variables in JavaScript439440Use the `useCSSVariable` hook:441442```tsx443import { useCSSVariable } from "@/tw";444445function MyComponent() {446const blue = useCSSVariable("--sf-blue");447448return <View style={{ borderColor: blue }} />;449}450```451452## Key Differences from NativeWind v4 / Tailwind v34534541. **No babel.config.js** - Configuration is now CSS-first4552. **PostCSS plugin** - Uses `@tailwindcss/postcss` instead of `tailwindcss`4563. **CSS imports** - Use `@import "tailwindcss/..."` instead of `@tailwind` directives4574. **Theme config** - Use `@theme` in CSS instead of `tailwind.config.js`4585. **Component wrappers** - Must wrap components with `useCssElement` for className support4596. **Metro config** - Use `withNativewind` with different options (`inlineVariables: false`)460461## Troubleshooting462463### Styles not applying4644651. Ensure you have the CSS file imported in your app entry4662. Check that components are wrapped with `useCssElement`4673. Verify Metro config has `withNativewind` applied468469### Platform colors not working4704711. Use `platformColor()` in `@media ios` blocks4722. Fall back to `light-dark()` for web/Android473474### TypeScript errors475476Add className to component props:477478```tsx479type Props = React.ComponentProps<typeof RNView> & { className?: string };480```481