Upgrading from v1 to v2
A comprehensive guide for migrating your Tamagui project from v1 to v2
Tamagui v2 aligns with modern web standards, improves performance, and simplifies APIs. The migration is moderate — mostly removing deprecated APIs, renaming props, and updating your config. This guide walks through every change.
Prerequisites
Tamagui 2 requires:
- React 19+
- React Native 0.81+ (with New Architecture support)
- TypeScript 5+
Upgrade these first before proceeding.
1. Update Dependencies
Bump all @tamagui/* and tamagui packages to ^2.0.0:
Terminal
# if using npm/yarn/pnpm, update all tamagui packagesnpx tamagui check
Removed packages
@tamagui/animations-moti— use@tamagui/animations-reanimated(same API)@tamagui/image-next— useImagefromtamaguidirectly
Add resolutions
In monorepos, you may want to add resolutions to avoid duplicate copies of core packages (critical for context/provider to work):
"resolutions": {"@tamagui/core": "^2.0.0","@tamagui/web": "^2.0.0","tamagui": "^2.0.0"}
2. Config Migration (v4 to v5)
You don’t have to migrate to Config v5, and it changes some default style behaviors that may cause some issues. It’s easier to migrate to Tamagui 2 first, then Config v5 after if you do choose to. Config v5 is mostly a superset of v5, but some media query names changed and some themes and colors are different.
Update imports
// beforeimport { defaultConfig } from '@tamagui/config/v4'// afterimport { defaultConfig } from '@tamagui/config/v5'import { animations } from '@tamagui/config/v5-css' // animations are now separate
Animations are no longer bundled with the config. Choose your driver:
@tamagui/config/v5-css— CSS transitions (smallest bundle, web-only)@tamagui/config/v5-rn— React Native Animated API@tamagui/config/v5-reanimated— Reanimated (best native performance)@tamagui/config/v5-motion— Motion (Web Animations API, experimental)
Add animations to config
import { defaultConfig } from '@tamagui/config/v5'import { animations } from '@tamagui/config/v5-css'import { createTamagui } from 'tamagui'export const config = createTamagui({...defaultConfig,animations,})export type Conf = typeof configdeclare module 'tamagui' {interface TamaguiCustomConfig extends Conf {}}
Settings changes
All root-level createTamagui settings have moved into the settings object:
// before (v1)createTamagui({defaultFont: 'body',disableRootThemeClass: true,// ...})// after (v2)createTamagui({...defaultConfig,settings: {...defaultConfig.settings,// your overrides},})
Removed settings:
maxDarkLightNesting— removed entirelycssStyleSeparator— removed entirelythemeClassNameOnRoot— handled byaddThemeClassNamedisableRootThemeClass— now part ofsettings
Flex and position defaults
v5 changes two important defaults:
flexBasischanges fromautoto0(React Native standard)positionchanges fromrelativetostatic(browser default)
If your layout relies on the old behavior, restore it:
settings: {...defaultConfig.settings,styleCompat: 'legacy', // restores flexBasis: autodefaultPosition: 'relative', // restores position: relative}
Otherwise, you may need to add position="relative" explicitly on containers that have absolutely-positioned children, and add flexBasis="auto" where needed.
Media query renames
$2xl→$xxl$2xs→$xxs$max2Xl→$max-xxl$maxXl→$max-xl$maxLg→$max-lg$maxMd→$max-md$maxSm→$max-sm
Max queries changed from camelCase to kebab-case. New height-based queries are also available ($height-sm, $height-md, etc.) and a $pointerTouch query.
Breakpoint values now match Tailwind CSS: 640, 768, 1024, 1280, 1536.
Colors and themes
Colors have been updated to Radix Colors v3 with slightly different values. Legacy colors are available at @tamagui/colors/legacy.
v5 adds new color themes: orange, pink, purple, teal, gray, neutral.
// before (v1) - manual theme-builderimport { createThemes, defaultComponentThemes } from '@tamagui/theme-builder'const themes = createThemes({ ... })// after (v2) - simplifiedimport { createV5Theme, defaultChildrenThemes } from '@tamagui/themes/v5'const themes = createV5Theme({childrenThemes: {...defaultChildrenThemes,// add custom color themescyan: { light: cyan, dark: cyanDark },},})
Component themes are off by default in v5. Use defaultProps in your config instead:
createTamagui({...defaultConfig,defaultProps: {Button: { theme: 'accent' },},})
Theme tree-shaking (SSR optimization)
On the client, you can skip loading theme JS and hydrate from CSS variables instead:
themes: process.env.VITE_ENVIRONMENT === 'client'? ({} as typeof themes): themes,
Once your v5 config is set up, run npx tamagui generate-prompt and commit the output
to your repo. This helps AI assistants understand your Tamagui config and can speed up
the rest of the migration.
3. Prop Renames
These are the most common find-and-replace changes.
animation to transition
animationtransition
// before<View animation="bouncy" /><Sheet.Overlay animation="lazy" />// after<View transition="bouncy" /><Sheet.Overlay transition="lazy" />
The TypeScript type also changed: AnimationProp to TransitionProp.
tag to render
tagrender
// before<View tag="nav" /><View tag="button" />// after<View render="nav" /><View render="button" />
For tag="a", consider using the Anchor component instead.
Stack to View
StackView
// beforeimport { Stack, type StackProps } from 'tamagui'// afterimport { View, type ViewProps } from 'tamagui'
themeInverse / <Theme inverse> to theme="accent"
themeInverse<Theme inverse>theme="accent"
// before<Button themeInverse>Primary</Button><Theme inverse><Card>...</Card></Theme>// after<Button theme="accent">Primary</Button><Theme name="accent"><Card>...</Card></Theme>
space / spaceDirection to gap
spacespaceDirectiongap
// before<YStack space="$4" spaceDirection="both">// after<YStack gap="$4">
onHoverIn / onHoverOut
onHoverInonHoverOut
// before<View onHoverIn={handler} onHoverOut={handler} />// after<View onPointerEnter={handler} onPointerLeave={handler} />
ellipse to numberOfLines
ellipsenumberOfLines
// before<Text ellipse>Long text...</Text>// after<Text numberOfLines={1}>Long text...</Text>
4. Shadow Migration
Replace React Native shadow props with CSS boxShadow:
// before<View shadowColor="$shadow3" shadowRadius={20} shadowOffset={{ height: 10, width: 0 }} />// after<View boxShadow="0 10px 20px $shadow3" />
The format is x y blur color. Multiple shadows are comma-separated. Spread and inset are also supported.
5. Accessibility Props
All React Native accessibility props are replaced with web-standard ARIA equivalents:
accessibilityLabel→aria-labelaccessibilityRole→roleaccessibilityHint→aria-describedbyaccessibilityState={{ disabled }}→aria-disabledaccessibilityState={{ selected }}→aria-selectedaccessibilityState={{ checked }}→aria-checkedaccessibilityState={{ busy }}→aria-busyaccessibilityState={{ expanded }}→aria-expandedaccessibilityValue→aria-valuemin,aria-valuemax,aria-valuenow,aria-valuetextaccessibilityElementsHidden→aria-hiddenaccessibilityViewIsModal→aria-modalaccessibilityLiveRegion→aria-liveaccessible→tabIndex={0}focusable→tabIndexnativeID→id
6. Component API Changes
Input
Input now uses web-standard HTML attributes as the primary API:
// before<Input keyboardType="email-address" secureTextEntry returnKeyType="send" textContentType="emailAddress" onChangeText={(text) => setText(text)} onKeyPress={(e) => { if (e.nativeEvent.key === 'Enter') submit() }} editable={false} />// after<Input inputMode="email" type="password" enterKeyHint="send" autoComplete="email" onChange={(e) => setText(e.target?.value ?? e.nativeEvent?.text ?? '')} onKeyDown={(e) => { if (e.key === 'Enter') submit() }} readOnly />
The old React Native props still work but are deprecated.
Image
Image now uses web-standard src instead of React Native’s source:
// before<Image source={{ uri: 'https://example.com/photo.jpg', width: 200, height: 200 }} resizeMode="cover" />// after<Image src="https://example.com/photo.jpg" width={200} height={200} objectFit="cover" />
Button
- Text style props (
fontFamily,fontSize, etc.) removed from the direct API — style text through child components - Now defaults to
type="button"to prevent accidental form submissions useButtonhook is deprecated
ListItem
- Text style props removed — use
ListItem.TextandListItem.Subtitlechild components - Internal spacing props (
spaceFlex,scaleSpace) removed
Tabs
activationModenow defaults to'manual'(was'automatic') — users must click or press Enter to change tabs, matching web standardsTabs.Triggerdeprecated in favor ofTabs.Tab
Group
Group.Itemwrapper is now required (no more auto-cloning direct children)- Removed props:
space,separator,scrollable,showScrollIndicator,disablePassBorderRadius,forceUseItem - Add
<Separator />manually between items when needed
Sheet / Popover.Sheet
Popover.Sheet sub-components are replaced with standalone Sheet:
// before<Adapt when="maxMd" platform="touch"><Popover.Sheet modal dismissOnSnapToBottom><Popover.Sheet.Frame p="$4"><Adapt.Contents /></Popover.Sheet.Frame><Popover.Sheet.Overlay animation="quick" /></Popover.Sheet></Adapt>// after<Adapt when="max-md" platform="touch"><Sheet modal dismissOnSnapToBottom><Sheet.Frame p="$4"><Adapt.Contents /></Sheet.Frame><Sheet.Overlay transition="quick" /></Sheet></Adapt>
Select
SelectLabel is now SizableText-based instead of ListItem-based.
7. Removed APIs
<Spacer />removed from core — import from@tamagui/spacercomposeEventHandlers— compose manually:(val) => { a(val); b?.(val) }useTheme(props)— use<Theme>component insteadThemeableStack— useViewbackgroundedprop — usebg="$background"selectableprop — useselect="text"animatePresenceprop (inline) — wrap with<AnimatePresence>componentscrollbarWidthprop — use CSSisWindowDefined— useisBrowserfrom@tamagui/constantsuseThemefrom@tamagui/next-theme— renamed touseThemeSetting@tamagui/react-native-use-responder-events— use pointer events (onPointerDown, etc.)
8. Native Setup
v2 requires explicit setup imports for native features. Add these at the top of your app entry before any Tamagui imports. Note these are optional and mostly new, so only if you were using native gradient, or toast before do you need to do this.
// portals (Sheet, Dialog, Popover, Select, Toast)import '@tamagui/native/setup-teleport'// LinearGradientimport '@tamagui/native/setup-expo-linear-gradient'// Toast (burnt)import '@tamagui/native/setup-burnt'// Menu (zeego)import '@tamagui/native/setup-zeego'// for smoother Sheet on native:import '@tamagui/native/setup-gesture-handler'
9. Build Config
Not much has changed, but there’s some nice improvements you can make:
Vite
import { tamaguiAliases, tamaguiPlugin } from '@tamagui/vite-plugin'export default {plugins: [tamaguiPlugin()],resolve: {alias: [...tamaguiAliases({rnwLite: true, // use lightweight react-native-websvg: true,}),],},}
Create a tamagui.build.ts at your project root:
import type { TamaguiBuildOptions } from 'tamagui'export default {components: ['tamagui'],config: './src/tamagui.config.ts',outputCSS: './src/tamagui.generated.css',} satisfies TamaguiBuildOptions
Metro (Expo / React Native)
Enable package exports (required for Tamagui v2 subpath imports):
// metro.config.jsconst { getDefaultConfig } = require('expo/metro-config')const config = getDefaultConfig(__dirname)config.resolver.unstable_enablePackageExports = trueconfig.resolver.unstable_conditionNames = ['require', 'react-native', 'import']module.exports = config
Next.js (Turbopack)
Add resolveExtensions and a react-native-safe-area-context shim:
// next.config.jsmodule.exports = {turbopack: {resolveAlias: {'react-native': 'react-native-web','react-native-svg': '@tamagui/react-native-svg','react-native-safe-area-context': './shims/react-native-safe-area-context.js',},resolveExtensions: ['.web.tsx','.web.ts','.web.js','.web.jsx','.tsx','.ts','.js','.jsx','.json',],},}
CSS
We recommend generating Tamagui CSS to tamagui.generated.css, just so it’s easier to configure linters and such:
// beforeimport './tamagui.css'// afterimport './tamagui.generated.css'
Generate it with: npx tamagui generate-css --output ./src/tamagui.generated.css
10. Quick Reference: Find-and-Replace Patterns
These are the most common replacements you can do across your codebase. Run carefully and review each change:
animation= -> transition=
AnimationProp -> TransitionProp
AnimationKeys -> TransitionKeys
tag= -> render=
themeInverse -> theme="accent"
<Theme inverse -> <Theme name="accent"
<Stack -> <View
</Stack> -> </View>
StackProps -> ViewProps
onHoverIn= -> onPointerEnter=
onHoverOut= -> onPointerLeave=
$2xl= -> $xxl=
$2xs= -> $xxs=
maxMD -> max-md
maxLG -> max-lg
maxSM -> max-sm
maxXL -> max-xl
max2Xl -> max-xxl
11. New Features Worth Knowing
While not required for migration, these v2 features can improve your app:
boxShadow— full CSS box-shadow support with tokensbackgroundImage— CSS gradients with token supportfilter,mixBlendMode— graphical effectsscopeprop on Dialog, Popover, Sheet, Tooltip — mount once at root for performanceactiveStyle/activeThemeon Switch, Checkbox, ToggleGroup, Tabs- Multiple animation drivers via
animatedByprop transitiondelay and enter/exit control —transition={{ enter: 'lazy', exit: 'quick' }}- Menu and ContextMenu — new components with native platform rendering
- Headless components —
@tamagui/switch-headless,@tamagui/checkbox-headless, etc. - CLI commands —
tamagui build,tamagui generate,tamagui generate-prompt,tamagui check - ~32% smaller core bundle (37KB to 25KB gzipped)
Migration Checklist
- Upgrade React 19+, React Native 0.81+, TypeScript 5+
- Bump all
@tamagui/*packages to^2.0.0 - Add package resolutions for
@tamagui/core,@tamagui/web,tamagui - Update config from
@tamagui/config/v4to@tamagui/config/v5 - Import animations separately (
@tamagui/config/v5-cssor other driver) - Add
declare module 'tamagui'type augmentation - Move root-level settings into
settingsobject - Rename
animationprop totransitioneverywhere - Rename
tagprop torender - Replace
StackwithView,StackPropswithViewProps - Replace
themeInverse/<Theme inverse>withtheme="accent" - Replace
space/spaceDirectionwithgap - Replace
onHoverIn/onHoverOutwith pointer events - Migrate shadows to
boxShadow - Update media query names (
$2xlto$xxl, max queries to kebab-case) - Update accessibility props to ARIA equivalents
- Update Input props to web-standard API
- Update Image from
sourcetosrc - Wrap Group children in
Group.Item - Update Tabs (
activationModedefault changed,TriggertoTab) - Replace
Popover.Sheet.*with standaloneSheet.* - Update Toast from
ToastProviderwrapper toToastersibling - Import
Spacerfrom@tamagui/spacerif used - Replace
@tamagui/animations-motiwith@tamagui/animations-reanimated - Add native setup imports (portals, LinearGradient, Toast, Menu)
- Review flex/position defaults (add
position="relative"where needed) - Update build config (Metro package exports, Vite aliases, CSS generation)
- Update snapshot tests (shadow colors now use
color-mix()) - Run
npx tamagui checkto verify dependency consistency