@mgcrea/react-native-tailwind 0.12.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/README.md +29 -2014
  2. package/dist/babel/config-loader.d.ts +3 -0
  3. package/dist/babel/config-loader.test.ts +2 -2
  4. package/dist/babel/config-loader.ts +37 -2
  5. package/dist/babel/index.cjs +2855 -2434
  6. package/dist/babel/plugin/componentScope.d.ts +26 -0
  7. package/dist/babel/plugin/componentScope.ts +87 -0
  8. package/dist/babel/plugin/state.d.ts +119 -0
  9. package/dist/babel/plugin/state.ts +177 -0
  10. package/dist/babel/plugin/visitors/className.d.ts +11 -0
  11. package/{src/babel/plugin.test.ts → dist/babel/plugin/visitors/className.test.ts} +74 -674
  12. package/dist/babel/plugin/visitors/className.ts +624 -0
  13. package/dist/babel/plugin/visitors/className.windowDimensions.test.ts +406 -0
  14. package/dist/babel/plugin/visitors/imports.d.ts +11 -0
  15. package/dist/babel/plugin/visitors/imports.test.ts +88 -0
  16. package/dist/babel/plugin/visitors/imports.ts +101 -0
  17. package/dist/babel/plugin/visitors/program.d.ts +15 -0
  18. package/dist/babel/plugin/visitors/program.test.ts +325 -0
  19. package/dist/babel/plugin/visitors/program.ts +99 -0
  20. package/dist/babel/plugin/visitors/tw.d.ts +16 -0
  21. package/dist/babel/plugin/visitors/tw.test.ts +620 -0
  22. package/dist/babel/plugin/visitors/tw.ts +148 -0
  23. package/dist/babel/plugin.d.ts +3 -96
  24. package/dist/babel/plugin.test.ts +470 -0
  25. package/dist/babel/plugin.ts +28 -953
  26. package/dist/babel/utils/colorSchemeModifierProcessing.ts +11 -0
  27. package/dist/babel/utils/componentSupport.test.ts +20 -7
  28. package/dist/babel/utils/componentSupport.ts +2 -0
  29. package/dist/babel/utils/modifierProcessing.ts +21 -0
  30. package/dist/babel/utils/platformModifierProcessing.ts +11 -0
  31. package/dist/babel/utils/styleInjection.d.ts +15 -0
  32. package/dist/babel/utils/styleInjection.ts +172 -17
  33. package/dist/babel/utils/twProcessing.ts +11 -0
  34. package/dist/babel/utils/windowDimensionsProcessing.d.ts +56 -0
  35. package/dist/babel/utils/windowDimensionsProcessing.ts +121 -0
  36. package/dist/components/TouchableOpacity.d.ts +35 -0
  37. package/dist/components/TouchableOpacity.js +1 -0
  38. package/dist/components/index.d.ts +3 -0
  39. package/dist/components/index.js +1 -0
  40. package/dist/config/markers.d.ts +5 -0
  41. package/dist/config/markers.js +1 -0
  42. package/dist/index.d.ts +2 -5
  43. package/dist/index.js +1 -1
  44. package/dist/parser/borders.d.ts +3 -1
  45. package/dist/parser/borders.js +1 -1
  46. package/dist/parser/borders.test.js +1 -1
  47. package/dist/parser/colors.js +1 -1
  48. package/dist/parser/colors.test.js +1 -1
  49. package/dist/parser/index.d.ts +1 -0
  50. package/dist/parser/index.js +1 -1
  51. package/dist/parser/layout.js +1 -1
  52. package/dist/parser/layout.test.js +1 -1
  53. package/dist/parser/sizing.js +1 -1
  54. package/dist/parser/typography.d.ts +2 -1
  55. package/dist/parser/typography.js +1 -1
  56. package/dist/parser/typography.test.js +1 -1
  57. package/dist/runtime.cjs +1 -1
  58. package/dist/runtime.cjs.map +4 -4
  59. package/dist/runtime.js +1 -1
  60. package/dist/runtime.js.map +4 -4
  61. package/package.json +1 -1
  62. package/src/babel/config-loader.test.ts +2 -2
  63. package/src/babel/config-loader.ts +37 -2
  64. package/src/babel/plugin/componentScope.ts +87 -0
  65. package/src/babel/plugin/state.ts +177 -0
  66. package/src/babel/plugin/visitors/className.test.ts +1312 -0
  67. package/src/babel/plugin/visitors/className.ts +624 -0
  68. package/src/babel/plugin/visitors/className.windowDimensions.test.ts +406 -0
  69. package/src/babel/plugin/visitors/imports.test.ts +88 -0
  70. package/src/babel/plugin/visitors/imports.ts +101 -0
  71. package/src/babel/plugin/visitors/program.test.ts +325 -0
  72. package/src/babel/plugin/visitors/program.ts +99 -0
  73. package/src/babel/plugin/visitors/tw.test.ts +620 -0
  74. package/src/babel/plugin/visitors/tw.ts +148 -0
  75. package/src/babel/plugin.ts +28 -953
  76. package/src/babel/utils/colorSchemeModifierProcessing.ts +11 -0
  77. package/src/babel/utils/componentSupport.test.ts +20 -7
  78. package/src/babel/utils/componentSupport.ts +2 -0
  79. package/src/babel/utils/modifierProcessing.ts +21 -0
  80. package/src/babel/utils/platformModifierProcessing.ts +11 -0
  81. package/src/babel/utils/styleInjection.ts +172 -17
  82. package/src/babel/utils/twProcessing.ts +11 -0
  83. package/src/babel/utils/windowDimensionsProcessing.ts +121 -0
  84. package/src/components/TouchableOpacity.tsx +71 -0
  85. package/src/components/index.ts +3 -0
  86. package/src/config/markers.ts +5 -0
  87. package/src/index.ts +4 -5
  88. package/src/parser/borders.test.ts +58 -0
  89. package/src/parser/borders.ts +18 -3
  90. package/src/parser/colors.test.ts +249 -0
  91. package/src/parser/colors.ts +38 -0
  92. package/src/parser/index.ts +4 -3
  93. package/src/parser/layout.test.ts +61 -0
  94. package/src/parser/layout.ts +55 -1
  95. package/src/parser/sizing.ts +11 -0
  96. package/src/parser/typography.test.ts +102 -0
  97. package/src/parser/typography.ts +61 -15
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  <!-- markdownlint-disable MD033 -->
4
4
  <p align="center">
5
- <a href="https://mgcrea.github.io/react-native-swiftui">
5
+ <a href="https://mgcrea.github.io/react-native-tailwind">
6
6
  <img src="./.github/assets/logo.png" alt="logo" width="480" height="300"/>
7
7
  </a>
8
8
  </p>
@@ -58,7 +58,9 @@ Compile-time Tailwind CSS for React Native with zero runtime overhead. Transform
58
58
 
59
59
  ![demo](./.github/assets/demo.gif)
60
60
 
61
- ## Installation
61
+ ## Quick Start
62
+
63
+ ### Installation
62
64
 
63
65
  ```bash
64
66
  npm install @mgcrea/react-native-tailwind
@@ -68,8 +70,6 @@ yarn add @mgcrea/react-native-tailwind
68
70
  pnpm add @mgcrea/react-native-tailwind
69
71
  ```
70
72
 
71
- ## Setup
72
-
73
73
  ### 1. Add Babel Plugin
74
74
 
75
75
  Update your `babel.config.js`:
@@ -83,20 +83,15 @@ module.exports = {
83
83
  };
84
84
  ```
85
85
 
86
- ### 2. Enable TypeScript Support (TypeScript)
86
+ ### 2. Enable TypeScript Support (Optional)
87
87
 
88
- Create a type declaration file in your project to enable `className` prop autocomplete:
89
-
90
- **Create `src/types/react-native-tailwind.d.ts`:**
88
+ Create a type declaration file to enable `className` prop autocomplete:
91
89
 
92
90
  ```typescript
91
+ // src/types/react-native-tailwind.d.ts
93
92
  import "@mgcrea/react-native-tailwind/react-native";
94
93
  ```
95
94
 
96
- This file will be automatically picked up by TypeScript and enables autocomplete for the `className` prop on all React Native components.
97
-
98
- > **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 `className` prop is recognized throughout your codebase.
99
-
100
95
  ### 3. Start Using className
101
96
 
102
97
  ```tsx
@@ -119,8 +114,6 @@ The Babel plugin transforms your code at compile time:
119
114
 
120
115
  ```tsx
121
116
  <View className="m-4 p-2 bg-blue-500 rounded-lg" />
122
- <ScrollView contentContainerClassName="items-center gap-4" />
123
- <FlatList columnWrapperClassName="gap-4" ListHeaderComponentClassName="p-4 bg-gray-100" />
124
117
  ```
125
118
 
126
119
  **Output** (what Babel generates):
@@ -129,8 +122,6 @@ The Babel plugin transforms your code at compile time:
129
122
  import { StyleSheet } from "react-native";
130
123
 
131
124
  <View style={_twStyles._bg_blue_500_m_4_p_2_rounded_lg} />;
132
- <ScrollView contentContainerStyle={_twStyles._gap_4_items_center} />;
133
- <FlatList columnWrapperStyle={_twStyles._gap_4} ListHeaderComponentStyle={_twStyles._bg_gray_100_p_4} />;
134
125
 
135
126
  const _twStyles = StyleSheet.create({
136
127
  _bg_blue_500_m_4_p_2_rounded_lg: {
@@ -139,67 +130,25 @@ const _twStyles = StyleSheet.create({
139
130
  backgroundColor: "#3B82F6",
140
131
  borderRadius: 8,
141
132
  },
142
- _gap_4_items_center: {
143
- gap: 16,
144
- alignItems: "center",
145
- },
146
- _gap_4: {
147
- gap: 16,
148
- },
149
- _bg_gray_100_p_4: {
150
- backgroundColor: "#F3F4F6",
151
- padding: 16,
152
- },
153
133
  });
154
134
  ```
155
135
 
156
- ## Usage Examples
157
-
158
- ### Basic Example
159
-
160
- ```tsx
161
- import { View, Text } from "react-native";
162
-
163
- export function MyComponent() {
164
- return (
165
- <View className="flex-1 bg-gray-100 p-4">
166
- <Text className="text-xl font-bold text-blue-500">Hello, Tailwind!</Text>
167
- </View>
168
- );
169
- }
170
- ```
171
-
172
- ### Card Component
173
-
174
- ```tsx
175
- import { View, Text } from "react-native";
176
- import { Pressable } from "@mgcrea/react-native-tailwind";
136
+ ## Documentation
177
137
 
178
- export function Card({ title, description, onPress }) {
179
- return (
180
- <View className="bg-white rounded-lg p-6 mb-4 border border-gray-200">
181
- <Text className="text-xl font-semibold text-gray-900 mb-2">{title}</Text>
182
- <Text className="text-base text-gray-600 mb-4">{description}</Text>
183
- <Pressable
184
- className="bg-blue-500 active:bg-blue-700 px-4 py-2 rounded-lg items-center"
185
- onPress={onPress}
186
- >
187
- <Text className="text-white font-semibold">Learn More</Text>
188
- </Pressable>
189
- </View>
190
- );
191
- }
192
- ```
138
+ 📚 **[Full Documentation](https://mgcrea.github.io/react-native-tailwind/)**
193
139
 
194
- ### Dynamic className (Hybrid Optimization)
140
+ - **[Getting Started](https://mgcrea.github.io/react-native-tailwind/getting-started/installation/)** - Installation and setup
141
+ - **[Guides](https://mgcrea.github.io/react-native-tailwind/guides/basic-usage/)** - Usage examples and patterns
142
+ - **[API Reference](https://mgcrea.github.io/react-native-tailwind/reference/spacing/)** - Complete utility reference
143
+ - **[Advanced](https://mgcrea.github.io/react-native-tailwind/advanced/custom-colors/)** - Configuration and customization
195
144
 
196
- 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:
145
+ ## Examples
197
146
 
198
- **Conditional Expression:**
147
+ ### Dynamic Styles
199
148
 
200
149
  ```tsx
201
150
  import { useState } from "react";
202
- import { View, Text, Pressable } from "react-native";
151
+ import { Pressable, Text } from "react-native";
203
152
 
204
153
  export function ToggleButton() {
205
154
  const [isActive, setIsActive] = useState(false);
@@ -215,330 +164,10 @@ export function ToggleButton() {
215
164
  }
216
165
  ```
217
166
 
218
- **Transforms to:**
219
-
220
- ```tsx
221
- <Pressable
222
- onPress={() => setIsActive(!isActive)}
223
- style={isActive ? _twStyles._bg_green_500_p_4 : _twStyles._bg_red_500_p_4}
224
- >
225
- <Text style={_twStyles._text_white}>{isActive ? "Active" : "Inactive"}</Text>
226
- </Pressable>
227
- ```
228
-
229
- **Template Literal (Static + Dynamic):**
230
-
231
- ```tsx
232
- <Pressable className={`border-2 rounded-lg ${isActive ? "bg-blue-500" : "bg-gray-300"} p-4`}>
233
- <Text className="text-white">Click Me</Text>
234
- </Pressable>
235
- ```
236
-
237
- **Transforms to:**
238
-
239
- ```tsx
240
- <Pressable
241
- style={[
242
- _twStyles._border_2,
243
- _twStyles._rounded_lg,
244
- isActive ? _twStyles._bg_blue_500 : _twStyles._bg_gray_300,
245
- _twStyles._p_4,
246
- ]}
247
- >
248
- <Text style={_twStyles._text_white}>Click Me</Text>
249
- </Pressable>
250
- ```
251
-
252
- **Logical Expression:**
253
-
254
- ```tsx
255
- <View className={`p-4 bg-gray-100 ${isActive && "border-4 border-purple-500"}`}>
256
- <Text>Content</Text>
257
- </View>
258
- ```
259
-
260
- **Transforms to:**
261
-
262
- ```tsx
263
- <View style={[_twStyles._p_4, _twStyles._bg_gray_100, isActive && _twStyles._border_4_border_purple_500]}>
264
- <Text>Content</Text>
265
- </View>
266
- ```
267
-
268
- **Multiple Conditionals:**
269
-
270
- ```tsx
271
- <View className={`${size === "lg" ? "p-8" : "p-4"} ${isActive ? "bg-blue-500" : "bg-gray-400"}`}>
272
- <Text>Dynamic Size & Color</Text>
273
- </View>
274
- ```
275
-
276
- **Key Benefits:**
277
-
278
- - ✅ All string literals are parsed at compile-time
279
- - ✅ Only conditional logic remains at runtime (no parser overhead)
280
- - ✅ Full type-safety and validation for all class names
281
- - ✅ Optimal performance with pre-compiled styles
282
-
283
- **What Won't Work:**
284
-
285
- ```tsx
286
- // ❌ Runtime variables in class names
287
- const spacing = 4;
288
- <View className={`p-${spacing}`} /> // Can't parse "p-${spacing}" at compile time
289
-
290
- // ✅ Use inline style for truly dynamic values:
291
- <View className="border-2" style={{ padding: spacing * 4 }} />
292
- ```
293
-
294
- ### Combining with Inline Styles
295
-
296
- You can use inline `style` prop alongside `className`:
297
-
298
- ```tsx
299
- <View className="flex-1 p-4 bg-blue-500" style={{ paddingTop: safeAreaInsets.top }}>
300
- <Text>Content</Text>
301
- </View>
302
- ```
303
-
304
- The Babel plugin will merge them:
305
-
306
- ```tsx
307
- <View style={[_twStyles._className_styles, { paddingTop: safeAreaInsets.top }]}>
308
- <Text>Content</Text>
309
- </View>
310
- ```
311
-
312
- ### Compile-Time `tw` Template Tag
313
-
314
- For static or compile-time determinable styles, you can use the `tw` template tag that gets transformed by the Babel plugin. This provides **zero runtime overhead** as all styles are compiled to `StyleSheet.create` calls.
315
-
316
- #### Import
317
-
318
- ```typescript
319
- import { tw } from "@mgcrea/react-native-tailwind";
320
- ```
321
-
322
- #### Basic Usage
323
-
324
- ```tsx
325
- import { View, Text, Pressable } from "react-native";
326
- import { tw } from "@mgcrea/react-native-tailwind";
327
-
328
- // Static styles - transformed at compile time
329
- const containerStyles = tw`flex-1 p-4 bg-gray-100`;
330
- const buttonStyles = tw`p-4 rounded-lg bg-blue-500`;
331
- const textStyles = tw`text-white font-bold text-center`;
332
-
333
- export function Example() {
334
- return (
335
- <View style={containerStyles.style}>
336
- <Pressable style={buttonStyles.style}>
337
- <Text style={textStyles.style}>Click me</Text>
338
- </Pressable>
339
- </View>
340
- );
341
- }
342
- ```
343
-
344
- #### With State Modifiers
345
-
346
- The compile-time `tw` supports state modifiers (`active:`, `focus:`, `disabled:`) that return a `TwStyle` object with separate style properties:
347
-
348
- ```tsx
349
- import { Pressable, Text } from "react-native";
350
- import { tw } from "@mgcrea/react-native-tailwind";
351
-
352
- const buttonStyles = tw`bg-blue-500 active:bg-blue-700 disabled:bg-gray-300`;
353
-
354
- export function Button({ disabled }) {
355
- return (
356
- <Pressable
357
- disabled={disabled}
358
- style={(state) => [
359
- buttonStyles.style,
360
- state.pressed && buttonStyles.activeStyle,
361
- disabled && buttonStyles.disabledStyle,
362
- ]}
363
- >
364
- <Text style={tw`text-white font-bold`.style}>Press me</Text>
365
- </Pressable>
366
- );
367
- }
368
- ```
369
-
370
- #### Transformation Example
371
-
372
- The Babel plugin transforms your code at compile time:
373
-
374
- ```tsx
375
- // Input
376
- const styles = tw`bg-blue-500 active:bg-blue-700 m-4`;
377
-
378
- // Compiled Output
379
- const styles = {
380
- style: _twStyles._bg_blue_500_m_4,
381
- activeStyle: _twStyles._active_bg_blue_700,
382
- };
383
-
384
- const _twStyles = StyleSheet.create({
385
- _bg_blue_500_m_4: { backgroundColor: "#2b7fff", margin: 16 },
386
- _active_bg_blue_700: { backgroundColor: "#1854d6" },
387
- });
388
- ```
389
-
390
- ### Runtime `tw` Template Tag
391
-
392
- For cases where you need **fully dynamic styling** (values only known at runtime), you can use the runtime `tw` template tag function. This provides runtime parsing of Tailwind classes with memoization for performance.
393
-
394
- > **Note:** Use the compile-time `tw` (imported from `@mgcrea/react-native-tailwind`) when possible for zero runtime overhead. Only use the runtime version (imported from `@mgcrea/react-native-tailwind/runtime`) when styles must be determined at runtime.
395
-
396
- #### Installation
397
-
398
- The runtime module is already included in the package. Import it separately:
399
-
400
- ```typescript
401
- import { tw, setConfig } from "@mgcrea/react-native-tailwind/runtime";
402
- ```
403
-
404
- #### Basic Usage
405
-
406
- ```tsx
407
- import { View, Text, Pressable } from "react-native";
408
- import { tw } from "@mgcrea/react-native-tailwind/runtime";
409
-
410
- export function RuntimeExample() {
411
- const [isActive, setIsActive] = useState(false);
412
-
413
- return (
414
- <View style={tw`flex-1 p-4 bg-gray-100`}>
415
- <Pressable
416
- onPress={() => setIsActive(!isActive)}
417
- style={tw`p-4 rounded-lg ${isActive ? "bg-green-500" : "bg-red-500"}`}
418
- >
419
- <Text style={tw`text-white font-bold text-center`}>{isActive ? "Active" : "Inactive"}</Text>
420
- </Pressable>
421
- </View>
422
- );
423
- }
424
- ```
425
-
426
- #### Configuration
427
-
428
- Configure custom colors and font families using `setConfig()`:
429
-
430
- ```typescript
431
- import { setConfig } from '@mgcrea/react-native-tailwind/runtime';
432
-
433
- // Match your tailwind.config structure
434
- setConfig({
435
- theme: {
436
- extend: {
437
- colors: {
438
- primary: '#007AFF',
439
- secondary: '#5856D6',
440
- brand: {
441
- light: '#FF6B6B',
442
- dark: '#CC0000',
443
- },
444
- },
445
- fontFamily: {
446
- sans: ['"SF Pro Rounded"'],
447
- custom: ['"My Custom Font"'],
448
- },
449
- },
450
- },
451
- });
452
-
453
- // Now you can use custom theme
454
- <View style={tw`bg-primary p-4`} />
455
- <Text style={tw`text-brand-light font-custom`}>Custom styling</Text>
456
- ```
457
-
458
- #### API Reference
459
-
460
- **`tw` tagged template**
461
-
462
- ```typescript
463
- function tw(strings: TemplateStringsArray, ...values: unknown[]): TwStyle;
464
-
465
- type TwStyle = {
466
- style: ViewStyle | TextStyle | ImageStyle;
467
- activeStyle?: ViewStyle | TextStyle | ImageStyle;
468
- focusStyle?: ViewStyle | TextStyle | ImageStyle;
469
- disabledStyle?: ViewStyle | TextStyle | ImageStyle;
470
- };
471
- ```
472
-
473
- Parses Tailwind classes at runtime and returns a `TwStyle` object with separate properties for base styles and state modifiers. Results are automatically memoized for performance.
474
-
475
- **`twStyle(className: string)`**
476
-
477
- ```typescript
478
- function twStyle(className: string): TwStyle | undefined;
479
- ```
480
-
481
- String version for cases where template literals aren't needed. Returns `undefined` for empty strings.
482
-
483
- **`setConfig(config: RuntimeConfig)`**
484
-
485
- ```typescript
486
- function setConfig(config: RuntimeConfig): void;
487
- ```
488
-
489
- Configure runtime theme settings (colors, etc.). Matches `tailwind.config.mjs` structure.
490
-
491
- **`clearCache()`**
492
-
493
- ```typescript
494
- function clearCache(): void;
495
- ```
496
-
497
- Clears the internal memoization cache. Useful for testing.
498
-
499
- **`getCacheStats()`**
500
-
501
- ```typescript
502
- function getCacheStats(): { size: number; keys: string[] };
503
- ```
504
-
505
- Returns cache statistics for debugging/monitoring.
506
-
507
- #### Performance Considerations
508
-
509
- - **Bundle Size**: The runtime module adds ~25KB minified (~15-20KB gzipped) to your bundle
510
- - **Caching**: All parsed styles are automatically memoized, so repeated className strings have minimal overhead
511
- - **When to Use**:
512
- - ✅ Truly dynamic values that can't be determined at compile-time
513
- - ✅ Prototyping and rapid development
514
- - ❌ Static styles (use compile-time `className` instead for zero overhead)
515
- - ❌ Performance-critical hot paths (compile-time is faster)
516
-
517
- #### Runtime vs Compile-Time
518
-
519
- | Feature | Compile-Time (`className`) | Runtime (`tw` tag) |
520
- | -------------- | ------------------------------- | ----------------------- |
521
- | Bundle Size | Only used styles (~4KB typical) | Full parser (~25KB) |
522
- | Performance | Zero overhead (pre-compiled) | Fast (memoized parsing) |
523
- | Dynamic Values | Conditional only | Fully dynamic |
524
- | Custom Colors | Via `tailwind.config.*` | Via `setConfig()` |
525
- | Type Safety | Full TypeScript support | Full TypeScript support |
526
-
527
- **Recommendation**: Use compile-time `className` by default for best performance. Use runtime `tw` only when you need fully dynamic styling that can't be expressed with conditional logic.
528
-
529
167
  ### State Modifiers
530
168
 
531
- Apply styles based on component state with zero runtime overhead. The Babel plugin automatically generates optimized style functions.
532
-
533
- #### Active Modifier (Pressable)
534
-
535
- Use the `active:` modifier to apply styles when a `Pressable` component is pressed. **Requires using the enhanced `Pressable` component from this package.**
536
-
537
- **Basic Example:**
538
-
539
169
  ```tsx
540
- import { Text } from "react-native";
541
- import { Pressable } from "@mgcrea/react-native-tailwind";
170
+ import { Pressable, Text } from "@mgcrea/react-native-tailwind";
542
171
 
543
172
  export function MyButton() {
544
173
  return (
@@ -549,159 +178,21 @@ export function MyButton() {
549
178
  }
550
179
  ```
551
180
 
552
- **Transforms to:**
553
-
554
- ```tsx
555
- <Pressable
556
- style={({ pressed }) => [_twStyles._bg_blue_500_p_4_rounded_lg, pressed && _twStyles._active_bg_blue_700]}
557
- >
558
- <Text style={_twStyles._font_semibold_text_white}>Press Me</Text>
559
- </Pressable>;
560
-
561
- // Generated styles:
562
- const _twStyles = StyleSheet.create({
563
- _bg_blue_500_p_4_rounded_lg: { backgroundColor: "#3B82F6", padding: 16, borderRadius: 8 },
564
- _active_bg_blue_700: { backgroundColor: "#1D4ED8" },
565
- _font_semibold_text_white: { fontWeight: "600", color: "#FFFFFF" },
566
- });
567
- ```
568
-
569
- **Multiple Active Modifiers:**
570
-
571
- ```tsx
572
- <Pressable className="bg-green-500 active:bg-green-700 p-4 active:p-6 rounded-lg">
573
- <Text className="text-white">Press for darker & larger padding</Text>
574
- </Pressable>
575
- ```
576
-
577
- **Complex Styling:**
578
-
579
- ```tsx
580
- <Pressable className="bg-purple-500 active:bg-purple-800 border-2 border-purple-700 active:border-purple-900 p-4 rounded-lg">
581
- <Text className="text-white">Background + Border Changes</Text>
582
- </Pressable>
583
- ```
584
-
585
- **Key Features:**
586
-
587
- - ✅ **Zero runtime overhead** — All parsing happens at compile-time
588
- - ✅ **Native Pressable API** — Uses Pressable's `style={({ pressed }) => ...}` pattern
589
- - ✅ **Type-safe** — Full TypeScript autocomplete for `active:` classes
590
- - ✅ **Optimized** — Styles deduplicated via `StyleSheet.create`
591
- - ✅ **Works with custom colors** — `active:bg-primary`, `active:bg-secondary`, etc.
592
-
593
- #### Focus Modifier (TextInput)
594
-
595
- Use the `focus:` modifier to apply styles when a `TextInput` component is focused. **Requires using the enhanced `TextInput` component from this package.**
596
-
597
- **Basic Example:**
598
-
599
- ```tsx
600
- import { TextInput } from "@mgcrea/react-native-tailwind";
601
-
602
- export function MyInput() {
603
- return (
604
- <TextInput
605
- className="border-2 border-gray-300 focus:border-blue-500 p-3 rounded-lg bg-white"
606
- placeholder="Email address"
607
- />
608
- );
609
- }
610
- ```
611
-
612
- **How it works:**
613
-
614
- The package exports an enhanced `TextInput` component that:
615
-
616
- 1. Manages focus state internally using `onFocus`/`onBlur` callbacks
617
- 2. Passes focus state to the style function: `style={({ focused }) => ...}`
618
- 3. Works seamlessly with the `focus:` modifier in className
619
-
620
- **Multiple Focus Modifiers:**
621
-
622
- ```tsx
623
- <TextInput className="border-2 border-gray-300 focus:border-green-500 bg-gray-50 focus:bg-white p-3 rounded-lg" />
624
- ```
625
-
626
- **Supported Modifiers by Component:**
627
-
628
- | Component | Supported Modifiers | Notes |
629
- | ---------------------- | ------------------------------------------ | ------------------------------------------ |
630
- | `Pressable` (enhanced) | `active:`, `hover:`, `focus:`, `disabled:` | Use `@mgcrea/react-native-tailwind` export |
631
- | `TextInput` (enhanced) | `focus:`, `disabled:` | Use `@mgcrea/react-native-tailwind` export |
632
-
633
- #### Disabled Modifier (Pressable & TextInput)
634
-
635
- Use the `disabled:` modifier to apply styles when a component is disabled. **Requires using the enhanced components from this package.**
636
-
637
- **Pressable Example:**
638
-
639
- ```tsx
640
- import { Pressable, Text } from "@mgcrea/react-native-tailwind";
641
-
642
- export function SubmitButton({ isLoading }) {
643
- return (
644
- <Pressable
645
- disabled={isLoading}
646
- className="bg-blue-500 active:bg-blue-700 disabled:bg-gray-400 p-4 rounded-lg"
647
- >
648
- <Text className="text-white font-semibold">{isLoading ? "Loading..." : "Submit"}</Text>
649
- </Pressable>
650
- );
651
- }
652
- ```
653
-
654
- **TextInput Example:**
181
+ ### Dark Mode
655
182
 
656
183
  ```tsx
657
- import { TextInput } from "@mgcrea/react-native-tailwind";
184
+ import { View, Text } from "react-native";
658
185
 
659
- export function MyInput({ isEditing }) {
186
+ export function ThemeCard() {
660
187
  return (
661
- <TextInput
662
- disabled={!isEditing}
663
- className="border-2 border-gray-300 focus:border-blue-500 disabled:bg-gray-100 disabled:border-gray-200 p-3 rounded-lg"
664
- placeholder="Enter text"
665
- />
188
+ <View className="bg-white dark:bg-gray-900 p-4 rounded-lg">
189
+ <Text className="text-gray-900 dark:text-white">Adapts to device theme</Text>
190
+ </View>
666
191
  );
667
192
  }
668
193
  ```
669
194
 
670
- **How it works:**
671
-
672
- The enhanced components inject the `disabled` prop value into the style function context:
673
-
674
- - **Pressable**: Extends the existing `{ pressed, hovered, focused }` state with `disabled`
675
- - **TextInput**: Provides `{ focused, disabled }` state to style functions
676
-
677
- **TextInput `disabled` Prop:**
678
-
679
- The enhanced `TextInput` also provides a convenient `disabled` prop that overrides React Native's `editable` prop:
680
-
681
- ```tsx
682
- // These are equivalent:
683
- <TextInput disabled={true} />
684
- <TextInput editable={false} />
685
-
686
- // If both are provided, disabled takes precedence:
687
- <TextInput disabled={true} editable={true} /> // Component is disabled
688
- ```
689
-
690
- **Important Notes:**
691
-
692
- - ⚠️ **Enhanced components required** — State modifiers require using the enhanced components from this package
693
- - ℹ️ **Component-specific** — Each modifier only works on compatible components
694
- - ℹ️ **No nested modifiers** — Combinations like `active:focus:bg-blue-500` are not currently supported
695
- - ✅ **Zero styling overhead** — All className parsing happens at compile-time
696
- - ✅ **Minimal runtime cost** — Only adds state management (focus tracking, disabled injection)
697
- - ✅ **Type-safe** — Full TypeScript autocomplete for all modifiers
698
- - ✅ **Works with custom colors** — `focus:border-primary`, `active:bg-secondary`, `disabled:bg-gray-200`, etc.
699
-
700
- ### Platform Modifiers
701
-
702
- Apply platform-specific styles using `ios:`, `android:`, and `web:` modifiers. These work on **all components** (not just enhanced ones) and compile to `Platform.select()` calls with zero runtime parsing overhead.
703
-
704
- **Basic Example:**
195
+ ### Platform-Specific Styles
705
196
 
706
197
  ```tsx
707
198
  import { View, Text } from "react-native";
@@ -709,1493 +200,17 @@ import { View, Text } from "react-native";
709
200
  export function PlatformCard() {
710
201
  return (
711
202
  <View className="p-4 ios:p-6 android:p-8 bg-white rounded-lg">
712
- <Text className="text-base ios:text-blue-600 android:text-green-600">Platform-specific styles</Text>
203
+ <Text className="text-base ios:text-blue-600 android:text-green-600">
204
+ Platform-specific styles
205
+ </Text>
713
206
  </View>
714
207
  );
715
208
  }
716
209
  ```
717
210
 
718
- **Transforms to:**
719
-
720
- ```tsx
721
- import { Platform, StyleSheet } from "react-native";
722
-
723
- <View
724
- style={[
725
- _twStyles._bg_white_p_4_rounded_lg,
726
- Platform.select({
727
- ios: _twStyles._ios_p_6,
728
- android: _twStyles._android_p_8,
729
- }),
730
- ]}
731
- >
732
- <Text
733
- style={[
734
- _twStyles._text_base,
735
- Platform.select({
736
- ios: _twStyles._ios_text_blue_600,
737
- android: _twStyles._android_text_green_600,
738
- }),
739
- ]}
740
- >
741
- Platform-specific styles
742
- </Text>
743
- </View>;
744
-
745
- // Generated styles:
746
- const _twStyles = StyleSheet.create({
747
- _bg_white_p_4_rounded_lg: {
748
- backgroundColor: "#FFFFFF",
749
- padding: 16,
750
- borderRadius: 8,
751
- },
752
- _ios_p_6: { padding: 24 },
753
- _android_p_8: { padding: 32 },
754
- _text_base: { fontSize: 16 },
755
- _ios_text_blue_600: { color: "#2563EB" },
756
- _android_text_green_600: { color: "#059669" },
757
- });
758
- ```
211
+ ## Contributing
759
212
 
760
- **Common Use Cases:**
761
-
762
- **Platform-specific colors:**
763
-
764
- ```tsx
765
- // Different colors per platform for brand consistency
766
- <View className="bg-blue-500 ios:bg-blue-600 android:bg-green-600">
767
- <Text className="text-white">Platform-specific background</Text>
768
- </View>
769
- ```
770
-
771
- **Platform-specific spacing:**
772
-
773
- ```tsx
774
- // More padding on Android due to larger default touch targets
775
- <View className="p-4 ios:p-6 android:p-8">
776
- <Text>Platform-specific padding</Text>
777
- </View>
778
- ```
779
-
780
- **Combined with base styles:**
781
-
782
- ```tsx
783
- // Base styles + platform-specific overrides
784
- <View className="border-2 border-gray-300 ios:border-blue-500 android:border-green-500 rounded-lg p-4">
785
- <Text className="text-gray-800 ios:text-blue-800 android:text-green-800">
786
- Base styles with platform overrides
787
- </Text>
788
- </View>
789
- ```
790
-
791
- **Multiple platform modifiers:**
792
-
793
- ```tsx
794
- // Combine multiple platform-specific styles
795
- <View className="bg-gray-100 ios:bg-blue-50 android:bg-green-50 p-4 ios:p-6 android:p-8 rounded-lg">
796
- <Text>Multiple platform styles</Text>
797
- </View>
798
- ```
799
-
800
- **Web platform support:**
801
-
802
- ```tsx
803
- // Different styles for React Native Web
804
- <View className="p-4 ios:p-6 android:p-8 web:p-2">
805
- <Text className="text-base web:text-lg">Cross-platform styling</Text>
806
- </View>
807
- ```
808
-
809
- **Mixing with state modifiers:**
810
-
811
- ```tsx
812
- import { Pressable } from "@mgcrea/react-native-tailwind";
813
-
814
- // Platform modifiers work alongside state modifiers
815
- <Pressable className="bg-blue-500 active:bg-blue-700 ios:border-2 android:border-0 p-4 rounded-lg">
816
- <Text className="text-white">Button with platform + state modifiers</Text>
817
- </Pressable>;
818
- ```
819
-
820
- **Key Features:**
821
-
822
- - ✅ **Works on all components** — No need for enhanced components (unlike state modifiers)
823
- - ✅ **Zero runtime overhead** — All parsing happens at compile-time
824
- - ✅ **Native Platform API** — Uses React Native's `Platform.select()` under the hood
825
- - ✅ **Type-safe** — Full TypeScript autocomplete for platform modifiers
826
- - ✅ **Optimized** — Styles deduplicated via `StyleSheet.create`
827
- - ✅ **Works with custom colors** — `ios:bg-primary`, `android:bg-secondary`, etc.
828
- - ✅ **Minimal runtime cost** — Only one `Platform.select()` call per element with platform modifiers
829
-
830
- **Supported Platforms:**
831
-
832
- | Modifier | Platform | Description |
833
- | ---------- | ---------------- | -------------------------- |
834
- | `ios:` | iOS | Styles specific to iOS |
835
- | `android:` | Android | Styles specific to Android |
836
- | `web:` | React Native Web | Styles for web platform |
837
-
838
- **How it works:**
839
-
840
- The Babel plugin:
841
-
842
- 1. Detects platform modifiers during compilation
843
- 2. Parses all platform-specific classes at compile-time
844
- 3. Generates `Platform.select()` expressions with references to pre-compiled styles
845
- 4. Auto-imports `Platform` from `react-native` when needed
846
- 5. Merges platform styles with base classes and other modifiers in style arrays
847
-
848
- This approach provides the best of both worlds: compile-time optimization for all styles, with minimal runtime platform detection only for the conditional selection logic.
849
-
850
- ### Color Scheme Modifiers
851
-
852
- Apply color scheme-specific styles using `dark:` and `light:` modifiers that automatically react to the device's appearance settings. These work on **all components in functional components** and compile to conditional expressions that use React Native's `useColorScheme()` hook.
853
-
854
- **Basic Example:**
855
-
856
- ```tsx
857
- import { View, Text } from "react-native";
858
-
859
- export function ThemeCard() {
860
- return (
861
- <View className="bg-white dark:bg-gray-900 p-4 rounded-lg">
862
- <Text className="text-gray-900 dark:text-white">Adapts to device theme</Text>
863
- </View>
864
- );
865
- }
866
- ```
867
-
868
- **Transforms to:**
869
-
870
- ```tsx
871
- import { useColorScheme, StyleSheet } from "react-native";
872
-
873
- export function ThemeCard() {
874
- const _twColorScheme = useColorScheme();
875
-
876
- return (
877
- <View
878
- style={[
879
- _twStyles._bg_white_p_4_rounded_lg,
880
- _twColorScheme === "dark" && _twStyles._dark_bg_gray_900,
881
- ]}
882
- >
883
- <Text style={[_twStyles._text_gray_900, _twColorScheme === "dark" && _twStyles._dark_text_white]}>
884
- Adapts to device theme
885
- </Text>
886
- </View>
887
- );
888
- }
889
-
890
- // Generated styles:
891
- const _twStyles = StyleSheet.create({
892
- _bg_white_p_4_rounded_lg: {
893
- backgroundColor: "#FFFFFF",
894
- padding: 16,
895
- borderRadius: 8,
896
- },
897
- _dark_bg_gray_900: { backgroundColor: "#111827" },
898
- _text_gray_900: { color: "#111827" },
899
- _dark_text_white: { color: "#FFFFFF" },
900
- });
901
- ```
902
-
903
- **Common Use Cases:**
904
-
905
- **Dark mode support:**
906
-
907
- ```tsx
908
- // Automatically switches between light and dark themes
909
- <View className="bg-white dark:bg-gray-900">
910
- <Text className="text-gray-900 dark:text-white">Theme-aware text</Text>
911
- </View>
912
- ```
913
-
914
- **Both light and dark overrides:**
915
-
916
- ```tsx
917
- // Specify both light and dark mode styles explicitly
918
- <View className="bg-gray-100 light:bg-white dark:bg-gray-900">
919
- <Text className="text-gray-600 light:text-gray-900 dark:text-gray-100">Custom light & dark styles</Text>
920
- </View>
921
- ```
922
-
923
- **Mixed color scheme and platform modifiers:**
924
-
925
- ```tsx
926
- // Combine color scheme with platform-specific styles
927
- <View className="p-4 ios:p-6 dark:bg-gray-900 android:rounded-xl">
928
- <Text className="text-base dark:text-white ios:text-blue-600">Platform + theme aware</Text>
929
- </View>
930
- ```
931
-
932
- **Theme-aware cards:**
933
-
934
- ```tsx
935
- // Card that looks great in both light and dark mode
936
- <View className="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 p-4 rounded-lg">
937
- <Text className="text-lg font-bold text-gray-900 dark:text-white mb-2">Card Title</Text>
938
- <Text className="text-gray-600 dark:text-gray-300">Card description text</Text>
939
- </View>
940
- ```
941
-
942
- **Key Features:**
943
-
944
- - ✅ **Reactive** — Automatically updates when user changes system appearance
945
- - ✅ **Zero runtime parsing** — All styles compiled at build time
946
- - ✅ **Auto-injected hook** — `useColorScheme()` automatically added to components
947
- - ✅ **Works with all modifiers** — Combine with platform and state modifiers
948
- - ✅ **Type-safe** — Full TypeScript autocomplete
949
- - ✅ **Optimized** — Minimal runtime overhead (just conditional checks)
950
-
951
- **Supported Modifiers:**
952
-
953
- | Modifier | Color Scheme | Description |
954
- | -------- | ------------ | ------------------------------ |
955
- | `dark:` | Dark mode | Styles when dark mode is active |
956
- | `light:` | Light mode | Styles when light mode is active |
957
-
958
- **How it works:**
959
-
960
- The Babel plugin:
961
-
962
- 1. Detects color scheme modifiers during compilation
963
- 2. Finds the parent function component
964
- 3. Auto-injects `const _twColorScheme = useColorScheme();` at the top of the component
965
- 4. Parses all color scheme-specific classes at compile-time
966
- 5. Generates conditional expressions: `_twColorScheme === 'dark' && styles._dark_bg_gray_900`
967
- 6. Auto-imports `useColorScheme` from `react-native` when needed
968
- 7. Reuses the same hook variable for multiple elements in the same component
969
-
970
- **Requirements:**
971
-
972
- - ⚠️ **Functional components only** — Color scheme modifiers require hooks (React Native's `useColorScheme()`)
973
- - ✅ Works with function declarations: `function Component() { ... }`
974
- - ✅ Works with arrow functions: `const Component = () => { ... }`
975
- - ✅ Works with concise arrow functions: `const Component = () => <View className="dark:..." />`
976
- - ✅ Works in nested callbacks: Hook injected at component level, not in callbacks
977
- - ❌ **Not supported in class components** — Will show a warning
978
- - ⚠️ **React Native 0.62+** — Requires the `useColorScheme` API
979
- - ✅ **Dynamic expressions supported** — Works in template literals and conditional expressions:
980
-
981
- ```tsx
982
- <View className={`p-4 ${isActive ? "dark:bg-blue-500" : "dark:bg-gray-900"}`} />
983
- ```
984
-
985
- **Performance:**
986
-
987
- - **Compile-time**: All styles parsed and registered during build
988
- - **Runtime**: One `useColorScheme()` hook call per component + minimal conditional checks
989
- - **Bundle size**: Only includes styles actually used in your code
990
-
991
- #### Scheme Modifier (Convenience)
992
-
993
- The `scheme:` modifier is a convenience feature that automatically expands to both `dark:` and `light:` modifiers for color classes. This is useful when you have custom colors with separate dark and light variants.
994
-
995
- **Basic Usage:**
996
-
997
- ```tsx
998
- import { View, Text } from "react-native";
999
-
1000
- export function ThemedCard() {
1001
- return (
1002
- <View className="scheme:bg-systemGray p-4 rounded-lg">
1003
- <Text className="scheme:text-systemLabel">Adaptive system colors</Text>
1004
- </View>
1005
- );
1006
- }
1007
- ```
1008
-
1009
- **Transforms to:**
1010
-
1011
- ```tsx
1012
- // Automatically expands to both dark: and light: modifiers
1013
- <View className="dark:bg-systemGray-dark light:bg-systemGray-light p-4 rounded-lg">
1014
- <Text className="dark:text-systemLabel-dark light:text-systemLabel-light">
1015
- Adaptive system colors
1016
- </Text>
1017
- </View>
1018
- ```
1019
-
1020
- **Requirements:**
1021
-
1022
- To use the `scheme:` modifier, you must define both color variants in your `tailwind.config.*`:
1023
-
1024
- ```javascript
1025
- // tailwind.config.mjs
1026
- export default {
1027
- theme: {
1028
- extend: {
1029
- colors: {
1030
- // Option 1: Nested structure
1031
- systemGray: {
1032
- light: "#8e8e93",
1033
- dark: "#8e8e93",
1034
- },
1035
- systemLabel: {
1036
- light: "#000000",
1037
- dark: "#ffffff",
1038
- },
1039
-
1040
- // Option 2: Flat structure with suffixes
1041
- "primary-light": "#bfdbfe",
1042
- "primary-dark": "#1e40af",
1043
- },
1044
- },
1045
- },
1046
- };
1047
- ```
1048
-
1049
- **Configuring Suffixes:**
1050
-
1051
- By default, the plugin looks for `-dark` and `-light` suffixes. You can customize these in your Babel configuration:
1052
-
1053
- ```javascript
1054
- // babel.config.js
1055
- module.exports = {
1056
- plugins: [
1057
- [
1058
- "@mgcrea/react-native-tailwind/babel",
1059
- {
1060
- schemeModifier: {
1061
- darkSuffix: "-dark", // default
1062
- lightSuffix: "-light", // default
1063
- },
1064
- },
1065
- ],
1066
- ],
1067
- };
1068
- ```
1069
-
1070
- **Custom Suffixes Example:**
1071
-
1072
- ```javascript
1073
- // babel.config.js with custom suffixes
1074
- module.exports = {
1075
- plugins: [
1076
- [
1077
- "@mgcrea/react-native-tailwind/babel",
1078
- {
1079
- schemeModifier: {
1080
- darkSuffix: "Dark",
1081
- lightSuffix: "Light",
1082
- },
1083
- },
1084
- ],
1085
- ],
1086
- };
1087
-
1088
- // tailwind.config.mjs
1089
- export default {
1090
- theme: {
1091
- extend: {
1092
- colors: {
1093
- systemGrayDark: "#8e8e93",
1094
- systemGrayLight: "#8e8e93",
1095
- },
1096
- },
1097
- },
1098
- };
1099
-
1100
- // Usage (same as before)
1101
- <View className="scheme:bg-systemGray" />
1102
- ```
1103
-
1104
- **Validation:**
1105
-
1106
- The plugin validates that both color variants exist at compile time:
1107
-
1108
- ```tsx
1109
- // ✅ Works - both variants exist
1110
- <View className="scheme:bg-systemGray" />
1111
- // Expands to: dark:bg-systemGray-dark light:bg-systemGray-light
1112
-
1113
- // ⚠️ Warning (development only) - missing light variant
1114
- <View className="scheme:bg-incomplete" />
1115
- // Only has: incomplete-dark
1116
- // Warning: "Missing: incomplete-light. This modifier will be ignored."
1117
-
1118
- // ⚠️ Warning - non-color class
1119
- <View className="scheme:p-4" />
1120
- // Warning: "scheme: modifier only supports color classes (text-*, bg-*, border-*)"
1121
- ```
1122
-
1123
- **Supported Color Classes:**
1124
-
1125
- The `scheme:` modifier only works with color utilities:
1126
-
1127
- - ✅ `scheme:text-{color}` — Text colors
1128
- - ✅ `scheme:bg-{color}` — Background colors
1129
- - ✅ `scheme:border-{color}` — Border colors
1130
- - ❌ Other utilities — Ignored with development warning
1131
-
1132
- **Use Cases:**
1133
-
1134
- **Semantic color names:**
1135
-
1136
- ```tsx
1137
- // Define semantic system colors that adapt to appearance
1138
- <View className="scheme:bg-systemBackground p-4">
1139
- <Text className="scheme:text-systemLabel">System-native appearance</Text>
1140
- <View className="scheme:border-systemSeparator border-t mt-2 pt-2">
1141
- <Text className="scheme:text-systemSecondaryLabel">Secondary text</Text>
1142
- </View>
1143
- </View>
1144
- ```
1145
-
1146
- **Brand colors with theme variants:**
1147
-
1148
- ```tsx
1149
- // Brand colors that look good in both themes
1150
- <View className="scheme:bg-brand p-6 rounded-xl">
1151
- <Text className="scheme:text-brandContrast text-xl font-bold">
1152
- Branded Card
1153
- </Text>
1154
- <Text className="scheme:text-brandSubtle mt-2">
1155
- Automatically adapts to user's theme preference
1156
- </Text>
1157
- </View>
1158
- ```
1159
-
1160
- **Mixed with other modifiers:**
1161
-
1162
- ```tsx
1163
- import { Pressable } from "@mgcrea/react-native-tailwind";
1164
-
1165
- // Combine with state and platform modifiers
1166
- <Pressable className="scheme:bg-interactive active:opacity-80 ios:p-6 android:p-4 rounded-lg">
1167
- <Text className="scheme:text-interactiveText font-semibold">
1168
- Press Me
1169
- </Text>
1170
- </Pressable>
1171
- ```
1172
-
1173
- **Key Features:**
1174
-
1175
- - ✅ **DRY principle** — Define color pairs once, use everywhere with `scheme:`
1176
- - ✅ **Compile-time expansion** — Expands to `dark:` and `light:` during build
1177
- - ✅ **Type-safe** — Full TypeScript autocomplete
1178
- - ✅ **Validates at compile-time** — Ensures both variants exist
1179
- - ✅ **Zero runtime overhead** — Same performance as writing `dark:` and `light:` manually
1180
- - ✅ **Configurable suffixes** — Adapt to your naming convention
1181
-
1182
- **How it works:**
1183
-
1184
- The Babel plugin:
1185
-
1186
- 1. Detects `scheme:` modifiers during compilation
1187
- 2. Validates that the class is a color utility (`text-*`, `bg-*`, `border-*`)
1188
- 3. Checks that both color variants exist in your custom colors (e.g., `systemGray-dark` and `systemGray-light`)
1189
- 4. Expands to both `dark:` and `light:` modifiers before further processing
1190
- 5. Processes the expanded modifiers using the standard color scheme logic (injects `useColorScheme()` hook)
1191
-
1192
- This means `scheme:bg-systemGray` is functionally identical to writing `dark:bg-systemGray-dark light:bg-systemGray-light`, but more concise and maintainable.
1193
-
1194
- ### ScrollView Content Container
1195
-
1196
- Use `contentContainerClassName` to style the ScrollView's content container:
1197
-
1198
- ```tsx
1199
- import { ScrollView, View, Text } from "react-native";
1200
-
1201
- export function MyScrollView() {
1202
- return (
1203
- <ScrollView className="flex-1 bg-gray-100" contentContainerClassName="items-center p-4 gap-4">
1204
- <View className="bg-white rounded-lg p-4">
1205
- <Text className="text-lg">Item 1</Text>
1206
- </View>
1207
- <View className="bg-white rounded-lg p-4">
1208
- <Text className="text-lg">Item 2</Text>
1209
- </View>
1210
- </ScrollView>
1211
- );
1212
- }
1213
- ```
1214
-
1215
- ### FlatList with Column Wrapper
1216
-
1217
- Use `columnWrapperClassName` for multi-column FlatLists:
1218
-
1219
- ```tsx
1220
- import { FlatList, View, Text } from "react-native";
1221
-
1222
- export function GridList({ items }) {
1223
- return (
1224
- <FlatList
1225
- className="flex-1 bg-gray-100"
1226
- contentContainerClassName="p-4"
1227
- columnWrapperClassName="gap-4 mb-4"
1228
- numColumns={2}
1229
- data={items}
1230
- renderItem={({ item }) => (
1231
- <View className="flex-1 bg-white rounded-lg p-4">
1232
- <Text className="text-lg">{item.name}</Text>
1233
- </View>
1234
- )}
1235
- />
1236
- );
1237
- }
1238
- ```
1239
-
1240
- ### FlatList with Header and Footer
1241
-
1242
- Use `ListHeaderComponentClassName` and `ListFooterComponentClassName`:
1243
-
1244
- ```tsx
1245
- import { FlatList, View, Text } from "react-native";
1246
-
1247
- export function ListWithHeaderFooter({ items }) {
1248
- return (
1249
- <FlatList
1250
- className="flex-1"
1251
- contentContainerClassName="p-4"
1252
- ListHeaderComponentClassName="p-4 bg-blue-500 mb-4 rounded-lg"
1253
- ListFooterComponentClassName="p-4 bg-gray-200 mt-4 rounded-lg"
1254
- data={items}
1255
- ListHeaderComponent={<Text className="text-white font-bold">Header</Text>}
1256
- ListFooterComponent={<Text className="text-gray-600">End of list</Text>}
1257
- renderItem={({ item }) => (
1258
- <View className="bg-white rounded-lg p-4 mb-2">
1259
- <Text>{item.name}</Text>
1260
- </View>
1261
- )}
1262
- />
1263
- );
1264
- }
1265
- ```
1266
-
1267
- ### Building Reusable Components
1268
-
1269
- 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):
1270
-
1271
- ```tsx
1272
- import { Pressable, Text, View, StyleProp, ViewStyle } from "react-native";
1273
-
1274
- type ButtonProps = {
1275
- title: string;
1276
- onPress?: () => void;
1277
- // REQUIRED: Must accept style props for className to work
1278
- style?: StyleProp<ViewStyle>;
1279
- containerStyle?: StyleProp<ViewStyle>;
1280
- // Optional: Include in type for TypeScript compatibility
1281
- className?: string; // compile-time only
1282
- containerClassName?: string; // compile-time only
1283
- };
1284
-
1285
- export function Button({ title, onPress, style, containerStyle }: ButtonProps) {
1286
- // Use static className strings - these get optimized at compile-time
1287
- return (
1288
- <View className="p-2 bg-gray-100 rounded-lg" style={containerStyle}>
1289
- <Pressable className="bg-blue-500 px-6 py-4 rounded-lg items-center" onPress={onPress} style={style}>
1290
- <Text className="text-white text-center font-semibold text-base">{title}</Text>
1291
- </Pressable>
1292
- </View>
1293
- );
1294
- }
1295
- ```
1296
-
1297
- **Key Points:**
1298
-
1299
- - ✅ Use **static className strings** internally for default styling
1300
- - ✅ **Must accept `style` props** - the Babel plugin transforms `className` → `style` before your component receives it
1301
- - ✅ Include `className` props in the type (for TypeScript compatibility)
1302
- - ✅ **Don't destructure** className props - they're already transformed to `style` and will be `undefined` at runtime
1303
- - ✅ Babel plugin optimizes all static strings to `StyleSheet.create` calls
1304
-
1305
- **Usage:**
1306
-
1307
- ```tsx
1308
- // Default styling (uses internal static classNames)
1309
- <Button title="Click Me" onPress={handlePress} />
1310
-
1311
- // Override with runtime styles
1312
- <Button
1313
- title="Custom"
1314
- style={{ backgroundColor: '#10B981' }}
1315
- containerStyle={{ padding: 16 }}
1316
- onPress={handlePress}
1317
- />
1318
-
1319
- // className props are accepted but transformed by Babel upstream
1320
- <Button className="bg-red-500 p-8" title="Red Button" />
1321
- // At compile-time, this becomes:
1322
- // <Button style={_twStyles._bg_red_500_p_8} title="Red Button" />
1323
- // At runtime, className is undefined (already transformed to style)
1324
- ```
1325
-
1326
- This pattern allows you to build component libraries with optimized default styling while still supporting full customization.
1327
-
1328
- ## API Reference
1329
-
1330
- ### Spacing
1331
-
1332
- **Margin & Padding:**
1333
-
1334
- - `m-{size}`, `p-{size}` — All sides
1335
- - `mx-{size}`, `my-{size}`, `px-{size}`, `py-{size}` — Horizontal/vertical
1336
- - `mt-{size}`, `mr-{size}`, `mb-{size}`, `ml-{size}` — Directional margin
1337
- - `pt-{size}`, `pr-{size}`, `pb-{size}`, `pl-{size}` — Directional padding
1338
- - `gap-{size}` — Gap between flex items
1339
-
1340
- **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`
1341
-
1342
- **Arbitrary values:** `m-[16px]`, `p-[20]`, `mx-[24px]`, `gap-[12px]` — Custom spacing values (px only)
1343
-
1344
- ### Layout
1345
-
1346
- **Flexbox:**
1347
-
1348
- - `flex`, `flex-1`, `flex-auto`, `flex-none` — Flex sizing
1349
- - `flex-row`, `flex-row-reverse`, `flex-col`, `flex-col-reverse` — Direction
1350
- - `flex-wrap`, `flex-wrap-reverse`, `flex-nowrap` — Wrapping
1351
- - `items-start`, `items-end`, `items-center`, `items-baseline`, `items-stretch` — Align items
1352
- - `justify-start`, `justify-end`, `justify-center`, `justify-between`, `justify-around`, `justify-evenly` — Justify content
1353
- - `self-auto`, `self-start`, `self-end`, `self-center`, `self-stretch`, `self-baseline` — Align self
1354
- - `grow`, `grow-0`, `shrink`, `shrink-0` — Flex grow/shrink
1355
-
1356
- **Other:**
1357
-
1358
- - `absolute`, `relative` — Position
1359
- - `overflow-hidden`, `overflow-visible`, `overflow-scroll` — Overflow
1360
- - `flex`, `hidden` — Display
1361
-
1362
- ### Colors
1363
-
1364
- - `bg-{color}-{shade}` — Background color
1365
- - `text-{color}-{shade}` — Text color
1366
- - `border-{color}-{shade}` — Border color
1367
-
1368
- **Available colors:** `gray`, `red`, `blue`, `green`, `yellow`, `purple`, `pink`, `orange`, `indigo`, `white`, `black`, `transparent`
1369
-
1370
- **Available shades:** `50`, `100`, `200`, `300`, `400`, `500`, `600`, `700`, `800`, `900`
1371
-
1372
- > **Note:** You can extend the color palette with custom colors via `tailwind.config.*` — see [Custom Theme Extensions](#custom-theme-extensions)
1373
-
1374
- **Opacity Modifiers:**
1375
-
1376
- Apply transparency to any color using the `/` operator with a percentage value (0-100):
1377
-
1378
- ```tsx
1379
- <View className="bg-black/50 p-4">
1380
- {" "}
1381
- {/* 50% opacity black background */}
1382
- <Text className="text-gray-900/80">
1383
- {" "}
1384
- {/* 80% opacity gray text */}
1385
- Semi-transparent content
1386
- </Text>
1387
- <View className="border-2 border-red-500/30" /> {/* 30% opacity red border */}
1388
- </View>
1389
- ```
1390
-
1391
- - Works with all color types: `bg-*`, `text-*`, `border-*`
1392
- - Supports preset colors: `bg-blue-500/75`, `text-red-600/50`
1393
- - Supports arbitrary colors: `bg-[#ff0000]/40`, `text-[#3B82F6]/90`
1394
- - Supports custom colors: `bg-primary/60`, `text-brand/80`
1395
- - Uses React Native's 8-digit hex format: `#RRGGBBAA`
1396
-
1397
- **Examples:**
1398
-
1399
- ```tsx
1400
- // Background opacity
1401
- <View className="bg-white/90" /> // #FFFFFFE6
1402
- <View className="bg-blue-500/50" /> // #3B82F680
1403
-
1404
- // Text opacity
1405
- <Text className="text-black/70" /> // #000000B3
1406
- <Text className="text-gray-900/60" /> // #11182799
1407
-
1408
- // Border opacity
1409
- <View className="border-2 border-purple-500/40" /> // #A855F766
1410
-
1411
- // Arbitrary colors with opacity
1412
- <View className="bg-[#ff6b6b]/25" /> // #FF6B6B40
1413
-
1414
- // Edge cases
1415
- <View className="bg-black/0" /> // Fully transparent
1416
- <View className="bg-blue-500/100" /> // Fully opaque
1417
- <View className="bg-transparent/50" /> // Remains transparent
1418
- ```
1419
-
1420
- ### Typography
1421
-
1422
- **Font Size:**
1423
-
1424
- `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`
1425
-
1426
- **Font Weight:**
1427
-
1428
- `font-thin`, `font-extralight`, `font-light`, `font-normal`, `font-medium`, `font-semibold`, `font-bold`, `font-extrabold`, `font-black`
1429
-
1430
- **Other:**
1431
-
1432
- - `italic`, `not-italic` — Font style
1433
- - `text-left`, `text-center`, `text-right`, `text-justify` — Text alignment
1434
- - `underline`, `line-through`, `no-underline` — Text decoration
1435
- - `uppercase`, `lowercase`, `capitalize`, `normal-case` — Text transform
1436
- - `leading-{3-10}` — Line height numeric scale (12px to 40px)
1437
- - `leading-none`, `leading-tight`, `leading-snug`, `leading-normal`, `leading-relaxed`, `leading-loose` — Line height named values
1438
-
1439
- ### Borders
1440
-
1441
- **Width:**
1442
-
1443
- - `border`, `border-0`, `border-2`, `border-4`, `border-8` — All sides
1444
- - `border-t`, `border-r`, `border-b`, `border-l` (with variants `-0`, `-2`, `-4`, `-8`) — Directional
1445
- - `border-[8px]`, `border-t-[12px]` — Arbitrary values
1446
-
1447
- **Radius:**
1448
-
1449
- - `rounded-none`, `rounded-sm`, `rounded`, `rounded-md`, `rounded-lg`, `rounded-xl`, `rounded-2xl`, `rounded-3xl`, `rounded-full` — All corners
1450
- - `rounded-t`, `rounded-r`, `rounded-b`, `rounded-l` (with size variants) — Directional
1451
- - `rounded-tl`, `rounded-tr`, `rounded-bl`, `rounded-br` (with size variants) — Individual corners
1452
- - `rounded-[12px]`, `rounded-t-[8px]`, `rounded-tl-[16px]` — Arbitrary values
1453
-
1454
- **Style:**
1455
-
1456
- `border-solid`, `border-dotted`, `border-dashed`
1457
-
1458
- ### Shadows & Elevation
1459
-
1460
- Apply platform-specific shadows and elevation to create depth and visual hierarchy. Automatically uses iOS shadow properties or Android elevation based on the platform:
1461
-
1462
- **Available shadow sizes:**
1463
-
1464
- - `shadow-sm` — Subtle shadow
1465
- - `shadow` — Default shadow
1466
- - `shadow-md` — Medium shadow
1467
- - `shadow-lg` — Large shadow
1468
- - `shadow-xl` — Extra large shadow
1469
- - `shadow-2xl` — Extra extra large shadow
1470
- - `shadow-none` — Remove shadow
1471
-
1472
- **Platform Differences:**
1473
-
1474
- | Platform | Properties Used | Example Output |
1475
- | ----------- | -------------------------------------------------------------- | ------------------------------------------ |
1476
- | **iOS** | `shadowColor`, `shadowOffset`, `shadowOpacity`, `shadowRadius` | Native iOS shadow rendering |
1477
- | **Android** | `elevation` | Native Android elevation (Material Design) |
1478
-
1479
- **Examples:**
1480
-
1481
- ```tsx
1482
- // Card with shadow
1483
- <View className="bg-white rounded-lg shadow-lg p-6 m-4">
1484
- <Text className="text-xl font-bold">Card Title</Text>
1485
- <Text className="text-gray-600">Card with large shadow</Text>
1486
- </View>
1487
-
1488
- // Button with subtle shadow
1489
- <Pressable className="bg-blue-500 shadow-sm rounded-lg px-6 py-3">
1490
- <Text className="text-white">Press Me</Text>
1491
- </Pressable>
1492
-
1493
- // Different shadow sizes
1494
- <View className="shadow-sm p-4">Subtle</View>
1495
- <View className="shadow p-4">Default</View>
1496
- <View className="shadow-md p-4">Medium</View>
1497
- <View className="shadow-lg p-4">Large</View>
1498
- <View className="shadow-xl p-4">Extra Large</View>
1499
- <View className="shadow-2xl p-4">2X Large</View>
1500
-
1501
- // Remove shadow
1502
- <View className="shadow-lg md:shadow-none p-4">
1503
- Conditional shadow removal
1504
- </View>
1505
- ```
1506
-
1507
- **iOS Shadow Values:**
1508
-
1509
- | Class | shadowOpacity | shadowRadius | shadowOffset |
1510
- | ------------ | ------------- | ------------ | ------------------------ |
1511
- | `shadow-sm` | 0.05 | 1 | { width: 0, height: 1 } |
1512
- | `shadow` | 0.1 | 2 | { width: 0, height: 1 } |
1513
- | `shadow-md` | 0.15 | 4 | { width: 0, height: 3 } |
1514
- | `shadow-lg` | 0.2 | 8 | { width: 0, height: 6 } |
1515
- | `shadow-xl` | 0.25 | 12 | { width: 0, height: 10 } |
1516
- | `shadow-2xl` | 0.3 | 24 | { width: 0, height: 20 } |
1517
-
1518
- **Android Elevation Values:**
1519
-
1520
- | Class | elevation |
1521
- | ------------ | --------- |
1522
- | `shadow-sm` | 1 |
1523
- | `shadow` | 2 |
1524
- | `shadow-md` | 4 |
1525
- | `shadow-lg` | 8 |
1526
- | `shadow-xl` | 12 |
1527
- | `shadow-2xl` | 16 |
1528
-
1529
- > **Note:** All shadow parsing happens at compile-time with zero runtime overhead. The platform detection uses React Native's `Platform.select()` API.
1530
-
1531
- ### Aspect Ratio
1532
-
1533
- Control the aspect ratio of views using preset or arbitrary values. Requires React Native 0.71+:
1534
-
1535
- **Preset values:**
1536
-
1537
- - `aspect-auto` — Remove aspect ratio constraint
1538
- - `aspect-square` — 1:1 aspect ratio
1539
- - `aspect-video` — 16:9 aspect ratio
1540
-
1541
- **Arbitrary values:**
1542
-
1543
- Use `aspect-[width/height]` for custom ratios:
1544
-
1545
- ```tsx
1546
- <View className="aspect-[4/3]" /> // 4:3 ratio (1.333...)
1547
- <View className="aspect-[16/9]" /> // 16:9 ratio (1.778...)
1548
- <View className="aspect-[21/9]" /> // 21:9 ultrawide
1549
- <View className="aspect-[9/16]" /> // 9:16 portrait
1550
- <View className="aspect-[3/2]" /> // 3:2 ratio (1.5)
1551
- ```
1552
-
1553
- **Examples:**
1554
-
1555
- ```tsx
1556
- // Square image container
1557
- <View className="w-full aspect-square bg-gray-200">
1558
- <Image source={avatar} className="w-full h-full" />
1559
- </View>
1560
-
1561
- // Video player container (16:9)
1562
- <View className="w-full aspect-video bg-black">
1563
- <VideoPlayer />
1564
- </View>
1565
-
1566
- // Instagram-style square grid
1567
- <View className="flex-row flex-wrap gap-2">
1568
- {photos.map((photo) => (
1569
- <View key={photo.id} className="w-[32%] aspect-square">
1570
- <Image source={photo.uri} className="w-full h-full rounded" />
1571
- </View>
1572
- ))}
1573
- </View>
1574
-
1575
- // Custom aspect ratio for wide images
1576
- <View className="w-full aspect-[21/9] rounded-lg overflow-hidden">
1577
- <Image source={banner} className="w-full h-full" resizeMode="cover" />
1578
- </View>
1579
-
1580
- // Portrait orientation
1581
- <View className="h-full aspect-[9/16]">
1582
- <Story />
1583
- </View>
1584
-
1585
- // Remove aspect ratio constraint
1586
- <View className="aspect-square md:aspect-auto">
1587
- Responsive aspect ratio
1588
- </View>
1589
- ```
1590
-
1591
- **Common Aspect Ratios:**
1592
-
1593
- | Ratio | Class | Decimal | Use Case |
1594
- | ----- | --------------- | ------- | ---------------------------- |
1595
- | 1:1 | `aspect-square` | 1.0 | Profile pictures, thumbnails |
1596
- | 16:9 | `aspect-video` | 1.778 | Videos, landscape photos |
1597
- | 4:3 | `aspect-[4/3]` | 1.333 | Standard photos |
1598
- | 3:2 | `aspect-[3/2]` | 1.5 | Classic photography |
1599
- | 21:9 | `aspect-[21/9]` | 2.333 | Ultrawide/cinematic |
1600
- | 9:16 | `aspect-[9/16]` | 0.5625 | Stories, vertical video |
1601
-
1602
- > **Note:** The aspect ratio is calculated as `width / height`. When combined with `w-full`, the height will be automatically calculated to maintain the ratio.
1603
-
1604
- ### Transforms
1605
-
1606
- Apply 2D and 3D transformations to views with React Native's transform API. All transforms compile to optimized transform arrays at build time:
1607
-
1608
- **Scale:**
1609
-
1610
- - `scale-{value}` — Scale uniformly (both X and Y)
1611
- - `scale-x-{value}`, `scale-y-{value}` — Scale on specific axis
1612
- - **Values:** `0`, `50`, `75`, `90`, `95`, `100`, `105`, `110`, `125`, `150`, `200`
1613
- - **Arbitrary:** `scale-[1.23]`, `scale-x-[0.5]`, `scale-y-[2.5]`
1614
-
1615
- **Rotate:**
1616
-
1617
- - `rotate-{degrees}`, `-rotate-{degrees}` — Rotate in 2D
1618
- - `rotate-x-{degrees}`, `rotate-y-{degrees}`, `rotate-z-{degrees}` — Rotate on specific axis
1619
- - **Values:** `0`, `1`, `2`, `3`, `6`, `12`, `45`, `90`, `180`
1620
- - **Arbitrary:** `rotate-[37deg]`, `-rotate-[15deg]`, `rotate-x-[30deg]`
1621
-
1622
- **Translate:**
1623
-
1624
- - `translate-x-{spacing}`, `translate-y-{spacing}` — Move on specific axis
1625
- - `-translate-x-{spacing}`, `-translate-y-{spacing}` — Negative translation
1626
- - **Values:** Uses spacing scale (same as `m-*`, `p-*`)
1627
- - **Arbitrary:** `translate-x-[50px]`, `translate-y-[100px]`, `translate-x-[50%]`
1628
-
1629
- **Skew:**
1630
-
1631
- - `skew-x-{degrees}`, `skew-y-{degrees}` — Skew on specific axis
1632
- - `-skew-x-{degrees}`, `-skew-y-{degrees}` — Negative skew
1633
- - **Values:** `0`, `1`, `2`, `3`, `6`, `12`
1634
- - **Arbitrary:** `skew-x-[15deg]`, `-skew-y-[8deg]`
1635
-
1636
- **Perspective:**
1637
-
1638
- - `perspective-{value}` — Apply perspective transformation
1639
- - **Values:** `0`, `100`, `200`, `300`, `400`, `500`, `600`, `700`, `800`, `900`, `1000`
1640
- - **Arbitrary:** `perspective-[1500]`, `perspective-[2000]`
1641
-
1642
- **Examples:**
1643
-
1644
- ```tsx
1645
- // Scale
1646
- <View className="scale-110 p-4">
1647
- {/* 110% scale (1.1x larger) */}
1648
- <Text>Scaled content</Text>
1649
- </View>
1650
-
1651
- // Rotate
1652
- <View className="rotate-45 w-16 h-16 bg-blue-500" />
1653
-
1654
- // Translate
1655
- <View className="translate-x-4 translate-y-2 bg-red-500 p-4">
1656
- {/* Moved 16px right, 8px down */}
1657
- </View>
1658
-
1659
- // Arbitrary values
1660
- <View className="scale-[1.23] w-16 h-16 bg-green-500" />
1661
- <View className="rotate-[37deg] w-16 h-16 bg-purple-500" />
1662
- <View className="translate-x-[50px] bg-orange-500 p-4" />
1663
-
1664
- // Negative values
1665
- <View className="-rotate-45 w-16 h-16 bg-pink-500" />
1666
- <View className="-translate-x-4 -translate-y-2 bg-indigo-500 p-4" />
1667
-
1668
- // 3D rotation
1669
- <View className="rotate-x-45 w-16 h-16 bg-yellow-500" />
1670
- <View className="rotate-y-30 w-16 h-16 bg-teal-500" />
1671
-
1672
- // Skew
1673
- <View className="skew-x-6 w-16 h-16 bg-cyan-500" />
1674
-
1675
- // Perspective
1676
- <View className="perspective-500">
1677
- <View className="rotate-x-45 w-16 h-16 bg-blue-500" />
1678
- </View>
1679
- ```
1680
-
1681
- **Multiple Transforms Limitation:**
1682
-
1683
- Due to the current architecture, multiple transform classes on the same element will overwrite each other. For example:
1684
-
1685
- ```tsx
1686
- // ❌ Only rotate-45 will apply (overwrites scale-110)
1687
- <View className="scale-110 rotate-45 w-16 h-16 bg-blue-500" />
1688
-
1689
- // ✅ Workaround: Use nested Views for multiple transforms
1690
- <View className="scale-110">
1691
- <View className="rotate-45">
1692
- <View className="w-16 h-16 bg-blue-500" />
1693
- </View>
1694
- </View>
1695
- ```
1696
-
1697
- 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.
1698
-
1699
- **What's Not Supported:**
1700
-
1701
- - `transform-origin` — Not available in React Native (transforms always use center as origin)
1702
- - Multiple transforms on one element — Use nested Views (see workaround above)
1703
-
1704
- > **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.
1705
-
1706
- ### Sizing
1707
-
1708
- - `w-{size}`, `h-{size}` — Width/height
1709
- - `min-w-{size}`, `min-h-{size}` — Min width/height
1710
- - `max-w-{size}`, `max-h-{size}` — Max width/height
1711
-
1712
- **Available sizes:**
1713
-
1714
- - **Numeric:** `0`-`96` (same as spacing scale)
1715
- - **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`
1716
- - **Special:** `full` (100%), `auto`
1717
- - **Arbitrary:** `w-[123px]`, `h-[50%]`, `min-w-[200px]`, `max-h-[80%]`
1718
-
1719
- > **Note:** Arbitrary sizing supports pixel values (`[123px]` or `[123]`) and percentages (`[50%]`). Other units (`rem`, `em`, `vh`, `vw`) are not supported in React Native.
1720
-
1721
- ## Advanced
1722
-
1723
- ### Custom Attributes
1724
-
1725
- By default, the Babel plugin transforms these className-like attributes to their corresponding style props:
1726
-
1727
- - `className` → `style`
1728
- - `contentContainerClassName` → `contentContainerStyle` (ScrollView, FlatList)
1729
- - `columnWrapperClassName` → `columnWrapperStyle` (FlatList)
1730
- - `ListHeaderComponentClassName` → `ListHeaderComponentStyle` (FlatList)
1731
- - `ListFooterComponentClassName` → `ListFooterComponentStyle` (FlatList)
1732
-
1733
- You can customize which attributes are transformed using the `attributes` plugin option:
1734
-
1735
- **Exact Matches:**
1736
-
1737
- ```javascript
1738
- // babel.config.js
1739
- module.exports = {
1740
- plugins: [
1741
- [
1742
- "@mgcrea/react-native-tailwind/babel",
1743
- {
1744
- attributes: ["className", "buttonClassName", "containerClassName"],
1745
- },
1746
- ],
1747
- ],
1748
- };
1749
- ```
1750
-
1751
- **Pattern Matching:**
1752
-
1753
- Use glob patterns to match multiple attributes:
1754
-
1755
- ```javascript
1756
- // babel.config.js
1757
- module.exports = {
1758
- plugins: [
1759
- [
1760
- "@mgcrea/react-native-tailwind/babel",
1761
- {
1762
- // Matches any attribute ending in 'ClassName'
1763
- attributes: ["*ClassName"],
1764
- },
1765
- ],
1766
- ],
1767
- };
1768
- ```
1769
-
1770
- **Combined:**
1771
-
1772
- ```javascript
1773
- // babel.config.js
1774
- module.exports = {
1775
- plugins: [
1776
- [
1777
- "@mgcrea/react-native-tailwind/babel",
1778
- {
1779
- // Mix exact matches and patterns
1780
- attributes: [
1781
- "className",
1782
- "*ClassName", // containerClassName, buttonClassName, etc.
1783
- "custom*", // customButton, customHeader, etc.
1784
- ],
1785
- },
1786
- ],
1787
- ],
1788
- };
1789
- ```
1790
-
1791
- **Usage Example:**
1792
-
1793
- ```tsx
1794
- // With custom attributes configured
1795
- function Button({ title, onPress, buttonClassName, containerClassName }) {
1796
- return (
1797
- <View containerClassName="p-2 bg-gray-100">
1798
- <Pressable buttonClassName="bg-blue-500 px-6 py-4 rounded-lg" onPress={onPress}>
1799
- <Text className="text-white font-semibold">{title}</Text>
1800
- </Pressable>
1801
- </View>
1802
- );
1803
- }
1804
-
1805
- // Transforms to:
1806
- function Button({ title, onPress, buttonStyle, containerStyle }) {
1807
- return (
1808
- <View style={[_twStyles._bg_gray_100_p_2, containerStyle]}>
1809
- <Pressable style={[_twStyles._bg_blue_500_px_6_py_4_rounded_lg, buttonStyle]} onPress={onPress}>
1810
- <Text style={_twStyles._font_semibold_text_white}>{title}</Text>
1811
- </Pressable>
1812
- </View>
1813
- );
1814
- }
1815
- ```
1816
-
1817
- **Naming Convention:**
1818
-
1819
- Attributes ending in `ClassName` are automatically converted to their `Style` equivalent:
1820
-
1821
- - `buttonClassName` → `buttonStyle`
1822
- - `containerClassName` → `containerStyle`
1823
- - `headerClassName` → `headerStyle`
1824
-
1825
- For attributes not ending in `ClassName`, the `style` prop is used.
1826
-
1827
- **TypeScript Support:**
1828
-
1829
- When using custom attributes, you'll need to augment the component types to include your custom className props. See the [TypeScript section](#2-enable-typescript-support-typescript) for details on module augmentation.
1830
-
1831
- ### Custom Styles Identifier
1832
-
1833
- By default, the Babel plugin generates a StyleSheet constant named `_twStyles`. You can customize this identifier to avoid conflicts or match your project's naming conventions:
1834
-
1835
- ```javascript
1836
- // babel.config.js
1837
- module.exports = {
1838
- plugins: [
1839
- [
1840
- "@mgcrea/react-native-tailwind/babel",
1841
- {
1842
- stylesIdentifier: "styles", // or 'tw', 'tailwind', etc.
1843
- },
1844
- ],
1845
- ],
1846
- };
1847
- ```
1848
-
1849
- **Default behavior:**
1850
-
1851
- ```tsx
1852
- // Input
1853
- <View className="p-4 bg-blue-500" />
1854
-
1855
- // Output
1856
- <View style={_twStyles._bg_blue_500_p_4} />
1857
-
1858
- const _twStyles = StyleSheet.create({
1859
- _bg_blue_500_p_4: { padding: 16, backgroundColor: '#3B82F6' }
1860
- });
1861
- ```
1862
-
1863
- **With custom identifier:**
1864
-
1865
- ```tsx
1866
- // Input (with stylesIdentifier: "styles")
1867
- <View className="p-4 bg-blue-500" />
1868
-
1869
- // Output
1870
- <View style={styles._bg_blue_500_p_4} />
1871
-
1872
- const styles = StyleSheet.create({
1873
- _bg_blue_500_p_4: { padding: 16, backgroundColor: '#3B82F6' }
1874
- });
1875
- ```
1876
-
1877
- **Use Cases:**
1878
-
1879
- - **Avoid conflicts:** If you already have a `_twStyles` variable in your code
1880
- - **Consistency:** Match your existing StyleSheet naming convention (`styles`, `styleSheet`, etc.)
1881
- - **Shorter names:** Use a shorter identifier like `tw` or `s` for more compact code
1882
- - **Team conventions:** Align with your team's coding standards
1883
-
1884
- **Important Notes:**
1885
-
1886
- - The identifier must be a valid JavaScript variable name
1887
- - Choose a name that won't conflict with existing variables in your files
1888
- - The same identifier is used across all files in your project
1889
-
1890
- ### Custom Color Scheme Hook
1891
-
1892
- By default, the plugin uses React Native's built-in `useColorScheme()` hook for `dark:` and `light:` modifiers. You can configure it to use a custom color scheme hook from theme providers like React Navigation, Expo, or your own implementation.
1893
-
1894
- **Configuration:**
1895
-
1896
- ```javascript
1897
- // babel.config.js
1898
- module.exports = {
1899
- plugins: [
1900
- [
1901
- "@mgcrea/react-native-tailwind/babel",
1902
- {
1903
- colorScheme: {
1904
- importFrom: "@/hooks/useColorScheme", // Module to import from
1905
- importName: "useColorScheme", // Hook name to import
1906
- },
1907
- },
1908
- ],
1909
- ],
1910
- };
1911
- ```
1912
-
1913
- **Use Cases:**
1914
-
1915
- #### 1. Custom Theme Provider
1916
-
1917
- Override system color scheme with user preferences from a store:
1918
-
1919
- ```typescript
1920
- // src/hooks/useColorScheme.ts
1921
- import { useColorScheme as useSystemColorScheme } from "react-native";
1922
- import { profileStore } from "@/stores/profileStore";
1923
- import { type ColorSchemeName } from "react-native";
1924
-
1925
- export const useColorScheme = (): ColorSchemeName => {
1926
- const systemColorScheme = useSystemColorScheme();
1927
- const userTheme = profileStore.theme; // 'dark' | 'light' | 'auto'
1928
-
1929
- // Return user preference, or fall back to system if set to 'auto'
1930
- return userTheme === 'auto' ? systemColorScheme : userTheme;
1931
- };
1932
- ```
1933
-
1934
- ```javascript
1935
- // babel.config.js
1936
- {
1937
- colorScheme: {
1938
- importFrom: "@/hooks/useColorScheme",
1939
- importName: "useColorScheme"
1940
- }
1941
- }
1942
- ```
1943
-
1944
- #### 2. React Navigation Theme
1945
-
1946
- Integrate with React Navigation's theme system:
1947
-
1948
- ```typescript
1949
- // Wrap React Navigation's useTheme to return ColorSchemeName
1950
- import { useTheme as useNavTheme } from "@react-navigation/native";
1951
- import { type ColorSchemeName } from "react-native";
1952
-
1953
- export const useColorScheme = (): ColorSchemeName => {
1954
- const { dark } = useNavTheme();
1955
- return dark ? "dark" : "light";
1956
- };
1957
- ```
1958
-
1959
- #### 3. Expo Router Theme
1960
-
1961
- Use Expo Router's theme hook:
1962
-
1963
- ```javascript
1964
- // babel.config.js
1965
- {
1966
- colorScheme: {
1967
- importFrom: "expo-router",
1968
- importName: "useColorScheme"
1969
- }
1970
- }
1971
- ```
1972
-
1973
- #### 4. Testing
1974
-
1975
- Mock color scheme for tests:
1976
-
1977
- ```typescript
1978
- // test/mocks/useColorScheme.ts
1979
- export const useColorScheme = () => "light"; // Or "dark" for dark mode tests
1980
- ```
1981
-
1982
- ```javascript
1983
- // babel.config.js (test environment)
1984
- {
1985
- colorScheme: {
1986
- importFrom: "@/test/mocks/useColorScheme",
1987
- importName: "useColorScheme"
1988
- }
1989
- }
1990
- ```
1991
-
1992
- #### How it works
1993
-
1994
- When you use `dark:` or `light:` modifiers:
1995
-
1996
- ```tsx
1997
- <View className="bg-white dark:bg-gray-900" />
1998
- ```
1999
-
2000
- The plugin will:
2001
-
2002
- 1. Import your custom hook: `import { useColorScheme } from "@/hooks/useColorScheme"`
2003
- 2. Inject it in components: `const _twColorScheme = useColorScheme();`
2004
- 3. Generate conditionals: `_twColorScheme === "dark" && styles._dark_bg_gray_900`
2005
-
2006
- #### Default behavior (no configuration)
2007
-
2008
- Without custom configuration, the plugin uses React Native's built-in hook:
2009
-
2010
- - Import: `import { useColorScheme } from "react-native"`
2011
- - This works out of the box for basic system color scheme detection
2012
-
2013
- #### Requirements
2014
-
2015
- - Your custom hook must return `ColorSchemeName` (type from React Native: `"light" | "dark" | null | undefined`)
2016
- - The hook must be compatible with React's rules of hooks (can only be called in function components)
2017
- - Import merging works automatically if you already import from the same source
2018
-
2019
- ### Arbitrary Values
2020
-
2021
- Use arbitrary values for custom sizes, spacing, and borders not in the preset scales:
2022
-
2023
- ```tsx
2024
- <View className="w-[350px] h-[85%] m-[16px] p-[24px] border-[3px] rounded-[20px]" />
2025
- ```
2026
-
2027
- **Supported:**
2028
-
2029
- - **Spacing:** `m-[...]`, `mx-[...]`, `my-[...]`, `mt-[...]`, `p-[...]`, `px-[...]`, `gap-[...]`, etc. (px only)
2030
- - **Sizing:** `w-[...]`, `h-[...]`, `min-w-[...]`, `min-h-[...]`, `max-w-[...]`, `max-h-[...]` (px and %)
2031
- - **Border width:** `border-[...]`, `border-t-[...]`, `border-r-[...]`, `border-b-[...]`, `border-l-[...]` (px only)
2032
- - **Border radius:** `rounded-[...]`, `rounded-t-[...]`, `rounded-tl-[...]`, etc. (px only)
2033
- - **Transforms:**
2034
- - **Scale:** `scale-[...]`, `scale-x-[...]`, `scale-y-[...]` (number only, e.g., `[1.23]`)
2035
- - **Rotate:** `rotate-[...]`, `rotate-x-[...]`, `rotate-y-[...]`, `rotate-z-[...]` (deg only, e.g., `[37deg]`)
2036
- - **Translate:** `translate-x-[...]`, `translate-y-[...]` (px or %, e.g., `[50px]` or `[50%]`)
2037
- - **Skew:** `skew-x-[...]`, `skew-y-[...]` (deg only, e.g., `[15deg]`)
2038
- - **Perspective:** `perspective-[...]` (number only, e.g., `[1500]`)
2039
-
2040
- **Formats:**
2041
-
2042
- - Pixels: `[123px]` or `[123]` — Supported by all utilities
2043
- - Percentages: `[50%]`, `[33.333%]` — Only supported by sizing utilities (`w-*`, `h-*`, etc.)
2044
-
2045
- > **Note:** CSS units (`rem`, `em`, `vh`, `vw`) are not supported by React Native.
2046
-
2047
- ### Custom Theme Extensions
2048
-
2049
- Extend the default color palette and font families via `tailwind.config.*` in your project root:
2050
-
2051
- ```javascript
2052
- // tailwind.config.mjs
2053
- export default {
2054
- theme: {
2055
- extend: {
2056
- colors: {
2057
- primary: "#1d4ed8",
2058
- secondary: "#9333ea",
2059
- brand: {
2060
- light: "#f0f9ff",
2061
- DEFAULT: "#0284c7",
2062
- dark: "#0c4a6e",
2063
- },
2064
- },
2065
- fontFamily: {
2066
- sans: ['"SF Pro Rounded"'],
2067
- custom: ['"My Custom Font"'],
2068
- },
2069
- },
2070
- },
2071
- };
2072
- ```
2073
-
2074
- Then use your custom theme:
2075
-
2076
- ```tsx
2077
- <View className="bg-primary p-4">
2078
- <Text className="text-brand font-custom">Custom branded text</Text>
2079
- <Text className="font-sans">SF Pro Rounded text</Text>
2080
- <View className="bg-brand-light rounded-lg" />
2081
- </View>
2082
- ```
2083
-
2084
- **How it works:**
2085
-
2086
- - Babel plugin discovers config by traversing up from source files
2087
- - Custom theme merged with defaults at build time (custom takes precedence)
2088
- - Nested color objects flattened with dash notation: `brand.light` → `brand-light`
2089
- - Font families use first font in array (React Native doesn't support font stacks)
2090
- - Zero runtime overhead — all loading happens during compilation
2091
-
2092
- **Supported formats:** `.js`, `.mjs`, `.cjs`, `.ts`
2093
-
2094
- > **Tip:** Use `theme.extend.*` to keep defaults. Using `theme.colors` or `theme.fontFamily` directly will override all defaults.
2095
-
2096
- ### Programmatic API
2097
-
2098
- Access the parser and constants programmatically:
2099
-
2100
- ```typescript
2101
- import { parseClassName, COLORS, SPACING_SCALE } from "@mgcrea/react-native-tailwind";
2102
-
2103
- // Parse className strings (no custom theme)
2104
- const styles = parseClassName("m-4 p-2 bg-blue-500");
2105
- // Returns: { margin: 16, padding: 8, backgroundColor: '#3B82F6' }
2106
-
2107
- // Parse with custom theme
2108
- const customStyles = parseClassName("m-4 bg-primary font-custom", {
2109
- colors: { primary: "#1d4ed8" },
2110
- fontFamily: { custom: "My Custom Font" },
2111
- });
2112
- // Returns: { margin: 16, backgroundColor: '#1d4ed8', fontFamily: 'My Custom Font' }
2113
-
2114
- // Access default scales
2115
- const blueColor = COLORS["blue-500"]; // '#3B82F6'
2116
- const spacing = SPACING_SCALE[4]; // 16
2117
- ```
2118
-
2119
- ## Troubleshooting
2120
-
2121
- ### TypeScript `className` Errors
2122
-
2123
- If TypeScript doesn't recognize the `className` prop:
2124
-
2125
- 1. Create the type declaration file:
2126
-
2127
- ```typescript
2128
- // src/types/react-native-tailwind.d.ts
2129
- import "@mgcrea/react-native-tailwind/react-native";
2130
- ```
2131
-
2132
- 2. Verify it's covered by your `tsconfig.json` `include` pattern
2133
- 3. Restart TypeScript server (VS Code: Cmd+Shift+P → "TypeScript: Restart TS Server")
2134
-
2135
- ### Babel Plugin Not Working
2136
-
2137
- **Clear Metro cache:**
2138
-
2139
- ```bash
2140
- npx react-native start --reset-cache
2141
- ```
2142
-
2143
- **Verify `babel.config.js`:**
2144
-
2145
- ```javascript
2146
- plugins: ["@mgcrea/react-native-tailwind/babel"];
2147
- ```
2148
-
2149
- ### Custom Colors Not Recognized
2150
-
2151
- 1. **Config location** — Must be in project root or parent directory
2152
- 2. **Config format** — Verify proper export:
2153
-
2154
- ```javascript
2155
- // CommonJS
2156
- module.exports = { theme: { extend: { colors: { ... } } } };
2157
-
2158
- // ESM
2159
- export default { theme: { extend: { colors: { ... } } } };
2160
- ```
2161
-
2162
- 3. **Clear cache** — Config changes require Metro cache reset
2163
- 4. **Use `theme.extend.colors`** — Don't use `theme.colors` directly (overrides defaults)
2164
-
2165
- ## Development
2166
-
2167
- ### Project Setup
2168
-
2169
- ```bash
2170
- git clone https://github.com/mgcrea/react-native-tailwind.git
2171
- cd react-native-tailwind
2172
- pnpm install
2173
- ```
2174
-
2175
- ### Build
2176
-
2177
- ```bash
2178
- pnpm build # Full build
2179
- pnpm build:babel # Compile TypeScript
2180
- pnpm build:babel-plugin # Bundle Babel plugin
2181
- pnpm build:types # Generate type declarations
2182
- ```
2183
-
2184
- ### Testing
2185
-
2186
- ```bash
2187
- pnpm test # Run all tests
2188
- pnpm lint # ESLint
2189
- pnpm check # TypeScript type check
2190
- pnpm spec # Jest tests
2191
- ```
2192
-
2193
- ### Example App
2194
-
2195
- ```bash
2196
- pnpm dev # Run example app
2197
- cd example && npm run dev -- --reset-cache
2198
- ```
213
+ Contributions are welcome! Please read our [Contributing Guide](https://mgcrea.github.io/react-native-tailwind/advanced/contributing/) for details.
2199
214
 
2200
215
  ## Authors
2201
216