Core
Guides
Community
How to set up Tamagui with Next.js
Runningnpm create tamagui@latest let's you choose thestarter-freestarter which is a very nicely configured Next.js app where you can take orleave whatever you want.
Create a newNext.js project
yarn dlx create-next-app@latest
If you are using Turbopack, we have a section for optimization at the end ofthis document. If you aren't, then you may want the optional@tamagui/next-plugin which helps smooth out a few settings. We'll show how toconfigure it for bothpages andapp router in this guide. See thecompiler install docs for more options.
Add@tamagui/next-plugin to your project:
yarn add @tamagui/next-plugin
We recommend starting with our default config which gives you media queries andother nice things:
tamagui.config.ts
import{ defaultConfig}from'@tamagui/config/v4'import{ createTamagui}from'tamagui'// or '@tamagui/core'const appConfig=createTamagui(defaultConfig)exporttypeAppConfig=typeof appConfigdeclaremodule'tamagui'{// or '@tamagui/core'// overrides TamaguiCustomConfig so your custom types// work everywhere you import `tamagui`interfaceTamaguiCustomConfigextendsAppConfig{}}exportdefault appConfig
From here, choose your Next.js routing option to continue:
Automatically generate routes based on the filenames.
Allows more complex patterns and setups.
Using Tamagui with Next.js Turbopack bundler.
Set up the optional Tamagui plugin tonext.config.js:
next.config.js
const{ withTamagui}=require('@tamagui/next-plugin')module.exports=function(name,{ defaultConfig}){let config={...defaultConfig,// ...your configuration}const tamaguiPlugin=withTamagui({config:'./tamagui.config.ts',components:['tamagui'],})return{...config,...tamaguiPlugin(config),}}
If you're using React Native Web components, you'll want to gather thereact-native-web styles in _document:
_document.tsx
import NextDocument,{DocumentContext,Head,Html,Main,NextScript,}from'next/document'import{ StyleSheet}from'react-native'exportdefaultclassDocumentextendsNextDocument{staticasyncgetInitialProps({ renderPage}: DocumentContext){const page=awaitrenderPage()// @ts-ignore RN doesn't have this typeconst rnwStyle= StyleSheet.getSheet()return{...page,styles:(<styleid={rnwStyle.id}dangerouslySetInnerHTML={{ __html: rnwStyle.textContent}}/>),}}render(){return(<Htmllang="en"><Head><metaid="theme-color"name="theme-color"/><metaname="color-scheme"content="light dark"/></Head><body><Main/><NextScript/></body></Html>)}}
Tamagui automatically injects styles at runtime. You can optionally generate astatic CSS file - see theStatic CSS Output section.
AddTamaguiProvider:
_app.tsx
import{ NextThemeProvider}from'@tamagui/next-theme'import{ AppProps}from'next/app'import Headfrom'next/head'import React,{ useMemo}from'react'import{ TamaguiProvider}from'tamagui'import tamaguiConfigfrom'../tamagui.config'exportdefaultfunctionApp({ Component, pageProps}: AppProps){// memo to avoid re-render on dark/light changeconst contents=useMemo(()=>{return<Component{...pageProps}/>},[pageProps])return(<><Head><title>Your page title</title><metaname="description"content="Your page description"/><linkrel="icon"href="/favicon.ico"/></Head><NextThemeProvider><TamaguiProviderconfig={tamaguiConfig}disableInjectCSSdisableRootThemeClass>{contents}</TamaguiProvider></NextThemeProvider></>)}
UsedisableInjectCSS for SSR apps to prevent duplicate style injection. Only omit it for client-only apps without server rendering.
We've created a package that works with Tamagui to properly support SSRlight/dark themes that also respect user system preference, called@tamagui/next-theme. It assumes yourlight/dark themes are named as such,but you can override it. This is pre-configured in the create-tamagui starter.
yarn add @tamagui/next-theme
Here's how you'd set up your_app.tsx:
_app.tsx
import{ NextThemeProvider, useRootTheme}from'@tamagui/next-theme'import{ AppProps}from'next/app'import Headfrom'next/head'import React,{ useMemo}from'react'import{ TamaguiProvider, createTamagui}from'tamagui'// you usually export this from a tamagui.config.ts file:import{ defaultConfig}from'@tamagui/config/v4'const tamaguiConfig=createTamagui(defaultConfig)// make TypeScript type everything based on your configtypeConf=typeof tamaguiConfigdeclaremodule'@tamagui/core'{interfaceTamaguiCustomConfigextendsConf{}}exportdefaultfunctionApp({ Component, pageProps}: AppProps){const[theme, setTheme]=useRootTheme()// memo to avoid re-render on dark/light changeconst contents=useMemo(()=>{return<Component{...pageProps}/>},[pageProps])return(<><Head><title>Your page title</title><metaname="description"content="Your page description"/><linkrel="icon"href="/favicon.ico"/></Head><NextThemeProvider// change default theme (system) here:// defaultTheme="light"onChangeTheme={setThemeasany}><TamaguiProviderconfig={tamaguiConfig}disableInjectCSSdisableRootThemeClassdefaultTheme={theme}>{contents}</TamaguiProvider></NextThemeProvider></>)}
You can generate a static CSS file for your themes and tokens. There are twoways to do this:
The simplest approach is to use the Tamagui CLI to generate the CSS file:
yarn dlx tamagui generate
This outputs CSS to.tamagui/tamagui.css. Copy it to your public folder orconfigureoutputCSS in yourtamagui.build.ts:
tamagui.build.ts
importtype{ TamaguiBuildOptions}from'@tamagui/core'exportdefault{components:['tamagui'],config:'./tamagui.config.ts',outputCSS:'./public/tamagui.css',} satisfies TamaguiBuildOptions
Then import it in your_app.tsx:
_app.tsx
import'../public/tamagui.css'
You can also have the plugin generate CSS during your Next.js build:
next.config.js
const tamaguiPlugin=withTamagui({config:'./tamagui.config.ts',components:['tamagui'],outputCSS:process.env.NODE_ENV==='production'?'./public/tamagui.css':null,// faster dev mode, keeps debugging helpers:disableExtraction: process.env.NODE_ENV==='development',})
Then import it in your_app.tsx:
_app.tsx
import'../public/tamagui.css'
WithoutputCSS, you don't needgetCSS() in your_document.tsx - allstyles are handled by the static CSS file and runtime style injection.
To ensure font loads globally, add a global style tostyles in_document_.tsx:
NextTamaguiProvider.tsx
<stylejsxglobal>{`html {font-family: 'Inter';}`}</style>
Tamagui includes Server Components support for the Next.js app directory withuse client support.
Note that "use client" does render on the server, and since Tamagui extracts toCSS statically and uses inline<style /> tags for non-static styling, we getexcellent performance as-is.
The Tamagui plugin is optional but helps with compatibility with the rest of theReact Native ecosystem. It requires CommonJS for now as the optimizing compilermakes use of a variety of resolving features that haven't been ported to ESMyet. Be sure to rename yournext.config.mjs tonext.config.js before addingit:
next.config.js
const{ withTamagui}=require('@tamagui/next-plugin')module.exports=function(name,{ defaultConfig}){let config={...defaultConfig,// ...your configuration}const tamaguiPlugin=withTamagui({config:'./tamagui.config.ts',components:['tamagui'],appDir:true,})return{...config,...tamaguiPlugin(config),}}
You need to pass theappDir boolean to@tamagui/next-plugin.
Create a new component to addTamaguiProvider:
The internal usage ofnext/head is not supported in the app directory, so you need to add theskipNextHead prop to your<NextThemeProvider>.
NextTamaguiProvider.tsx
'use client'import{ ReactNode}from'react'import{ StyleSheet}from'react-native'import{ useServerInsertedHTML}from'next/navigation'import{ NextThemeProvider}from'@tamagui/next-theme'import{ TamaguiProvider}from'tamagui'import tamaguiConfigfrom'../tamagui.config'exportconstNextTamaguiProvider=({ children}:{ children: ReactNode})=>{// only if using react-native-web components like ScrollView:useServerInsertedHTML(()=>{// @ts-ignoreconst rnwStyle= StyleSheet.getSheet()return(<><styledangerouslySetInnerHTML={{ __html: rnwStyle.textContent}}id={rnwStyle.id}/></>)})return(<NextThemeProviderskipNextHead><TamaguiProviderconfig={tamaguiConfig}disableRootThemeClass>{children}</TamaguiProvider></NextThemeProvider>)}
ThegetNewCSS helper in Tamagui will keep track of the last call and onlyreturn new styles generated since the last usage.
Then add it to yourapp/layout.tsx:
layout.tsx
import{ Metadata}from'next'import{ NextTamaguiProvider}from'./NextTamaguiProvider'exportconst metadata: Metadata={title:'Your page title',description:'Your page description',icons:'/favicon.ico',}exportdefaultfunctionRootLayout({children,}:{children: React.ReactNode}){return(<htmllang="en"><body><NextTamaguiProvider>{children}</NextTamaguiProvider></body></html>)}
You can usesuppressHydrationWarning to avoid the warning about mismatchedcontent during hydration in dev mode.
Now you're ready to start adding components toapp/page.tsx:
page.tsx
'use client'import{ Button}from'tamagui'exportdefaultfunctionHome(){return<Button>Hello world!</Button>}
We've created a package that works with Tamagui to properly support SSRlight/dark themes that also respect user system preference, called@tamagui/next-theme. It assumes yourlight/dark themes are named as such,but you can override it. This is pre-configured in the create-tamagui starter.
yarn add @tamagui/next-theme
Here's how you'd set up yourNextTamaguiProvider.tsx:
NextTamaguiProvider.tsx
'use client'import'@tamagui/font-inter/css/400.css'import'@tamagui/font-inter/css/700.css'import{ ReactNode}from'react'import{ StyleSheet}from'react-native'import{ useServerInsertedHTML}from'next/navigation'import{ NextThemeProvider, useRootTheme}from'@tamagui/next-theme'import{ TamaguiProvider}from'tamagui'import tamaguiConfigfrom'../tamagui.config'exportconstNextTamaguiProvider=({ children}:{ children: ReactNode})=>{const[theme, setTheme]=useRootTheme()// only needed if using react-native-web components:useServerInsertedHTML(()=>{// @ts-ignoreconst rnwStyle= StyleSheet.getSheet()return(<styledangerouslySetInnerHTML={{ __html: rnwStyle.textContent}}id={rnwStyle.id}/>)})return(<NextThemeProviderskipNextHead// change default theme (system) here:// defaultTheme="light"onChangeTheme={(next)=>{setTheme(nextasany)}}><TamaguiProviderconfig={tamaguiConfig}disableRootThemeClassdefaultTheme={theme}>{children}</TamaguiProvider></NextThemeProvider>)}
You can generate a static CSS file for your themes and tokens. Use either theCLI or the Next.js plugin:
next.config.js
const tamaguiPlugin=withTamagui({config:'./tamagui.config.ts',components:['tamagui'],outputCSS:process.env.NODE_ENV==='production'?'./public/tamagui.css':null,// faster dev mode, keeps debugging helpers:disableExtraction: process.env.NODE_ENV==='development',})
Then link the generated CSS file in yourapp/layout.tsx:
app/layout.tsx
import'../public/tamagui.css'
With React 19, Tamagui automatically injects runtime styles via style tags onthe server. TheoutputCSS file handles themes and tokens generated at buildtime, so you don't need anygetCSS() calls in your provider.
To ensure font loads globally, add a global style touseServerInsertedHTML inNextTamaguiProvider.tsx:
NextTamaguiProvider.tsx
<stylejsxglobal>{`html {font-family: 'Inter';}`}</style>
TheNextThemeProvider is a provider that allows you to set the theme for yourapp. It also provides a hook to access the current theme and a function tochange the theme.
boolean
Required in app router. The internal usage of next/head is not supported in the app directory, so you need to add it.
boolean
Whether to switch between dark and light themes based on prefers-color-scheme.
string
If enableSystem is `false`, the default theme is light. Default theme name (for v0.0.12 and lower the default was light).
string
Forced theme name for the current page.
(name: string) => void
Used to change the current theme. The function receives the theme name as a parameter.
string
System theme name for the current page.
boolean
Whether to indicate to browsers which color scheme is used (dark or light) for built-in UI like inputs and buttons.
boolean
Disable all CSS transitions when switching themes.
string
Key used to store theme setting in localStorage.
string[]
List of all available theme names.
ValueObject
Mapping of theme name to HTML attribute value. Object where key is the theme name and value is the attribute value.
If you need to access the current theme, say for a toggle button, you will thenuse theuseThemeSetting hook. We'll release an update in the future that makesthis automatically work better with Tamagui's built-inuseThemeSetting.
SwitchThemeButton.tsx
import{ useState}from'react'import{ Button, useIsomorphicLayoutEffect}from'tamagui'import{ useThemeSetting, useRootTheme}from'@tamagui/next-theme'exportconstSwitchThemeButton=()=>{const themeSetting=useThemeSetting()const[theme]=useRootTheme()const[clientTheme, setClientTheme]=useState<string|undefined>('light')useIsomorphicLayoutEffect(()=>{setClientTheme(themeSetting.forcedTheme|| themeSetting.current|| theme)},[themeSetting.current, themeSetting.resolvedTheme])return(<ButtononPress={themeSetting.toggle}>Change theme:{clientTheme}</Button>)}
Turbopack doesn't support Webpack plugins, so you'll use the Tamagui CLI tooptimize your build. In dev mode, Tamagui works without any setup.
Install the CLI:
yarn add -D @tamagui/cli
Createtamagui.build.ts:
tamagui.build.ts
importtype{ TamaguiBuildOptions}from'@tamagui/core'exportdefault{components:['@tamagui/core'],// or ['tamagui']config:'./tamagui.config.ts',outputCSS:'./public/tamagui.css',} satisfies TamaguiBuildOptions
Yournext.config.js can be empty:
next.config.js
module.exports={}
The CLI can wrap your build command, optimizing files beforehand and restoringthem after:
package.json
{"scripts":{"dev":"next dev --turbopack","build":"tamagui build --target web ./src -- next build"}}
The-- separator tells the CLI to runnext build after optimization, thenrestore your source files automatically.
The CLI generates theme CSS tooutputCSS. Commit this file to git and importit in your layout:
app/layout.tsx
import'../public/tamagui.css'import{ TamaguiProvider}from'@tamagui/core'import configfrom'../tamagui.config'exportdefaultfunctionRootLayout({children,}:{children: React.ReactNode}){return(<htmllang="en"><body><TamaguiProviderconfig={config}>{children}</TamaguiProvider></body></html>)}
With React 19, Tamagui automatically injects runtime styles via style tags.TheoutputCSS file handles themes and tokens that are generated at buildtime.
Runnpx tamagui build once to generate the initial CSS file, then commit it.
Use--expect-optimizations to fail builds if the compiler optimizes fewer thanthe expected minimum number of components:
{"build":"tamagui build --target web --expect-optimizations 5 ./src -- next build"}
This will fail the build if fewer than 5 components are optimized, helping catchconfiguration issues in CI.