Flat Conditional Values
The V3 flat-value syntax and migration workflow
V3 puts every condition in the value of the property it changes. Each property accepts string clauses or a typed object, and both render identically.
Tokens and theme values are bare names. V2 condition objects such as
hoverStyle={{ backgroundColor: ... }} have no runtime path.
Value grammar
Typed objects use modifier names as keys and default for the base value.
Compound keys such as 'dark:hover' require every condition to match. These
are per-property values, distinct from the removed V2 condition objects.
String clauses use this grammar:
The base and each payload are ordinary property values. A registered modifier chain starts a new clause; the last matching clause wins.
Payloads run until the next registered modifier chain, including when they
contain spaces. An empty payload is invalid; clear a property with a real value such as none,
transparent, initial, or unset.
Do not put clauses in the raw transform prop. Use the flattened transform
family instead:
Token and theme lookup
Quoted identifiers resolve config-first. A configured token, theme key, or variable wins; a miss remains literal text so CSS keywords and custom values pass through.
Use a qualified token when the same name exists in more than one category:
A raw number remains a platform value:
Built-in multi-part V3 names are kebab-case. The runtime does not guess aliases:
| V2 name | V3 name |
|---|---|
backgroundHover | background-hover |
backgroundPress | background-press |
borderColor | border-color |
borderColorHover | border-color-hover |
placeholderColor | placeholder-color |
shadowColor | shadow-color |
- V2 name
backgroundHover- V3 name
background-hover- V2 name
backgroundPress- V3 name
background-press- V2 name
borderColor- V3 name
border-color- V2 name
borderColorHover- V3 name
border-color-hover- V2 name
placeholderColor- V3 name
placeholder-color- V2 name
shadowColor- V3 name
shadow-color
Modifiers
The registry combines built-ins with names from your config.
| Kind | Spellings |
|---|---|
| interaction | hover: press: focus: focus-visible: focus-within: disabled: |
| presence | enter: exit: |
| theme | dark: light: and configured theme names |
| media | configured keys such as sm: md: max-md: |
| platform | web: native: ios: android: tv: androidtv: tvos: |
| group | group-hover: and named forms such as group-hover/card: |
| container | @sm: and named forms such as @sm/layout: |
- Kind
- interaction
- Spellings
hover:press:focus:focus-visible:focus-within:disabled:- Kind
- presence
- Spellings
enter:exit:- Kind
- theme
- Spellings
dark:light:and configured theme names- Kind
- media
- Spellings
- configured keys such as
sm:md:max-md: - Kind
- platform
- Spellings
web:native:ios:android:tv:androidtv:tvos:- Kind
- group
- Spellings
group-hover:and named forms such asgroup-hover/card:- Kind
- container
- Spellings
@sm:and named forms such as@sm/layout:
Modifiers in one clause are an AND. For example, dark:hover: applies only
while both conditions match.
Merging
Shorthands and longhands merge one property program at a time. A later base replaces the earlier base without erasing unrelated clauses:
Groups and containers
A group exposes parent state. A query container supplies dimensions.
Plain sm: is a viewport query. @sm: measures the nearest query container.
Only size-measuring media keys register a container form.
Editor support
For the string spelling, TypeScript can only take autocomplete so far. It
offers each prop’s token union plus every bare modifier prefix (hover:,
sm:, dark:), but not the cross product: that union is over a million
members, which TypeScript rejects outright and then returns no completions at
all. So the vocabulary lives in a language server instead.
yarn
npm
bun
pnpm
It reads .tamagui/tamagui.config.json, the artifact the compiler already
writes, and gives you completions filtered as you type, diagnostics for unknown
modifiers and misspelled values with “did you mean” suggestions, hover showing a
token’s resolved value per theme, and inline color swatches.
Completing a clause replaces only that clause, so accepting background-hover
in bg="background hover:back" keeps the background you already wrote.
It speaks stdio LSP, so every editor needs a few lines and no plugin code. The VS Code extension, which spawns the binary for you, is not on the Marketplace yet. For Neovim:
Helix, Zed, Emacs, Sublime and JetBrains are all a similar few lines; see the package README.
If completions never appear, the server is waiting for the config artifact: run
your dev server or tamagui generate once so the compiler emits it. When the
config changes the server picks it up and republishes on its own, with no
editor restart.
For CI and pre-commit, tamagui check validates every flat value in a project
and exits non-zero, which is also the right hook for coding agents:
Terminal
Migrate an existing app
The codemod in the Tamagui repository is the migration path. It recognizes old token spellings and V2 condition objects, rewrites every statically eligible site, and reports anything that still needs a human decision.
For example, this V2 source is migration input only:
It becomes:
That output preserves the old palette name because the codemod cannot choose a
new shade for a custom design system. Check the active config before changing
it. blue10 remains valid when the application keeps the frozen v5 pack. If
the application moved to v6, replace it with the intended v6 token, for
example dark:blue-500, before running the converted application.
Run write mode from your project root against one or more files or directories:
yarn
npm
bun
pnpm
The write is transactional. A source or generated parse error means no files are written. A path that matches no source file exits with status 2, so a typo cannot look like a clean migration.
The report records host capability checks, values that require relocation, and syntax the tool cannot safely infer. Common manual cases include:
- a condition that changes a component variant rather than a style property;
- a dynamic condition object or value whose type is not provable;
- a named container whose declaring parent is outside the scanned file;
- a transform array or other structured native value;
- ordering across an opaque spread that may set the same property.
Fix those rows directly in flat syntax. Do not add a compatibility setting or restore a V2 condition-object parser.
After the write, search the migrated source for old authoring:
Terminal
Legacy spellings should remain only in explicit migration fixtures or tooling that reads V2 source. Then typecheck, build, and exercise state, media, theme, group, container, platform, and presence behavior on every target your app ships.
The codemod package itself can be checked with:
yarn
npm
bun
pnpm