Design Systems

Put together your own design system.

To skip to some real examples:

Tamagui allows you to build our your own set of components that are optimized with the compiler whenever they are used in your app.

By default, if you just use styled() in your app, Tamagui won't be able to optimize those components. This is because the compiler needs to be know about those components at build-time.

Let's break down how to set this up in more detail.

Step 1: Create a package

Your design system needs to live in it's own npm module, which can be private to just your app. That way you can later direct the compiler to look for that package.

Design systems can extend off each other. In fact tamagui extends off @tamagui/core, which contains simple base level components.

So, for example, if you'd like to use the Tamagui XStack, YStack, Button and Paragraph in your design system, you would add tamagui to your design system's package.json.

If you want to build more from scratch, then use @tamagui/core and only import either the Stack or Text view. For the purpose of this guide, we'll just extend @tamagui/core.

Let's name our package @ourapp/components:

{
"name": "@ourapp/components",
"types": "./types/index.d.ts",
"main": "dist/cjs",
"module": "dist/esm",
"module:jsx": "dist/jsx",
"files": ["types", "src", "dist"],
"sideEffects": ["*.css"],
"dependencies": {
"@tamagui/core": "*"
},
"scripts": {
"build": "tamagui-build",
"watch": "tamagui-build --watch"
},
"devDependencies": {
"@tamagui/build": "*"
}
}

And be sure to have a tsconfig.json with whichever options you'd like.

There are a few things to note here:

  • We're using @tamagui/build to build this package, which is a small script built around esbuild and typescript that makes sure you output your components with JSX preserved.
  • We then set module:jsx, which then needs to be added to your webpack resolve.mainFields.
    • The Next.js plugin handles this for you automatically.
  • sideEffects field is important, otherwise webpack will remove the generated CSS in production.

Step 2: Define and export components

Now, create and export the components. You can re-export components from tamagui or @tamagui/core as well. Let's create a ZCircle component

Circle.tsx:

import { GetProps, YStack, styled } from 'tamagui' // or @tamagui/core if extending just that
export const Circle = styled(YStack, {
alignItems: 'center',
justifyContent: 'center',
borderRadius: 100_000_000,
overflow: 'hidden',
variants: {
size: {
'...size': (size, { tokens }) => {
return {
width: tokens.size[size] ?? size,
height: tokens.size[size] ?? size,
}
},
},
},
})
export type CircleProps = GetProps<typeof Circle>

And then export from index.tsx:

export * from './Circle'

Step 3: Set up your build

Now in your app, add @ourapp/components and tamagui (since we are extending it) to your package.json, and update your tamagui build configuration.

Webpack

In your webpack.config.js:

{
loader: 'tamagui-loader',
options: {
config: './tamagui.config.ts',
components: ['@ourapp/components', 'tamagui'],
},
}

Next.js

In your next.config.js:

export default withPlugins([
withTamagui({
config: './tamagui.config.ts',
components: ['@ourapp/components', 'tamagui'],
})
])

React Native

In your babel.config.js:

export default {
plugins: [
[
'@tamagui/babel-plugin',
{
exclude: /node_modules/,
config: './tamagui.config.ts',
components: ['@ourapp/components', 'tamagui']
},
],
],
}

Configuration notes

You only need tamagui in your components array if you extend it, otherwise no need. You can also extend your own base level module so long as they export Tamagui styled components.

Step 4: Test it out

In your app, you should now be able to import and use your Circle component. Using the debug pragma, you can also verify the extraction is working. Make sure the build settings logTimings: true and disableExtraction: false are set so you can see the compiler at work:

Anywhere in your app:

import { Circle } from '@ourapp/components'
export default () => <Circle size="$large" />

When it compiles you should see something like:

» app.tsx 16ms ׁ· 1 optimized · 1 flattened

To get more information on any extraction, use the // debug pragma:

// debug
// ^ the above pragma will direct Tamagui to output a lot of information on the extraction
import { Circle } from '@ourapp/components'
export default () => <Circle size="$large" />

You should see much more log output with details on how it extracted, including the final CSS and JS.