DEV Community

Cover image for Tamagui: Building Cross-Platform Apps Made Simple
Harshal Ranjhani for CodeParrot

Posted on • Originally published at codeparrot.ai

1 1 1 1 1

Tamagui: Building Cross-Platform Apps Made Simple

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.

Tamagui Logo

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:

  1. @tamagui/core - The foundation style library that expands on React Native's style API
  2. @tamagui/static - An optimizing compiler that improves performance
  3. 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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>
  )
}
Enter fullscreen mode Exit fullscreen mode

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:

  1. Tokens - These are like CSS variables that define values for things like colors, spacing, font sizes, etc.
  2. Themes - Collections of token values that can be switched between (like light/dark mode)
  3. Media Queries - Definitions for responsive design breakpoints
  4. Shorthands - Aliases for longer style property names (like m for margin)
  5. Animations - Predefined animation configurations
  6. 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
Enter fullscreen mode Exit fullscreen mode

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:

  1. Token Registration - All your tokens become available to use in your components with the $ prefix (e.g., $space.md or $color.blue)
  2. Theme Setup - Your themes are registered and the default theme is applied
  3. Media Query Initialization - Your responsive breakpoints are set up and start listening for screen size changes
  4. 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"
/>
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

Advanced Configuration Options

Tamagui's configuration system is highly flexible and offers many advanced options:

  1. Animations - You can define reusable animations that work across platforms
  2. Fonts - Define font families, sizes, weights, and more
  3. 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,
})
Enter fullscreen mode Exit fullscreen mode

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>
  )
}
Enter fullscreen mode Exit fullscreen mode

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}
/>
Enter fullscreen mode Exit fullscreen mode

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>
  )
}
Enter fullscreen mode Exit fullscreen mode

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>
  )
}
Enter fullscreen mode Exit fullscreen mode

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>
  )
}
Enter fullscreen mode Exit fullscreen mode

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>
  )
}
Enter fullscreen mode Exit fullscreen mode

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,
  },
})
Enter fullscreen mode Exit fullscreen mode

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:

Tamagui Template Cursor

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
Enter fullscreen mode Exit fullscreen mode

and it will automatically spin up a Next.js instance on port 3000.

The template looked like this:

Next.js Template

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:

  1. Create a new Expo project:
npx create-expo-app -t expo-template-blank-typescript
Enter fullscreen mode Exit fullscreen mode
  1. Install Tamagui:
npm install tamagui @tamagui/babel-plugin
Enter fullscreen mode Exit fullscreen mode
  1. 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',
    ],
  }
}
Enter fullscreen mode Exit fullscreen mode
  1. Create a tamagui.config.ts file as shown earlier.

  2. 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 />
}
Enter fullscreen mode Exit fullscreen mode

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!

Sentry mobile image

Tired of users complaining about slow app loading and janky UI?

Improve performance with key strategies like TTID/TTFD & app start analysis.

Read the blog post

Top comments (0)

AWS GenAI LIVE image

How is generative AI increasing efficiency?

Join AWS GenAI LIVE! to find out how gen AI is reshaping productivity, streamlining processes, and driving innovation.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay