Install NativewindUI

Note: These docs do not currently support React Navigation, only Expo Router. These docs also assume that you have already installed Expo Router.

1. Install the NativewindUI dependencies

Run the following command in the root of your Expo project to install the NativewindUI dependencies:
npx expo install nativewind react-native-reanimated tailwindcss@^3.4.0 prettier-plugin-tailwindcss rn-icon-mapper expo-symbols @shopify/flash-list class-variance-authority clsx expo-dev-client tailwind-merge

2. Setup Tailwind CSS

Run
npx tailwindcss init
in the root of your Expo project to create a
tailwind.config.js
.

Make sure to update the
content
array with the file paths of any pre-existing components in your project styled with Nativewind. As seen below, add the NativewindUI theme to your
tailwind.config.js
.
tailwind.config.js
const { hairlineWidth, platformSelect } = require('nativewind/theme');

/** @type {import('tailwindcss').Config} */
module.exports = {
  // NOTE: Update this to include the paths to all of your component files.
  content: ['./app/**/*.{js,jsx,ts,tsx}', './components/**/*.{js,jsx,ts,tsx}'],
  presets: [require('nativewind/preset')],
  theme: {
    extend: {
      colors: {
        border: withOpacity('border'),
        input: withOpacity('input'),
        ring: withOpacity('ring'),
        background: withOpacity('background'),
        foreground: withOpacity('foreground'),
        primary: {
          DEFAULT: withOpacity('primary'),
          foreground: withOpacity('primary-foreground'),
        },
        secondary: {
          DEFAULT: withOpacity('secondary'),
          foreground: withOpacity('secondary-foreground'),
        },
        destructive: {
          DEFAULT: withOpacity('destructive'),
          foreground: withOpacity('destructive-foreground'),
        },
        muted: {
          DEFAULT: withOpacity('muted'),
          foreground: withOpacity('muted-foreground'),
        },
        accent: {
          DEFAULT: withOpacity('accent'),
          foreground: withOpacity('accent-foreground'),
        },
        popover: {
          DEFAULT: withOpacity('popover'),
          foreground: withOpacity('popover-foreground'),
        },
        card: {
          DEFAULT: withOpacity('card'),
          foreground: withOpacity('card-foreground'),
        },
      },
      borderWidth: {
        hairline: hairlineWidth(),
      },
    },
  },
  plugins: [],
};

function withOpacity(variableName) {
  return ({ opacityValue }) => {
    if (opacityValue !== undefined) {
      return platformSelect({
        ios: `rgb(var(--${variableName}) / ${opacityValue})`,
        android: `rgb(var(--android-${variableName}) / ${opacityValue})`,
      });
    }
    return platformSelect({
      ios: `rgb(var(--${variableName}))`,
      android: `rgb(var(--android-${variableName}))`,
    });
  };
}
Create a
global.css
file in the root of your Expo project. This file will include the Tailwind directives as well as the color configurations for light and dark modes.
global.css
@tailwind base;
@tailwind components;
@tailwind utilities;

@layer base {
  :root {
    --background: 242 242 247;
    --foreground: 0 0 0;
    --card: 255 255 255;
    --card-foreground: 8 28 30;
    --popover: 230 230 235;
    --popover-foreground: 0 0 0;
    --primary: 0 123 254;
    --primary-foreground: 255 255 255;
    --secondary: 45 175 231;
    --secondary-foreground: 255 255 255;
    --muted: 175 176 180;
    --muted-foreground: 142 142 147;
    --accent: 255 40 84;
    --accent-foreground: 255 255 255;
    --destructive: 255 56 43;
    --destructive-foreground: 255 255 255;
    --border: 230 230 235;
    --input: 210 210 215;
    --ring: 230 230 235;

    --android-background: 249 249 255;
    --android-foreground: 0 0 0;
    --android-card: 255 255 255;
    --android-card-foreground: 24 28 35;
    --android-popover: 215 217 228;
    --android-popover-foreground: 0 0 0;
    --android-primary: 0 112 233;
    --android-primary-foreground: 255 255 255;
    --android-secondary: 176 201 255;
    --android-secondary-foreground: 20 55 108;
    --android-muted: 193 198 215;
    --android-muted-foreground: 65 71 84;
    --android-accent: 169 73 204;
    --android-accent-foreground: 255 255 255;
    --android-destructive: 186 26 26;
    --android-destructive-foreground: 255 255 255;
    --android-border: 215 217 228;
    --android-input: 210 210 215;
    --android-ring: 215 217 228;
  }

  @media (prefers-color-scheme: dark) {
    :root {
      --background: 0 0 0;
      --foreground: 255 255 255;
      --card: 21 21 24;
      --card-foreground: 255 255 255;
      --popover: 40 40 42;
      --popover-foreground: 255 255 255;
      --primary: 3 133 255;
      --primary-foreground: 255 255 255;
      --secondary: 100 211 254;
      --secondary-foreground: 255 255 255;
      --muted: 70 70 73;
      --muted-foreground: 142 142 147;
      --accent: 255 52 95;
      --accent-foreground: 255 255 255;
      --destructive: 254 67 54;
      --destructive-foreground: 255 255 255;
      --border: 40 40 42;
      --input: 55 55 57;
      --ring: 40 40 42;

      --android-background: 0 0 0;
      --android-foreground: 255 255 255;
      --android-card: 16 19 27;
      --android-card-foreground: 224 226 237;
      --android-popover: 39 42 50;
      --android-popover-foreground: 224 226 237;
      --android-primary: 3 133 255;
      --android-primary-foreground: 255 255 255;
      --android-secondary: 28 60 114;
      --android-secondary-foreground: 189 209 255;
      --android-muted: 216 226 255;
      --android-muted-foreground: 139 144 160;
      --android-accent: 83 0 111;
      --android-accent-foreground: 238 177 255;
      --android-destructive: 147 0 10;
      --android-destructive-foreground: 255 255 255;
      --android-border: 39 42 50;
      --android-input: 55 55 57;
      --android-ring: 39 42 50;
    }
  }
}

3. Add the Babel preset

Configure babel to support Nativewind via the relevant presets.
babel.config.js
module.exports = function (api) {
  api.cache(true);
  const plugins = [];

  plugins.push('react-native-worklets/plugin');

  return {
    presets: [['babel-preset-expo', { jsxImportSource: 'nativewind' }], 'nativewind/babel'],
    plugins,
  };
};

4. Configure Metro

If your Expo project does not have a
metro.config.js
in the project root, run the following command:

npx expo customize metro.config.js


For those using Expo SDK 50+, your config should look like this:
metro.config.js
const { getDefaultConfig } = require('expo/metro-config');
const { withNativeWind } = require('nativewind/metro');

// eslint-disable-next-line no-undef
const config = getDefaultConfig(__dirname);

module.exports = withNativeWind(config, {
  input: './global.css',
  inlineRem: 16,
});

5. Use TypeScript

Add
nativewind-env.d.ts
to the root of your project to satisfy the Typescript gods
nativewind-env.d.ts
/// <reference types="nativewind/types" />
Additionally, ensure you have
expo-env.d.ts
in the root of your project and make sure toadd it to your
.gitignore
.
expo-env.d.ts
/// <reference types="expo/types" />

// NOTE: This file should not be edited and should be in your git ignore

6. Add the navigation theme and colors

Add a
lib
folder to the root of your project to make it easy to access our colors.

Now, add the following files to said folder:
@/lib/useColorScheme.tsx
import { useColorScheme as useNativewindColorScheme } from 'nativewind';

          import { COLORS } from '@/theme/colors';
          
          function useColorScheme() {
            const { colorScheme, setColorScheme } = useNativewindColorScheme();
          
            function toggleColorScheme() {
              return setColorScheme(colorScheme === 'light' ? 'dark' : 'light');
            }
          
            return {
              colorScheme: colorScheme ?? 'light',
              isDarkColorScheme: colorScheme === 'dark',
              setColorScheme,
              toggleColorScheme,
              colors: COLORS[colorScheme ?? 'light'],
            };
          }
          
          export { useColorScheme };
@/lib/cn.ts
import { clsx, type ClassValue } from 'clsx';
import { twMerge } from 'tailwind-merge';

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs));
}
Add a
theme
folder in the project root to make it easy to access our theme configurations. Add the following files to said folder:
@/theme/colors.ts
import { Platform } from 'react-native';

const IOS_SYSTEM_COLORS = {
  white: 'rgb(255, 255, 255)',
  black: 'rgb(0, 0, 0)',
  light: {
    grey6: 'rgb(242, 242, 247)',
    grey5: 'rgb(230, 230, 235)',
    grey4: 'rgb(210, 210, 215)',
    grey3: 'rgb(199, 199, 204)',
    grey2: 'rgb(175, 176, 180)',
    grey: 'rgb(142, 142, 147)',
    background: 'rgb(242, 242, 247)',
    foreground: 'rgb(0, 0, 0)',
    root: 'rgb(255, 255, 255)',
    card: 'rgb(255, 255, 255)',
    cardForeground: 'rgb(8, 28, 30)',
    popover: 'rgb(230, 230, 235)',
    popoverForeground: 'rgb(0, 0, 0)',
    destructive: 'rgb(255, 56, 43)',
    primary: 'rgb(0, 123, 254)',
    primaryForeground: 'rgb(255, 255, 255)',
    secondary: 'rgb(45, 175, 231)',
    secondaryForeground: 'rgb(255, 255, 255)',
    muted: 'rgb(175, 176, 180)',
    mutedForeground: 'rgb(142, 142, 147)',
    accent: 'rgb(255, 40, 84)',
    accentForeground: 'rgb(255, 255, 255)',
    border: 'rgb(230, 230, 235)',
    input: 'rgb(210, 210, 215)',
    ring: 'rgb(230, 230, 235)',
  },
  dark: {
    grey6: 'rgb(21, 21, 24)',
    grey5: 'rgb(40, 40, 42)',
    grey4: 'rgb(55, 55, 57)',
    grey3: 'rgb(70, 70, 73)',
    grey2: 'rgb(99, 99, 102)',
    grey: 'rgb(142, 142, 147)',
    background: 'rgb(0, 0, 0)',
    foreground: 'rgb(255, 255, 255)',
    root: 'rgb(0, 0, 0)',
    card: 'rgb(21, 21, 24)',
    cardForeground: 'rgb(255, 255, 255)',
    popover: 'rgb(40, 40, 42)',
    popoverForeground: 'rgb(255, 255, 255)',
    destructive: 'rgb(254, 67, 54)',
    primary: 'rgb(3, 133, 255)',
    primaryForeground: 'rgb(255, 255, 255)',
    secondary: 'rgb(100, 211, 254)',
    secondaryForeground: 'rgb(255, 255, 255)',
    muted: 'rgb(70, 70, 73)',
    mutedForeground: 'rgb(142, 142, 147)',
    accent: 'rgb(255, 52, 95)',
    accentForeground: 'rgb(255, 255, 255)',
    border: 'rgb(40, 40, 42)',
    input: 'rgb(55, 55, 57)',
    ring: 'rgb(40, 40, 42)',
  },
} as const;

const ANDROID_COLORS = {
  white: 'rgb(255, 255, 255)',
  black: 'rgb(0, 0, 0)',
  light: {
    grey6: 'rgb(249, 249, 255)',
    grey5: 'rgb(215, 217, 228)',
    grey4: 'rgb(193, 198, 215)',
    grey3: 'rgb(113, 119, 134)',
    grey2: 'rgb(65, 71, 84)',
    grey: 'rgb(24, 28, 35)',
    background: 'rgb(249, 249, 255)',
    foreground: 'rgb(0, 0, 0)',
    root: 'rgb(255, 255, 255)',
    card: 'rgb(255, 255, 255)',
    cardForeground: 'rgb(24, 28, 35)',
    popover: 'rgb(215, 217, 228)',
    popoverForeground: 'rgb(0, 0, 0)',
    destructive: 'rgb(186, 26, 26)',
    primary: 'rgb(0, 112, 233)',
    primaryForeground: 'rgb(255, 255, 255)',
    secondary: 'rgb(176, 201, 255)',
    secondaryForeground: 'rgb(20, 55, 108)',
    muted: 'rgb(193, 198, 215)',
    mutedForeground: 'rgb(65, 71, 84)',
    accent: 'rgb(169, 73, 204)',
    accentForeground: 'rgb(255, 255, 255)',
    border: 'rgb(215, 217, 228)',
    input: 'rgb(210, 210, 215)',
    ring: 'rgb(215, 217, 228)',
  },
  dark: {
    grey6: 'rgb(16, 19, 27)',
    grey5: 'rgb(39, 42, 50)',
    grey4: 'rgb(49, 53, 61)',
    grey3: 'rgb(54, 57, 66)',
    grey2: 'rgb(139, 144, 160)',
    grey: 'rgb(193, 198, 215)',
    background: 'rgb(0, 0, 0)',
    foreground: 'rgb(255, 255, 255)',
    root: 'rgb(0, 0, 0)',
    card: 'rgb(16, 19, 27)',
    cardForeground: 'rgb(224, 226, 237)',
    popover: 'rgb(39, 42, 50)',
    popoverForeground: 'rgb(224, 226, 237)',
    destructive: 'rgb(147, 0, 10)',
    primary: 'rgb(3, 133, 255)',
    primaryForeground: 'rgb(255, 255, 255)',
    secondary: 'rgb(28, 60, 114)',
    secondaryForeground: 'rgb(189, 209, 255)',
    muted: 'rgb(216, 226, 255)',
    mutedForeground: 'rgb(139, 144, 160)',
    accent: 'rgb(83, 0, 111)',
    accentForeground: 'rgb(238, 177, 255)',
    border: 'rgb(39, 42, 50)',
    input: 'rgb(55, 55, 57)',
    ring: 'rgb(39, 42, 50)',
  },
} as const;

const COLORS = Platform.OS === 'ios' ? IOS_SYSTEM_COLORS : ANDROID_COLORS;

export { COLORS };
@/theme/index.ts
import { Theme, DefaultTheme, DarkTheme } from '@react-navigation/native';

import { COLORS } from './colors';

const NAV_THEME: { light: Theme; dark: Theme } = {
  light: {
    dark: false,
    colors: {
      background: COLORS.light.background,
      border: COLORS.light.grey5,
      card: COLORS.light.card,
      notification: COLORS.light.destructive,
      primary: COLORS.light.primary,
      text: COLORS.black,
    },
    fonts: DefaultTheme.fonts,
  },
  dark: {
    dark: true,
    colors: {
      background: COLORS.dark.background,
      border: COLORS.dark.grey5,
      card: COLORS.dark.grey6,
      notification: COLORS.dark.destructive,
      primary: COLORS.dark.primary,
      text: COLORS.white,
    },
    fonts: DarkTheme.fonts,
  },
};

export { NAV_THEME };
Lastly, add a
components/nativewindui
folder in the project root, this is where our components will live:
@/components/nativewindui/Text.tsx
import { VariantProps, cva } from 'class-variance-authority';
import * as React from 'react';
import { Text as RNText } from 'react-native';

import { cn } from '@/lib/cn';

const textVariants = cva('text-foreground', {
  variants: {
    variant: {
      largeTitle: 'text-4xl',
      title1: 'text-2xl',
      title2: 'text-[22px] leading-7',
      title3: 'text-xl',
      heading: 'text-[17px] leading-6 font-semibold',
      body: 'text-[17px] leading-6',
      callout: 'text-base',
      subhead: 'text-[15px] leading-6',
      footnote: 'text-[13px] leading-5',
      caption1: 'text-xs',
      caption2: 'text-[11px] leading-4',
    },
    color: {
      primary: '',
      secondary: 'text-secondary-foreground/90',
      tertiary: 'text-muted-foreground/90',
      quarternary: 'text-muted-foreground/50',
    },
  },
  defaultVariants: {
    variant: 'body',
    color: 'primary',
  },
});

const TextClassContext = React.createContext<string | undefined>(undefined);

function Text({
  className,
  variant,
  color,
  ...props
}: React.ComponentPropsWithoutRef<typeof RNText> & VariantProps<typeof textVariants>) {
  const textClassName = React.useContext(TextClassContext);
  return (
    <RNText className={cn(textVariants({ variant, color }), textClassName, className)} {...props} />
  );
}

export { Text, TextClassContext, textVariants };
@/components/nativewindui/Icon/index.ts
import { cssInterop } from 'nativewind';

import { Icon } from './Icon';

cssInterop(Icon, {
  className: {
    target: 'style',
    nativeStyleToProp: {
      color: 'color',
      height: 'size',
      width: 'size',
    },
  },
});

export { Icon };
@/components/nativewindui/Icon/Icon.tsx
import MaterialCommunityIcons from '@expo/vector-icons/MaterialCommunityIcons';
import MaterialIcons from '@expo/vector-icons/MaterialIcons';
import {
  SF_SYMBOLS_TO_MATERIAL_COMMUNITY_ICONS,
  SF_SYMBOLS_TO_MATERIAL_ICONS,
} from 'rn-icon-mapper';

import type { IconProps } from './types';

import { useColorScheme } from '../../../lib/useColorScheme';

function Icon({
  name,
  materialCommunityIcon,
  materialIcon,
  sfSymbol: _sfSymbol,
  size = 24,
  ...props
}: IconProps) {
  const { colors } = useColorScheme();
  const defaultColor = colors.foreground;

  if (materialCommunityIcon) {
    return (
      <MaterialCommunityIcons
        size={size}
        color={defaultColor}
        {...props}
        {...materialCommunityIcon}
      />
    );
  }
  if (materialIcon) {
    return <MaterialIcons size={size} color={defaultColor} {...props} {...materialIcon} />;
  }
  const materialCommunityIconName =
    SF_SYMBOLS_TO_MATERIAL_COMMUNITY_ICONS[
      name as keyof typeof SF_SYMBOLS_TO_MATERIAL_COMMUNITY_ICONS
    ];
  if (materialCommunityIconName) {
    return (
      <MaterialCommunityIcons
        name={materialCommunityIconName}
        size={size}
        color={defaultColor}
        {...props}
      />
    );
  }
  const materialIconName =
    SF_SYMBOLS_TO_MATERIAL_ICONS[name as keyof typeof SF_SYMBOLS_TO_MATERIAL_ICONS];
  if (materialIconName) {
    return <MaterialIcons name={materialIconName} size={size} color={defaultColor} {...props} />;
  }
  return <MaterialCommunityIcons name="help" size={size} color={defaultColor} {...props} />;
}

export { Icon };
@/components/nativewindui/Icon/Icon.ios.tsx
import { SymbolView } from 'expo-symbols';

import type { IconProps } from './types';

import { useColorScheme } from '../../../lib/useColorScheme';

function Icon({
  materialCommunityIcon: _materialCommunityIcon,
  materialIcon: _materialIcon,
  sfSymbol,
  name,
  color,
  size = 24,
  ...props
}: IconProps) {
  const { colors } = useColorScheme();
  return (
    <SymbolView
      name={name ?? 'questionmark'}
      tintColor={rgbaToHex(color ?? colors.foreground)}
      size={size}
      resizeMode="scaleAspectFit"
      {...props}
      {...sfSymbol}
    />
  );
}

export { Icon };

// NOTE: seems like the need to convert rgba to hex color is a bug in expo-symbols, accordion to the docs, it should accept a hex color, but it doesn't.

function rgbaToHex(color: string): string {
  if (!color) return 'black';
  const rgbaRegex =
    /^rgba?(s*(d{1,3})s*,s*(d{1,3})s*,s*(d{1,3})(?:s*,s*(d*.?d+))?s*)$/i;
  const match = color.match(rgbaRegex);

  if (!match) {
    return color;
  }

  const [, rStr, gStr, bStr, aStr] = match;
  const r = Math.min(255, parseInt(rStr));
  const g = Math.min(255, parseInt(gStr));
  const b = Math.min(255, parseInt(bStr));
  const a = aStr !== undefined ? Math.round(parseFloat(aStr) * 255) : 255;

  const toHex = (n: number) => n.toString(16).padStart(2, '0');

  return `#${toHex(r)}${toHex(g)}${toHex(b)}${a < 255 ? toHex(a) : ''}`;
}
@/components/nativewindui/Icon/types.ts
import type MaterialCommunityIcons from '@expo/vector-icons/MaterialCommunityIcons';
import type MaterialIcons from '@expo/vector-icons/MaterialIcons';
import type { SymbolViewProps } from 'expo-symbols';
import type { IconMapper } from 'rn-icon-mapper';

type MaterialCommunityIconsProps = React.ComponentProps<typeof MaterialCommunityIcons>;
type MaterialIconsProps = React.ComponentProps<typeof MaterialIcons>;

type Style = SymbolViewProps['style'] &
  MaterialIconsProps['style'] &
  MaterialCommunityIconsProps['style'];

type IconProps = IconMapper<SymbolViewProps, MaterialIconsProps, MaterialCommunityIconsProps> & {
  style?: Style;
  className?: string;
};

export type { IconProps };
@/components/nativewindui/ThemeToggle.tsx
import { Pressable, View } from 'react-native';
import Animated, { LayoutAnimationConfig, ZoomInRotate } from 'react-native-reanimated';

import { Icon } from '@/components/nativewindui/Icon';
import { cn } from '@/lib/cn';
import { useColorScheme } from '@/lib/useColorScheme';
import { COLORS } from '@/theme/colors';

export function ThemeToggle() {
  const { colorScheme, toggleColorScheme } = useColorScheme();
  return (
    <LayoutAnimationConfig skipEntering>
      <Animated.View
        className="items-center justify-center"
        key={`toggle-${colorScheme}`}
        entering={ZoomInRotate}>
        <Pressable onPress={toggleColorScheme} className="opacity-80">
          {colorScheme === 'dark'
            ? ({ pressed }) => (
                <View className={cn('px-0.5', pressed && 'opacity-50')}>
                  <Icon name="moon.stars" color={COLORS.white} />
                </View>
              )
            : ({ pressed }) => (
                <View className={cn('px-0.5', pressed && 'opacity-50')}>
                  <Icon name="sun.min" color={COLORS.black} />
                </View>
              )}
        </Pressable>
      </Animated.View>
    </LayoutAnimationConfig>
  );
}

7. Update the Navigation Theme

Wrap your root navigation stack in
<NavThemeProvider>
:
@/app/_layout.tsx
import '../global.css';
import 'expo-dev-client';
{YOUR_OTHER_IMPORTS}
import { StatusBar } from 'expo-status-bar';
import { ThemeProvider as NavThemeProvider } from '@react-navigation/native';

import { NAV_THEME } from '@/theme';

export {
  // Catch any errors thrown by the Layout component.
  ErrorBoundary,
} from 'expo-router';

export default function RootLayout() {
  const { colorScheme, isDarkColorScheme } = useColorScheme();

  return (
    <>
      <StatusBar
        key={`root-status-bar-${isDarkColorScheme ? 'light' : 'dark'}`}
        style={isDarkColorScheme ? 'light' : 'dark'}
      />

      <NavThemeProvider value={NAV_THEME[colorScheme]}>
        {YOUR_ROOT_NAVIGATOR}
      </NavThemeProvider>
    </>
  );
}
Make sure to replace all instances of
import { Text } from 'react-native';
, throughout your project, with
import { Text } from '@/components/nativewindui/Text';
.

8. Usage Example

Here is an example
index.tsx
that uses NativewindUI:
@/app/index.tsx
import { useHeaderHeight } from '@react-navigation/elements';
import { FlashList } from '@shopify/flash-list';
import { cssInterop } from 'nativewind';
import * as React from 'react';
import {
  Linking,
  useWindowDimensions,
  View,
} from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';

import { Icon } from '@/components/nativewindui/Icon';
import { Text } from '@/components/nativewindui/Text';
import { useColorScheme } from '@/lib/useColorScheme';

cssInterop(FlashList, {
  className: 'style',
  contentContainerClassName: 'contentContainerStyle',
});

export default function Screen() {
  return (
    <FlashList
      contentInsetAdjustmentBehavior="automatic"
      keyboardShouldPersistTaps="handled"
      data={COMPONENTS}
      contentContainerClassName="py-4"
      keyExtractor={keyExtractor}
      ItemSeparatorComponent={renderItemSeparator}
      renderItem={renderItem}
      ListEmptyComponent={COMPONENTS.length === 0 ? ListEmptyComponent : undefined}
    />
  );
}

function ListEmptyComponent() {
  const insets = useSafeAreaInsets();
  const dimensions = useWindowDimensions();
  const headerHeight = useHeaderHeight();
  const { colors } = useColorScheme();
  const height = dimensions.height - headerHeight - insets.bottom - insets.top;

  return (
    <View style={{ height }} className="flex-1 items-center justify-center gap-1 px-12">
      <Icon name="doc.badge.plus" size={42} color={colors.grey} />
      <Text variant="title3" className="pb-1 text-center font-semibold">
        No Components Installed
      </Text>
      <Text color="tertiary" variant="subhead" className="pb-4 text-center">
        You can install any of the free components from the{' '}
        <Text
          onPress={() => Linking.openURL('https://nativewindui.com')}
          variant="subhead"
          className="text-primary">
          NativewindUI
        </Text>
        {' website.'}
      </Text>
    </View>
  );
}

type ComponentItem = { name: string; component: React.FC };

function keyExtractor(item: ComponentItem) {
  return item.name;
}

function renderItemSeparator() {
  return <View className="p-2" />;
}

function renderItem({ item }: { item: ComponentItem }) {
  return (
    <Card title={item.name}>
      <item.component />
    </Card>
  );
}

function Card({ children, title }: { children: React.ReactNode; title: string }) {
  return (
    <View className="px-4">
      <View className="gap-4 rounded-xl border border-border bg-card p-4 pb-6 shadow-sm shadow-black/10 dark:shadow-none">
        <Text className="text-center text-sm font-medium tracking-wider opacity-60">{title}</Text>
        {children}
      </View>
    </View>
  );
}

const COMPONENTS: ComponentItem[] = [];

9. Build and run your project

Now that you are using NativewindUI, you will need to use a custom development client, rather than Expo Go.

To create this custom development client, run
npx expo prebuild --clean
followed by
npm run start
to run your project. We use npm but you can use your favorite package manager.
© Ronin Technologies LLC 2025