Building applications that work seamlessly across multiple platforms has always been a challenge for a long time. The dream of "write once, run anywhere" has led to numerous frameworks and libraries, each with their own strengths and limitations. This is where modern tools like Tamagui enter the picture, offering new approaches to solve these long-standing challenges.
What is Tamagui?
Tamagui is a collection of libraries designed to help developers build user interfaces that work seamlessly across both React (web) and React Native (mobile) platforms. If you've ever struggled with maintaining separate codebases for web and mobile or dealt with performance issues in cross-platform apps, Tamagui might be the solution you're looking for.
At its core, Tamagui consists of three main parts:
- @tamagui/core - The foundation style library that expands on React Native's style API
- @tamagui/static - An optimizing compiler that improves performance
- tamagui - A UI kit with components that adapt to each platform
What makes Tamagui special is that it works both at runtime and compile-time, giving you the flexibility to develop quickly while still achieving excellent performance when you need it.
Why Consider Tamagui?
1. True Cross-Platform Parity
Unlike some solutions that offer limited compatibility between platforms, Tamagui provides 100% parity between React and React Native. This means you can write your UI code once and have it work properly on both web and mobile.
2. Performance Optimization
The optional compiler (@tamagui/static
) analyzes your code and optimizes it for each platform. On the web, it converts styled components into simple HTML elements with atomic CSS. On mobile, it hoists style objects for better performance.
3. Familiar Styling API
If you're already familiar with React Native's styling approach, you'll feel right at home with Tamagui. It builds upon that foundation while adding many features from modern CSS.
4. Comprehensive UI Kit
Beyond just the styling system, Tamagui offers a complete UI kit with both unstyled and styled components that you can use to build common UI elements.
Getting Started with Tamagui
The easiest way to start with Tamagui is to use their CLI to create a new project:
npm create tamagui@latest
This command will guide you through setting up a new project with Tamagui already configured.
You will be prompted to select a template from a list of options. I have selected the very first one with Expo and Next.js Monorepo. They have multiple options, you can select the one you feel is suitable for you.
Manual Installation
If you prefer to add Tamagui to an existing project, you can install it manually:
# For just the core styling library
npm install @tamagui/core
# Or for the full UI kit (includes core)
npm install tamagui
After installation, you'll need to set up the TamaguiProvider
at the root of your app:
import { TamaguiProvider, View } from 'tamagui'
import config from './tamagui.config' // your configuration
export default function App() {
return (
<TamaguiProvider config={config}>
<View width={200} height={200} backgroundColor="$background" />
</TamaguiProvider>
)
}
Understanding Tamagui Configuration
The configuration file is a central piece of Tamagui that defines your design system. It's where you set up your tokens, themes, media queries, and other settings that will be used throughout your application. Think of it as the foundation that ensures consistency across your UI.
A Tamagui configuration file typically includes:
- Tokens - These are like CSS variables that define values for things like colors, spacing, font sizes, etc.
- Themes - Collections of token values that can be switched between (like light/dark mode)
- Media Queries - Definitions for responsive design breakpoints
-
Shorthands - Aliases for longer style property names (like
m
formargin
) - Animations - Predefined animation configurations
- Fonts - Typography definitions
Here's a simple example of a configuration file:
// tamagui.config.ts
import { createTamagui } from 'tamagui'
import { defaultConfig } from '@tamagui/config/v4'
const config = createTamagui(defaultConfig)
// Make imports typed
type Conf = typeof config
declare module 'tamagui' {
interface TamaguiCustomConfig extends Conf {}
}
export default config
In this example, we're using the defaultConfig
from @tamagui/config/v4
, which provides a set of pre-configured tokens, themes, and other settings. This is a great starting point for most applications.
The TypeScript declarations at the bottom ensure that your custom configuration is properly typed throughout your application, providing autocomplete and type checking for your tokens and themes.
How Configuration is Used
When you wrap your application with TamaguiProvider
and pass in your configuration, several things happen:
-
Token Registration - All your tokens become available to use in your components with the
$
prefix (e.g.,$space.md
or$color.blue
) - Theme Setup - Your themes are registered and the default theme is applied
- Media Query Initialization - Your responsive breakpoints are set up and start listening for screen size changes
- Style Processing - Tamagui's style engine is configured to use your settings for processing style props
For example, with the configuration in place, you can use tokens in your components:
<View
padding="$4" // Uses the space token with key "4"
backgroundColor="$background" // Uses the theme color "background"
borderRadius="$2" // Uses the radius token with key "2"
/>
Creating a Custom Configuration
While using the default configuration is convenient, you'll likely want to customize it for your application. Here's a more detailed example:
// tamagui.config.ts
import { createTamagui, createTokens } from 'tamagui'
// Define your tokens
const tokens = createTokens({
size: {
0: 0,
1: 4,
2: 8,
3: 16,
4: 24,
// ... more sizes
},
space: {
0: 0,
1: 4,
2: 8,
3: 16,
4: 24,
// ... more spaces
// You can also define negative spaces
'-1': -4,
'-2': -8,
},
color: {
primary: '#3498db',
secondary: '#2ecc71',
background: '#ffffff',
text: '#333333',
// ... more colors
},
radius: {
0: 0,
1: 4,
2: 8,
3: 16,
// ... more radius values
},
})
// Create your themes
const themes = {
light: {
background: tokens.color.background,
color: tokens.color.text,
// ... more theme values
},
dark: {
background: '#121212',
color: '#ffffff',
// ... more theme values
},
}
// Create your configuration
const config = createTamagui({
tokens,
themes,
// Define media queries for responsive design
media: {
sm: { maxWidth: 640 },
md: { maxWidth: 768 },
lg: { maxWidth: 1024 },
xl: { maxWidth: 1280 },
// You can also define other types of media queries
dark: { colorScheme: 'dark' },
light: { colorScheme: 'light' },
hoverNone: { hover: 'none' },
pointerCoarse: { pointer: 'coarse' },
},
// Define shorthands for style properties
shorthands: {
m: 'margin',
mt: 'marginTop',
mr: 'marginRight',
mb: 'marginBottom',
ml: 'marginLeft',
mx: 'marginHorizontal',
my: 'marginVertical',
p: 'padding',
pt: 'paddingTop',
pr: 'paddingRight',
pb: 'paddingBottom',
pl: 'paddingLeft',
px: 'paddingHorizontal',
py: 'paddingVertical',
// ... more shorthands
} as const,
})
// Type setup for TypeScript
type Conf = typeof config
declare module 'tamagui' {
interface TamaguiCustomConfig extends Conf {}
}
export default config
With this configuration, you can now use your custom tokens, themes, and shorthands throughout your application:
<YStack
p="$3" // Using the padding shorthand with token 3
bg="$background" // Using the background color from the current theme
borderRadius="$2" // Using radius token 2
$sm={{ p: "$2" }} // Responsive styling for small screens
>
<Text color="$primary">Hello Tamagui!</Text>
</YStack>
Advanced Configuration Options
Tamagui's configuration system is highly flexible and offers many advanced options:
- Animations - You can define reusable animations that work across platforms
- Fonts - Define font families, sizes, weights, and more
- Settings - Control various behaviors like SSR compatibility, theme handling, and style validation
For example, you can add animations to your configuration:
import { createAnimations } from '@tamagui/animations-react-native'
const animations = createAnimations({
bouncy: {
damping: 9,
mass: 0.9,
stiffness: 150,
},
lazy: {
damping: 18,
stiffness: 50,
},
})
const config = createTamagui({
// ... other config options
animations,
})
You can read more about all the configuration options in the official documentation.
Basic Components
Tamagui provides all the basic building blocks you need for UI development:
import { View, Text, Stack, XStack, YStack, Button } from 'tamagui'
export function SimpleExample() {
return (
<YStack padding="$4" space="$2">
<Text fontSize="$6" fontWeight="bold">
Hello Tamagui
</Text>
<XStack space="$2">
<Button theme="blue">Primary</Button>
<Button theme="gray">Secondary</Button>
</XStack>
</YStack>
)
}
In this example:
-
YStack
is a vertical stack (like a column) -
XStack
is a horizontal stack (like a row) -
$4
and$2
are token values from your design system -
theme="blue"
applies a predefined color theme
Styling with Tamagui
Tamagui offers multiple ways to style your components:
1. Inline Styles
<View
backgroundColor="$background"
padding={10}
borderRadius={8}
/>
2. Using styled()
import { styled } from 'tamagui'
const Card = styled(View, {
backgroundColor: '$background',
padding: 10,
borderRadius: 8,
// Variants allow for different styles based on props
variants: {
size: {
small: { padding: 5 },
large: { padding: 20 },
},
outlined: {
true: {
borderWidth: 1,
borderColor: '$borderColor',
},
},
},
})
// Usage
function MyComponent() {
return (
<Card size="large" outlined>
<Text>This is a card</Text>
</Card>
)
}
Theming in Tamagui
Tamagui has a powerful theming system that makes it easy to support light/dark modes and custom themes:
import { Theme, Button, Text, YStack } from 'tamagui'
export function ThemeExample() {
return (
<YStack padding="$4" space="$4">
{/* Default theme */}
<Button>Default Theme Button</Button>
{/* Blue theme */}
<Theme name="blue">
<Button>Blue Theme Button</Button>
</Theme>
{/* Red theme */}
<Theme name="red">
<Button>Red Theme Button</Button>
</Theme>
{/* Dark mode */}
<Theme name="dark">
<YStack padding="$4" backgroundColor="$background">
<Text color="$color">Dark Mode Text</Text>
</YStack>
</Theme>
</YStack>
)
}
You can also use the useTheme
hook to programmatically access or change themes:
import { useTheme, Button } from 'tamagui'
function ThemeSwitcher() {
const theme = useTheme()
const [isDark, setIsDark] = useState(false)
return (
<Button
onPress={() => setIsDark(!isDark)}
theme={isDark ? 'dark' : 'light'}
>
Switch to {isDark ? 'Light' : 'Dark'} Mode
</Button>
)
}
Responsive Design with Tamagui
Tamagui makes it easy to create responsive layouts using the useMedia
hook:
import { useMedia, XStack, YStack, Text } from 'tamagui'
function ResponsiveLayout() {
const media = useMedia()
return (
<YStack padding="$4">
{/* Stack will be vertical on small screens, horizontal on larger ones */}
{media.sm ? (
<YStack space="$2">
<Text>Item 1</Text>
<Text>Item 2</Text>
</YStack>
) : (
<XStack space="$2">
<Text>Item 1</Text>
<Text>Item 2</Text>
</XStack>
)}
</YStack>
)
}
You can also use media queries directly in your styled components:
const ResponsiveCard = styled(View, {
padding: '$2',
// Different styles based on screen size
$sm: {
padding: '$4',
},
$lg: {
padding: '$6',
maxWidth: 800,
},
})
Running the starter app
Sometime back we initialized our starter app with Expo + Next.js template. When I first opened it in Cursor, it looked like this:
As you can see, it's a monorepo with the code for both web and react native combined into one. It's so easy and helpful.
I wanted to run the app on the web for the time being so to do that, while being at the root of the folder, you need to run:
npm run web
and it will automatically spin up a Next.js instance on port 3000
.
The template looked like this:
It's already so intuitive with the option to use pages / app router as well, which is insane!
Using Tamagui with Expo
Tamagui works great with Expo, which is a popular framework for building React Native apps. Here's how to set it up:
- Create a new Expo project:
npx create-expo-app -t expo-template-blank-typescript
- Install Tamagui:
npm install tamagui @tamagui/babel-plugin
- Update your
babel.config.js
:
module.exports = function (api) {
api.cache(true)
return {
presets: ['babel-preset-expo'],
plugins: [
[
'@tamagui/babel-plugin',
{
components: ['tamagui'],
config: './tamagui.config.ts',
logTimings: true,
disableExtraction: process.env.NODE_ENV === 'development',
},
],
// If using reanimated
'react-native-reanimated/plugin',
],
}
}
Create a
tamagui.config.ts
file as shown earlier.Load fonts (optional but recommended):
import { useFonts } from 'expo-font'
function App() {
const [loaded] = useFonts({
Inter: require('@tamagui/font-inter/otf/Inter-Medium.otf'),
InterBold: require('@tamagui/font-inter/otf/Inter-Bold.otf'),
})
if (!loaded) {
return null
}
return <MyApp />
}
Community and Support
Tamagui has a growing community of developers. If you need help or want to contribute, you can find resources at:
Conclusion
Tamagui offers a powerful solution for building cross-platform applications with React and React Native. Its combination of a familiar styling API, performance optimizations, and comprehensive UI kit makes it a compelling choice for developers looking to share code between web and mobile platforms.
Whether you're building a simple app or a complex application, Tamagui provides the tools you need to create consistent, performant user interfaces across platforms.
The best part is that you can adopt Tamagui gradually - start with just the styling system and add the compiler and UI components as needed. This flexibility makes it accessible for both new projects and existing applications looking to improve their cross-platform capabilities.
Give Tamagui a try on your next project and experience the benefits of true cross-platform development!
Top comments (0)