Docs

Dev tools

Expo SDKExpo CLIExpo MCPExpo GoSnackOrbit

Services

WorkflowsBuildSubmitUpdateHostingLaunchObserve
new
Simulators
preview

Explore

ChangelogExpo Services (EAS)ContactAI
EnterprisePricingBlog
50K
Log in
Sign up

Site footer

Expo

Newsletter

Stay in touch with all things Expo

Product

  • Star us on GitHub
  • Expo CLI on GitHub
  • Expo Services (EAS)
  • EAS CLI on GitHub
  • Expo Go
  • Expo Orbit
  • Snack

Resources

  • Documentation
  • Blog
  • Changelog
  • Support
  • Trust Center
  • Join Discord

Solutions

  • Enterprise
  • Startup
  • Solo devs
  • React web devs
  • E-commerce
  • Crypto
  • Finserv
  • QSR

Company

  • Home
  • Pricing
  • Customers
  • Consultants
  • About
  • Branding
  • Careers

Legal

  • Terms of service
  • Acceptable use policy
  • Privacy policy
  • Privacy explained
  • Cookie policy
  • Security & Compliance
  • Enterprise trust
  • Community guidelines
© 2026 650 Industries, Inc.
All systems operational

February 3, 2026::Development·React Native

Building high-quality UIs with Expo and NativeWind

Learn how to design React Native apps directly in code using Expo, NativeWind, and Reanimated. Build reusable components, implement theming, and skip Figma.

Thomino

Thomino

Guest Author

How to build high-quality UIs with Expo and NativeWind

Contents

  • Reusable Components
  • Building a flexible header component with NativeWind
  • Creating animated tab navigation in React Native
  • Building multi-step forms and onboarding flows
  • Reusable chip components for filters and forms
  • Implementing theme variables with NativeWind
  • Building an animated dark mode toggle with Reanimated
  • Pre-built screen templates for Login, Onboarding, and Settings

This is a guest post from Thomino - he is the creator of Native Templates and is a great follow on X.

…

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.

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.

Creating animated tab navigation in React Native

Choose an icon, animation or avatar.

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.

Reusable chip components for filters and forms

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

You can check all the components in my 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.

Building an animated dark mode toggle with Reanimated

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!

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 or my Expo Playground on GitHub.

NativeWind
React Native

Share article

Related articles

All posts →
5 tips to increase mobile app downloads and retention in 2026

January 30, 2026

5 tips to increase mobile app downloads and retention in 2026

How to create Apple maps style liquid glass sheets in Expo

November 25, 2025

How to create Apple Maps style liquid glass sheets in Expo (the real way)

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;
// 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" />
]}
/>
<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>
<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>;
<Chip
label="Custom"
className="my-2 mx-1"
size="xl"
/>
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)",
}),
};
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;
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>
);
}