Compile-time Tailwind CSS for React Native with zero runtime overhead. Transform className props to optimized StyleSheet.create calls at build time using a Babel plugin.
- ⚡ Zero runtime overhead — All transformations happen at compile time
- 🔧 No dependencies — Direct-to-React-Native style generation without tailwindcss package
- 🎯 Babel-only setup — No Metro configuration required
- 📝 TypeScript-first — Full type safety and autocomplete support
- 🚀 Optimized performance — Compiles down to
StyleSheet.createfor optimal performance - 📦 Small bundle size — Only includes actual styles used in your app
- 🎨 Custom colors — Extend the default palette via
tailwind.config.* - 📐 Arbitrary values — Use custom sizes and borders:
w-[123px],rounded-[20px] - 🔀 Dynamic className — Conditional styles with hybrid compile-time optimization
- 🎯 State modifiers —
active:,hover:,focus:, anddisabled:modifiers for interactive components - 📜 Special style props — Support for
contentContainerClassName,columnWrapperClassName, and more
npm install @mgcrea/react-native-tailwind
# or
yarn add @mgcrea/react-native-tailwind
# or
pnpm add @mgcrea/react-native-tailwindUpdate your babel.config.js:
module.exports = {
presets: ["module:@react-native/babel-preset"],
plugins: [
"@mgcrea/react-native-tailwind/babel", // Add this line
],
};Create a type declaration file in your project to enable className prop autocomplete:
Create src/types/react-native-tailwind.d.ts:
import "@mgcrea/react-native-tailwind/react-native";This file will be automatically picked up by TypeScript and enables autocomplete for the className prop on all React Native components.
Why is this needed? TypeScript module augmentation requires the declaration file to be part of your project's compilation context. This one-time setup ensures the
classNameprop is recognized throughout your codebase.
import { View, Text } from "react-native";
export function MyComponent() {
return (
<View className="flex-1 bg-gray-100 p-4">
<Text className="text-xl font-bold text-blue-500">Hello, Tailwind!</Text>
</View>
);
}The Babel plugin transforms your code at compile time:
Input (what you write):
<View className="m-4 p-2 bg-blue-500 rounded-lg" />
<ScrollView contentContainerClassName="items-center gap-4" />
<FlatList columnWrapperClassName="gap-4" ListHeaderComponentClassName="p-4 bg-gray-100" />Output (what Babel generates):
import { StyleSheet } from "react-native";
<View style={_twStyles._bg_blue_500_m_4_p_2_rounded_lg} />;
<ScrollView contentContainerStyle={_twStyles._gap_4_items_center} />;
<FlatList columnWrapperStyle={_twStyles._gap_4} ListHeaderComponentStyle={_twStyles._bg_gray_100_p_4} />;
const _twStyles = StyleSheet.create({
_bg_blue_500_m_4_p_2_rounded_lg: {
margin: 16,
padding: 8,
backgroundColor: "#3B82F6",
borderRadius: 8,
},
_gap_4_items_center: {
gap: 16,
alignItems: "center",
},
_gap_4: {
gap: 16,
},
_bg_gray_100_p_4: {
backgroundColor: "#F3F4F6",
padding: 16,
},
});import { View, Text } from "react-native";
export function MyComponent() {
return (
<View className="flex-1 bg-gray-100 p-4">
<Text className="text-xl font-bold text-blue-500">Hello, Tailwind!</Text>
</View>
);
}import { View, Text } from "react-native";
import { Pressable } from "@mgcrea/react-native-tailwind";
export function Card({ title, description, onPress }) {
return (
<View className="bg-white rounded-lg p-6 mb-4 border border-gray-200">
<Text className="text-xl font-semibold text-gray-900 mb-2">{title}</Text>
<Text className="text-base text-gray-600 mb-4">{description}</Text>
<Pressable
className="bg-blue-500 active:bg-blue-700 px-4 py-2 rounded-lg items-center"
onPress={onPress}
>
<Text className="text-white font-semibold">Learn More</Text>
</Pressable>
</View>
);
}You can use dynamic expressions in className for conditional styling. The Babel plugin will parse all static strings at compile-time and preserve the conditional logic:
Conditional Expression:
import { useState } from "react";
import { View, Text, Pressable } from "react-native";
export function ToggleButton() {
const [isActive, setIsActive] = useState(false);
return (
<Pressable
onPress={() => setIsActive(!isActive)}
className={isActive ? "bg-green-500 p-4" : "bg-red-500 p-4"}
>
<Text className="text-white">{isActive ? "Active" : "Inactive"}</Text>
</Pressable>
);
}Transforms to:
<Pressable
onPress={() => setIsActive(!isActive)}
style={isActive ? _twStyles._bg_green_500_p_4 : _twStyles._bg_red_500_p_4}
>
<Text style={_twStyles._text_white}>{isActive ? "Active" : "Inactive"}</Text>
</Pressable>Template Literal (Static + Dynamic):
<Pressable className={`border-2 rounded-lg ${isActive ? "bg-blue-500" : "bg-gray-300"} p-4`}>
<Text className="text-white">Click Me</Text>
</Pressable>Transforms to:
<Pressable
style={[
_twStyles._border_2,
_twStyles._rounded_lg,
isActive ? _twStyles._bg_blue_500 : _twStyles._bg_gray_300,
_twStyles._p_4,
]}
>
<Text style={_twStyles._text_white}>Click Me</Text>
</Pressable>Logical Expression:
<View className={`p-4 bg-gray-100 ${isActive && "border-4 border-purple-500"}`}>
<Text>Content</Text>
</View>Transforms to:
<View style={[_twStyles._p_4, _twStyles._bg_gray_100, isActive && _twStyles._border_4_border_purple_500]}>
<Text>Content</Text>
</View>Multiple Conditionals:
<View className={`${size === "lg" ? "p-8" : "p-4"} ${isActive ? "bg-blue-500" : "bg-gray-400"}`}>
<Text>Dynamic Size & Color</Text>
</View>Key Benefits:
- ✅ All string literals are parsed at compile-time
- ✅ Only conditional logic remains at runtime (no parser overhead)
- ✅ Full type-safety and validation for all class names
- ✅ Optimal performance with pre-compiled styles
What Won't Work:
// ❌ Runtime variables in class names
const spacing = 4;
<View className={`p-${spacing}`} /> // Can't parse "p-${spacing}" at compile time
// ✅ Use inline style for truly dynamic values:
<View className="border-2" style={{ padding: spacing * 4 }} />You can use inline style prop alongside className:
<View className="flex-1 p-4 bg-blue-500" style={{ paddingTop: safeAreaInsets.top }}>
<Text>Content</Text>
</View>The Babel plugin will merge them:
<View style={[_twStyles._className_styles, { paddingTop: safeAreaInsets.top }]}>
<Text>Content</Text>
</View>Apply styles based on component state with zero runtime overhead. The Babel plugin automatically generates optimized style functions.
Use the active: modifier to apply styles when a Pressable component is pressed. Requires using the enhanced Pressable component from this package.
Basic Example:
import { Text } from "react-native";
import { Pressable } from "@mgcrea/react-native-tailwind";
export function MyButton() {
return (
<Pressable className="bg-blue-500 active:bg-blue-700 p-4 rounded-lg">
<Text className="text-white font-semibold">Press Me</Text>
</Pressable>
);
}Transforms to:
<Pressable
style={({ pressed }) => [_twStyles._bg_blue_500_p_4_rounded_lg, pressed && _twStyles._active_bg_blue_700]}
>
<Text style={_twStyles._font_semibold_text_white}>Press Me</Text>
</Pressable>;
// Generated styles:
const _twStyles = StyleSheet.create({
_bg_blue_500_p_4_rounded_lg: { backgroundColor: "#3B82F6", padding: 16, borderRadius: 8 },
_active_bg_blue_700: { backgroundColor: "#1D4ED8" },
_font_semibold_text_white: { fontWeight: "600", color: "#FFFFFF" },
});Multiple Active Modifiers:
<Pressable className="bg-green-500 active:bg-green-700 p-4 active:p-6 rounded-lg">
<Text className="text-white">Press for darker & larger padding</Text>
</Pressable>Complex Styling:
<Pressable className="bg-purple-500 active:bg-purple-800 border-2 border-purple-700 active:border-purple-900 p-4 rounded-lg">
<Text className="text-white">Background + Border Changes</Text>
</Pressable>Key Features:
- ✅ Zero runtime overhead — All parsing happens at compile-time
- ✅ Native Pressable API — Uses Pressable's
style={({ pressed }) => ...}pattern - ✅ Type-safe — Full TypeScript autocomplete for
active:classes - ✅ Optimized — Styles deduplicated via
StyleSheet.create - ✅ Works with custom colors —
active:bg-primary,active:bg-secondary, etc.
Use the focus: modifier to apply styles when a TextInput component is focused. Requires using the enhanced TextInput component from this package.
Basic Example:
import { TextInput } from "@mgcrea/react-native-tailwind";
export function MyInput() {
return (
<TextInput
className="border-2 border-gray-300 focus:border-blue-500 p-3 rounded-lg bg-white"
placeholder="Email address"
/>
);
}How it works:
The package exports an enhanced TextInput component that:
- Manages focus state internally using
onFocus/onBlurcallbacks - Passes focus state to the style function:
style={({ focused }) => ...} - Works seamlessly with the
focus:modifier in className
Multiple Focus Modifiers:
<TextInput className="border-2 border-gray-300 focus:border-green-500 bg-gray-50 focus:bg-white p-3 rounded-lg" />Supported Modifiers by Component:
| Component | Supported Modifiers | Notes |
|---|---|---|
Pressable (enhanced) |
active:, hover:, focus:, disabled: |
Use @mgcrea/react-native-tailwind export |
TextInput (enhanced) |
focus:, disabled: |
Use @mgcrea/react-native-tailwind export |
Use the disabled: modifier to apply styles when a component is disabled. Requires using the enhanced components from this package.
Pressable Example:
import { Pressable, Text } from "@mgcrea/react-native-tailwind";
export function SubmitButton({ isLoading }) {
return (
<Pressable
disabled={isLoading}
className="bg-blue-500 active:bg-blue-700 disabled:bg-gray-400 p-4 rounded-lg"
>
<Text className="text-white font-semibold">{isLoading ? "Loading..." : "Submit"}</Text>
</Pressable>
);
}TextInput Example:
import { TextInput } from "@mgcrea/react-native-tailwind";
export function MyInput({ isEditing }) {
return (
<TextInput
disabled={!isEditing}
className="border-2 border-gray-300 focus:border-blue-500 disabled:bg-gray-100 disabled:border-gray-200 p-3 rounded-lg"
placeholder="Enter text"
/>
);
}How it works:
The enhanced components inject the disabled prop value into the style function context:
- Pressable: Extends the existing
{ pressed, hovered, focused }state withdisabled - TextInput: Provides
{ focused, disabled }state to style functions
TextInput disabled Prop:
The enhanced TextInput also provides a convenient disabled prop that overrides React Native's editable prop:
// These are equivalent:
<TextInput disabled={true} />
<TextInput editable={false} />
// If both are provided, disabled takes precedence:
<TextInput disabled={true} editable={true} /> // Component is disabledImportant Notes:
⚠️ Enhanced components required — State modifiers require using the enhanced components from this package- ℹ️ Component-specific — Each modifier only works on compatible components
- ℹ️ No nested modifiers — Combinations like
active:focus:bg-blue-500are not currently supported - ✅ Zero styling overhead — All className parsing happens at compile-time
- ✅ Minimal runtime cost — Only adds state management (focus tracking, disabled injection)
- ✅ Type-safe — Full TypeScript autocomplete for all modifiers
- ✅ Works with custom colors —
focus:border-primary,active:bg-secondary,disabled:bg-gray-200, etc.
Use contentContainerClassName to style the ScrollView's content container:
import { ScrollView, View, Text } from "react-native";
export function MyScrollView() {
return (
<ScrollView className="flex-1 bg-gray-100" contentContainerClassName="items-center p-4 gap-4">
<View className="bg-white rounded-lg p-4">
<Text className="text-lg">Item 1</Text>
</View>
<View className="bg-white rounded-lg p-4">
<Text className="text-lg">Item 2</Text>
</View>
</ScrollView>
);
}Use columnWrapperClassName for multi-column FlatLists:
import { FlatList, View, Text } from "react-native";
export function GridList({ items }) {
return (
<FlatList
className="flex-1 bg-gray-100"
contentContainerClassName="p-4"
columnWrapperClassName="gap-4 mb-4"
numColumns={2}
data={items}
renderItem={({ item }) => (
<View className="flex-1 bg-white rounded-lg p-4">
<Text className="text-lg">{item.name}</Text>
</View>
)}
/>
);
}Use ListHeaderComponentClassName and ListFooterComponentClassName:
import { FlatList, View, Text } from "react-native";
export function ListWithHeaderFooter({ items }) {
return (
<FlatList
className="flex-1"
contentContainerClassName="p-4"
ListHeaderComponentClassName="p-4 bg-blue-500 mb-4 rounded-lg"
ListFooterComponentClassName="p-4 bg-gray-200 mt-4 rounded-lg"
data={items}
ListHeaderComponent={<Text className="text-white font-bold">Header</Text>}
ListFooterComponent={<Text className="text-gray-600">End of list</Text>}
renderItem={({ item }) => (
<View className="bg-white rounded-lg p-4 mb-2">
<Text>{item.name}</Text>
</View>
)}
/>
);
}When building reusable components, use static className strings internally. To support className props from parent components, you must accept the corresponding style props (the Babel plugin transforms className to style before your component receives it):
import { Pressable, Text, View, StyleProp, ViewStyle } from "react-native";
type ButtonProps = {
title: string;
onPress?: () => void;
// REQUIRED: Must accept style props for className to work
style?: StyleProp<ViewStyle>;
containerStyle?: StyleProp<ViewStyle>;
// Optional: Include in type for TypeScript compatibility
className?: string; // compile-time only
containerClassName?: string; // compile-time only
};
export function Button({ title, onPress, style, containerStyle }: ButtonProps) {
// Use static className strings - these get optimized at compile-time
return (
<View className="p-2 bg-gray-100 rounded-lg" style={containerStyle}>
<Pressable className="bg-blue-500 px-6 py-4 rounded-lg items-center" onPress={onPress} style={style}>
<Text className="text-white text-center font-semibold text-base">{title}</Text>
</Pressable>
</View>
);
}Key Points:
- ✅ Use static className strings internally for default styling
- ✅ Must accept
styleprops - the Babel plugin transformsclassName→stylebefore your component receives it - ✅ Include
classNameprops in the type (for TypeScript compatibility) - ✅ Don't destructure className props - they're already transformed to
styleand will beundefinedat runtime - ✅ Babel plugin optimizes all static strings to
StyleSheet.createcalls
Usage:
// Default styling (uses internal static classNames)
<Button title="Click Me" onPress={handlePress} />
// Override with runtime styles
<Button
title="Custom"
style={{ backgroundColor: '#10B981' }}
containerStyle={{ padding: 16 }}
onPress={handlePress}
/>
// className props are accepted but transformed by Babel upstream
<Button className="bg-red-500 p-8" title="Red Button" />
// At compile-time, this becomes:
// <Button style={_twStyles._bg_red_500_p_8} title="Red Button" />
// At runtime, className is undefined (already transformed to style)This pattern allows you to build component libraries with optimized default styling while still supporting full customization.
Margin & Padding:
m-{size},p-{size}— All sidesmx-{size},my-{size},px-{size},py-{size}— Horizontal/verticalmt-{size},mr-{size},mb-{size},ml-{size}— Directional marginpt-{size},pr-{size},pb-{size},pl-{size}— Directional paddinggap-{size}— Gap between flex items
Available sizes: 0, 0.5, 1, 1.5, 2, 2.5, 3, 3.5, 4, 5, 6, 7, 8, 9, 10, 11, 12, 14, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 60, 64, 72, 80, 96
Arbitrary values: m-[16px], p-[20], mx-[24px], gap-[12px] — Custom spacing values (px only)
Flexbox:
flex,flex-1,flex-auto,flex-none— Flex sizingflex-row,flex-row-reverse,flex-col,flex-col-reverse— Directionflex-wrap,flex-wrap-reverse,flex-nowrap— Wrappingitems-start,items-end,items-center,items-baseline,items-stretch— Align itemsjustify-start,justify-end,justify-center,justify-between,justify-around,justify-evenly— Justify contentself-auto,self-start,self-end,self-center,self-stretch,self-baseline— Align selfgrow,grow-0,shrink,shrink-0— Flex grow/shrink
Other:
absolute,relative— Positionoverflow-hidden,overflow-visible,overflow-scroll— Overflowflex,hidden— Display
bg-{color}-{shade}— Background colortext-{color}-{shade}— Text colorborder-{color}-{shade}— Border color
Available colors: gray, red, blue, green, yellow, purple, pink, orange, indigo, white, black, transparent
Available shades: 50, 100, 200, 300, 400, 500, 600, 700, 800, 900
Note: You can extend the color palette with custom colors via
tailwind.config.*— see Custom Colors
Opacity Modifiers:
Apply transparency to any color using the / operator with a percentage value (0-100):
<View className="bg-black/50 p-4">
{" "}
{/* 50% opacity black background */}
<Text className="text-gray-900/80">
{" "}
{/* 80% opacity gray text */}
Semi-transparent content
</Text>
<View className="border-2 border-red-500/30" /> {/* 30% opacity red border */}
</View>- Works with all color types:
bg-*,text-*,border-* - Supports preset colors:
bg-blue-500/75,text-red-600/50 - Supports arbitrary colors:
bg-[#ff0000]/40,text-[#3B82F6]/90 - Supports custom colors:
bg-primary/60,text-brand/80 - Uses React Native's 8-digit hex format:
#RRGGBBAA
Examples:
// Background opacity
<View className="bg-white/90" /> // #FFFFFFE6
<View className="bg-blue-500/50" /> // #3B82F680
// Text opacity
<Text className="text-black/70" /> // #000000B3
<Text className="text-gray-900/60" /> // #11182799
// Border opacity
<View className="border-2 border-purple-500/40" /> // #A855F766
// Arbitrary colors with opacity
<View className="bg-[#ff6b6b]/25" /> // #FF6B6B40
// Edge cases
<View className="bg-black/0" /> // Fully transparent
<View className="bg-blue-500/100" /> // Fully opaque
<View className="bg-transparent/50" /> // Remains transparentFont Size:
text-xs, text-sm, text-base, text-lg, text-xl, text-2xl, text-3xl, text-4xl, text-5xl, text-6xl, text-7xl, text-8xl, text-9xl
Font Weight:
font-thin, font-extralight, font-light, font-normal, font-medium, font-semibold, font-bold, font-extrabold, font-black
Other:
italic,not-italic— Font styletext-left,text-center,text-right,text-justify— Text alignmentunderline,line-through,no-underline— Text decorationuppercase,lowercase,capitalize,normal-case— Text transformleading-none,leading-tight,leading-snug,leading-normal,leading-relaxed,leading-loose— Line height
Width:
border,border-0,border-2,border-4,border-8— All sidesborder-t,border-r,border-b,border-l(with variants-0,-2,-4,-8) — Directionalborder-[8px],border-t-[12px]— Arbitrary values
Radius:
rounded-none,rounded-sm,rounded,rounded-md,rounded-lg,rounded-xl,rounded-2xl,rounded-3xl,rounded-full— All cornersrounded-t,rounded-r,rounded-b,rounded-l(with size variants) — Directionalrounded-tl,rounded-tr,rounded-bl,rounded-br(with size variants) — Individual cornersrounded-[12px],rounded-t-[8px],rounded-tl-[16px]— Arbitrary values
Style:
border-solid, border-dotted, border-dashed
Apply platform-specific shadows and elevation to create depth and visual hierarchy. Automatically uses iOS shadow properties or Android elevation based on the platform:
Available shadow sizes:
shadow-sm— Subtle shadowshadow— Default shadowshadow-md— Medium shadowshadow-lg— Large shadowshadow-xl— Extra large shadowshadow-2xl— Extra extra large shadowshadow-none— Remove shadow
Platform Differences:
| Platform | Properties Used | Example Output |
|---|---|---|
| iOS | shadowColor, shadowOffset, shadowOpacity, shadowRadius |
Native iOS shadow rendering |
| Android | elevation |
Native Android elevation (Material Design) |
Examples:
// Card with shadow
<View className="bg-white rounded-lg shadow-lg p-6 m-4">
<Text className="text-xl font-bold">Card Title</Text>
<Text className="text-gray-600">Card with large shadow</Text>
</View>
// Button with subtle shadow
<Pressable className="bg-blue-500 shadow-sm rounded-lg px-6 py-3">
<Text className="text-white">Press Me</Text>
</Pressable>
// Different shadow sizes
<View className="shadow-sm p-4">Subtle</View>
<View className="shadow p-4">Default</View>
<View className="shadow-md p-4">Medium</View>
<View className="shadow-lg p-4">Large</View>
<View className="shadow-xl p-4">Extra Large</View>
<View className="shadow-2xl p-4">2X Large</View>
// Remove shadow
<View className="shadow-lg md:shadow-none p-4">
Conditional shadow removal
</View>iOS Shadow Values:
| Class | shadowOpacity | shadowRadius | shadowOffset |
|---|---|---|---|
shadow-sm |
0.05 | 1 | { width: 0, height: 1 } |
shadow |
0.1 | 2 | { width: 0, height: 1 } |
shadow-md |
0.15 | 4 | { width: 0, height: 3 } |
shadow-lg |
0.2 | 8 | { width: 0, height: 6 } |
shadow-xl |
0.25 | 12 | { width: 0, height: 10 } |
shadow-2xl |
0.3 | 24 | { width: 0, height: 20 } |
Android Elevation Values:
| Class | elevation |
|---|---|
shadow-sm |
1 |
shadow |
2 |
shadow-md |
4 |
shadow-lg |
8 |
shadow-xl |
12 |
shadow-2xl |
16 |
Note: All shadow parsing happens at compile-time with zero runtime overhead. The platform detection uses React Native's
Platform.select()API.
Control the aspect ratio of views using preset or arbitrary values. Requires React Native 0.71+:
Preset values:
aspect-auto— Remove aspect ratio constraintaspect-square— 1:1 aspect ratioaspect-video— 16:9 aspect ratio
Arbitrary values:
Use aspect-[width/height] for custom ratios:
<View className="aspect-[4/3]" /> // 4:3 ratio (1.333...)
<View className="aspect-[16/9]" /> // 16:9 ratio (1.778...)
<View className="aspect-[21/9]" /> // 21:9 ultrawide
<View className="aspect-[9/16]" /> // 9:16 portrait
<View className="aspect-[3/2]" /> // 3:2 ratio (1.5)Examples:
// Square image container
<View className="w-full aspect-square bg-gray-200">
<Image source={avatar} className="w-full h-full" />
</View>
// Video player container (16:9)
<View className="w-full aspect-video bg-black">
<VideoPlayer />
</View>
// Instagram-style square grid
<View className="flex-row flex-wrap gap-2">
{photos.map((photo) => (
<View key={photo.id} className="w-[32%] aspect-square">
<Image source={photo.uri} className="w-full h-full rounded" />
</View>
))}
</View>
// Custom aspect ratio for wide images
<View className="w-full aspect-[21/9] rounded-lg overflow-hidden">
<Image source={banner} className="w-full h-full" resizeMode="cover" />
</View>
// Portrait orientation
<View className="h-full aspect-[9/16]">
<Story />
</View>
// Remove aspect ratio constraint
<View className="aspect-square md:aspect-auto">
Responsive aspect ratio
</View>Common Aspect Ratios:
| Ratio | Class | Decimal | Use Case |
|---|---|---|---|
| 1:1 | aspect-square |
1.0 | Profile pictures, thumbnails |
| 16:9 | aspect-video |
1.778 | Videos, landscape photos |
| 4:3 | aspect-[4/3] |
1.333 | Standard photos |
| 3:2 | aspect-[3/2] |
1.5 | Classic photography |
| 21:9 | aspect-[21/9] |
2.333 | Ultrawide/cinematic |
| 9:16 | aspect-[9/16] |
0.5625 | Stories, vertical video |
Note: The aspect ratio is calculated as
width / height. When combined withw-full, the height will be automatically calculated to maintain the ratio.
Apply 2D and 3D transformations to views with React Native's transform API. All transforms compile to optimized transform arrays at build time:
Scale:
scale-{value}— Scale uniformly (both X and Y)scale-x-{value},scale-y-{value}— Scale on specific axis- Values:
0,50,75,90,95,100,105,110,125,150,200 - Arbitrary:
scale-[1.23],scale-x-[0.5],scale-y-[2.5]
Rotate:
rotate-{degrees},-rotate-{degrees}— Rotate in 2Drotate-x-{degrees},rotate-y-{degrees},rotate-z-{degrees}— Rotate on specific axis- Values:
0,1,2,3,6,12,45,90,180 - Arbitrary:
rotate-[37deg],-rotate-[15deg],rotate-x-[30deg]
Translate:
translate-x-{spacing},translate-y-{spacing}— Move on specific axis-translate-x-{spacing},-translate-y-{spacing}— Negative translation- Values: Uses spacing scale (same as
m-*,p-*) - Arbitrary:
translate-x-[50px],translate-y-[100px],translate-x-[50%]
Skew:
skew-x-{degrees},skew-y-{degrees}— Skew on specific axis-skew-x-{degrees},-skew-y-{degrees}— Negative skew- Values:
0,1,2,3,6,12 - Arbitrary:
skew-x-[15deg],-skew-y-[8deg]
Perspective:
perspective-{value}— Apply perspective transformation- Values:
0,100,200,300,400,500,600,700,800,900,1000 - Arbitrary:
perspective-[1500],perspective-[2000]
Examples:
// Scale
<View className="scale-110 p-4">
{/* 110% scale (1.1x larger) */}
<Text>Scaled content</Text>
</View>
// Rotate
<View className="rotate-45 w-16 h-16 bg-blue-500" />
// Translate
<View className="translate-x-4 translate-y-2 bg-red-500 p-4">
{/* Moved 16px right, 8px down */}
</View>
// Arbitrary values
<View className="scale-[1.23] w-16 h-16 bg-green-500" />
<View className="rotate-[37deg] w-16 h-16 bg-purple-500" />
<View className="translate-x-[50px] bg-orange-500 p-4" />
// Negative values
<View className="-rotate-45 w-16 h-16 bg-pink-500" />
<View className="-translate-x-4 -translate-y-2 bg-indigo-500 p-4" />
// 3D rotation
<View className="rotate-x-45 w-16 h-16 bg-yellow-500" />
<View className="rotate-y-30 w-16 h-16 bg-teal-500" />
// Skew
<View className="skew-x-6 w-16 h-16 bg-cyan-500" />
// Perspective
<View className="perspective-500">
<View className="rotate-x-45 w-16 h-16 bg-blue-500" />
</View>Multiple Transforms Limitation:
Due to the current architecture, multiple transform classes on the same element will overwrite each other. For example:
// ❌ Only rotate-45 will apply (overwrites scale-110)
<View className="scale-110 rotate-45 w-16 h-16 bg-blue-500" />
// ✅ Workaround: Use nested Views for multiple transforms
<View className="scale-110">
<View className="rotate-45">
<View className="w-16 h-16 bg-blue-500" />
</View>
</View>This limitation exists because the current parser architecture uses Object.assign() which overwrites the transform property when multiple transform classes are present. This will be addressed in a future update by modifying the Babel plugin to detect multiple transform classes and generate style arrays.
What's Not Supported:
transform-origin— Not available in React Native (transforms always use center as origin)- Multiple transforms on one element — Use nested Views (see workaround above)
Note: All transform parsing happens at compile-time with zero runtime overhead. Each transform compiles to a React Native transform array:
transform: [{ scale: 1.1 }],transform: [{ rotate: '45deg' }], etc.
w-{size},h-{size}— Width/heightmin-w-{size},min-h-{size}— Min width/heightmax-w-{size},max-h-{size}— Max width/height
Available sizes:
- Numeric:
0-96(same as spacing scale) - Fractional:
1/2,1/3,2/3,1/4,3/4,1/5,2/5,3/5,4/5,1/6,2/6,3/6,4/6,5/6 - Special:
full(100%),auto - Arbitrary:
w-[123px],h-[50%],min-w-[200px],max-h-[80%]
Note: Arbitrary sizing supports pixel values (
[123px]or[123]) and percentages ([50%]). Other units (rem,em,vh,vw) are not supported in React Native.
Use arbitrary values for custom sizes, spacing, and borders not in the preset scales:
<View className="w-[350px] h-[85%] m-[16px] p-[24px] border-[3px] rounded-[20px]" />Supported:
- Spacing:
m-[...],mx-[...],my-[...],mt-[...],p-[...],px-[...],gap-[...], etc. (px only) - Sizing:
w-[...],h-[...],min-w-[...],min-h-[...],max-w-[...],max-h-[...](px and %) - Border width:
border-[...],border-t-[...],border-r-[...],border-b-[...],border-l-[...](px only) - Border radius:
rounded-[...],rounded-t-[...],rounded-tl-[...], etc. (px only) - Transforms:
- Scale:
scale-[...],scale-x-[...],scale-y-[...](number only, e.g.,[1.23]) - Rotate:
rotate-[...],rotate-x-[...],rotate-y-[...],rotate-z-[...](deg only, e.g.,[37deg]) - Translate:
translate-x-[...],translate-y-[...](px or %, e.g.,[50px]or[50%]) - Skew:
skew-x-[...],skew-y-[...](deg only, e.g.,[15deg]) - Perspective:
perspective-[...](number only, e.g.,[1500])
- Scale:
Formats:
- Pixels:
[123px]or[123]— Supported by all utilities - Percentages:
[50%],[33.333%]— Only supported by sizing utilities (w-*,h-*, etc.)
Note: CSS units (
rem,em,vh,vw) are not supported by React Native.
Extend the default color palette via tailwind.config.* in your project root:
// tailwind.config.mjs
export default {
theme: {
extend: {
colors: {
primary: "#1d4ed8",
secondary: "#9333ea",
brand: {
light: "#f0f9ff",
DEFAULT: "#0284c7",
dark: "#0c4a6e",
},
},
},
},
};Then use your custom colors:
<View className="bg-primary p-4">
<Text className="text-brand">Custom branded text</Text>
<View className="bg-brand-light rounded-lg" />
</View>How it works:
- Babel plugin discovers config by traversing up from source files
- Custom colors merged with defaults at build time (custom takes precedence)
- Nested objects flattened with dash notation:
brand.light→brand-light - Zero runtime overhead — all loading happens during compilation
Supported formats: .js, .mjs, .cjs, .ts
Tip: Use
theme.extend.colorsto keep default Tailwind colors. Usingtheme.colorsdirectly will override all defaults.
Access the parser and constants programmatically:
import { parseClassName, COLORS, SPACING_SCALE } from "@mgcrea/react-native-tailwind";
// Parse className strings
const _twStyles = parseClassName("m-4 p-2 bg-blue-500");
// Returns: { margin: 16, padding: 8, backgroundColor: '#3B82F6' }
// Access default scales
const blueColor = COLORS["blue-500"]; // '#3B82F6'
const spacing = SPACING_SCALE[4]; // 16If TypeScript doesn't recognize the className prop:
-
Create the type declaration file:
// src/types/react-native-tailwind.d.ts import "@mgcrea/react-native-tailwind/react-native";
-
Verify it's covered by your
tsconfig.jsonincludepattern -
Restart TypeScript server (VS Code: Cmd+Shift+P → "TypeScript: Restart TS Server")
Clear Metro cache:
npx react-native start --reset-cacheVerify babel.config.js:
plugins: ["@mgcrea/react-native-tailwind/babel"];-
Config location — Must be in project root or parent directory
-
Config format — Verify proper export:
// CommonJS module.exports = { theme: { extend: { colors: { ... } } } }; // ESM export default { theme: { extend: { colors: { ... } } } };
-
Clear cache — Config changes require Metro cache reset
-
Use
theme.extend.colors— Don't usetheme.colorsdirectly (overrides defaults)
git clone https://github.com/mgcrea/react-native-tailwind.git
cd react-native-tailwind
pnpm installpnpm build # Full build
pnpm build:babel # Compile TypeScript
pnpm build:babel-plugin # Bundle Babel plugin
pnpm build:types # Generate type declarationspnpm test # Run all tests
pnpm lint # ESLint
pnpm check # TypeScript type check
pnpm spec # Jest testspnpm dev # Run example app
cd example && npm run dev -- --reset-cacheMIT License
Copyright (c) 2025 Olivier Louvignes <olivier@mgcrea.io>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
