Babel macro for easily writing responsive Tailwind styles in React Native (+ Web).
Easily style components using the tw
prop:
import "react-native-tailwind.macro"
import { View, Text } from "react-native"
const Badge = () => (
<View tw="px-3 py-0.5 rounded-xl ios:rounded-full bg-blue-100 dark:bg-blue-800">
<Text tw="text-sm lg:text-lg font-medium text-blue-800">Badge</Text>
</View>
)
Apply conditions and create memoized and complex styles using useTailwindStyles
:
import { useTailwindStyles } from "react-native-tailwind.macro"
import { View, Text } from "react-native"
const Status = ({ isActive, labelColor }) => {
const { box, text } = useTailwindStyles(
(tw) => ({
box: [tw`rounded-3xl px-8 py-4 bg-gray-100`, isActive && tw`bg-blue-800`],
text: [tw`font-medium`, isActive && tw`font-bold`, { color: labelColor }],
}),
[isActive, labelColor]
)
return (
<View style={box}>
<Text style={text}>{isActive ? "Active" : "Inactive"}</Text>
</View>
)
}
-
⚡️ Responsive styles on native and web (with CSS Media Queries and SSR support - no more layout flashes!)
bg-blue-500 md:bg-purple-500 xl:bg-indigo-500
-
🌓 Dark Mode
text-black dark:text-white
-
📱 Platform selectors
ios:bg-blue-800 android:bg-purple-800
-
🖥 Web only selectors
focus:bg-blue-400 active:bg-indigo-400
-
🕺 Stackable selectors
ios:md:font-bold android:(text-blue-800 sm:dark:text-blue-100)
-
🧠 All styles are statically generated at build time and memoized
-
🛠
useTailwindStyles
hook for optimized conditional and complex styles -
📝 Respects your tailwind.config.js and allows for custom classes and plugins made possible by the amazing tailwind-react-native-classnames
This library relies on babel-plugin-macros.
yarn add --dev babel-plugin-macros
yarn add react-native-tailwind.macro
Add babel-plugin-macros to your .babelrc
or babel.config.js
and you're all set up!
{
"plugins": ["macros"]
}
The best and easiest usage is to simply use the tw
prop that is artificially added to all JSX elements. Under the hood, the macro removes the tw
prop completely and instead applies or extends a style
prop and also adds a web-only data-tw id used to apply CSS-based media queries.
All you have to do is have any import of react-native-tailwind.macro in your file, either import "react-native-tailwind.macro"
or import { /* whatever import you need */ } from "react-native-tailwind.macro"
.
Example:
import "react-native-tailwind.macro"
import { View } from "react-native"
import { MyComponent } from "./my-component"
const Example = () => (
<>
<View tw="w-[100px] h-[100px] bg-blue-500 md:bg-purple-500" />
{/* Only works if MyComponent accepts a "style" prop */}
<MyComponent tw="bg-pink-500 ios:dark:bg-indigo-800" />
</>
)
<MyComponent />
to render responsive styles on the web, you need to also pass down a dataSet
prop to the element receiving the style. The easiest way to achieve this would be to use the rest-spread syntax for MyComponent
's props and pass all non-used props to the style-carrying element:
const MyComponent = ({ disabled, ...props }) => (
<View {...props}>
<Text>{disabled ? "Disabled" : "Enabled"}</Text>
</View>
)
In cases where you don't have access to a style prop or need to apply styles using other props, like contentContainerStyle
on a ScrollView
, you can use useTailwindStyles
to produce the desired style objects.
import { useTailwindStyles } from "react-native-tailwind.macro"
import { ScrollView } from "react-native"
const Example = () => {
const styles = useTailwindStyles((tw) => ({
contentContainer: tw`py-8`,
}))
return (
<ScrollView contentContainerStyle={styles.contentContainer} {/* ... */}>
{/* Content */}
</ScrollView>
)
}
This also comes in handy, when you want to apply styles conditionally, pass in Reanimated animated styles or do any other fancy stuff with your styles.
import { useTailwindStyles } from "react-native-tailwind.macro"
import { TouchableOpacity, Text } from "react-native"
import Animated, {
useSharedValue,
useAnimatedStyle,
} from "react-native-reanimated"
const Example = ({ rounded, backgroundColor }) => {
// Reanimated 🔥
const offset = useSharedValue(0)
const animatedStyles = useAnimatedStyle(() => {
return {
transform: [{ translateX: offset.value * 255 }],
}
})
const styles = useTailwindStyles(
(tw) => ({
box: [
tw`w-[100px] h-[100px] lg:(w-[300px] h-[300px])`,
rounded && tw`rounded-3xl`, // conditional Tailwind style
animatedStyles, // apply Reanimated styles
{ backgroundColor }, // apply passed in backgroundColor
],
text: tw`text-lg font-bold`,
}),
// only recompute when any of these deps change, skip all unnecessary re-renders
[rounded, animatedStyles, backgroundColor]
)
return (
<>
<Animated.View
style={styles.box}
{/* Required for responsive styles on the web, hence it's preferred to apply all responsive styles through the `tw` prop */}
dataSet={{ tw: styles.box.id }}
/>
<TouchableOpacity
{/* Mix and match tw prop and styles in your code */}
tw="mt-8"
onPress={() => (offset.value = Math.random())}
>
<Text style={styles.text}>Move</Text>
</TouchableOpacity>
</>
)
}
Even if you don't rely on any Tailwind styles, useTailwindStyles
can be used to generate memoized styles as performance boost, see this gist for explanation.
const styles = useTailwindStyles(
(tw) => ({
box: [{ backgroundColor: "red" }, isActive && { backgroundColor: "pink" }],
}),
[isActive]
)
By default, the device's color scheme preference is used to enable dark mode. If you want to dynamically change whether dark mode is enabled, you can wrap your App with TailwindProvider
and pass in your dark mode preference.
On the web, the set value will automatically be persisted in a cookie to enable SSR and SSG without flashes on load.
import {
TailwindProvider,
getInitialColorScheme,
} from "react-native-tailwind.macro"
const App = () => {
const [darkMode, setDarkMode] = useState(getInitialColorScheme() === "dark")
return <TailwindProvider dark={darkMode}>{/* ... */}</TailwindProvider>
}
Returns either the cookie-persisted preference on web or falls back to the system preference.
import { getInitialColorScheme } from "react-native-tailwind.macro"
getInitialColorScheme() // returns "light" or "dark"
You can apply options to the macro by adding a babel-plugin-macros.config.js
or specifying them in your package.json
like below:
// babel-plugin-macros.config.js
module.exports = {
reactNativeTailwind: {
// your options
},
}
Alternatively:
// package.json
{
//...
"babelMacros": {
"reactNativeTailwind": {
// your options
}
}
}
Available Options
Name | Default | Description |
---|---|---|
config | "./tailwind.config.js" |
The path to your Tailwind config. |
In order to enable SSR support via media queries on Next.js, update your custom document as follows:
// pages/_document.js
+ import { flush } from "react-native-tailwind.macro"
/* ... */
export class Document extends NextDocument {
static async getInitialProps({ renderPage }) {
AppRegistry.registerComponent("Main", () => Main)
const { getStyleElement } = AppRegistry.getApplication("Main")
const page = renderPage()
const styles = [
getStyleElement(),
+ flush(),
]
return { ...page, styles: React.Children.toArray(styles) }
}
render() {
/* ... */
}
}
Behind the scenes, react-native-tailwind.macro
turns your simple code from this
import "react-native-tailwind.macro"
const Example = () => (
<View tw="w-[100px] h-[100px] bg-purple-500 dark:ios:lg:bg-pink-500 hover:bg-indigo-500" />
)
... to something along the lines of this:
// Import the necessary utilities
import * as ReactNativeTailwindMacro from "react-native-tailwind.macro/exports"
// Creates a hook based on the static output from Tailwind style compilation
const useStyles = ReactNativeTailwindMacro.createUseTailwindStyles({
// Compiled Tailwind styles with unique id and information on when to apply
a7gsbs: [
{
dark: false,
selectors: [],
style: {
width: 100,
height: 100,
backgroundColor: "#8b5cf6",
},
},
{
dark: true,
breakpoint: {
label: "lg",
minWidth: "1024px",
},
selectors: [],
platform: "ios",
style: {
backgroundColor: "#ec4899",
},
},
{
dark: false,
// Styles on web will only be applied on web
selectors: ["hover"],
style: {
backgroundColor: "#6366f1",
},
},
],
})
const Example = () => {
// Call to the produced hook, takes into account the current context and returns
// memoized styles that only change when the context changes
const tailwindStyles = useStyles()
return (
<View
// Apply the memoized style
style={tailwindStyles["a7gsbs"]}
// Apply data-tw id for CSS-based media queries
dataSet={{ tw: tailwindStyles["a7gsbs"].id }}
/>
)
}
For more examples and use cases, check the macro test snapshots.
-
Only works in function components due to dependency on context
-
useTailwindStyles
doesn't properly support responsive styles on the web, prefer to use thetw
prop for responsive styles if possible -
<View tw="..."/>
andtw`...`
only accept static styles without string interpolation
-
tailwind-react-native-classnames: Used for compiling Tailwind styles
-
react-native-media-query: Provides the base implementation used to enable CSS media query support
-
twin.macro: Inspiration for writing a babel macro
See the contributing guide to learn how to contribute to the repository and the development workflow.
MIT