Upgrading to v3
A complete guide for migrating Tamagui v2 apps to Tamagui v3.
Tamagui v3 removes deprecated APIs, makes tokens and themes more predictable, and gives every adaptive overlay the same composition model.
Most migrations are mechanical, but the Sheet anatomy, token changes, and focus behavior should be verified in real screens.
If you are on v1, migrate through the v2 guide first or run the v1-to-v3 AI migration prompt from the CLI. v3 assumes the v2 web-standard prop model, defaults to Config v6, and has a React 19, React Native 0.81+, and TypeScript 5+ baseline.
Start with a smaller, complete upgrade
The runtime version and config version are separate. V3 APIs with your existing design values is the recommended first checkpoint.
- From V2: migrate the packages and required APIs while preserving your token, theme, font, media, and animation values. Do not replace a custom config with v6 defaults as part of the syntax conversion.
- From Config v5 or v5-subtle: keep that supported config through the API migration. Keep its 12-step colors and physical scales; the v6 remaps later in this guide are for a separate design migration.
- Already on V3 with Config v5: check the remaining API work and validate it. You do not need to adopt Config v6 to finish upgrading to V3.
Record representative screens before editing. Finish with typecheck, build, report
review, and the same browser/native interactions and screenshots. Defer Config v6,
wholesale html.* conversion, and Tailwind adoption unless you chose them as
separate work. The agent setup prompt makes this checkpoint explicit:
Required: migrate tokens and conditional styles
V3 accepts bare token and theme names, with conditions expressed as typed
objects or string clauses on each style property. There is no runtime
compatibility path for sigil-prefixed values or V2 condition objects such as
hoverStyle.
Run the codemod from your project root before the API-specific steps below:
yarn
npm
bun
pnpm
For example, this V2 migration input:
becomes:
Read and hand-fix every flagged report row, then rerun until the migrated path has no V2 authoring sites. See Flat Conditional Values for the grammar and full workflow.
Declare query containers separately from groups
group now provides parent interaction state only. It no longer adds CSS
containment or native layout measurement. A named size clause needs an explicit
container on the ancestor it targets:
Use container for an unnamed inline-size container, container="main" for a
named inline-size container, and container="main" containerType="size" for
containment on both axes. container={false} and container={undefined} do
nothing, so conditional roots can use container={isRoot}. Raw CSS
containerName and containerType longhands remain available in style() and
style objects. An explicit longhand wins the property it names when used with
container.
Remove webContainerType from createTamagui settings. V3 has no global
container-type compatibility setting.
Plan for that review before starting a large migration. On the V2 Bento corpus,
the codemod found 2,113 sites. It classified 1,681 as clean and left 412 of
2,052 JSX sites plus 20 of 61 styled() configuration sites for a person to
review. That is 432 sites, or 20.4% of the corpus. After the proposed conversion,
63 of 208 files still contained legacy condition objects. A successful codemod
run means the report was generated, not that the application is ready for V3.
1. Update packages
Bump all tamagui and @tamagui/* packages together. The tamagui command
is @tamagui/cli, so add it as a dev dependency first if the app does not have
it yet.
yarn
npm
bun
pnpm
Then install the v3 versions with your package manager and make sure your lockfile
does not contain duplicate @tamagui/core, @tamagui/web, or tamagui copies.
Choose the component surface
V3 gives each styling mode an explicit import path. Import the default styled skin
from its generated tamagui/<component> subpath:
The tamagui root remains the convenient full barrel. Component subpaths are the
smaller delivery path for apps and compiler configuration that only need selected
skins. Use tamagui/unstyled or @tamagui/ui for behavior primitives without the
default skin, and @tamagui/tailwind for Tailwind-authored primitives. The Styled,
Unstyled, and Tailwind switch above every v3 docs page changes examples between
these authoring modes.
The V3 roots for Avatar, Tabs, and Group retain behavior and structural
layout, but no longer supply the old opinionated visual defaults. There are no
tamagui/avatar or tamagui/group default-skin subpaths. Keep the behavior
imports and add the exact visual styles your application needs:
Apply the same treatment to Group and its children. Existing code that relied
on V2 defaults can still compile while rendering without that chrome, so inspect
these components visually rather than relying only on typecheck.
Remove tooling packages that no longer exist in v3:
| Removed package | Migration |
|---|---|
@tamagui/babel-plugin | Install @tamagui/cli and use @tamagui/cli/vite, @tamagui/cli/metro, or the tamagui build precompile command for Turbopack. |
@tamagui/get-button-sized | Call resolveSizing from @tamagui/core with the size name; see §5. |
@tamagui/sizable-context | Use createStyledContext from @tamagui/core with your own size names. |
@tamagui/static-sync | Remove direct imports. Compiler sessions are owned by the supported bundler adapters. |
@tamagui/static-worker | Remove direct imports. Metro owns its graph/cache workers through @tamagui/cli/metro. |
@tamagui/theme-builder | Freeze the generated themes or use @tamagui/config-v5 for the intermediate checkpoint. Adopt @tamagui/themes/builder recipes only in a separate v6 migration. |
- Removed package
@tamagui/babel-plugin- Migration
- Install
@tamagui/cliand use@tamagui/cli/vite,@tamagui/cli/metro, or thetamagui buildprecompile command for Turbopack. - Removed package
@tamagui/get-button-sized- Migration
- Call
resolveSizingfrom@tamagui/corewith the size name; see §5. - Removed package
@tamagui/sizable-context- Migration
- Use
createStyledContextfrom@tamagui/corewith your own size names. - Removed package
@tamagui/static-sync- Migration
- Remove direct imports. Compiler sessions are owned by the supported bundler adapters.
- Removed package
@tamagui/static-worker- Migration
- Remove direct imports. Metro owns its graph/cache workers through
@tamagui/cli/metro. - Removed package
@tamagui/theme-builder- Migration
- Freeze the generated themes or use
@tamagui/config-v5for the intermediate checkpoint. Adopt@tamagui/themes/builderrecipes only in a separate v6 migration.
LinearGradient needs no native setup
LinearGradient draws a backgroundImage gradient on native, so
@tamagui/native/setup-expo-linear-gradient is gone. Delete that import, and
remove expo-linear-gradient from your dependencies unless your own code uses
it. Native gradients need React Native 0.76 or later with the new
architecture.
Config and theme compatibility
V3 defaults to Config v6. It also keeps frozen static v5 and v5-subtle packs so existing applications can preserve their token values and generated themes while migrating the rest of the v3 API.
The dynamic v5 theme builders are not part of @tamagui/config. An application
that generates its own v5 themes with createV5Theme, subtleChildrenThemes,
or @tamagui/theme-builder has three options:
- Freeze the generated themes. The builder is a value generator. Run it once on your current version, serialize the result to a static literal, and the application stops importing the removed API while every resolved value survives unchanged. This is the cheapest path and the right one whenever the palette is settled, especially if its values were measured or tuned against a visual reference.
@tamagui/config-v5, an opt-in package carrying the dynamic builders. It is deliberately separate from@tamagui/configso the builder code and its dependencies stay out of the default install.- Rebuild on the v6 recipe API. The real destination, and a migration to schedule on its own rather than alongside the flat-values conversion.
No config bundles an animation driver, so import one alongside it. The old
v5-* animation paths remain aliases for compatibility, while the unversioned
paths are preferred for new code.
| V2 entry | V3 status or replacement |
|---|---|
@tamagui/config (root, config / configWithoutAnimations) | defaultConfig from @tamagui/config/v6 |
@tamagui/config/v3 or /v4 | defaultConfig from @tamagui/config/v6 |
@tamagui/config/v5 or /v5-subtle | Retained as frozen static compatibility packs. Use /v6 for new applications. |
@tamagui/config animations / reanimated | @tamagui/config/animations-css, animations-rn, animations-reanimated, or animations-motion |
@tamagui/config/v5-css, /v5-rn, /v5-reanimated, or /v5-motion | Retained aliases for the matching unversioned animation entries. |
old config fonts, media, breakpoints, and shorthands | the matching exports from @tamagui/config/v6-base |
@tamagui/config createGenericFont | createSystemFont from @tamagui/config/v6-base |
old @tamagui/themes root behavior | v6 static themes and tokens from @tamagui/themes |
@tamagui/themes/v5, /v5-subtle, or /v5-tokens | Retained as frozen static compatibility packs. The /v5-builder entries are removed. |
@tamagui/theme-builder | recipe helpers from @tamagui/themes/builder |
@tamagui/colors/legacy (the pre-v2 Radix ramps: blue/blueDark, gray/grayDark, …) | @tamagui/colors, which exports the same names from the current Radix scales |
- V2 entry
@tamagui/config(root,config/configWithoutAnimations)- V3 status or replacement
defaultConfigfrom@tamagui/config/v6- V2 entry
@tamagui/config/v3or/v4- V3 status or replacement
defaultConfigfrom@tamagui/config/v6- V2 entry
@tamagui/config/v5or/v5-subtle- V3 status or replacement
- Retained as frozen static compatibility packs. Use
/v6for new applications. - V2 entry
@tamagui/configanimations/reanimated- V3 status or replacement
@tamagui/config/animations-css,animations-rn,animations-reanimated, oranimations-motion- V2 entry
@tamagui/config/v5-css,/v5-rn,/v5-reanimated, or/v5-motion- V3 status or replacement
- Retained aliases for the matching unversioned animation entries.
- V2 entry
- old config
fonts,media,breakpoints, andshorthands - V3 status or replacement
- the matching exports from
@tamagui/config/v6-base - V2 entry
@tamagui/configcreateGenericFont- V3 status or replacement
createSystemFontfrom@tamagui/config/v6-base- V2 entry
- old
@tamagui/themesroot behavior - V3 status or replacement
- v6 static
themesandtokensfrom@tamagui/themes - V2 entry
@tamagui/themes/v5,/v5-subtle, or/v5-tokens- V3 status or replacement
- Retained as frozen static compatibility packs. The
/v5-builderentries are removed. - V2 entry
@tamagui/theme-builder- V3 status or replacement
- recipe helpers from
@tamagui/themes/builder - V2 entry
@tamagui/colors/legacy(the pre-v2 Radix ramps:blue/blueDark,gray/grayDark, …)- V3 status or replacement
@tamagui/colors, which exports the same names from the current Radix scales
The v6 replacements above adopt new design defaults. For the intermediate
checkpoint, retain @tamagui/config/v5 or /v5-subtle and import
defaultConfig from that entry. If coming from older config entries or custom
builders, freeze their resolved tokens and themes before replacing the removed
imports; switching to v5 or v6 by name does not preserve those values.
For example, when separately adopting v6:
Replace the React Native animation driver on web
V3 keeps @tamagui/animations-react-native on native and removes its web
implementation. A web config that calls its createAnimations now throws during
setup. Choose @tamagui/animations-css, @tamagui/animations-motion, or
@tamagui/animations-reanimated for web. Use platform-specific config files if the
same application keeps @tamagui/animations-react-native on native.
Apps that use Sheet or the animated-number hooks with the CSS driver must import
createAnimations from @tamagui/animations-css/extras. The root
@tamagui/animations-css entry omits those hooks, so Sheet throws when it tries
to animate its position.
Remove @tamagui/babel-plugin from Babel configuration and dependencies. Vite
and Metro perform compilation through their dedicated plugins:
Clear Metro after config changes
The beta Metro plugin does not yet invalidate Metro’s transform cache when a
Tamagui lowering plan changes but the app source does not. After editing
tamagui.config.ts or another compiler input, stop the live Metro process and
restart it with a cleared cache:
yarn
npm
bun
pnpm
For a warm CLI production build, remove the app-local Tamagui cache and use the Expo command’s Metro reset flag:
Terminal
Without this step, Metro can serve stale compiled output from the earlier plan. Expect the first rebuild after clearing caches to be slower because Metro must replan every module. The beta measured about 12 ms per module: roughly 27 seconds for a 2,328-module starter and about two minutes for a 10,000-module application. Later warm builds are unaffected.
Recommended: defer Config v6 to its own migration
Upgrading to V3 and moving from Config v5 to v6 are two separate migrations,
and a mature application should do only the first. Stay on
@tamagui/config/v5, which V3 supports as a frozen static pack.
They cost differently. The V3 upgrade is a large but mechanical syntax change with a codemod, a report, and a visible finish line. The v6 move is a design change: it buys Tailwind alignment and costs a visible shift in spacing, sizing and color across the whole application, and no tool can tell you whether the result is right.
Stacking them means every visual difference has two possible causes and none of them can be bisected. Staying on v5 preserves the scales while you convert syntax. Component API changes can still affect layout, so compare actual controls and interactions against the baseline rather than accepting visual changes automatically.
Adopt v6 afterwards, piece by piece, when there is budget for the visual review.
The space, size and radius scales change value
Read this before running the codemod with --write against a v6 config. The
codemod converts spelling and never edits createTamagui(), so a token keeps
its name, changes its value, and the report calls the site clean.
The v5 scale is a hand-tuned curve; the v6 scale is Tailwind’s 4px grid. 15 of
16 integer space tokens differ, and they diverge further the larger the token:
$4 is 18px in v5 and 16px in v6, $10 moves 60px to 40px, and $20 moves
186px to 80px.
Do not migrate these by name. Mapping by resolved value lands far closer:
| v5 token | value | by name | by nearest value |
|---|---|---|---|
$1.5 | 4 | 1-5 = 6 | 1 = 4, exact |
$3.5 | 16 | 3-5 = 14 | 4 = 16, exact |
$5 | 24 | 5 = 20 | 6 = 24, exact |
$6 | 32 | 6 = 24 | 8 = 32, exact |
$16 | 144 | 16 = 64 | 36 = 144, exact |
- v5 token
$1.5- value
- 4
- by name
1-5= 6- by nearest value
1= 4, exact- v5 token
$3.5- value
- 16
- by name
3-5= 14- by nearest value
4= 16, exact- v5 token
$5- value
- 24
- by name
5= 20- by nearest value
6= 24, exact- v5 token
$6- value
- 32
- by name
6= 24- by nearest value
8= 32, exact- v5 token
$16- value
- 144
- by name
16= 64- by nearest value
36= 144, exact
Note also that the v6 spelling uses a hyphen: $2.5 becomes 2-5. Quarter
steps such as $0.75 have no v6 equivalent at any spelling, so those sites need
a real value rather than a rename, and a raw number is often the honest answer.
Theme migration
You can defer this section while using the frozen v5 compatibility packs. Apply it when moving the application’s values and themes to Config v6.
V3’s default adaptive ramp has 11 keys, and every key is hyphenated. The numbers
still mean steps relative to the active theme’s own scheme, not Tailwind’s
absolute 50 through 950 shades; the hyphen is the only spelling change.
Because the ramp is one step shorter, the middle of a V2 12-step ramp compresses:
| V2 | V3 | V2 | V3 |
|---|---|---|---|
color1 | color-1 | color7 | color-6 |
color2 | color-2 | color8 | color-7 |
color3 | color-3 | color9 | color-8 |
color4 | color-4 | color10 | color-9 |
color5 | color-5 | color11 | color-10 |
color6 | color-6 | color12 | color-11 |
- V2
color1- V3
color-1- V2
color7- V3
color-6- V2
color2- V3
color-2- V2
color8- V3
color-7- V2
color3- V3
color-3- V2
color9- V3
color-8- V2
color4- V3
color-4- V2
color10- V3
color-9- V2
color5- V3
color-5- V2
color11- V3
color-10- V2
color6- V3
color-6- V2
color12- V3
color-11
The endpoints map exactly. color6 and color7 both land on color-6, so
inspect contrast where those two used to be distinct.
Palette-specific steps such as blue9 are a separate migration. The default v6
config does not define blue-9 or any other palette step; it defines absolute
Tailwind tokens such as blue-500. Either replace the old name with an absolute
token, enter theme="blue" and use an adaptive color-N value from that theme,
or opt into 12-step scales with createV6Config({ scales }), which adds
blue-1 through blue-12 to the base themes.
A missing color value is dropped without an error or warning, and the web and
native renderers can expose different fallback colors underneath it. The
flat-values codemod cannot choose the intended replacement, because custom
configs may still define those names and the closest Tailwind shade depends on
the design. It preserves $blue10 as blue10 and emits a non-blocking
legacy-palette-token configuration warning for every old palette name it
finds. Write mode still applies the safe syntax conversion, so resolve every
warning before running the converted application. This search also finds old
ramp and palette names in source the flat-values codemod does not need to edit:
Terminal
Shadows are per-theme values again, as in v5: shadow1 through shadow6
become shadow-1 through shadow-6, and the default themes carry a seventh
step. Dark themes use stronger shadows than light themes, shadow-color is the
active theme’s shadow-3, and boxShadow="0 8px 24px shadow-4" reads
shadow-4 from the active theme rather than a fixed token.
Replace old surface themes with relative levels:
| V2 | V3 |
|---|---|
surface1 | level2 |
surface2 | level3 |
surface3 | level4 |
surface4 | level4 (clamped) |
- V2
surface1- V3
level2- V2
surface2- V3
level3- V2
surface3- V3
level4- V2
surface4- V3
level4(clamped)
Levels compose relative to the current theme. A nested level2 below an
existing level2 resolves to absolute level 3 while preserving a surrounding
color theme.
Replace the old builder input:
Custom theme generation now uses the tree API:
Remove the v2 builder exports and options defaultComponentThemes,
componentThemes, templates, masks, childrenThemes, and
grandChildrenThemes. Put hierarchy directly in the tree, semantic shade
choices in scale objects, and exact one-theme overrides in values. Components
no longer resolve uppercase name segments such as
light_Button automatically. A component that needs a raised surface should create a
normal level2 boundary in its own skin.
Search configuration and application code together:
Terminal
See Creating Themes for the recipe API and Surfaces and levels for nesting semantics.
In monorepos, keep resolutions/overrides aligned:
2. Migrate Sheet anatomy
Sheet.Frame has been replaced by Sheet.Container and Sheet.Background.
The flat-values codemod above rewrites every Sheet.Frame it can prove is
Tamagui’s: the element becomes Sheet.Container, and a Sheet.Background
carrying the surface props (bg, border*, shadow*,
disableHideBottomOverflow) is inserted as its first child. A spread on the
Frame and a styled(Sheet.Frame, …) target are rewritten too but reported
for review, because the codemod cannot see which of those props are surface
props. Review every changed Sheet against the rules below.
Rules to verify:
Sheet.Overlaymust be a direct child ofSheet.Sheet.Containerowns layout props likepadding,gap,height,maxHeight, and flex props.Sheet.Backgroundowns visual surface props likebg,borderRadius,shadow*, and decorative absolute layers.Sheet.Containerno longer clips withoverflow="hidden". Add clipping explicitly if your content relied on the old frame clipping.Sheet.BackgroundownsdisableHideBottomOverflow.
3. Replace removed props and aliases
Search for APIs that need migration or review. This scan deliberately excludes
the supported Config v5 entries and their retained palette names. color-12,
surface1–surface4, and builders imported from @tamagui/config-v5 do not
require a v6 theme migration to complete this checkpoint.
Terminal
Use these replacements:
| Removed | Replacement |
|---|---|
focusable | tabIndex |
fullscreen | explicit position and inset props |
themeInverse | theme="inverse" |
<Theme inverse> | <Theme name="inverse"> |
Sheet.Frame | Sheet.Container plus Sheet.Background |
Component.styleable(fn) | createStyledHOC(Component, fn) |
createStyledHOC(Component)<Props>(fn) | createStyledHOC(Component, (props: Props, ref) => ...) |
| forwardRef wrapper statics | direct refs and normal component composition |
inlineWhenUnflattened | remove it |
| deprecated UI kit aliases | import the current component names |
| old platform condition keys | use flat web:, native:, ios:, android:, etc. clauses |
forceRemoveScrollEnabled | disableRemoveScroll with inverted intent |
selectable on Text | userSelect (core maps it to RN’s selectable on native) |
AnimatePresence enterVariant / exitVariant / enterExitVariant | custom={{ ... }} plus a variant on the child that emits enter: / exit: clauses |
Avatar.Fallback delayMs | delay |
Sheet disableTransparencyHide | disableHideWhenClosed |
Select.Item index | remove it, registry order is authoritative |
TamaguiProvider / ThemeProvider disableRootThemeClass and themeClassNameOnRoot | createTamagui({ settings: { addThemeClassName } }) |
@tamagui/input v1 Input / TextArea | the current Input and TextArea from @tamagui/input |
Button from @tamagui/button | Button from tamagui; @tamagui/button ships ButtonFrame, ButtonText, ButtonIcon and useButton only |
isWindowDefined | isBrowser |
createMedia from @tamagui/react-native-media-driver | remove the call, createTamagui sets the driver up |
getExpandedShorthands | getExpandedShorthand(key, props) when behavior code needs one authored prop and accepts configured shorthands |
useProps, useStyle(props), usePropsAndStyle | keep conditional values on styled Tamagui components; use splitStyleProps only when a wrapper must partition authored props |
createCheckbox sizeAdjust | explicit sizing math or component styles |
animateOnly | properties inside the transition, see Migrate transition values |
transition array form, default, per-property type | the v3 transition grammar |
@tamagui/animations-moti | @tamagui/animations-reanimated |
@tamagui/config/reanimated | @tamagui/config/animations-reanimated |
accept option in styled() | piece-typed props (e.g. StylePiece) or real style keys |
'...size' / '...fontSize' spread variant keys | codemod writes styled.dynamic<TokenType>(...); report flags sibling-prop reads for .resolve |
':string' / ':number' / ':boolean' type-key variants | codemod writes one typed dynamic and combines object returns with typeof; report flags other bodies and mixed exact keys |
'...' catch-all variant key | report only; choose the explicit styled.dynamic<YourValue>(...) generic yourself |
- Removed
focusable- Replacement
tabIndex- Removed
fullscreen- Replacement
- explicit
positionandinsetprops - Removed
themeInverse- Replacement
theme="inverse"- Removed
<Theme inverse>- Replacement
<Theme name="inverse">- Removed
Sheet.Frame- Replacement
Sheet.ContainerplusSheet.Background- Removed
Component.styleable(fn)- Replacement
createStyledHOC(Component, fn)- Removed
createStyledHOC(Component)<Props>(fn)- Replacement
createStyledHOC(Component, (props: Props, ref) => ...)- Removed
- forwardRef wrapper statics
- Replacement
- direct refs and normal component composition
- Removed
inlineWhenUnflattened- Replacement
- remove it
- Removed
- deprecated UI kit aliases
- Replacement
- import the current component names
- Removed
- old platform condition keys
- Replacement
- use flat
web:,native:,ios:,android:, etc. clauses - Removed
forceRemoveScrollEnabled- Replacement
disableRemoveScrollwith inverted intent- Removed
selectableon Text- Replacement
userSelect(core maps it to RN’sselectableon native)- Removed
AnimatePresenceenterVariant/exitVariant/enterExitVariant- Replacement
custom={{ ... }}plus a variant on the child that emitsenter:/exit:clauses- Removed
Avatar.FallbackdelayMs- Replacement
delay- Removed
SheetdisableTransparencyHide- Replacement
disableHideWhenClosed- Removed
Select.Itemindex- Replacement
- remove it, registry order is authoritative
- Removed
TamaguiProvider/ThemeProviderdisableRootThemeClassandthemeClassNameOnRoot- Replacement
createTamagui({ settings: { addThemeClassName } })- Removed
@tamagui/inputv1Input/TextArea- Replacement
- the current
InputandTextAreafrom@tamagui/input - Removed
Buttonfrom@tamagui/button- Replacement
Buttonfromtamagui;@tamagui/buttonshipsButtonFrame,ButtonText,ButtonIconanduseButtononly- Removed
isWindowDefined- Replacement
isBrowser- Removed
createMediafrom@tamagui/react-native-media-driver- Replacement
- remove the call,
createTamaguisets the driver up - Removed
getExpandedShorthands- Replacement
getExpandedShorthand(key, props)when behavior code needs one authored prop and accepts configured shorthands- Removed
useProps,useStyle(props),usePropsAndStyle- Replacement
- keep conditional values on styled Tamagui components; use
splitStylePropsonly when a wrapper must partition authored props - Removed
createCheckboxsizeAdjust- Replacement
- explicit sizing math or component styles
- Removed
animateOnly- Replacement
propertiesinside the transition, see Migrate transition values- Removed
transitionarray form,default, per-propertytype- Replacement
- the v3 transition grammar
- Removed
@tamagui/animations-moti- Replacement
@tamagui/animations-reanimated- Removed
@tamagui/config/reanimated- Replacement
@tamagui/config/animations-reanimated- Removed
acceptoption instyled()- Replacement
- piece-typed props (e.g.
StylePiece) or real style keys - Removed
'...size'/'...fontSize'spread variant keys- Replacement
- codemod writes
styled.dynamic<TokenType>(...); report flags sibling-prop reads for.resolve - Removed
':string'/':number'/':boolean'type-key variants- Replacement
- codemod writes one typed dynamic and combines object returns with
typeof; report flags other bodies and mixed exact keys - Removed
'...'catch-all variant key- Replacement
- report only; choose the explicit
styled.dynamic<YourValue>(...)generic yourself
V2 could spread one style across its base prop, pseudo-style objects such as
hoverStyle, media objects such as $sm, and platform objects. The removed
hooks had to gather those objects to produce resolved props and styles. In v3,
the base value and every conditional clause for a style live on that style’s
single property:
String
Typed
The styled component interprets that value. Pass the property through instead
of flattening the component’s styles in JavaScript. If a wrapper must separate
style props from regular props, use splitStyleProps:
Pass a filter map to select only some canonical style keys. Rejected style
props remain in the second object, which supports cases such as splitting text
props from frame props without another loop. The filter may also be a callback
that receives (key, value, originalKey, isStyleProp).
If behavior code needs one authored property and supports a configured shorthand, read it with the singular helper:
getExpandedShorthand only chooses the longhand or shorthand property. It does
not resolve tokens or choose an active conditional clause. Neither does
splitStyleProps. Use useMedia() or useTheme() when behavior itself needs
active responsive or theme state.
The curried createStyledHOC form is a separate signature break from the
.styleable() rename. Move the custom prop type onto the render parameter when
you combine the two calls:
Spread, resolver-name, and type-key variants have no runtime matching in v3. A
leftover '...size', Size, or number key only matches that exact literal.
The flat-values codemod rewrites known spread and type keys to one branded
dynamic. Its report leaves catch-all keys, mixed exact branches, and sibling-prop
reads for manual migration:
accept is removed
The accept configuration option in styled() has been removed in v3. It previously handled two patterns:
- Piece-typed style props: Props that accept style objects (such as
activeStyleonCheckbox,ToggleGroup, andTabs, orcontentContainerStyleonScrollView) now accept aStylePiececreated withstyle(). - Input color style keys:
placeholderTextColor,selectionColor,cursorColor, andselectionHandleColoronInputandTextAreaare now real style properties. On web, they emit CSS rules (::placeholder,caret-color,::selection). On native, they map through the React Native compatibility layer.
Examples:
Web-standard style keys
Files you convert to html.* should use web-standard style keys. View and
Text keep accepting the React Native keys with no warnings, so only rename
where you adopt the web contract. Find legacy keys with:
Terminal
Use these replacements:
| Legacy key | Replacement |
|---|---|
marginHorizontal | marginInline (mx stays physical left/right, not RTL-aware inline start/end) |
marginVertical | marginBlock (my also works) |
paddingHorizontal | paddingInline (px also works) |
paddingVertical | paddingBlock (py also works) |
marginStart | marginInlineStart |
marginEnd | marginInlineEnd |
paddingStart | paddingInlineStart |
paddingEnd | paddingInlineEnd |
elevation | boxShadow |
shadowColor, shadowOffset, shadowOpacity, shadowRadius | one boxShadow string |
textAlignVertical | verticalAlign |
writingDirection | direction |
includeFontPadding | remove it, standard CSS font metrics govern text bounds |
- Legacy key
marginHorizontal- Replacement
marginInline(mxstays physical left/right, not RTL-aware inline start/end)- Legacy key
marginVertical- Replacement
marginBlock(myalso works)- Legacy key
paddingHorizontal- Replacement
paddingInline(pxalso works)- Legacy key
paddingVertical- Replacement
paddingBlock(pyalso works)- Legacy key
marginStart- Replacement
marginInlineStart- Legacy key
marginEnd- Replacement
marginInlineEnd- Legacy key
paddingStart- Replacement
paddingInlineStart- Legacy key
paddingEnd- Replacement
paddingInlineEnd- Legacy key
elevation- Replacement
boxShadow- Legacy key
shadowColor,shadowOffset,shadowOpacity,shadowRadius- Replacement
- one
boxShadowstring - Legacy key
textAlignVertical- Replacement
verticalAlign- Legacy key
writingDirection- Replacement
direction- Legacy key
includeFontPadding- Replacement
- remove it, standard CSS font metrics govern text bounds
On native, direction keeps the Yoga direction and also maps to
writingDirection, while verticalAlign maps to textAlignVertical, so
web-authored styles apply on both platforms.
Multi-value strings such as margin="10px 20px" and gap="10px 20px" expand
per CSS slot order on native.
Image source compatibility
Image prefers web-standard src for URLs and native require() results.
React Native’s source remains supported during migration, but is deprecated:
The width and height props control layout. Percentage dimensions also work
when the parent has defined dimensions.
4. Remove true token references
The default v3 configs no longer export the legacy $true token key. The
flat-values codemod writes $true on a style prop as 4, the bare key the
default config aliased it to, and reports the site with a legacy-true-token
warning so you can confirm your config agrees. On the size-typed variant props
size and iconSize it writes the boolean instead, which
resolves to the component’s default size.
The codemod does not edit token definitions or custom variants, so search config files and any remaining code for the alias:
Terminal
Only replace token keys. Boolean variant values like circular or unstyled={false}
are unrelated.
5. Control sizes are named
Every control that takes size (Button, Input, Select, Tabs, ListItem, Label,
Card, Toggle, Checkbox, RadioGroup, Switch, Slider, Progress, Avatar, Group)
accepts the same five names: xs, sm, md, lg, xl. A name is a
rung in the shared sizing ladder, never a height: it picks a font size,
horizontal and vertical padding, and a radius, and the control ends up its
line-height plus padding tall. That keeps the frame, its text, and its icon aligned by construction.
The default is md.
Under @tamagui/config/v6 the ladder mirrors shadcn:
| name | font size / line-height | paddingX | paddingY | radius | icon | height |
|---|---|---|---|---|---|---|
xs | 12 / 16 | 8 | 4 | 4 | 12 | 24 |
sm | 14 / 20 | 12 | 6 | 6 | 16 | 32 |
md | 14 / 20 | 16 | 8 | 6 | 16 | 36 |
lg | 16 / 24 | 24 | 8 | 6 | 16 | 40 |
xl | 18 / 28 | 32 | 10 | 8 | 20 | 48 |
- name
xs- font size / line-height
- 12 / 16
- paddingX
- 8
- paddingY
- 4
- radius
- 4
- icon
- 12
- height
- 24
- name
sm- font size / line-height
- 14 / 20
- paddingX
- 12
- paddingY
- 6
- radius
- 6
- icon
- 16
- height
- 32
- name
md- font size / line-height
- 14 / 20
- paddingX
- 16
- paddingY
- 8
- radius
- 6
- icon
- 16
- height
- 36
- name
lg- font size / line-height
- 16 / 24
- paddingX
- 24
- paddingY
- 8
- radius
- 6
- icon
- 16
- height
- 40
- name
xl- font size / line-height
- 18 / 28
- paddingX
- 32
- paddingY
- 10
- radius
- 8
- icon
- 20
- height
- 48
Height excludes the 1px border. @tamagui/config/v5 ships its own sizing on
the v5 keys with the same geometry pinned, so the same names work under either
config. Button, ListItem, Checkbox, RadioGroup, and Switch derive every value
from the ladder; add rungs by spreading defaultSizing (see
Sizing).
Token keys and numbers no longer size controls: size on a control only takes
a name (or true for the default), so size="4" on a control is a type error.
Numbers stay geometry on shapes (Square, Circle, Avatar take size tokens)
and font keys on text and icons.
Text components speak Tailwind’s own scale (xs, sm, base, lg, xl,
2xl and up). <Button size="md"> text and <Paragraph size="sm"> text are
the same 14/20. Text keeps base rather than md because text-base is what
Tailwind users know.
If you also adopt the unstyled primitives from @tamagui/ui, those own no size
scale at all, so you provide sizing yourself or through your copied styles.
An explicit undefined prop no longer clears a default
undefined
<Button size={props.size}> with props.size undefined now renders the md
default. In v2 an explicitly passed undefined replaced the defaultVariants
value and the styled-context value for that key, and the control rendered with
no size styles at all. Core now treats an explicit undefined the way React
defaultProps do: as absent (and the size resolvers default it too). This
applies to every styled component and every createStyledContext provider, not
only size. To deliberately turn a default variant off, pass null:
Smaller size-related removals
Checkbox,RadioGroupandSwitchnow step through five real sizes (17, 20, 22, 25 and 28px tall). In the first betassm,mdandlgall rendered at 22px.Tooltipno longer acceptssize. It only ever set the arrow px forTooltipSimple, which keepssizefor that purpose.@tamagui/uino longer exports aListItemIconcomponent. The behavior package publishescolorthroughListItemContext; the icon px is the skin’s decision (thetamaguiListItem still shipsListItem.Icon). TheListItemIconPropstype stays exported.- The unstyled
SliderandSlider.Thumbtakesizeas a px number or a font size key. Skin size names (sm,md) belong to the styledtamaguiSlider, which resolves them itself. useToggleGroupItem().colorisundefinedwhen no color was set, where it used to be an empty string.
6. Replace token stepping
@tamagui/get-token no longer supports runtime scale stepping. Removed APIs:
stepTokenUpOrDowngetTokenRelative- the second options argument to
getSize,getSpace, andgetRadius - options such as
shift,bounds, andexcludeHalfSteps
If you need to move from 5 to 4, use explicit token keys or a helper that
only handles numeric token names. Do not rebuild runtime scale sorting in app
code.
7. Migrate line height and audit exact font sizes
V3 changes numeric lineHeight authored on Tamagui components from an absolute
value to a ratio on both web and native. The migration command above selects the
old source semantics explicitly, so every proven V2 number becomes a px string.
The value does not use a magnitude heuristic: V2 lineHeight={1.5} becomes
lineHeight="1.5px", while a new V3 ratio stays lineHeight={1.5}.
The codemod follows Tamagui bindings through JSX, styled() styles and variants,
resolvers, and inline style objects. It leaves raw React Native and Restyle styles
alone. A shared style object, untyped dynamic expression, or extracted token
.val is reported for a manual decision at the Tamagui boundary. Rerunning with
--source-semantics v2-pixels is safe because explicit px output is idempotent.
Review existing numeric strings separately. The codemod leaves strings unchanged;
lineHeight="24" is a ratio unless 24 names a configured font token. Use
lineHeight="24px" if that string previously represented a native pixel length.
Font sizes keep their existing contract:
fontSize={17}is a numeric platform value and keeps the platform-default line-height path.fontSize="17px"is an exact pixel value. Web passes it through as CSS; native parses it to a number.
Font configuration is a separate boundary. Numeric lineHeight entries remain
absolute pixels, including the existing v5 and v6 scales. Use a numeric string
such as "1.5" for a relative font token, or "24px" to make an absolute
length explicit.
On native, a text node with its own font size and no local line height keeps a runtime inheritance boundary, even when its children are literal text. An ancestor in another module can supply a ratio that must be recomputed at this node’s font size. Token-sized Paragraph and SizableText with their own default line height can still be flattened by the compiler.
8. Update FocusScope usage
FocusScope now renders a display: contents wrapper. Function-as-children is
removed; pass JSX children directly.
Use noFocus when a surface should temporarily reject all focus:
noFocus takes precedence over trapped and auto-focus behavior. It is web-only;
FocusScope remains a no-op on native.
9. Update Dialog, Popover, and Select Adapt usage
Dialog, Popover, and Select now share the same Adapt handoff model. Open state
lives above AdaptParent, and adapted sheet content remains mounted until the
sheet finishes hiding.
Use the current Adapt anatomy:
Component-specific notes:
Dialog.Contentno longer accepts the no-opsizevariant.- Non-modal
Dialog.Contentno longer enables RemoveScroll while open. - Dialog, Popover, and Select parts own their presence lifecycles.
- Animation drivers can now complete part-level exits through the typed
onTransitionlifecycle. Popover.Content forceMountnow matches Dialog semantics and disables part presence gating.- Removed legacy Popover and Select sheet-controller internals. If app code
imported internal helpers like
useShowPopoverSheetoruseSelectBreakpointActive, remove those imports and use Adapt state instead.
10. Update Select APIs
Select has a few public API changes and parity additions.
Keep your form props. Select still accepts name on web, and custom Select
renders hidden <input> elements for it, so the selected value(s) submit with a
surrounding form. form associates the control with an external form by id. Both
are inert on native.
Do not remove name during migration. Removing it drops the hidden form inputs
and breaks form submission. Earlier drafts of this guide told you to delete
name; that was wrong.
Removed from Select root:
autoCompletewas removed. It never drove autofill in the custom Select, so delete it.
Added or expanded:
Select.SeparatorSelect.Content onEscapeKeyDownSelect.Content onInteractOutsidedata-state="open" | "closed"onSelect.Triggerdata-state="open" | "closed"on the webSelect.Viewport
11. Update themed icon usage
Themed icons now size token values through the current font size scale, not the space/size token scale. Raw numeric sizes are unchanged.
Themed icons no longer accept Tamagui media or pseudo props directly because they no longer run full style resolution.
Theme color, fill, stroke, size, strokeWidth, style, and testID still
work.
12. Check ScrollView web usage
@tamagui/scroll-view now has its own web implementation. It no longer routes
through react-native-web.
Covered web surface:
scrollToscrollToEndgetScrollableNode- RN-shaped
onScroll contentContainerStylehorizontal- scroll indicator props
Unsupported old lite/RNW props should be replaced:
- momentum scroll events
snapTo*propskeyboardDismissMode- any app-specific reliance on RNW’s legacy style system
13. Migrate transition values
V3 has one transition grammar: a CSS transition string, or an object whose keys
are the same values under names. The v2 array form, the default key, the
per-property type key, inline spring physics, and the animateOnly prop are
all gone. The flat-values codemod rewrites every static site, so run it first
and hand-migrate what it flags.
| V2 | V3 |
|---|---|
transition={['quick', { opacity: 'lazy' }]} | transition={{ preset: 'quick', opacity: 'lazy' }} |
transition={{ default: 'quick' }} | transition="quick" |
transition={{ x: { type: 'bouncy' } }} | transition={{ x: 'bouncy' }} |
transition={{ x: { type: 'bouncy', overshootClamping: true } }} | transition={{ x: { preset: 'bouncy', spring: { overshootClamping: true } } }} |
transition={{ damping: 20, stiffness: 250 }} | transition={{ spring: { damping: 20, stiffness: 250 } }} |
transition={{ bounciness: 8, speed: 12 }} | transition={{ spring: { stiffness: 342.101, damping: 24.684 } }} |
animateOnly={['transform', 'opacity']} | properties: 'transform, opacity' inside the transition |
animateOnly={[]} | transition="none" |
- V2
transition={['quick', { opacity: 'lazy' }]}- V3
transition={{ preset: 'quick', opacity: 'lazy' }}- V2
transition={{ default: 'quick' }}- V3
transition="quick"- V2
transition={{ x: { type: 'bouncy' } }}- V3
transition={{ x: 'bouncy' }}- V2
transition={{ x: { type: 'bouncy', overshootClamping: true } }}- V3
transition={{ x: { preset: 'bouncy', spring: { overshootClamping: true } } }}- V2
transition={{ damping: 20, stiffness: 250 }}- V3
transition={{ spring: { damping: 20, stiffness: 250 } }}- V2
transition={{ bounciness: 8, speed: 12 }}- V3
transition={{ spring: { stiffness: 342.101, damping: 24.684 } }}- V2
animateOnly={['transform', 'opacity']}- V3
properties: 'transform, opacity'inside the transition- V2
animateOnly={[]}- V3
transition="none"
The array form and default were two more ways to say “the base timing”, which
is what a bare string and the preset key already say. Per-property type
meant a preset name in one place and a spring-or-timing mechanism in another;
in v3 a preset name is always a preset name, and physics always live under
spring.
bounciness, speed, tension, and friction are removed. The codemod
converts them with React Native’s own Origami formulas, so a migrated spring
keeps the motion it had.
animateOnly is now part of the transition
animateOnly was an exclusive filter over the properties a transition named,
which is what a CSS transition-property list already is. Name the properties
in the transition itself:
A property the transition does not name is set rather than animated, on every
driver. transition="none" animates nothing and completes exits immediately,
which is what an empty animateOnly array did.
A standalone transitionProperty longhand narrows plain-CSS transitions in the
browser, but it does not reach the animation drivers. Use properties inside
the transition, or the property token in a transition string, so the list means
the same thing on all four drivers.
Preset names and values changed
Presets are springs now, defined once and identical on every driver. In v2 the
same name was a different motion per driver: bouncy was a 350ms cubic-bezier
on the web, a stiffness-120 spring on React Native, and a stiffness-90 spring on
Motion. Expect timing to shift slightly and check screens where motion matters.
The '100ms'-style preset names are removed. A duration is CSS in v3, so
transition="100ms" and transition="100ms ease-out" work with nothing
configured.
14. Update animation and sheet lifecycle callbacks
The untyped, enter-only onDidAnimate prop is gone. Every animated component now
takes a typed onTransition
that fires at the start and end of enter, exit, and in-place update transitions.
Sheet’s bespoke onAnimationComplete prop is replaced by a sheet-shaped
onTransition:
onPositionChange is unchanged.
The sheet no longer fades itself. A fully-closed sheet is hidden with
display: 'none' instead of by animating opacity to transparent, and there is no
overlay fade baked in. If you relied on the built-in fade, drive it yourself with
one of the two overlay fade patterns.
unmountChildrenWhenHidden semantics are unchanged.
The stable-2.4.x disableTransparencyHide prop is renamed to
disableHideWhenClosed (matching unmountChildrenWhenHidden). The old name is
removed, not aliased.
Sheet.Handle ships no opacity of its own in v3. If you used the default handle
dim or its fade-in on open, set opacity (and an open-driven opacity) on your
own Sheet.Handle.
15. Update active-state background colors
Checkbox and Switch checked states, Tabs active tab, and Toggle Group active
toggle used to default to backgroundActive. V3 components now read
background-press. The frozen v5 themes still retain backgroundActive, so
audit the component behavior even when keeping Config v5; the presence of the
old token alone does not mean the config must be migrated to v6.
Each of these components applies its default active background only when you do
not pass activeStyle, and a supplied activeStyle wins because it is applied
last. Override the active color per-instance with activeStyle:
If a custom theme defined backgroundActive to color these states, move that
color to background-press or pass it through activeStyle.
16. Optional: adopt the Tailwind frontend
Tailwind authoring is optional for v3. Select it by importing components from
@tamagui/tailwind; there is no global config mode.
Regular Tamagui components and Tailwind components can share a tree. Each
component has one styling language: Tailwind components accept className,
while components imported from tamagui or @tamagui/core accept Tamagui
style props.
Read Tailwind for Tamagui for current utility coverage and known gaps.
17. Migrate createTamagui defaultProps
createTamagui({ defaultProps }) is removed in v3.
Each of its uses has a more direct replacement:
- Styling defaults move to theme updates: declare
the value once in
variables, read it in your component styles, and redefine it per subtree on<ThemeUpdate>instead of forcing props onto every instance of a component. - A component’s default look belongs in the component itself — v3 components are behavior primitives plus styles you own, so edit your copied component instead of patching it from config.
- Prop propagation to children uses createStyledContext, which makes the shared keys typed props on every styled component that consumes the context.
18. Migrate off the imperative Toast
v3 removes the old imperative Toast. @tamagui/toast (and the Toast export from
tamagui) is now the composable + toast() API, and it’s the only Toast — there
is no longer a separate “v2” toast entry point. These removed exports have no
replacement under the same name:
| Removed (v2) | Replacement (v3) |
|---|---|
ToastProvider + ToastViewport | <Toast> + <Toast.Viewport><Toast.List /></Toast.Viewport> |
useToastController().show(title, opts) | toast(title, { description: opts.message, ...opts }) |
useToastController().hide() | toast.dismiss() |
useToastState() | rendering is handled by <Toast.List>; use useToasts() for the list |
- Removed (v2)
ToastProvider+ToastViewport- Replacement (v3)
<Toast>+<Toast.Viewport><Toast.List /></Toast.Viewport>- Removed (v2)
useToastController().show(title, opts)- Replacement (v3)
toast(title, { description: opts.message, ...opts })- Removed (v2)
useToastController().hide()- Replacement (v3)
toast.dismiss()- Removed (v2)
useToastState()- Replacement (v3)
- rendering is handled by
<Toast.List>; useuseToasts()for the list
The toast() function is global (no provider needed around callers). Mount the
toast parts once at the app root and trigger toasts from anywhere:
19. Replace ThemeableStack and SizableStack
ThemeableStack and SizableStack (and their themeableVariants /
themeableVariantStyles) are removed from @tamagui/stacks. Their one good idea —
composable chrome variants — moves into the copied layer as the Surface fixture
and its facets.
-
Extending
ThemeableStack: usestyled(YStack, { … })and add the styles you actually used inline. The removed variants map to plain styles: -
elevate→ a shadow read from generics. -
bordered→borderWidth+borderColor: 'border-color'. -
circular→borderRadius: 100_000(+ equal width/height for a circle). -
For panels/wells/toolbars, copy the
Surfacefixture from the registry (npx shadcn add surface). It is aYStack+ alevelvariant (themelevel2-4) + the composable facetsfilled outlined elevated rounded interactive, with nothing on by default:Facets read theme generics (
background,border-color, …), never the color scale, so they restyle for free under anylevelorthemechange.
20. Verification checklist
Run validation at the layer your app changed.
Code checks:
yarn
npm
bun
pnpm
Build checks:
yarn
npm
bun
pnpm
Runtime checks:
- Open every screen that uses
Sheet, especially fit sheets and sheets with scrollable content. - Test
Dialog,Popover, andSelectat the breakpoints where they Adapt to Sheet. - Verify close animations complete before content unmounts.
- Verify any
onDidAnimate/ SheetonAnimationCompletecallbacks were moved toonTransitionand fire as expected. - Verify sheet overlays still fade if you depended on the old built-in fade.
- Verify Checkbox, Switch, Tabs, and Toggle Group active/checked states still
read the way you want after the
backgroundActiveremoval. - Inspect Avatar, Tabs, and Group anywhere the V2 default chrome was part of the design. Add app-owned skins where needed.
- Verify non-modal Dialogs do not lock page scroll.
- Verify keyboard focus, Escape, outside click, and return focus behavior.
- Inspect icons next to text at every app size token you use.
- Check any custom config tokens that used
true, numeric font sizes, or token stepping. - After changing Tamagui config, restart Metro with
expo start -cbefore evaluating compiled output. - If you use Tailwind mode, compare web and native output for your supported utility classes.
What a clean typecheck will not catch
A flat value is a string program, so most of this grammar is not something TypeScript can check. The first large app through this migration reached zero type errors with several things quietly broken, and none of them would have been found without running it. Each item below is a real defect from that migration.
Token detection by sigil. Code shaped like value[0] !== '$' was a correct
way to tell a token from a raw color in v2 and is wrong in v3, where tokens have
no sigil. One icon helper doing this passed every token straight through as a
literal SVG fill and blanked every icon in the app. Resolve through the theme
and treat what it does not know as a literal value:
Conditions the codemod cannot reach. The codemod converts a style object at
a conversion site: a JSX attribute list, a styled() config, or a variant
branch. A hoverStyle returned from a helper or spread in conditionally is none
of those, so it survives, and v3 forwards an unrecognized hoverStyle prop
rather than applying it. The interaction silently stops happening.
Note that hover and press now ride the property they modify, so a helper that
used to return a separate hoverStyle object has to own the resting value too.
A caller that still sets its own bg next to the spread will overwrite the
whole program:
Flat values are space separated, so any color spliced into a program has to be
space free. rgba(0,0,0,0.5) is fine and rgba(0, 0, 0, 0.5) splits into
garbage clauses.
Variants that look like style props. The codemod converts style props and
the size-typed variant props size and iconSize, since v3 reads
those values as size-scale keys. On one element it rewrites color="$color12"
to color="color12" and size="$2" to size="2". It drops the sigil without
renaming the ramp, so apply the hyphen table above afterwards. A custom variant
that takes a token value keeps its $ spelling, and it typechecks while
resolving to nothing, so grep for ="$ after the run.
Half steps are renamed, not removed. $0.5 becomes 0-5 and $1.5 becomes
1-5. Keeping the same v5 scale preserves the resolved values through this
spelling change. Moving from v5 to v6 also changes the scale values; it needs
separate mapping and visual review, even when a token has the same name.
undefined inside an interpolated value. A conditional whose false branch is
undefined becomes the literal string "undefined" once it is wrapped in a
template literal, which produces invalid padding, color, and font values. Keep
the conditional as an expression instead:
Provider order. SafeAreaProvider now has to wrap TamaguiProvider. The v3
provider mounts a tracker that reads safe-area context to publish the inset
tokens, so the v2 nesting throws NO_INSETS_ERROR at startup and the whole app
renders an error screen. The message names SafeAreaProvider, which points away
from the upgrade.
Quick reference
Terminal
v1 to v3 path
For v1 apps, migrate in two passes:
- Apply the v1 to v2 changes: React 19, React Native 0.81+, TypeScript 5,
Config v5,
animationtotransition,tagtorender,StacktoView,spacetogap, ARIA/web prop renames, native setup imports, and v2 component API changes. - Apply the v2 to v3 changes in this guide.
The CLI migration prompt can generate an AI-agent checklist for either path:
yarn
npm
bun
pnpm