wwwwwwwwwwwwwwwwwww
Introduction
Installation
Releases

Core

Configuration
Tokens
Stack & Text
styled
Variants
Props
Themes
Animations
Theme
useMedia
useTheme
FontLanguage
Extras

Compiler

About
Install
Benchmarks

Theme

Colors
Tokens

Tamagui

Stacks
Headings
Text

Forms

Button
Checkbox
Form
Input & TextArea
Label
Progress
RadioGroup
Select
Slider
Switch
ToggleGroup

Panels

AlertDialog
Dialog
Popover
Sheet
Tooltip
Toast

Organize

Accordion
Group
Tabs

Content

Avatar
Card
Image
ListItem

Visual

LinearGradient
Separator
Square & Circle

Etc

Anchor
HTML Elements
ScrollView
Spinner
Unspaced
VisuallyHidden

Extras

Lucide Icons

Guides

Design Systems
Creating Custom Themes
How to Build a Button
Developing
Next.js
Expo
Vite
create-tamagui

Community

Community
Blog
GitHub
Twitter
Discord

Next.js Guide

How to set up Tamagui with Next.js

Running npm create tamagui let's you choose the starter-free starter which is a very nicely configured Next.js app where you can take or leave whatever you want.

Check out the source for this site  to see a good example of a full featured Next.js website as well, especially the next.config.js and tamagui.config.ts.

next.config.js

To configure Tamagui for Next.js, you'll want to add the @tamagui/next-plugin and set it up in your next config. See the compiler install docs for more options:

module.exports = function (name, { defaultConfig }) {
let config = {
...defaultConfig,
// ...your configuration
}
const tamaguiPlugin = withTamagui({
config: './tamagui.config.ts',
components: ['tamagui'],
// build-time generate CSS styles for better performance
// we recommend only using this for production so you get reloading during dev mode
outputCSS: process.env.NODE_ENV === 'production' ? './public/tamagui.css' : null,
// optional advanced settings:
// set to false if you never call addTheme or updateTheme
// when combined with outputCSS this saves you 1Kb more bundle size
doesMutateThemes: true, // default true
})
return {
...config,
...tamaguiPlugin(config),
}
}

pages/_app.tsx

Add the reset to get more consistent styles across browsers:

import '@tamagui/core/reset.css'

And then add this if you are using the outputCSS option to include the CSS file generated at build-time:

if (process.env.NODE_ENV === 'production') {
require('../public/tamagui.css')
}

pages/_document.tsx

You'll want to set up a _document.tsx and gather both the react-native-web style object using AppRegistry, as well as Tamagui styles using Tamagui.getCSS() into the head element.

Note that we only are importing react-native here because if you use tamagui views like Input or Image they rely on React Native Image/Input (and therefore on the web, react-native-web). If you are just using core, or aren't using Image or Input in tamagui, you can forego the entire AppRegistry import and setup here. We're working on making even tamagui completely de-coupled from RN/RNW by building their own image and input components eventually.

import NextDocument, { Head, Html, Main, NextScript } from 'next/document'
import { Children } from 'react'
import { StyleSheet } from 'react-native'
import Tamagui from '../tamagui.config'
export default class Document extends NextDocument {
static async getInitialProps({ renderPage }) {
const page = await renderPage()
// @ts-ignore RN doesn't have this type
const rnwStyle = StyleSheet.getSheet()
return {
...page,
styles: (
<>
<style id={rnwStyle.id} dangerouslySetInnerHTML={{ __html: rnwStyle.textContent }} />
<style dangerouslySetInnerHTML={{ __html: Tamagui.getCSS({ // if you are using "outputCSS" option, you should use this "exclude" // if not, then you can leave the option out exclude: process.env.NODE_ENV === 'production' ? 'design-system' : null, }), }} />
</>
),
}
}
render() {
return (
<Html>
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
}

Themes

We've created a package that works with Tamagui to properly support SSR light/dark themes that also respect user system preference, called @tamagui/next-theme. It assumes your light/dark themes are named as such, but you can override it. This is pre-configured in the create-tamagui starter.

We recommend memo-ing children so they don't re-render. Here's how you'd set up your _app.tsx:

import { NextThemeProvider, useRootTheme } from '@tamagui/next-theme'
import { AppProps } from 'next/app'
import Head from 'next/head'
import React, { useMemo, useState } from 'react'
import { TamaguiProvider } from 'tamagui'
import config from '../tamagui.config'
export default function App({ Component, pageProps }: AppProps) {
const [theme, setTheme] = useRootTheme()
// memo to avoid re-render on dark/light change
const contents = useMemo(() => {
return <Component {...pageProps} />
}, [pageProps])
// because we do our custom getCSS() above, we disableInjectCSS here
return (
<>
<Head>{/* ... */}</Head>
<NextThemeProvider onChangeTheme={setTheme}>
<TamaguiProvider config={config} disableInjectCSS disableRootThemeClass defaultTheme={theme} >
{contents}
</TamaguiProvider>
</NextThemeProvider>
</>
)
}

If you need to access the current theme, say for a toggle button, you will then use the useTheme hook. We'll release an update in the future that makes this automatically work better with Tamagui's built-in useTheme.

import { useTheme } from '@tamagui/next-theme'
export default () => {
const { theme, toggleTheme } = useTheme()
return <Button>Change theme (currently: {theme})</Button>
}

Mount animations

Animations that run through JS drivers and have enterStyle set will want to add the following script that allows for hiding those animations before the JS runs. This is the "right way" to handle things, as it allows for disabling JS entirely and not accidentally hiding all unmounted things. Meanwhile, it still properly avoids a flash of mounted style for SSR pages.

Add this to your _app.tsx render output:

<NextHead>
<script key="tamagui-animations-mount" dangerouslySetInnerHTML={{ // avoid flash of animated things on enter __html: `document.documentElement.classList.add('t_unmounted')`, }} />
</NextHead>

Server components

Tamagui includes Server Components support for the Next.js app directory. It should work fully server side eventually, but we have to work with the Next.js team to figure out the final steps here. Until then, use client support is here already.

Create a new component:

'use client'
import { useServerInsertedHTML } from 'next/navigation'
import { TamaguiProvider } from 'tamagui'
export function NextTamaguiProvider({ children }) {
useServerInsertedHTML(() => {
// this first time this runs you'll get the full CSS including all themes
// after that, it will only return CSS generated since the last call
return <>{Tamagui.getNewCSS()}</>
})
// see Tamagui provider setup in the example above
return <TamaguiProvider>{children}</TamaguiProvider>
}

The new getNewCSS helper in Tamagui will keep track of the last call and only return new styles generated since the last usage.

And then use it in your app/layout.tsx like this:

import { NextTamaguiProvider } from './NextTamaguiProvider'
export default function Layout({ children }) {
return <NextTamaguiProvider>{children}</NextTamaguiProvider>
}

Additionally, you will need to add the skipNextHead prop to your <NextThemeProvider>, as the internal usage of next/head is not supported in the app directory.

And you'll need to pass the appDir boolean to @tamagui/next-plugin:

withTamagui({
appDir: true,
})

Previous

Developing

Next

Expo