Getting started with NativeWind: Tailwind for React Native
Tags: tailwindcss, reactnative
Published: 2024-03-21 14:28:20

**Written by [Chinwike Maduabuchi
](https://blog.logrocket.com/author/chinwikemaduabuchi/)✏️**

It is no secret that Tailwind CSS has significantly influenced the web development community. Its utility-first approach makes it easy for developers to quickly create user interfaces in whichever frontend framework they use. Recognizing the value of Tailwind's design philosophy, the community has extended its capabilities to the mobile development sphere with [NativeWind](https://www.nativewind.dev/). 

NativeWind is a Tailwind CSS integration for mobile development, boasting customizability, consistent design language, and mobile-first responsive design. In this article, we’ll learn how to use the NativeWind library to write Tailwind classes in our native apps. To easily follow along, you’ll need:

*   A basic understanding of React
*   [Expo Go](https://expo.dev/client) installed on a physical device
*   Node.js installed on your machine

Some experience developing mobile applications using React Native and Expo would help but isn’t necessary. Now let’s dive in!

## How does NativeWind work?

NativeWind, created by [Mark Lawlor](https://twitter.com/mark__lawlor?lang=en), is a native library that abstracts Tailwind CSS’s features into a format digestible by mobile applications. Because mobile apps do not have a CSS engine like the browser, NativeWind compiles your Tailwind classes into the supported `StyleSheet.create` syntax. 

Here’s a short snippet of code that shows how components are styled in RN:

```javascript
export default function App() {
  return (
    <View style={styles.container}>
      <Text>Open up App.js to start working on your app!</Text>
    </View>
  )
}
const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
  },
})
```

In RN, the `className` property isn’t supported. However, NativeWind makes it possible to use the `className` prop with Tailwind classes thanks to some Babel magic. 

NativeWind then uses the Tailwind engine to compile and pass them to the [NativeWindStyleSheet API](https://www.nativewind.dev/api/native-wind-style-sheet), which is a wrapper around `StyleSheet.create`, to render the styles correctly. 

Now that we have a basic understanding of what NativeWind does behind the scenes, let’s install it in a fresh RN application.

## Setting up a dev workflow with React Native and Expo

Create a new folder anywhere on your machine and open that directory in your terminal. Then run this command:

```bash
npx create-expo-app .
```

This will create a new React Native project using Expo in your folder. Now, you can start the development server by running this:

```bash
npm run start
```

This command will initiate the Metro bundler, and shortly afterward, a QR code should appear in your terminal. 

To view your application on your phone during development, ensure that you have Expo Go installed on your mobile device beforehand. If you're on Android, launch Expo Go and select the "Scan QR code" option. For iOS users, open the camera app and scan the QR code displayed. Once scanned, you'll receive a prompt with a link to open the application in Expo Go.

## Installing NativeWind

To add NativeWind to your project, install the `nativewind` package and its peer dependency `tailwindcss`:

```bash
npm install nativewind && npm install -D tailwindcss@3.3.2
```

We're locking on this specific version of Tailwind CSS to prevent [this bug](https://github.com/marklawlor/nativewind/issues/498) — it should be addressed in NativeWind v4, but more on that later. 

Next, run the following command to create a `tailwind.config.js` file at the root of your project:

```bash
npx tailwindcss init 
```

Then, update the `content` property to specify which directories you want to write Tailwind styles in. In our case, we’ll include the entry file, `App.js`, and other React components in the `components` directory — which you can create now:

```javascript
// tailwind.config.js
module.exports = {
+ content: ['./App.{js,jsx,ts,tsx}', './components/**/*.{js,jsx,ts,tsx}'],
  theme: {
    extend: {},
  },
  plugins: [],
}
```

If you’re going to be writing Tailwind styles in other directories, be sure to include them in the content array. Finally, add the Babel plugin for NativeWind to `babel.config.js`:

```javascript
// babel.config.js
module.exports = function (api) {
  api.cache(true);
  return {
    presets: ["babel-preset-expo"],
+   plugins: ["nativewind/babel"],
  };
};
```

And that’s all it takes to start writing Tailwind CSS classes in your React Native applications! Note that because we have made changes to the Bable config file, we’re required to restart the development server before our changes can apply. Make sure you do so before proceeding.

## Creating a simple ecommerce UI with NativeWind

In this article, we’re going to attempt to recreate the UI below using NativeWind and core React Native components. We’ll revise core Tailwind CSS styling concepts like hover effects, fonts, dark mode, and responsive design. We’ll also learn how NativeWind APIs like `useColorScheme` and `platformSelect` can be used to create intuitive UIs:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k0eha4xx0rw0vmcvz6y6.png)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wt6lv85y8c92ha43iy6q.png)


The project assets, including the images and the product data, can all be found in this [GitHub repository](https://github.com/Chinwike1/nativewind-sandbox).

## Customizing NativeWind

One of the first things I do when working on a new project that uses Tailwind is to tweak some settings in the config file to match my design. In our case, we can start by adding some extra colors to our app’s interface:

```javascript
// tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
  content: ['./App.{js,jsx,ts,tsx}', './components/**/*.{js,jsx,ts,tsx}'],
  theme: {
    extend: {
      colors: {
        'off-white': {
          DEFAULT: '#E3E3E3',
        },
        dark: {
          DEFAULT: '#1C1C1E',
        },
        'soft-dark': {
          DEFAULT: '#2A2A2F',
        },
      },
    },
  },
  plugins: [],
}
```

We’ll be using this color palette throughout our application so make sure to update your `tailwind.config.js` file to match it.

## Styling the app layout

You may have observed that the layout we are trying to recreate has two columns. To create this, let’s start by cleaning up the `App.js` component and setting the flex value of the parent view to `1` so that it takes up the full height of the device’s screen:

```javascript
// App.js
import { StatusBar } from 'expo-status-bar'
import { View, SafeAreaView } from 'react-native'
import { products } from './utils/products'

export default function App() {
  return (
    <View className='flex-[1] bg-white pt-8'>
      <StatusBar style="auto" />
      <SafeAreaView />
    </View>
  )
}
```

We’ve also set the background to `white` and imported the [`SafeAreaView`](https://reactnative.dev/docs/safeareaview) component — this is an iOS-specific component that ensures the content of our app is not obscured by camera notches, status bars, or other system-provided areas. 

With that done, we can proceed to create the two-column layout. We’ll achieve this with React Native’s [`FlatList`](https://reactnative.dev/docs/flatlist) component.

## Creating a two-column layout with `FlatList`

React Native’s `FlatList` component provides a performance-optimized way to render large collections of data with useful features like lazy loading content that's not yet visible in the viewport, a pull-to-refresh functionality, scroll loading, and more. 

Using `FlatList` in React Native is similar to the way you'd map over some data in React DOM. However, `FlatList` handles the iteration for you and exposes some props to help render your layout however you want. There are three important `FlatList` props to keep in mind:

*   `data`: The actual data that will be iterated through and rendered
*   `renderItem`: A function that should return a JSX element representing the view of each iteration. The function takes each item from `data` as a parameter
*   `keyExtractor`: Used to specify a unique key for each item in the list, helping React to efficiently identify and update list items. It also takes each item in `data` as a prop

Import the product data from `utils/product.js` and render the `Flatlist` like so:

```javascript
// App.js
import { StatusBar } from 'expo-status-bar'
import { FlatList, View, SafeAreaView } from 'react-native'
import { products } from './utils/products'

export default function App() {
  return (
    <View className='flex-[1] bg-white pt-8'>
      <StatusBar style="auto" />
      <SafeAreaView />
      <FlatList
        data={products}
        numColumns={2}
        renderItem={(product_data) => {
          return (
            <View className='justify-center p-3'>
              <Image
                className='m-5 h-56 w-full mx-auto object-cover bg-slate-500 rounded-lg'
                source={product_data.item.image_url}
              />
              <Text className='text-dark mb-3'>
                {product_data.item.name.substring(0, 30) + '...'}
              </Text>
              <Text className='text-dark font-bold'>
                {`${product_data.item.price}.00`}
              </Text>
            </View>
        }}
        keyExtractor={(item) => {
          return item.key
        }}
      />
      <Navbar />
    </View>
  )
}
```

In the snippet above, we've rendered a `View` that shows the product image, along with its name and price — all styled with Tailwind classes. You'll also notice the `numColums` prop set to `2`, which we use to split the content into both halves of the screen: ![User Interface With Borders Around Items](https://blog.logrocket.com/wp-content/uploads/2024/03/ui-flatlist-borders.jpeg) Currently, the widths of these columns aren't constrained the way we'd want them to be. We can fix this by making use of React Native’s `Dimensions` API to:

1.  Grab the current device's width
2.  Divide its value by the number of columns (2)
3.  Set that result as the width for each column in the `FlatList`

Here's how:

```javascript
// App.js
import { StatusBar } from 'expo-status-bar'
import { FlatList, View, Dimensions, SafeAreaView } from 'react-native'
import { products } from './utils/products'
import Product from './components/product'

// calculate the width of each column using the screen dimensions
const numColumns = 2
const screen_width = Dimensions.get('window').width
const column_width = screen_width / numColumns

export default function App() {
  return (
    <View className='flex-[1] bg-white pt-8'>
      <StatusBar style="auto" />
      <SafeAreaView />
      <FlatList
        data={products}
        numColumns={numColumns}
        renderItem={(product_data) => {
          return (
            <Product
              image_url={product_data.item.image_url}
              name={product_data.item.name}
              price={product_data.item.price}
              column_width={column_width}
            />
          )
        }}
        keyExtractor={(item) => {
          return item.key
        }}
      />
    </View>
  )
}
```

Now here’s what we have: ![Evenly Distributed Borders On Our FlatList User Interface](https://blog.logrocket.com/wp-content/uploads/2024/03/evenly-distributed-flatlist-ui.jpeg) For clarity, I've abstracted the product view into a `Product` component and passed down data from the `FlatList` to it as props. Here's the `Product` component:

```javascript
// components/product.jsx
import { View, Text, Image } from 'react-native'

export default function Product({ image_url, name, price, column_width }) {
  return (
    <View style={{ width: column_width }} className='justify-center p-3'>
      <Image
        className='m-5 h-56 w-full mx-auto object-cover bg-slate-500 rounded-lg'
        source={image_url}
      />
      <Text className='text-dark mb-3'>
        {name.substring(0, 30) + '...'}
      </Text>
      <Text className='text-dark dark:text-white font-bold'>{`${price}.00`}</Text>
    </View>
  )
}
```

## Creating the navbar component

Our navbar component will consist of a flexbox layout with some icons. Let’s now install an icon library along with the `react-native-svg` dependency, which is required to [help SVGs render correctly in mobile applications](https://blog.logrocket.com/how-to-use-svgs-react-native-tutorial-with-examples/). Run the following command in your terminal:

```bash
npm i react-native-heroicons react-native-svg
```

When the process is complete, you will be able to import icons from the [Heroicons](https://www.npmjs.com/package/react-native-heroicons) library into your React Native project and use props like `color` and `size` to change their appearance:

```javascript
// components/navbar.jsx
import { Pressable, Platform, View } from 'react-native'
import {
  HomeIcon,
  HeartIcon,
  ShoppingCartIcon,
  SunIcon,
  MoonIcon,
} from 'react-native-heroicons/outline'

export default function Navbar() {
  return (
    <View
      className='px-8 py-6 bg-white shadow-top flex-row items-center justify-between'
    >
      <HomeIcon color="black" size={28} />
      <HeartIcon color="black" size={28} />
      <ShoppingCartIcon color="black" size={28} />
      <Pressable>
        <MoonIcon color="black" size={28} />
      </Pressable>
    </View>
  )
}
```

In the snippet provided, you'll observe that the `View` component's flex-direction is explicitly set to `row`. If you're new to styling in React Native, you might question why this is necessary because `row` is the default value for the flex-direction property. 

However, flexbox works differently in RN because the default flex-direction of every View component is set to `column`. This default setting alters the orientation of the main-axis and the cross-axis, therefore changing the meaning of the `justify-content` and `align-items` properties. 

In our styling, we've intentionally set the `flex-direction` to `row` and aligned the icons in the middle. The last spot in the icon list is reserved for the theme icons — sun and moon — wrapped in a `Pressable` component. This setup lays the foundation for adding the toggle-theme functionality later on. 

Now we can import the navbar into `App.js`:

```javascript
// App.js
import { StatusBar } from 'expo-status-bar'
import { FlatList, View, Dimensions, SafeAreaView } from 'react-native'
import { products } from './utils/products'
import Navbar from './components/navbar'
import Product from './components/product'

// calculate the width of each column using the screen dimensions
const numColumns = 2
const screen_width = Dimensions.get('window').width
const column_width = screen_width / numColumns

export default function App() {
  return (
    <View className='flex-[1] bg-white pt-8'>
      {/* status bar, safeareaview, and flatlist */}
      <Navbar />
    </View>
  )
}
```

## Platform-specific styling with `platformSelect`

NativeWind also has a hook called `platformSelect`, which is a wrapper around React Native’s `Platform` API. NativeWind allows us to use this hook to apply platform-specific styles in our apps through the Tailwind config file. Let’s see how:

```javascript
// tailwind.config.js
const { platformSelect } = require('nativewind/dist/theme-functions')
/** @type {import('tailwindcss').Config} */
module.exports = {
  content: ['./App.{js,jsx,ts,tsx}', './components/**/*.{js,jsx,ts,tsx}'],
  theme: {
    extend: {
      colors: {
       'platform-color': platformSelect({
          // Now you can provide platform specific values
          ios: "green",
          android: "blue",
          default: "#BABABA",
        }),
        dark: {
          DEFAULT: '#1C1C1E',
        },
        'soft-dark': {
          DEFAULT: '#2A2A2F',
        },
      },
    },
  },
  plugins: [],
}
```

You can then use this class in your components like so:

```javascript
import { Text, View } from 'react-native'

export default function MyComponent() {
  <View>
    {/* renders green text on iOS and blue text on Android */}
    <Text className="text-off-white">Platform Specific!</Text>
  </View>
}
```

In our layout, we're aiming to include a shadow effect on the navbar component. However, because the `boxShadow` CSS property isn't supported on mobile devices, attempting to configure a Tailwind class for it may not work as expected. As an alternative, we'll use React Native's raw `Platform` API to achieve this effect:

```javascript
// components/navbar.tsx
import { Pressable, Platform, View } from 'react-native'
import {
  HomeIcon,
  HeartIcon,
  ShoppingCartIcon,
  SunIcon,
  MoonIcon,
} from 'react-native-heroicons/outline'

export default function Navbar() {
  return (
    <View
      style={{
{/* use the Platform API to apply shadow styling for different platforms */}
        ...Platform.select({
          ios: {
            shadowColor: 'black',
            shadowOffset: { width: 0, height: -5 },
            shadowOpacity: 0.3,
            shadowRadius: 20,
          },
          android: {
            elevation: 3,
          },
        }),
      }}
      className='px-8 py-6 bg-white shadow-top dark:bg-soft-dark flex-row items-center justify-between'
    >
      <HomeIcon color="black" size={28} />
      <HeartIcon color="black" size={28} />
      <ShoppingCartIcon color="black" size={28} />
      <Pressable>
        <SunIcon color="black" size={28} />
      </Pressable>
    </View>
  )
}
```

![User Interface With Navbar Component](https://blog.logrocket.com/wp-content/uploads/2024/03/user-interface-with-navbar-component.jpeg) Now, let’s learn how to apply dark mode styles with NativeWind.

## Using dark mode in NativeWind

NativeWind supports Tailwind dark mode styling practices seamlessly, allowing you to style your app based on your user’s preferences. With the `useColorScheme` Hook and the `dark:` variant selector provided by NativeWind, you can easily write dark mode classes in your React Native application. 

The hook returns the current color scheme and methods to set or toggle the color scheme manually between light and dark modes:

```javascript
import { useColorScheme } from "nativewind";

function MyComponent() {
  const { colorScheme, setColorScheme, toggleColorScheme } = useColorScheme();

  console.log(colorScheme) // 'light' | 'dark'
  setColorScheme('dark')
  toggleColorScheme() // changes colorScheme to opposite of its current value

  return (
    {/* ... */}
  );
}
```

We can now use this hook in different parts of this application. Let’s start by creating the toggle color scheme functionality in the navbar when a user clicks on the `Pressable` element:

```javascript
// component/navbar.jsx
import { useColorScheme } from 'nativewind'
import { Pressable, Platform, View } from 'react-native'
import {
  HomeIcon,
  HeartIcon,
  ShoppingCartIcon,
  SunIcon,
  MoonIcon,
} from 'react-native-heroicons/outline'
export default function Navbar() {
  const { colorScheme, toggleColorScheme } = useColorScheme()

  return (
    <View
      className='px-8 py-6 bg-white shadow-top dark:bg-soft-dark flex-row items-center justify-between'
    >
      <HomeIcon color={colorScheme === 'light' ? 'black' : 'white'} size={28} />
      <HeartIcon
        color={colorScheme === 'light' ? 'black' : 'white'}
        size={28}
      />
      <ShoppingCartIcon
        color={colorScheme === 'light' ? 'black' : 'white'}
        size={28}
      />
      <Pressable onPress={toggleColorScheme}>
        {colorScheme === 'light' && (
          <SunIcon
            color={colorScheme === 'light' ? 'black' : 'white'}
            size={28}
          />
        )}
        {colorScheme === 'dark' && (
          <MoonIcon
            color={colorScheme === 'light' ? 'black' : 'white'}
            size={28}
          />
        )}
      </Pressable>
    </View>
  )
}
```

Clicking the last icon on the navbar should now toggle the theme between light and dark. 

Let’s now move on to apply more dark mode classes around our app for a more congruent look. We’ll start with the `Product` component:

```javascript
// components/product.jsx
import { View, Text, Image } from 'react-native'
export default function Product({ image_url, name, price, column_width }) {
  return (
    <View style={{ width: column_width }} className='justify-center p-3'>
      <Image
        className='m-5 h-56 w-full mx-auto object-cover bg-slate-500 rounded-lg'
        source={image_url}
      />
      <Text className='text-dark dark:text-white mb-3'>
        {name.substring(0, 30) + '...'}
      </Text>
      <Text className='text-dark dark:text-white font-bold'>{`${price}.00`}</Text>
    </View>
  )
}
```

Next, we’ll add dark mode classes to the parent `View` of the entire application. We can also style the appearance of `StatusBar` to be responsive to the color scheme:

```javascript
// App.js
import { StatusBar } from 'expo-status-bar'
import { FlatList, View, Dimensions, SafeAreaView } from 'react-native'
import { products } from './utils/products'
import Navbar from './components/navbar'
import Product from './components/product'
import { useColorScheme } from 'nativewind'

// calculate the width of each column using the screen dimensions
const numColumns = 2
const screen_width = Dimensions.get('window').width
const column_width = screen_width / numColumns

export default function App() {
  const { colorScheme } = useColorScheme()
  return (
    <View className='flex-[1] bg-white dark:bg-dark pt-8'>
      <StatusBar style={colorScheme === 'light' ? 'dark' : 'light'} />
      <SafeAreaView />
      <FlatList
        data={products}
        numColumns={numColumns}
        renderItem={(product_data) => {
          return (
            <Product
              image_url={product_data.item.image_url}
              name={product_data.item.name}
              price={product_data.item.price}
              column_width={column_width}
            />
          )
        }}
        keyExtractor={(item) => {
          return item.key
        }}
      />
      <Navbar />
    </View>
  )
}
```

## Limitations of NativeWind compared to Tailwind CSS

While NativeWind brings the convenience of Tailwind CSS to mobile app development, it's important to understand its limitations in comparison to what the tool was originally made for — the web. 

Here are some common quirks to keep in mind when using Nativewind:

1.  **Native-specific styling**: In RN, some styles on particular elements can only be applied through a style object. Take a `FlatList` for example — the [`columnWrapperStyle`](https://reactnative.dev/docs/flatlist#columnwrapperstyle) property can only be applied using a style object and cannot be reached through NativeWind classes. You’ll also encounter many other scenarios where you would have to resort to using the native `Stylesheet` object so embrace mixing your NativeWind with the `style` prop
2.  **Limited style properties**: Some style properties commonly used in web development don’t work as they should. As you’ve seen above, shadows work differently compared to the web

While NativeWind isn’t perfect yet, it does look very promising, especially with a new version around the corner.

## The upcoming release of NativeWind 4

The NativeWind open source community has recently been teasing the release of NativeWind v4\. This new version boasts enhancements in both functionality and performance. Notably, it eliminates the dependency on `styledComponent`s and improves compatibility with the Tailwind framework used on the web. 

Additionally, the Expo team has recruited the creator of NativeWind to enhance Tailwind support within Expo. Sneak peeks into the Nativewind v4 documentation reveal many exciting updates:

*   **Streamlined installation**: NativeWind v4 is expected to introduce a more efficient installation process, integrating `react-reanimated` as a peer dependency
*   **Bug fixes and compatibility**: With NativeWind v4, efforts are likely underway to address any breaking changes introduced by recent updates to Tailwind CSS, ensuring smoother integration and consistent functionality
*   **New APIs**: There is an introduction of fresh APIs like `vars()` and `withNativewind()`, offering enhanced control over styling workflows within React Native applications

## Conclusion

NativeWind is doing a great job serving as a bridge for writing Tailwind CSS in mobile applications. NativeWind does a lot of heavy lifting behind the scenes to ensure that the way we write Tailwind CSS for the web is greatly supported on mobile. Check out [NativeWind’s official documentation](https://www.nativewind.dev/overview/) for more information.

---

##LogRocket: Instantly recreate issues in your React Native apps

[![LogRocket Signup](https://blog.logrocket.com/wp-content/uploads/2021/10/react-native-plug_v2-2.png)](https://lp.logrocket.com/blg/react-native-signup)

[LogRocket](https://lp.logrocket.com/blg/react-native-signup)is a React Native monitoring solution that helps you reproduce issues instantly, prioritize bugs, and understand performance in your React Native apps.

LogRocket also helps you increase conversion rates and product usage by showing you exactly how users are interacting with your app. LogRocket's product analytics features surface the reasons why users don't complete a particular flow or don't adopt a new feature.

Start proactively monitoring your React Native apps — [try LogRocket for free](https://lp.logrocket.com/blg/react-native-signup).