---
title: Building high-quality UIs with Expo and NativeWind
authors: 'Thomino '
published: February 3, 2026
categories: Development, React Native
tags: NativeWind, React Native
---

_This is a guest post from Thomino - he is the creator of [Native Templates](https://www.native-templates.com/) and is a great [follow on X](https://x.com/ThominoDesign?s=20). _

…

I have been building for the web since the era of IE6. I remember using sliced images for box shadows, transparent GIFs for rounded corners, and nested tables for structure. Those were the "Dark Ages" of front-end development.

Luckily, times have changed. With the combination of **Expo**, **NativeWind** and **Reanimated** designing apps is a breeze. In fact, I don’t even use Figma anymore. I design directly in code with Expo Go live preview.

Let me show you how I build my apps with the reusable components that I use in my workflows. These components allow me to design directly in the editor. I’ll also cover how I handle theming and how to build an animated Theme toggle.

## Reusable Components

Each app is different, but most of them share many screens, flows and components. That’s why I decided to build templates that I can always use as a base on a new project according to my needs. The key to doing this effectively was to create reusable components that could be styled easily according to the particular app.

Here are a few examples I’ve implemented in [NativeTemplates](https://www.native-templates.com/).

### Building a flexible header component with NativeWind

Arguably the most important and fundamental one. It was very important to make the header component flexible and easy to adjust according to one’s needs.

```javascript
import React from "react";
import { View, Text, TouchableOpacity } from "react-native";
import { useThemeColors } from "app/contexts/ThemeColors";
import { Link, router } from "expo-router";
import Icon, { IconName } from "./Icon";
import { useSafeAreaInsets } from "react-native-safe-area-context";

type HeaderProps = {
  title: string,
  showBackButton?: boolean,
  rightComponents?: React.ReactNode[],
};

const Header: React.FC<HeaderProps> = ({
  title,
  showBackButton = false,
  rightComponents = [],
}) => {
  const colors = useThemeColors();
  const insets = useSafeAreaInsets();

  const handleBackPress = () => {
    router.back();
  };

  return (
    <View
      style={{ paddingTop: insets.top }}
      className="w-full pb-2 flex-row justify-between px-6 bg-background"
    >
      <View className="flex-row items-center flex-1">
        {showBackButton && (
          <TouchableOpacity onPress={handleBackPress} className="mr-4 py-4">
            <Icon name="ArrowLeft" size={24} color={colors.icon} />
          </TouchableOpacity>
        )}

        <View className="py-4">
          <Text className="text-lg font-bold" style={{ color: colors.text }}>
            {title}
          </Text>
        </View>
      </View>

      {rightComponents.length > 0 && (
        <View className="flex-row items-center justify-end flex-1">
          {rightComponents.map((component, index) => (
            <View key={index} className="ml-6">
              {component}
            </View>
          ))}
        </View>
      )}
    </View>
  );
};

export default Header;
```

```javascript
// Basic header with title
<Header title="Home" />
 
// Header with back button
<Header 
  title="Details" 
  showBackButton 
/>
 
// Header with action icons
<Header 
  title="Messages" 
  rightComponents={[
    <HeaderIcon key="search" icon="Search" href="/search" />,
    <HeaderIcon key="settings" icon="Settings" href="/settings" />
  ]}
/>
```

![basic header](https://cdn.sanity.io/images/9r24npb8/production/ebabbc33e038ca3361e2ea1b74ccb1684b2a91c5-3840x2160.png)

### Creating animated tab navigation in React Native

Choose an icon, animation or avatar.

```javascript
<TabTrigger name="home" href="/" asChild>
  <TabButton labelAnimated={true} icon="Home">Home</TabButton>
</TabTrigger>
<TabTrigger name="profile" href="/profile" asChild>
  <TabButton labelAnimated={true} avatar={require('@/assets/img/thomino.jpg')}>Profile</TabButton>
</TabTrigger>
```

### Building multi-step forms and onboarding flows

I use this one very heavily. It is perfect for on-boardings and other user flows. Onboarding is essential for activating your users and reducing that early churn. 

```javascript
<MultiStep
  onComplete={handleComplete}
  onClose={handleClose}
  headerTitle="Get Started"
>
  <Step title="Profile">
    <Profile />
  </Step>
  <Step title="Role">
    <Role />
  </Step>
  <Step title="Capabilities">
    <Capabilities />
  </Step>
  <Step title="Review">
    <Review />
  </Step>
</MultiStep>;
```

![Multi-step tabs](https://cdn.sanity.io/images/9r24npb8/production/f0a42d693eb4138425a67440a541eef3ae83ada5-3840x2160.png)

### Reusable chip components for filters and forms

This one is very basic but used heavily for filters or forms.

```javascript
<Chip 
  label="Custom" 
  className="my-2 mx-1" 
  size="xl"
/>
```

You can check all the components [in my docs](https://www.native-templates.com/docs).

### Implementing theme variables with NativeWind

NativeWind makes theming incredibly powerful. By using `vars`, we can toggle the entire theme of an app in one file. This is how I ensure that light and dark modes aren't just an afterthought, but a core part of the design system.

![Theming](https://cdn.sanity.io/images/9r24npb8/production/7df920662fa254b467486795395e264779523570-3840x2160.png)

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

export const themes = {
  light: vars({
    "--color-primary": "#000000", 
    "--color-invert": "#ffffff", 
    "--color-secondary": "#ffffff",
    "--color-background": "#F4F4F5",
    "--color-darker": "#F4F4F5",
    "--color-text": "#000000",     
    "--color-highlight": "#7E55D8",
    "--color-border": "rgba(0, 0, 0, 0.15)",
  }),
  dark: vars({
    "--color-primary": "#ffffff",
    "--color-invert": "#000000",
    "--color-secondary": "#1e1e1e",
    "--color-background": "#141414",
    "--color-darker": "#000000",
    "--color-text": "#ffffff",  
    "--color-highlight": "#7E55D8",
    "--color-border": "rgba(255, 255, 255, 0.15)",
  }),
}; 
```

### Building an animated dark mode toggle with Reanimated

[Tweet](https://x.com/ThominoDesign/status/1921420878971097306?s=20)

```javascript
import { Pressable, View } from "react-native";
import { useTheme } from "@/app/contexts/ThemeContext";
import Feather from "@expo/vector-icons/Feather";
import Animated, {
  useSharedValue,
  useAnimatedStyle,
  withSpring,
} from "react-native-reanimated";
import { useEffect } from "react";

const ThemeToggle = () => {
  const { theme, toggleTheme } = useTheme();
  const isDark = theme === "dark";
  const translateX = useSharedValue(isDark ? 36 : 3.5);

  useEffect(() => {
    translateX.value = withSpring(isDark ? 36 : 3.5, {
      damping: 15,
      stiffness: 150,
    });
  }, [isDark]);

  const animatedStyle = useAnimatedStyle(() => {
    return {
      transform: [{ translateX: translateX.value }],
    };
  });

  return (
    <Pressable
      onPress={toggleTheme}
      className="w-20 h-10 p-1 bg-secondary relative flex-row rounded-full items-center justify-between"
    >
      <Icon icon="sun" />
      <Icon icon="moon" />
      <Animated.View
        style={[animatedStyle]}
        className="w-9 h-9 bg-background rounded-full items-center justify-center flex flex-row absolute"
      />
    </Pressable>
  );
};

const Icon = (props: any) => {
  const { theme } = useTheme();
  const isDark = theme === "dark";

  return (
    <View className="w-9 h-9 relative z-50 rounded-full items-center justify-center flex flex-row">
      <Feather
        name={props.icon}
        size={16}
        color={`${isDark ? "white" : "black"}`}
      />
    </View>
  );
};

export default ThemeToggle;
```

### Pre-built screen templates for Login, Onboarding, and Settings

With a solid component base, building templates is super fast. There are screens that almost every app has in common with like **Login, Signup, Onboarding, Profile Settings, Privacy,** etc. But here, all the components come to life!

![Screens](https://cdn.sanity.io/images/9r24npb8/production/044b206d866765a6840a0d8eb928b92c525640f3-3840x2160.png)

```javascript
export default function ProfileScreen() {
  return (
    <View className="flex-1 bg-light-primary dark:bg-dark-primary">
      <Header
        leftComponent={<ThemeToggle />}
        rightComponents={[
          <HeaderIcon icon="ChartBar" href="/screens/analytics" />,
        ]}
      />
      <View className="flex-1 bg-light-primary dark:bg-dark-primary">
        <ThemedScroller>
          <AnimatedView className="pt-4" animation="scaleIn">
            <SubscriptionCard />
            <View className="gap-1 bg-secondary rounded-3xl">
              <ListLink
                className="px-4 py-2 border-b border-border"
                showChevron
                title="Account settings"
                icon="Settings"
                href="/screens/settings"
              />
              <ListLink
                className="px-4 py-2 border-b border-border"
                showChevron
                title="Billing"
                icon="CreditCard"
                href="/screens/billing"
              />
            </View>
          </AnimatedView>
        </ThemedScroller>
      </View>
    </View>
  );
}
```

Expo has given us the platform, and NativeWind has given us the styling language to make apps that don't just work but also look amazing.

If you're looking to jumpstart your next project, you can explore all [React Native Expo Templates here](https://www.native-templates.com/) or my [Expo Playground on GitHub](https://github.com/thomino/expo-playground).