Tamagui for React Native: Create faster design systems

Published on Jan 9, 2022

10 min read

EXPO

Originally Published at Blog.Logrocket.com on December 24, 2021

The React Native system is gradually progressing towards solutions for sharing code between React Native and React Native web applications.

One recent addition to the React Native ecosystem is Tamagui, a UI kit that includes a series of themes, media queries, and typed inline styles, as well as an optimizing compiler. Tamagui aims to close the gap between React Native and React Native web applications by covering the foundational elements of an app, like styling, theming, and providing cross-platform components, all while keeping the app’s performance in mind.

In this article, we’ll learn how to configure Tamagui in a React Native app and a React Native Web app, checking out the components it offers in its current state. Let’s get started!

Create a new React Native app

🔗

We’ll start by creating a new React Native project using Expo CLI, which enhances the developer experience with tools in the React Native ecosystem, for example, a variety of templates. Choosing one of these templates is a great starting point for the demo app we’ll build in this tutorial. Open up a terminal window and execute the following command:

npx expo init tamagui-app
# after the project directory is created, navigate inside it
cd tamagui-app

On running the command, you’ll be prompted to choose a template. Choose blank (TypeScript). It will create a project with minimal TypeScript configuration.

Installing dependencies

🔗

After navigating inside the project directory, we’ll install the libraries required to configure Tamagui inside a React Native project. But first, run the following command from the terminal:

yarn add tamagui@1.0.0-alpha.37 @tamagui/babel-plugin@1.0.0-alpha.37

Since Tamagui is still in its alpha release, I'm using specific package versions. However, when a more stable version is released in the future, this may change. Be sure to refer to the official documentation for the most up to date information on installing dependencies.

tamagui is the UI kit, and @tamagui/babel-plugin is the babel plugin that loads the design system properties defined inside another file called tamagui.config.ts. You’ll need to create this file in the root of your project, but you can leave it blank for now. We’ll return to it later.

The @unimodules/core dependency is required by the Tamagui UI kit to work with an Expo project. Open the terminal window and execute the following command:

expo install @unimodules/core

Next, we need to install dev dependencies to use Tamagui with Expo web. Open the terminal window and execute the command below:

yarn add -D @expo/webpack-config@0.16.14 esbuild-loader@2.17.0 tamagui-loader@1.0.0-alpha.37 thread-loader@3.0.4

As before, we've installed packages with specific versions. In the package.json file, you’ll find the following summary of dependencies and dev dependencies that we installed:

1{
2 "dependencies": {
3 "@tamagui/babel-plugin": "^1.0.0-alpha.37",
4 "@unimodules/core": "~7.2.0",
5 "expo": "~43.0.2",
6 "expo-status-bar": "~1.1.0",
7 "react": "17.0.2",
8 "react-dom": "17.0.2",
9 "react-native": "0.64.3",
10 "react-native-web": "0.17.5",
11 "tamagui": "^1.0.0-alpha.37"
12 },
13 "devDependencies": {
14 "@babel/core": "^7.12.9",
15 "@types/react": "~17.0.21",
16 "@types/react-native": "~0.66.6",
17 "typescript": "~4.5.2",
18 "@expo/webpack-config": "^0.16.14",
19 "esbuild-loader": "^2.17.0",
20 "tamagui-loader": "^1.0.0-alpha.37",
21 "thread-loader": "^3.0.4"
22 }
23}

Now that our dependencies are installed, add @tamagui/babel-plugin to the babel.config.js file:

1module.exports = function (api) {
2 api.cache(true);
3 return {
4 presets: ['babel-preset-expo'],
5 plugins: [
6 [
7 '@tamagui/babel-plugin',
8 {
9 components: ['tamagui'],
10 config: './tamagui.config.ts'
11 }
12 ]
13 ]
14 };
15};

In the code snippet above, ensure that the path defined for config is the relative path to the Tamagui config file. components contains an array of npm modules containing Tamagui components. For this example app, we're using Tamagui base components. Therefore, we don't need to add any further configuration.

Setting up Tamagui configuration

🔗

Tamagui lets you create themes, define tokens, add shorthands, and more. However, it requires you to describe all the properties and set up the foundation of a design system before you dive into building the components for your React Native app.

To set up the required Tamagui configuration, we’ll use the createTamagui function. We’ll need to define the following:

  • tokens: Generate variables in theme and app
  • media: Defines reusable responsive media queries
  • themes: Defines your design theme
  • shorthands: Allows you to define keys that expand the style value props. For example, you can define f for flex, ai for alignItems, jc for justifyContent, etc.

You can start setting up the configuration with the size and space properties. We’ll also need defaultFont using the createFont function with a configuration object that contains values for font family, size, lineHeight, fontWeight, and letter spacing.

All of the values above are used inside of the createTokens function, which allows you to create tokens, or the variables mapped to CSS variables at build time. The createTokens function requires the size , space, font, color, radius, and zIndex properties in its configuration object.

The code block below contains a minimal configuration that I've created for the demo app with all of the required properties:

1import { createFont, createTokens, createTamagui } from 'tamagui';
2const size = {
3 0: 0,
4 1: 4,
5 2: 8
6};
7const space = {
8 ...size,
9 '-0': -0,
10 '-1': -5
11};
12const defaultFont = createFont({
13 family: 'Arial',
14 size: {
15 1: 14,
16 2: 18,
17 3: 22
18 },
19 lineHeight: {
20 1: 15,
21 2: 20
22 },
23 weight: {
24 4: '300',
25 7: '600'
26 },
27 letterSpacing: {
28 4: 0,
29 7: -1
30 }
31});
32const tokens = createTokens({
33 size,
34 space,
35 font: {
36 title: defaultFont,
37 body: defaultFont
38 },
39 color: {
40 lightPurple: '#EDD2F3',
41 darkPurple: '#544179'
42 },
43 radius: {
44 0: 0,
45 1: 3,
46 2: 5,
47 3: 10,
48 4: 15,
49 5: 20
50 },
51 zIndex: {
52 0: 0,
53 1: 100,
54 2: 200,
55 3: 300,
56 4: 400,
57 5: 500
58 }
59});
60const shorthands = {
61 ai: 'alignItems',
62 bg: 'backgroundColor',
63 br: 'borderRadius',
64 f: 'flex',
65 h: 'height',
66 jc: 'justifyContent',
67 m: 'margin',
68 p: 'padding',
69 w: 'width',
70 lh: 'lineHeight',
71 ta: 'textAlign'
72} as const;
73const media = {
74 xs: { maxWidth: 660 },
75 gtXs: { minWidth: 660 + 1 },
76 sm: { maxWidth: 860 },
77 gtSm: { minWidth: 860 + 1 },
78 md: { minWidth: 980 },
79 gtMd: { minWidth: 980 + 1 },
80 lg: { minWidth: 1120 },
81 gtLg: { minWidth: 1120 + 1 },
82 xl: { minWidth: 1280 },
83 xxl: { minWidth: 1420 },
84 short: { maxHeight: 820 },
85 tall: { minHeight: 820 },
86 hoverNone: { hover: 'none' },
87 pointerCoarse: { pointer: 'coarse' }
88};
89const config = createTamagui({
90 defaultTheme: 'light',
91 shorthands,
92 media,
93 tokens,
94 themes: {
95 light: {
96 bg: tokens.color.lightPurple
97 }
98 }
99});
100type Conf = typeof config;
101declare module 'tamagui' {
102 interface TamaguiCustomConfig extends Conf {}
103}
104export default config;

Using Tamagui Provider

🔗

Tamagui configuration provides a Tamagui.Provider component that wraps all the other components inside your app:

1import React from 'react';
2import Tamagui from './tamagui.config';
3export default function App() {
4 return <Tamagui.Provider>{/* The rest of your app here */}</Tamagui.Provider>;
5}

Tamagui views, utility props, and shorthands

🔗

In Tamagui, stacks are the core view elements for creating flex-based layouts. There are three different types of stacks available, XStack, YStack, and ZStack, and each implies a different axis.

In the example below, the defaultTheme takes the value of the theme you've defined in the config file. The XStack uses several shorthands; for example, f stands for flex, ai for alignItems, jc for justifyContent, and bg for backgroundColor.

The value of the $bg prop is also coming from the config file, where we’ve explicitly defined that the bg property for the light theme should have a particular color value. The value of space on the YStack is set to $2 from the config file itself:

1import { StatusBar } from 'expo-status-bar';
2import React from 'react';
3import { YStack, Text, XStack } from 'tamagui';
4import Tamagui from './tamagui.config';
5export default function App() {
6 return (
7 <Tamagui.Provider defaultTheme="light">
8 <StatusBar style="dark" />
9 <XStack f={1} ai="center" jc="center" bg="$bg">
10 <YStack space="$2">
11 <Text fontSize={20} marginBottom={20}>
12 Tamagui
13 </Text>
14 <Text fontSize={20}>Tamagui</Text>
15 </YStack>
16 </XStack>
17 </Tamagui.Provider>
18 );
19}

You can further define shorthands for properties like margin, marginBottom, and padding and use them as utility props on components, giving you more control over your styles and themes, as seen in the following example

ss1

Making Tamagui work on the web

🔗

Now, we’ll take advantage of the @expo/webpack-config package that we installed earlier, which is used to create a custom webpack configuration. When running the expo start --web command, the Expo CLI checks whether the project has a custom webpack configuration in the root directory. If the project does not have a custom webpack configuration, Expo uses the default configuration.

To create our custom webpack configuration, we’ll first run the following command from a terminal window, which will create a config file for you to customize the webpack configuration. When you run this command, you’ll be prompted to choose from several options. Select the webpack.config.js option:

expo customize:web

Next, add the custom configuration to the webpack.config.js file, as recommend by the [Tamagui documentation](http://(https://tamagui.dev/docs/intro/installation). The configuration will allow us to run tamagui-loader on the web:

1const createExpoWebpackConfigAsync = require('@expo/webpack-config');
2module.exports = async function (env, argv) {
3 const config = await createExpoWebpackConfigAsync(env, argv);
4 // Customize the config before returning it.
5 // add TAMAGUI_TARGET = web to defines
6 const DefinePlugin = config.plugins.find(
7 x => x.constructor.name === 'DefinePlugin'
8 );
9 DefinePlugin.definitions\['process.env'\]['TAMAGUI_TARGET'] = `"web"`;
10 // replace babel-loader with our snackui + esbuild loaders
11 const rules = config.module.rules[1].oneOf;
12 const ruleIndex = rules.findIndex(x =>
13 x.use?.loader?.includes('babel-loader')
14 );
15 rules[ruleIndex] = {
16 test: /\.(mjs|[jt]sx?)$/,
17 use: [
18 'thread-loader',
19 {
20 loader: require.resolve('esbuild-loader'),
21 options: {
22 loader: 'tsx',
23 target: 'es2019',
24 keepNames: true
25 }
26 },
27 {
28 loader: require.resolve('tamagui-loader'),
29 options: {
30 config: './tamagui.config.ts',
31 components: ['tamagui']
32 }
33 }
34 ]
35 };
36 return config;
37};

After adding the configuration, execute the command yarn web or expo start --web. You’ll get the following output at http://localhost:19006/:

ss2

Responsive styles using media queries

🔗

You can incorporate media queries directly in the UI elements of your app. After defining your media queries in the tamagui.config.ts file, you’ll use a Hook called useMedia provided by the library:

1import { YStack, Text, XStack, useMedia } from 'tamagui';

Now, let's add a background color to YStack. The color value will vary on the screen's minimum width using the media query md: { minWidth: 980 }.

In the code snippet below, the backgroundColor value is set to yellow if the screen width is less than 980, otherwise, it's set to red:

1export default function App() {
2 const media = useMedia();
3 return (
4 <Tamagui.Provider defaultTheme="light">
5 <StatusBar style="dark" />
6 <XStack f={1} ai="center" jc="center" bg="$bg">
7 <YStack
8 space="$2"
9 width={200}
10 height={100}
11 p={10}
12 bg={media.md ? 'red' : 'yellow'}
13 >
14 <Text fontSize={media.md ? 32 : 18} marginBottom={20}>
15 Tamagui
16 </Text>
17 <Text fontSize={20}>React Native</Text>
18 </YStack>
19 </XStack>
20 </Tamagui.Provider>
21 );
22}

You can check out the following example:

ss3.

Similarly, the fontSize property also changes based on the media query, as seen in the example:

ss4

Conclusion

🔗

Although Tamagui is still in its alpha release, it provides benchmarks over other UI kits in the React Native space that support web and outperforms them. In this tutorial, we took a first look at Tamagui, learning the best way to configure it for different use cases.

As a developer, I'll be keeping a close eye on Tamagui’s development and growth. The idea of creating a custom design system from scratch to support both native and web platforms is both fascinating and useful. I hope you enjoyed this tutorial!


More Posts

Browse all posts

Join 1300+ devs & subscribe to my newsletter!

NEWSLETTER

🔗 Subscribe on Revue

Aman Mittal author

Software Developer and Technical Writer since 2017. Loves learning and writing about Node.js, React, React Native & Expo. Previously, worked as Developer Advocate, independent consultant, and technical writer with companies like Draftbit, Vercel and Crowdbotics.


Copyright ©  2019-2022 Aman Mittal · All Rights Reserved.