# Tamagui Complete Documentation
> Tamagui is a complete UI solution for React Native and Web, with a fully-featured UI kit, styling engine, and optimizing compiler.
## core/settings
---
title: Settings
description: Strictness, SSR, theme class placement, and other createTamagui settings.
---
Pass a `settings` object to `createTamagui` to control type strictness, SSR
behavior, flex behavior, units, and how themes reach the DOM.
```tsx fileName="tamagui.config.ts"
export const config = createTamagui({
// ...
settings: {
disableSSR: true,
allowedStyleValues: 'somewhat-strict-web',
defaultFont: 'body',
},
})
```
[`@tamagui/config/v6`](/docs/core/config-v6) sets sensible values for all of these.
Spread `defaultConfig.settings` and override the keys you need.
## Options
',
description: `Which media queries count as active on the first render. Used for SSR.`,
},
{
name: 'addThemeClassName',
required: false,
type: "false | 'html' | 'body'",
default: false,
description: `Where to put the root theme className on web. Styling with theme CSS variables needs the class on body or above. When false, the class goes on the element TamaguiProvider renders.`,
},
{
name: 'selectionStyles',
required: false,
type: '(theme) => ({ backgroundColor: Variable | string; color: Variable | string })',
default: false,
description: `Generates ::selection styles for text selection on the web.`,
},
{
name: 'shouldAddPrefersColorThemes',
required: false,
type: 'boolean',
default: `true`,
description: `Generates prefers-color-scheme media queries for your light and dark themes. Increases total CSS size.`,
},
{
name: 'onlyAllowShorthands',
required: false,
type: 'boolean',
default: false,
description: `Removes the types for the long-form version of every shorthand you define. See Shorthands.`,
},
{
name: 'styleValueSyntax',
required: false,
type: `'string' | 'object'`,
default: `undefined`,
description: `Restrict conditional style values to one syntax. Flat values accept both the string form (bg="red hover:blue") and the object form (bg={{ default: 'red', hover: 'blue' }}). Setting this narrows the types to one form, makes the runtime warn on the other in development, and tells tamagui generate-prompt and the skills to only show that form.`,
},
{
name: `allowedStyleValues`,
required: false,
type: 'AllowedStyleValuesSetting',
description: `Controls type validation of single-token style values. See below. Run tamagui check --strict to validate conditional payloads against the project config too.`,
},
{
name: 'styleCompat',
required: false,
type: `'legacy' | 'react-native' | 'web'`,
default: `'web'`,
description: `Which flex expansion semantics to follow where Yoga and CSS differ. Numeric lineHeight values are ratios in every mode.`,
},
{
name: 'remBaseFontSize',
required: false,
type: 'number',
default: `16`,
description: `Base font size for rem calculations on native. On web, browsers use the root font size.`,
},
{
name: 'defaultPosition',
required: false,
type: `'static' | 'relative'`,
default: `'static'`,
description: `Default position value for all Tamagui components.`,
},
{
name: 'onlyShorthandStyleProps',
required: false,
type: 'boolean',
default: false,
description: `Removes the longhand border, outline, and shadow props (borderWidth, outlineWidth, shadowColor, and the rest) from the types, encouraging the combined border, outline, and boxShadow props instead. Type-level only.`,
},
{
name: 'fastSchemeChange',
required: false,
type: 'boolean',
default: false,
description: `On iOS, returns color values as DynamicColorIOS so light and dark switches skip a re-render. The defaultTheme you pass to TamaguiProvider must match the current system color scheme.`,
},
{
name: 'optimizeFor',
required: false,
type: `'updates' | 'first-render'`,
default: `'updates' on web, 'first-render' on native`,
description: `Chooses the render performance tradeoff. "updates" tracks which theme and media keys each component reads so changes re-render only their consumers, at a small per-component first-render cost. "first-render" skips per-key tracking for the fastest initial render. Theme and media changes still apply, but re-render coarsely. This is a startup-level setting. Do not change it at runtime.`,
},
]}
/>
## allowedStyleValues
Controls which values the types accept for token-backed style props:
- `false` (default): any string, or number for styles that accept numbers
- `strict`: only tokens for any token-enabled property
- `strict-web`: same as strict, plus web-only values like `auto` and `inherit`
- `somewhat-strict`: tokens, or:
- for `space` and `size`: percentage strings or numbers
- for `radius`: numbers
- for `zIndex`: numbers
- for `color`: named colors or rgba and hsla strings
- `somewhat-strict-web`: same as somewhat-strict, plus web-only values
Pass one value for every category, or an object to set each category on its own:
```tsx
type AllowedValueSetting =
| boolean
| 'strict'
| 'somewhat-strict'
| 'strict-web'
| 'somewhat-strict-web'
type AllowedStyleValuesSetting =
| AllowedValueSetting
| {
space?: AllowedValueSetting
size?: AllowedValueSetting
radius?: AllowedValueSetting
zIndex?: AllowedValueSetting
color?: AllowedValueSetting
}
```
## Environment variables
A few behaviors are set with environment variables rather than `settings`. Your
bundler can then drop the code paths you do not use. On web, use the Tamagui Vite or
Next.js plugin, or define them in your own bundler config. On native the extra size
is small enough not to matter.
## core/html-primitives
---
title: HTML elements
description: Semantic HTML elements that render on web and native, in early development
---
The `html` export renders semantic HTML on web and the matching React Native
primitives on native. It follows web and React Strict DOM semantics, and suits
screens that are mostly web content.
Each member is an ordinary Tamagui component, so it takes Tamagui style props
alongside the DOM props for that element.
```tsx
import { html } from 'tamagui'
export function Article() {
return (
A semantic, styled heading
The same source renders semantic tags on web and native primitives in a
native app.
)
}
```
You can also import `html` from `@tamagui/core`.
## Before you use it
`html.*` is in early development. Its elements keep the browser's default
`display`, so on web `html.div` and `html.p` are `display: block` and `html.span`
is inline. React Native only has flex layout, so the same tree can lay out
differently on native. [`View` and `Text`](/docs/core/view-and-text) are flex
columns everywhere, which is why they stay the default for cross-platform
screens.
Every `html.*` member is built on `View` or `Text`, so both take the same style
props, tokens, themes, and variants, and you can mix them in one tree.
## What renders
On web, `html.article`, `html.h1`, and `html.p` render literal `article`, `h1`,
and `p` elements. Tamagui applies explicit element defaults so the result does
not depend on whether the page loaded a CSS reset.
On native, the same members render through one of four backing primitives:
| Source element | Native backing |
| --- | --- |
| layout elements such as `div`, `article`, `main`, `section`, and `ul` | `View` |
| text elements such as `span`, `p`, `strong`, and `h1` | `Text` |
| `img` | `Image` |
| `input` and `textarea` | `TextInput` |
The native runtime maps supported DOM prop names to their React Native names.
It also wraps literal text inside a View-backed element, because a React Native
`View` cannot render a raw string. The compiler performs the same mapping and
text wrapping ahead of time when it can lower the element.
## Available elements
The root export includes:
```txt
a article aside b bdi bdo blockquote br button code del div em
fieldset footer form h1 h2 h3 h4 h5 h6 header hr i img input
ins kbd label li main mark nav ol optgroup option p pre s section
select span strong sub sup textarea u ul
```
Props are specific to the element. For example, `html.a` accepts `href`,
`html.img` accepts `src` and `alt`, and `html.input` accepts input props. Void
elements such as `br`, `hr`, `img`, and `input` reject children in TypeScript.
Because these are Tamagui components, style props use the same tokens, themes,
shorthands, and [flat conditional values](/docs/guides/flat-values) as `View`
and `Text`:
```tsx
Save
```
## Native support is explicit
A browser feature without a faithful native equivalent does not silently turn
into a different control. Native builds reject `html.select`, `html.option`, and
`html.optgroup`, because React Native has no menu-based select control. Unsupported
props and events also produce a diagnostic naming the unsupported behavior.
Some elements preserve their useful native subset. `html.form` and
`html.fieldset`, for example, render containers on native, while browser form
submission, validation, and reset behavior remain web-only.
The compiler and runtime read the same generated compatibility tables. Running
with or without compiler lowering therefore uses the same supported tag and prop
contract.
## Compiler behavior
The compiler treats `html.*` as regular lowering candidates. On web it can
replace a component with its literal tag, extracted classes, and any remaining
host props. On native it can replace the same component with its backing
primitive and mapped props. If the compiler cannot lower an ordinary use, the
root export still has a runtime implementation on both platforms.
For new code, use the root `html` export shown here. The standalone
`tamagui/dom` and `@tamagui/core/dom` entries are deprecated implementation
surfaces. Their `style()` handles are compile-only and throw if they reach the
runtime.
## core/tamagui-provider
---
title: TamaguiProvider
description: Mount your config, generate its CSS, and trim theme JS from the client bundle.
---
`TamaguiProvider` makes the object returned by `createTamagui` available to
every component below it.
Render it once at the root of your app:
```tsx line=3-5 fileName="App.tsx"
import { TamaguiProvider } from 'tamagui'
import { config } from './tamagui.config'
export default function App() {
return (
)
}
```
Import `tamagui.config.ts` in one place near the root. Importing it from many files
invites circular imports and breaks hot reloading. Other files reach the config
through style props, `useMedia`, `useTheme`, or getters like `getTokenValue`. The full
object is available from `getConfig` when you need it.
Set your root theme with `defaultTheme` instead of wrapping the app in ``.
That keeps the [`fastSchemeChange` setting](/docs/core/settings) working on native,
and lets light and dark switches on web use media query updates during SSR.
## Props
## Generating CSS
On the client, Tamagui injects the CSS for your configuration into `document.head`.
For production you want that CSS served with the page. Set `disableInjectCSS` on
`TamaguiProvider`, then pick one of three ways to emit it.
If your framework has server-side layouts, render it inline:
```tsx fileName=app/_layout.tsx
import { config } from './tamagui.config'
export default () => (
)
```
To share one CSS file across every page, use the `outputCSS` option of a bundler
plugin and import the generated file in your app:
```tsx fileName=vite.config.ts
import { tamaguiPlugin } from '@tamagui/cli/vite'
export default {
plugins: [
tamaguiPlugin({
config: './src/tamagui.config.ts',
outputCSS: './src/tamagui.generated.css',
}),
],
}
```
Or generate it with the CLI. Create a `tamagui.build.ts`:
```tsx fileName=tamagui.build.ts
import type { TamaguiBuildOptions } from 'tamagui'
export default {
components: ['tamagui'],
config: './config/tamagui.config.ts',
outputCSS: './tamagui.generated.css',
} satisfies TamaguiBuildOptions
```
Then run:
```bash
npx @tamagui/cli generate
```
The [CLI guide](/docs/core/cli#generate) covers `generate` and the other commands.
## Tree shaking themes
Theme JS can reach 20KB or more. Tamagui writes every theme value to a CSS variable,
and the client can read those variables back from the DOM instead of loading the
theme object. Dropping themes from the client bundle helps Lighthouse scores.
This only applies to server rendered web apps such as Next.js, One, and Vite SSR.
The CSS must come from `config.getCSS()` or a bundler plugin's `outputCSS` so it is
present before hydration. Metro, Expo, and static sites keep their themes in JS.
```tsx fileName="tamagui.config.ts"
import { defaultConfig, themes } from '@tamagui/config/v6'
import { createTamagui } from 'tamagui'
export const config = createTamagui({
...defaultConfig,
// only load themes on server - client hydrates from CSS
// for non-One Vite apps, use import.meta.env.SSR instead
themes: process.env.VITE_ENVIRONMENT === 'client' ? ({} as typeof themes) : themes,
})
```
## core/exports
---
title: Exports
description: Constants, helpers, hooks, components, and types
---
Import core utilities from `tamagui` or `@tamagui/core`. Ref utilities are
also available from `@tamagui/element`.
## Constants
[Constants are re-exported from @tamagui/constants](https://github.com/tamagui/tamagui/blob/main/code/core/constants/src/constants.ts):
- `isWeb`: true if targeting the web (true for SSR and client)
- `isBrowser`: true when both `navigator` and `location` are defined
- `isServer`: `isWeb && !isBrowser`
- `isClient`: `isWeb && isBrowser`
- `isChrome`: client-side Chrome
- `isWebTouchable`: web-only touch device (client side only)
- `isTouchable`: true on native or touch-capable web clients
---
## Helpers
### getConfig
`getConfig` returns your fully parsed Tamagui config. It avoids the circular
imports that come with importing the config directly. Prefer `useTheme` for
themes and `getTokens`/`getToken`/`getTokenValue` for tokens, and use this for
other parts of the config.
### insertFont
```tsx
type insertFont = (name: string, fontIn: GenericFont) => ParsedFont
```
Adds a new font after the initial `createTamagui` call. `GenericFont` matches a
font passed to `createTamagui`, and `ParsedFont` is the font with subkeys
turned into variable objects.
### updateFont
The same as insertFont, but will update an existing font.
### isTamaguiComponent
```tsx
type isTamaguiComponent = (component: any, name?: string) => boolean
```
If no name given, true if a Tamagui component, if name given ensures it's the
specific named Tamagui component.
### isTamaguiElement
```tsx
type isTamaguiElement = (child: any, name?: string) => boolean
```
If no name given, true if a Tamagui ReactElement, if name given ensures it's the
specific named Tamagui component element.
### getTokens
```tsx
;() => TokensParsed
```
Returns the parsed Tamagui config object of all your tokens, can be used at
runtime to get values from tokens. If you are looking to get a single token
value, prefer `getToken` or `getTokenValue`.
### getToken
```tsx
;(name: Token, group?: keyof Tokens) => any
```
Given the specific name of a token or a name + group, will return the value as
either a variable on the web, or raw value on native. So if you define a size
token with key `small` and value of `14`:
```tsx
getToken('size.small') // returns on web var(--size-small), native 14
getToken('small', 'size') // returns on web var(--size-small), native 14
```
### getTokenValue
```tsx
;(name: Token, group?: keyof Tokens) => any
```
Similar to getToken, but always returns the raw value rather than the variable
name. If you define a size token with key `small` and value of `14`:
```tsx
getTokenValue('size.small') // returns 14
getTokenValue('small', 'size') // returns 14
```
### getVariable
```tsx
type getVariable = (value: Variable) => string
```
Accepts a `Variable` from a parsed theme or token (returned by
`createTamagui`, `useTheme`, or `getTokens`).
Calling `getVariable(useTheme().color)` returns `var(--color)` on web, and
`#fff` on other platforms.
### splitStyleProps
`splitStyleProps` partitions an object in one pass and returns
`[selectedProps, remainingProps]`. With no options, the first object contains
style props and the second contains everything else. Props keep their authored
keys by default. It is exported from `@tamagui/core`, `@tamagui/ui`, and
`tamagui`:
```tsx
import { splitStyleProps } from 'tamagui'
const [styleProps, regularProps] = splitStyleProps(props)
```
Pass `expandShorthands: true` to write selected shorthand props under their
canonical keys:
```tsx
const [styleProps, regularProps] = splitStyleProps(props, {
expandShorthands: true,
})
```
A filter map selects only its canonical keys. Style props excluded by the map
remain in the second object. This also lets a component intentionally select
non-style behavior props:
```tsx
import { splitStyleProps, stylePropsTextOnly } from '@tamagui/core'
const [textProps, frameProps] = splitStyleProps(props, {
expandShorthands: true,
filter: {
...stylePropsTextOnly,
ellipsis: true,
numberOfLines: true,
},
})
```
For dynamic rules, `filter` can be a callback. It receives the canonical key,
the value, the original authored key, and whether the canonical key is a known
style prop:
```tsx
const [selectedProps, remainingProps] = splitStyleProps(props, {
filter: (key, value, originalKey, isStyleProp) => {
return (isStyleProp && key.startsWith('font')) || originalKey === 'numberOfLines'
},
})
```
Filter maps infer the selected keys precisely. A callback can return a type
predicate on `originalKey` when its rule narrows to a known key union.
### getWebElement
A runtime utility that narrows a `TamaguiElement` to an `HTMLElement`. Throws if
the element is not an HTMLElement. Useful for platform-specific code where you
know you're on web.
```tsx
import { getWebElement } from 'tamagui' // or '@tamagui/element'
// Narrows to HTMLElement
const el = getWebElement(ref.current)
// Or narrow to a specific element type
const input = getWebElement(ref.current)
input.selectionStart // typed correctly
```
---
## Hooks
See [useMedia](/docs/core/use-media) for media queries and
[useTheme](/docs/core/use-theme) for theme values.
### useIsomorphicLayoutEffect
Uses `useEffect` on the server and `useLayoutEffect` on the client to avoid
layout effect warnings during server rendering.
### useWebRef
For platform-specific files (`.tsx` and `.native.tsx`), this hook creates a
properly typed ref for web that also composes with a forwarded ref. When you
need to access `HTMLElement`-specific properties like `selectionStart` on
inputs, this avoids needing `any` casts.
```tsx
import { useWebRef } from 'tamagui' // or '@tamagui/element'
// In your .tsx (web) file:
function MyInput(props, forwardedRef) {
const { ref, composedRef } = useWebRef(forwardedRef)
// ref.current is typed as HTMLInputElement
// Access web-specific properties safely:
const start = ref.current?.selectionStart
// composedRef forwards to both ref and forwardedRef
return
}
```
### useNativeRef
For platform-specific files (`.native.tsx`), this hook creates a properly typed
ref for React Native that also composes with a forwarded ref. Pairs with
`useWebRef` for cross-platform component development.
```tsx
import { useNativeRef } from 'tamagui' // or '@tamagui/element'
// In your .native.tsx file:
function MyComponent(props, forwardedRef) {
const { ref, composedRef } = useNativeRef(forwardedRef)
// ref.current is typed as View
return
}
```
For input components, use `useNativeInputRef` which types to `TextInput`:
```tsx
import { useNativeInputRef } from 'tamagui' // or '@tamagui/element'
function MyInput(props, forwardedRef) {
const { ref, composedRef } = useNativeInputRef(forwardedRef)
// ref.current is typed as TextInput
return
}
```
### useThemeName
Returns the string name of the current theme.
### useIsTouchDevice
SSR-friendly, only true if native touchable or web touchable device (client
side, not server side).
### useDidFinishSSR
SSR-friendly, returns true if SSR has completed on the client, false before
hydration done. On server it's always false.
## Components
### ClientOnly
A component that opts its children out of SSR. When wrapped in `ClientOnly`,
Tamagui avoids double-renders in various places which results in faster
client-side rendering.
```tsx
import { ClientOnly } from 'tamagui' // or '@tamagui/core'
// Children will render only on the client, skipping SSR entirely
// Can be conditionally enabled
```
---
## Type helpers
### GetProps
Fetches the type of props for a Tamagui component:
```tsx
import { View, GetProps, styled } from '@tamagui/core'
const X = styled(View, {})
type XProps = GetProps
```
### GetRef
Fetches the type of a ref for a Tamagui component, or any React component:
```tsx
import { View, GetRef, styled } from '@tamagui/core'
const X = styled(View, {})
const MyComponent = () => {
const ref = useRef>()
return
}
```
## core/theme
---
title: Theme
description: Change the theme for any subtree
---
Change the theme for any subtree by passing its name to the `Theme` component.
## Usage
Change the theme for any subtree:
```tsx
import { Button, Theme } from 'tamagui'
export default () => (
)
```
## Theme updates
Use `` to override theme values for a subtree, much like redefining
a CSS custom property on one node. Listed keys override and everything else
inherits from the parent theme.
```tsx
import { ThemeUpdate, html } from 'tamagui'
export default () => (
)
```
Values use the same flat grammar as style props. `` applies its values
to a whole subtree, so it supports modifiers that describe a subtree:
- **Theme:** `dark:`, `light:`, or any configured theme name. `blue:` applies
under `dark_blue`, `light_blue`, or a theme named `blue`.
- **Platform:** `ios:`, `android:`, `web:`, and `native:`.
Interaction states such as `hover:` and `press:`, and media queries such as
`sm:`, describe an element rather than a subtree. They warn in development and
are dropped here.
A named theme and an update compose through nesting:
```tsx
{/* dark, with background overridden */}
```
Keys must be theme keys or [custom variables](#custom-variables) declared in
the config. Unknown keys warn in development and are dropped so web and native
behave the same. `` accepts children and typed theme or custom
variable keys. Theme controls such as `name` and `shallow` stay on
``.
### Custom variables
Declare design-system values in `createTamagui` to make them work anywhere a
theme key works: style props, `useTheme()`, and `` values.
```tsx
const config = createTamagui({
// ...tokens, themes
variables: {
surfaceBorder: 'border-color',
disabledOpacity: 0.5,
focusRingColor: 'blue-500',
focusRingWidth: 2,
pressScale: 0.97,
accent: { light: '#005ac8', dark: '#78b4ff' },
},
})
```
Use and redefine them like any other theme value:
```tsx
{/* this subtree sees the red surface border */}
```
### References and units
Inline values can reference theme keys and tokens:
- `background` finds the theme key or custom variable visible at the ``
node.
- `size.4` and `color.blue-500` reference tokens by category.
- A bare token name such as `blue-500` checks theme keys first, then scans color,
space, size, radius, and zIndex tokens. Use the qualified form when a name
exists in more than one category.
References resolve at the `` node and descendants inherit the result.
Chains are supported, while a reference cycle drops the involved keys on both
platforms with a development warning.
Numbers become `px` on web and stay numbers on native, matching numeric style
props. Keys ending in `opacity`, `scale`, `zIndex`, `weight`, `flex`, `grow`,
`shrink`, or `ratio` stay unitless. Use a string such as `"10px"`
or `px(10)` for an explicit pixel value.
### Nesting and updates
ThemeUpdate layers stack, with the nearest definition winning per key. A ``
that resolves a new name resets keys defined by that theme. Put the ThemeUpdate
inside that boundary when it should survive the switch. Custom variables are
defined on base themes, so sub-themes inherit their updates.
Changing update values leaves unrelated descendants alone. Tamagui tracks
which keys each component reads, and only readers of a changed key update. On
web, styled components restyle through CSS custom properties without a
JavaScript render. JavaScript readers such as `useTheme()` and animation
drivers see the same values on both platforms. With `settings.fastSchemeChange`
on iOS, keys whose light and dark values are literals retain the
`DynamicColorIOS` fast path.
## Defining themes
Themes sit above tokens. Tokens are static values, while themes change per
subtree like CSS variables.
Define themes however you want, but the `tamagui` components style themselves
from a set of pre-defined keys:
- `background`, `background-hover`, `background-press`, `background-focus`
- `border-color`, `border-color-hover`, `border-color-press`, `border-color-focus`
- `shadow-color`
These are optional, as you can always set `unstyled` on any Tamagui component to
use your own styles.
A simplified theme definition:
```tsx
import { createTamagui, createTokens } from 'tamagui'
const tokens = createTokens({
color: {
pinkDark: '#610c62',
pinkLight: '#f17efc',
},
// ... see configuration docs for required tokens
})
export default createTamagui({
tokens,
themes: {
dark: {
background: '#000',
color: '#fff',
},
light: {
color: '#000',
background: '#fff',
},
dark_pink: {
background: tokens.color.pinkDark,
color: tokens.color.pinkLight,
},
light_pink: {
background: tokens.color.pinkLight,
color: tokens.color.pinkDark,
},
},
})
```
Passing tokens to themes will reduce CSS, but is not required.
Access theme values through `styled()` or directly on a component:
```tsx
const P = styled(Text, {
color: 'color-11'
})
// or directly
```
Because we defined sub-themes `light_pink` and `dark_pink`, the `Theme`
component will now let us do this:
```tsx
import { Button, Theme } from 'tamagui'
export default () => {
return (
)
}
```
Notice we just use the name `pink`.
For larger typed theme suites, see [Creating themes](/docs/guides/theme-builder),
which introduces `createThemes`, recipe trees, scales, and relative levels.
## core/animation-drivers
---
title: Animation Drivers
description: Choose and configure animation drivers for your platform
---
Tamagui supports four animation drivers across its platforms. Web supports
CSS, Reanimated, and Motion. Native supports React Native and Reanimated.
You can swap drivers per platform, or dynamically load heavier drivers later in your app. For example, you can start with the lightweight CSS driver on initial load and upgrade to Motion once the user is authenticated.
---
## Using config v6
With [`@tamagui/config/v6`](/docs/core/config-v6), add one animation entry for your platform. The entries share matching keys:
```tsx fileName="tamagui.config.ts"
import { defaultConfig } from '@tamagui/config/v6'
import { animations } from '@tamagui/config/animations-css' // or animations-motion, animations-rn, animations-reanimated
import { createTamagui } from 'tamagui'
export const config = createTamagui({
...defaultConfig,
animations,
})
```
Every entry ships the same preset table, so `bouncy`, `quick`, and `lazy` mean
the same motion whichever driver you pick. There are no `100ms`-style keys: a
duration is CSS now, so `transition="100ms"` works with nothing configured.
---
## CSS Driver
The lightest bundle size. Springs work here too: they compile to a `linear()`
easing that traces the real spring curve with no JavaScript running. Best for
simple web-only apps.
### Installation
```bash
yarn add @tamagui/animations-css
```
### Configuration
```tsx
import { createAnimations } from '@tamagui/animations-css'
import { createTamagui } from 'tamagui'
export default createTamagui({
animations: createAnimations({
fast: { duration: 160, bounce: 0.25 },
medium: { duration: 300, bounce: 0.15 },
slow: 'ease-in 450ms',
}),
// ...
})
```
An entry is a spring written as `{ duration, bounce }`, or a plain CSS timing
string.
**Supported easing functions:** `ease`, `linear`, `ease-in`, `ease-out`, `ease-in-out`, `cubic-bezier(x1, y1, x2, y2)`
---
## React Native Driver
Uses React Native's built-in Animated API with no extra animation-library bundle.
Use CSS, Motion, or Reanimated on web.
### Installation
```bash
yarn add @tamagui/animations-react-native
```
### Configuration
```tsx
import { createAnimations } from '@tamagui/animations-react-native'
import { createTamagui } from 'tamagui'
export default createTamagui({
animations: createAnimations({
fast: { duration: 160, bounce: 0.25 },
medium: { duration: 300, bounce: 0.15 },
slow: { duration: 450, bounce: 0.1 },
}),
// ...
})
```
**Spring parameters:**
- `duration`: the spring's undamped period in milliseconds, defining how fast it feels
- `bounce`: 0 is critically damped, toward 1 is loose, and negative is sluggish
- `stiffness`, `damping`, and `mass` still work for a config already tuned
against them
---
## Reanimated Driver
Supports both native and web with advanced features, but has a larger bundle size. Best performance on native platforms with off-thread animations.
### Installation
```bash
yarn add @tamagui/animations-reanimated react-native-reanimated
```
Follow the [Reanimated installation guide](https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/getting-started/) to complete setup.
### Configuration
```tsx
import { createAnimations } from '@tamagui/animations-reanimated'
import { createTamagui } from 'tamagui'
export default createTamagui({
animations: createAnimations({
fast: { duration: 160, bounce: 0.25 },
medium: { duration: 300, bounce: 0.15 },
// timing animations also supported
quick: { type: 'timing', duration: 300, easing: 'ease-out' },
}),
// ...
})
```
---
## Motion Driver
Off-thread performance via WAAPI with excellent spring physics and a medium bundle size. Best for web-only apps that need smooth, physics-based animations.
### Installation
```bash
yarn add @tamagui/animations-motion motion
```
### Configuration
```tsx
import { createAnimations } from '@tamagui/animations-motion'
import { createTamagui } from 'tamagui'
export default createTamagui({
animations: createAnimations({
bouncy: { duration: 400, bounce: 0.5 },
quick: { duration: 220, bounce: 0.3 },
}),
// ...
})
```
Motion uses the Web Animations API (WAAPI) which runs animations on the compositor thread when possible.
---
## Swapping Drivers
Tamagui supports several ways to use different drivers: at build time per platform, at runtime in specific parts of your app, or dynamically loaded after initial render.
### Per-Platform with File Extensions
Use `.native.ts` and `.ts` file extensions to bundle different drivers per platform:
```tsx fileName="animations.ts"
// web
import { createAnimations } from '@tamagui/animations-motion'
export const animations = createAnimations({
bouncy: { duration: 400, bounce: 0.5 },
})
```
```tsx fileName="animations.native.ts"
// native
import { createAnimations } from '@tamagui/animations-reanimated'
export const animations = createAnimations({
bouncy: { duration: 400, bounce: 0.5 },
})
```
```tsx fileName="tamagui.config.ts"
import { animations } from './animations'
export default createTamagui({
animations,
// ...
})
```
### Dynamic with Configuration
Use the `` component to swap the animation driver for a subtree. This is useful for lazy loading heavier drivers only when needed. For example, you can keep initial page load fast with CSS animations, then upgrade to Motion for authenticated users:
```tsx fileName="app/_layout.tsx"
import { Configuration, Slot } from 'tamagui'
import { createAnimations } from '@tamagui/animations-motion'
const motionDriver = createAnimations({
bouncy: { duration: 400, bounce: 0.5 },
})
export function AuthenticatedLayout() {
return (
)
}
```
This pattern works well with route-based code splitting, because the Motion driver is only loaded when the user navigates to an authenticated route.
### Multiple Drivers with animatedBy
Configure multiple drivers at the root and select per-component:
```tsx fileName="tamagui.config.ts"
import { createAnimations as createCSS } from '@tamagui/animations-css'
import { createAnimations as createSpring } from '@tamagui/animations-motion'
export default createTamagui({
animations: {
default: createCSS({ bouncy: 'ease-in 200ms' }),
spring: createSpring({ bouncy: { duration: 400, bounce: 0.5 } }),
},
})
```
```tsx
{/* uses default (CSS) */}
{/* uses spring (Motion) */}
```
### Runtime Loading with loadAnimationDriver
Add drivers at runtime after app initialization:
```tsx
import { loadAnimationDriver } from 'tamagui'
import { createAnimations } from '@tamagui/animations-motion'
// call this after some condition (user auth, route change, etc.)
const driver = createAnimations({ bouncy: { duration: 400, bounce: 0.5 } })
loadAnimationDriver('spring', driver)
// now components can use animatedBy="spring"
```
---
## See also
- [Animations Overview](/docs/core/animations)
- [AnimatePresence](/docs/core/animate-presence)
- [Config v6](/docs/core/config-v6#animations)
## core/animations-css
---
title: CSS Driver
description: Lightweight CSS transition-based animations
---
The `@tamagui/animations-css` package provides lightweight, typed animations
through CSS transitions on web.
## Installation
```bash
yarn add @tamagui/animations-css
```
## Configuration
Add it to your [Tamagui config](/docs/core/configuration):
```tsx
import { createAnimations } from '@tamagui/animations-css'
import { createTamagui } from 'tamagui'
export default createTamagui({
animations: createAnimations({
fast: { duration: 150, bounce: 0.2 },
medium: { duration: 300, bounce: 0.15 },
slow: 'ease-in 450ms',
}),
// ...
})
```
The config format is the same for all four drivers, so a name means the same
motion whichever driver renders it. See
[Animations](/docs/core/animations#presets) for the shipped table.
The root entry contains CSS component transitions only. If your app uses
`useAnimatedNumber`, `useAnimatedNumberStyle`, `useAnimatedNumbersStyle`, or
`useAnimatedNumberReaction`, import the same factory from the extras entry:
```tsx
import { createAnimations } from '@tamagui/animations-css/extras'
```
`Sheet` uses animated numbers for its position and `Toast` uses them for swipe
gestures, so apps using either component with the CSS driver need the extras
entry. The root entry keeps apps that only use CSS transitions from shipping
the JavaScript spring and linked-style runtime.
## How it works
At runtime, the plugin does very little except to set the `transition` property
in CSS. At compile-time, the compiler does the same, ensuring you get all the
benefits of prop removal and view flattening even when using animations.
## Animation configuration
A config entry is a spring, written as `{ duration, bounce }`:
```tsx
createAnimations({
quick: { duration: 220, bounce: 0.3 },
bouncy: { duration: 400, bounce: 0.5 },
})
```
Springs work under this driver: they compile to a `linear()` easing that traces
the real spring curve, overshoot included, with no JavaScript running.
An entry can also be a plain CSS timing string, in `` order or the CSS order:
```tsx
createAnimations({
quick: 'ease-out 100ms',
slow: 'ease-in-out 500ms',
})
```
### Delay
```tsx
```
The delay (in milliseconds) is applied as CSS `transition-delay`. A CSS
transition string spells it the CSS way, `transition="200ms ease-out 100ms"`.
### Supported easing functions
- `ease`: default easing (equivalent to `cubic-bezier(0.25, 0.1, 0.25, 1)`)
- `linear`: no easing, constant speed
- `ease-in`: slow start
- `ease-out`: slow end
- `ease-in-out`: slow start and end
- `cubic-bezier(x1, y1, x2, y2)`: custom cubic bezier curve
## When to use
**Advantages:**
- **Smallest bundle size**: minimal JavaScript overhead
- **Broad compatibility**: works everywhere CSS works
- **Compiler optimizations**: benefits from static extraction
**Limitations:**
- Springs are sampled into a `linear()` easing up front, so they can't react to
an interruption mid-flight the way a running JS spring can
- Limited to CSS animatable properties
- Less control over animation lifecycle
## Example
```tsx
import { createAnimations } from '@tamagui/animations-css'
import { YStack, createTamagui } from 'tamagui'
const animations = createAnimations({
quick: { duration: 220, bounce: 0.3 },
bouncy: { duration: 400, bounce: 0.5 },
})
export default createTamagui({
animations,
// ... rest of config
})
// Usage in components
export const MyComponent = () => (
)
```
## See also
- [Animations Introduction](/docs/core/animations)
- [React Native Driver](/docs/core/animations-react-native)
- [Motion Driver](/docs/core/animations-motion)
- [Reanimated Driver](/docs/core/animations-reanimated)
## core/create-styled-context
---
title: createStyledContext()
description: Share variant props across compound components
---
`createStyledContext()` passes variant props through every styled component in
a compound component tree.
Compound component APIs let related elements coordinate their props:
```tsx
export default () => (
)
```
Setting `size="lg"` on the outer Button frame should pass down to the Text so the frame and text sizes stay aligned, without requiring explicit size props on every child component.
`createStyledContext()` provides this coordination for styled components, sharing variant props through React context.
You can set it up as follows:
```tsx
import {
View,
Text,
createStyledContext,
styled,
withStaticProperties,
} from '@tamagui/core'
export const ButtonContext = createStyledContext<{
size?: 'sm' | 'md' | 'lg'
}>({
size: 'md',
})
export const ButtonFrame = styled(View, {
displayName: 'Button',
context: ButtonContext,
variants: {
size: {
sm: { paddingInline: '3', paddingBlock: '1.5' },
md: { paddingInline: '4', paddingBlock: '2' },
lg: { paddingInline: '6', paddingBlock: '2' },
},
} as const,
defaultVariants: {
size: 'md',
},
})
export const ButtonText = styled(Text, {
displayName: 'ButtonText',
context: ButtonContext,
variants: {
size: {
sm: { fontSize: 'sm' },
md: { fontSize: 'sm' },
lg: { fontSize: 'base' },
},
} as const,
})
export const Button = withStaticProperties(ButtonFrame, {
Props: ButtonContext.Provider,
Text: ButtonText,
})
```
A few things to note here:
- ButtonContext should only be typed and given properties that work across both components. Since both define a `size` variant, this works, even though the frame and text read different token scales.
- You can use `` now to set default props for a Button from above.
- The context pattern currently skips compiler flattening. Avoid using it on low-level primitives like generic Stacks or Text. For higher-level controls like Button, it works well: the compiler still extracts CSS and optimizes render logic.
## core/native
---
title: Native
description: The native style runtime and the integrations that make Tamagui feel native on iOS and Android
---
Tamagui renders real native views on iOS and Android, with the same components,
tokens, and themes you use on web. Nothing extra is required to get started. The
runtime and integrations below make it faster and more native once you need them.
Configure Tamagui with the same [provider](/docs/core/configuration) on native
and web.
## Native runtime
The native runtime is in beta. It needs React Native 0.82 or newer with the New
Architecture (Fabric).
By default, a theme change or a style update re-renders the React components that
use it. `@tamagui/native-registry` moves that work into a small C++ registry. It
links eligible native views to the registry and applies theme and style updates
directly through Fabric, so the components don't re-render.
On the iOS uniwind-benchmarks app, Tamagui 3.0.6 rendered 2,003 views in 39.5 ms
and switched themes in 3.5 ms, faster than every other themed library. See the
[benchmarks](/docs/intro/benchmarks#native-uniwind-benchmarks).
Install it along with Nitro Modules, then rebuild your iOS and Android binaries:
```bash
npm install @tamagui/native-registry react-native-nitro-modules
```
Turn on the compiler output in `tamagui.build.ts`:
```ts
import type { TamaguiBuildOptions } from 'tamagui'
export default {
config: './tamagui.config.ts',
components: ['tamagui'],
experimental: {
nativeFastPath: true,
},
} satisfies TamaguiBuildOptions
```
Register the style engine once at startup, before your app tree mounts:
```ts
import * as nativeRegistry from '@tamagui/native-registry'
import { setNativeStyleEngine } from 'tamagui'
if (nativeRegistry.isAvailable()) {
setNativeStyleEngine(nativeRegistry)
}
```
If the native module isn't available on a platform, components render the normal
React way, so the same code is safe everywhere.
## Integrations
`@tamagui/native` connects Tamagui to popular React Native libraries. Each
integration is opt-in: install the library, then import its setup module at your
app entry point, before any Tamagui imports.
```bash
npm install @tamagui/native
```
| Integration | Library | What you get |
| --------------------------------------------- | ----------------------------------- | -------------------------------------------------------- |
| [Gesture Handler](#gesture-handler) | `react-native-gesture-handler` | Correct nested presses, native Sheet drag and scroll |
| [Portals](#portals) | `react-native-teleport` | Portaled content that keeps your React context |
| [Reanimated](#reanimated) | `react-native-reanimated` | Animations on the UI thread |
| [Keyboard Controller](#keyboard-controller) | `react-native-keyboard-controller` | Sheets that track the keyboard frame by frame |
| [Safe Area](#safe-area) | `react-native-safe-area-context` | The `safe` style value and safe area aware sheets |
| [Menus](#menus) | `@expo/ui` or `zeego` | Platform menus and context menus |
| [Toast](#toast) | `burnt` | System toasts and indicators |
A typical entry file with several integrations looks like this:
```tsx
// index.js, before any tamagui imports
import '@tamagui/native/setup-gesture-handler'
import '@tamagui/native/setup-teleport'
import '@tamagui/native/setup-keyboard-controller'
import '@tamagui/native/setup-safe-area'
```
In a monorepo, install the native libraries in the app package, since native
modules are linked from there.
### Gesture Handler
With React Native Gesture Handler, `onPress` works the way React Native
`Pressable` does: when pressables are nested, only the innermost one fires. Sheet
also uses it to hand off smoothly between dragging the sheet and scrolling its
content.
```bash
npm install react-native-gesture-handler
```
```tsx
import '@tamagui/native/setup-gesture-handler'
import { GestureHandlerRootView } from 'react-native-gesture-handler'
export default function App() {
return (
{/* your app */}
)
}
```
To use it for only one of the two, call `setupGestureHandler` instead:
```tsx
import { setupGestureHandler } from '@tamagui/native/setup-gesture-handler'
// presses only, Sheet keeps its default gestures
setupGestureHandler({ pressEvents: true, sheet: false })
```
With press events enabled, `onPress` calls `stopPropagation()` on web as well, so
nested pressables behave the same on both platforms. Use `onClick` on web when you
want normal DOM bubbling.
#### PressBoundary
Tamagui pressables coordinate with each other automatically. A React Native
`Pressable` or a custom native wrapper doesn't take part in that, so tapping it can
also fire a Tamagui pressable around it. Wrap it in `PressBoundary` to stop that:
```tsx
import { PressBoundary } from '@tamagui/native/gesture-handler'
import { Pressable } from 'react-native'
export function AvatarLink() {
return (
{}}>{/* your content */}
)
}
```
`PressBoundary` only affects Tamagui pressables. It doesn't change how React
Native events bubble in general. Native `Menu.Trigger` and `ContextMenu.Trigger`
already do this for you.
### Portals
On native, Tamagui's default portal re-mounts content at the root, which cuts it
off from the React context it was declared in. With `react-native-teleport`,
content inside Sheet, Dialog, Popover, and other portaled components keeps your
contexts, such as navigation and app state.
```bash
npm install react-native-teleport
```
```tsx
import '@tamagui/native/setup-teleport'
```
In a [One](https://onestack.dev) app, One's native portals do the same job without
another native library:
```tsx
import '@tamagui/native/setup-one-portal'
```
Use one of the two setups, not both.
See [Portal](/ui/portal) for details.
### Reanimated
Reanimated runs animations on the UI thread, so they stay smooth while JavaScript
is busy. It plugs in as an animation driver, so it needs no setup import:
```bash
npm install @tamagui/animations-reanimated react-native-reanimated
```
```tsx
import { createAnimations } from '@tamagui/animations-reanimated'
```
See the [Reanimated driver](/docs/core/animations-reanimated) for configuration,
and [animation drivers](/docs/core/animation-drivers) to use it on native with a
lighter driver on web.
### Keyboard Controller
With `react-native-keyboard-controller`, Sheet follows the keyboard frame by frame
as it opens and closes, and dragging the sheet can dismiss the keyboard
interactively.
```bash
npm install react-native-keyboard-controller
```
```tsx
import '@tamagui/native/setup-keyboard-controller'
import { KeyboardProvider } from 'react-native-keyboard-controller'
export default function App() {
return {/* your app */}
}
```
### Safe Area
This setup lets styles use the device safe area insets, and Sheet uses it to stay
clear of the notch and home indicator.
```bash
npm install react-native-safe-area-context
```
```tsx
import '@tamagui/native/setup-safe-area'
import { SafeAreaProvider } from 'react-native-safe-area-context'
import { TamaguiProvider } from 'tamagui'
export default function App() {
return (
{/* your app */}
)
}
```
`SafeAreaProvider` has to wrap `TamaguiProvider`. Then pass `safe` to any padding
or margin prop, or to `top`, `bottom`, `left`, `right`, and `inset`:
```tsx
```
On web, `safe` becomes the CSS `env(safe-area-inset-*)` value, so the same code
works in the browser without this setup.
### Menus
`Menu` and `ContextMenu` can render the platform's own menus, with system haptics
and blur, when you pass the `native` prop.
```bash
npm install @expo/ui
```
```tsx
import '@tamagui/native/setup-expo-ui-menu'
```
```tsx
```
The Zeego adapter is still available for apps on React Native versions it
supports:
```bash
npm install zeego @react-native-menu/menu react-native-ios-context-menu react-native-ios-utilities sf-symbols-typescript
```
```tsx
import '@tamagui/native/setup-zeego'
```
See [Menu](/ui/menu) and [ContextMenu](/ui/context-menu).
### Toast
With `burnt`, toasts use the system UI: SPIndicator on iOS, `ToastAndroid` on
Android, and the Notification API on web.
```bash
npm install burnt
```
```tsx
import '@tamagui/native/setup-burnt'
```
```tsx
{/* your app */}
```
See [Toast](/ui/toast).
## core/styled
---
title: styled()
description: Extend and build custom and optimizable components
---
`styled()` extends a component with base styles and typed variants while
preserving Tamagui's compiler optimizations.
For the full list of accepted style properties, see the
[Styles page](/docs/intro/styles).
Start from `View` or `Text` for new components. `styled()` adds base styles and
typed variants, and the compiler still optimizes the result:
```tsx
import { GetProps, styled, View } from '@tamagui/core'
export const RoundedSquare = styled(View, {
borderRadius: 20,
})
// helper to get props for any TamaguiComponent
export type RoundedSquareProps = GetProps
```
`styled()` wraps any Tamagui component, including the
[HTML elements](/docs/core/html-primitives) such as `html.article`.
Usage:
```tsx
```
You can pass any prop that is supported by the component you are wrapping in styled.
## Variants
With variants:
```tsx
import { type SizeTokens, View, styled } from '@tamagui/core'
export const RoundedSquare = styled(View, {
borderRadius: 20,
variants: {
pin: {
top: {
position: 'absolute',
top: 0,
},
},
centered: {
true: {
alignItems: 'center',
justifyContent: 'center',
},
},
size: styled.dynamic((size, { tokens }) => ({
width: tokens.size[size] ?? size,
height: tokens.size[size] ?? size,
})),
} as const,
})
```
Please use `as const` for the variants definition until Typescript gains the ability to
infer generics as const.
Usage:
```tsx
```
To learn more, [see the docs on variants](/docs/core/variants).
### Non-working React Native views
React Native utility views like Pressable and TouchableOpacity are not supported targets for `styled()`. Their event handling logic conflicts with Tamagui. Tamagui components provide press handling and interaction states internally, or you can render `Pressable` directly when needed.
## Using on the web
The `styled()` function supports Tamagui views, React Native views, and any other React component that accepts a `style` prop. If you wrap an external component that Tamagui doesn't recognize, Tamagui will assume it only supports the `style` prop and not optimize it.
If it does accept `className`, you can opt-in to className, CSS media queries, and compile-time optimization by adding `acceptsClassName`:
```tsx
import { SomeCustomComponent } from 'some-library'
import { styled } from 'tamagui' // or '@tamagui/core'
export const TamaguiCustomComponent = styled(SomeCustomComponent, {
acceptsClassName: true,
})
```
## render
The `render` prop lets you control which element or component is rendered. It accepts three forms:
### String (HTML element)
Render as a specific HTML element on web while maintaining native View on mobile:
```tsx
const Button = styled(View, {
render: 'button',
padding: '4',
backgroundColor: 'background',
})
const Anchor = styled(Text, {
render: 'a',
color: 'blue-500',
})
const Nav = styled(View, {
render: 'nav',
})
```
This is the recommended approach for semantic HTML and accessibility. String render props are optimized by the Tamagui compiler.
### JSX element
Pass a JSX element to clone with Tamagui's computed props:
```tsx
}
padding="4"
backgroundColor="background"
>
About Page
```
The JSX element's props are merged with Tamagui's computed styles, classNames, and event handlers. This is useful when you need to pass element-specific props like `href` or `target`.
JSX element render props are not optimized by the compiler and will cause a deopt.
### Function
For full control, pass a render function that receives props and component state:
```tsx
import { TamaguiComponentState } from '@tamagui/core'
(
)}
padding="4"
backgroundColor="hover:blue-200"
>
Custom Button
```
The state object includes:
- `hover`: true when hovered (web)
- `press`: true when pressed
- `focus`: true when focused
- `disabled`: true when disabled
Function render props are not optimized by the compiler and will cause a deopt.
### Runtime override
You can also override the render prop at runtime:
```tsx
const Box = styled(View, {
padding: '4',
})
// Use as a button
Click me
// Use as a link
About
```
## createStyledHOC
If a functional component that renders a Tamagui-styled component should itself accept `styled()`, wrap it with `createStyledHOC`. Example:
```tsx
// 1. you create a `styled` component as usual:
const StyledText = styled(Text)
// 2. you create a wrapper component that adds some logic
// but still returns a styled component that receives the props:
const HigherOrderStyledText = (props) =>
// 3. you want that wrapper component itself to be able to use with `styled`:
const StyledHigherOrderStyledText = styled(HigherOrderStyledText, {
variants: {
// oops, variants will merge incorrectly
},
})
```
Without the wrapper, Tamagui resolves the style props before passing them down, so the inner component merges variants differently than you'd expect.
The way to fix this is to wrap your `HigherOrderStyledText` with `createStyledHOC`. You'll also want to forward the ref, which is forwarded for you:
```tsx
const StyledText = styled(Text)
// note the createStyledHOC wrapper here:
const HigherOrderStyledText = createStyledHOC(StyledText, (props, ref) => (
))
const StyledHigherOrderStyledText = styled(HigherOrderStyledText, {
variants: {
// variants now merge correctly
},
})
```
Now your component will handle everything properly, even if a theme is changed on `HigherOrderStyledText`, it will be applied.
Pass all Tamagui style props given to `HigherOrderStyledText` down to a single `StyledText` for everything to work correctly.
And if you'd like to add new props on top of the existing props, annotate the render function's props parameter. The annotated props merge over the wrapped component's props:
```tsx
import { createStyledHOC, View, ViewProps } from '@tamagui/core'
type ExtraProps = {
someCustomProp: boolean
}
export type CustomProps = ViewProps & ExtraProps
const Custom = createStyledHOC(View, (props: ExtraProps) => {
// ...
return null
})
```
---
## Component resolvers (.resolve)
The `.resolve` method attaches a dynamic style resolver to a styled component. It receives the component's complete resolved props and the style environment:
```tsx
import { View, styled } from 'tamagui' // or '@tamagui/core'
export const AlertBox = styled(View, {
backgroundColor: 'background',
padding: '4',
variants: {
tone: styled.dynamic<'neutral' | 'critical'>(),
disabled: {
true: { opacity: 0.5 },
},
} as const,
}).resolve((props, env) => ({
backgroundColor: props.tone === 'critical' ? env.theme['red-10'] : undefined,
borderColor: props.tone === 'critical' ? env.theme['red-8'] : undefined,
}))
```
### Complete props and environment
The resolver callback receives two arguments:
1. `props`: the complete styled component prop type, including base props and all declared variants.
2. `env`: an object containing `{ theme, tokens, fonts, font, fontFamily }`.
Because `props` includes the component's full prop types, `.resolve` replaces compound variants and sibling-prop inspection logic.
### Immutable chaining
Calling `.resolve` returns a new component. It never mutates the original component:
```tsx
const Original = styled(View, {})
const Resolved = Original.resolve(() => ({ opacity: 0.5 }))
// Original is unchanged; Resolved has the resolver attached
```
Each styled layer may attach one resolver. When extending a component with `styled()`, child components inherit their parent's resolver chain.
### Parent-first execution
The resolver chain executes in parent-first order. The child resolver runs after its parent resolver and overrides conflicting keys:
```tsx
const Parent = styled(View, {
variants: {
tone: styled.dynamic<'neutral' | 'critical'>(),
},
}).resolve((props, env) => ({
backgroundColor: props.tone === 'critical' ? env.theme['red-10'] : undefined,
width: 100,
}))
const Child = styled(Parent, {}).resolve((props) => ({
width: props.tone === 'critical' ? 200 : undefined,
}))
```
For ``, `width` is `200` (child resolver wins) and `backgroundColor` is `env.theme['red-10']` (parent resolver lands).
### Precedence tiers
Component resolvers sit at layer 2 in Tamagui's precedence hierarchy:
```
0 base styles < 1 variants < 2 component resolvers < 3 callsite style props < 4 style prop
```
Rules for resolution:
- A resolver beats base styles and variants for the same property.
- A callsite style prop (such as ``) beats the resolver.
- The `style` prop beats everything.
- Setting a property to `undefined` in resolver output means absent. The value falls through to lower tiers (variants or base styles).
### Static shape rule
For compiler extraction, write resolver return objects with static keys and scalar expressions. Return `undefined` for inactive branches instead of spreading objects into the return literal:
```tsx
// good: static keys, undefined for inactive branch
const Box = styled(View, {
variants: {
tone: styled.dynamic<'neutral' | 'critical'>(),
},
}).resolve((props, env) => ({
backgroundColor: props.tone === 'critical' ? env.theme['red-10'] : undefined,
opacity: props.disabled ? 0.5 : undefined,
}))
// avoid: spreads and computed keys deopt compiler extraction
const DeoptBox = styled(View, {
variants: {
tone: styled.dynamic<'neutral' | 'critical'>(),
},
}).resolve((props) => ({
...(props.tone === 'critical' && { backgroundColor: 'red' }),
}))
```
## core/animations-motion
---
title: Motion Driver
description: High-performance web animations with WAAPI
---
[Motion](https://motion.dev) combines JavaScript animation controls with the
Web Animations API for smooth, browser-native animation.
## Installation
```bash
yarn add @tamagui/animations-motion motion
```
## Configuration
Add your animations to your [Tamagui config](/docs/core/configuration):
```tsx
import { createAnimations } from '@tamagui/animations-motion'
import { createTamagui } from 'tamagui'
export default createTamagui({
animations: createAnimations({
quick: { duration: 220, bounce: 0.3 },
bouncy: { duration: 400, bounce: 0.5 },
superBouncy: { duration: 400, bounce: 0.75 },
}),
// ...
})
```
The config format is the same for all four drivers, so a name means the same
motion whichever driver renders it. There is no need for `'200ms'`-style entries
any more: a duration is CSS, so `transition="200ms"` works with nothing
configured.
## How it works
At runtime, the Motion driver uses the `useAnimate()` hook from the Motion
library to batch style changes and animate only what changed. It runs through
the Web Animations API (WAAPI), which animates on the compositor thread when
possible, keeping animations smooth even when the main thread is busy.
## Animation configuration
Motion animations support both spring and timing animations:
### Spring animations
```tsx
{
duration: 400, // the spring's undamped period, in milliseconds
bounce: 0.5, // 0 is critically damped, toward 1 is loose, negative is sluggish
}
```
`stiffness`, `damping`, and `mass` still work for a config already tuned against
them, and `velocity`, `overshootClamping`, and the rest thresholds pass through.
### Timing animations
```tsx
{
type: 'timing',
duration: 200, // Duration in milliseconds
easing: 'ease-out', // any css timing function
}
```
A plain CSS string means the same thing: `'200ms ease-out'`.
### Delay
```tsx
```
The delay is specified in milliseconds and is converted to seconds for Motion
internally.
## Highlights
### Zero re-renders
The Motion driver bypasses internal style recalculations and hands off updates directly to Motion.
### WAAPI performance
Off-thread performance comparable to Reanimated, but for web-only applications.
## Platform-specific configuration
For cross-platform apps, use Motion on web and Reanimated on native:
```tsx
// animations.ts (web)
import { createAnimations } from '@tamagui/animations-motion'
export const animations = createAnimations({
bouncy: { duration: 400, bounce: 0.5 },
})
```
```tsx
// animations.native.ts
import { createAnimations } from '@tamagui/animations-reanimated'
export const animations = createAnimations({
bouncy: { duration: 400, bounce: 0.5 },
})
```
Then import without the extension:
```tsx
import { animations } from './animations'
```
Your bundler will automatically pick the right file based on the platform.
## See also
- [Animations Introduction](/docs/core/animations)
- [CSS Driver](/docs/core/animations-css)
- [React Native Driver](/docs/core/animations-react-native)
- [Reanimated Driver](/docs/core/animations-reanimated)
- [Motion Documentation](https://motion.dev)
## core/tokens
---
title: Tokens
description: Named design values for space, size, radius, color, and z-index
---
Tokens give style props typed names for shared design values such as space,
size, radius, color, and z-index.
Create them with `createTokens`, then pass them to `createTamagui` as part of
the [configuration](/docs/core/configuration) object.
## Getting tokens
For example, if you define some tokens:
```tsx
const tokens = createTokens({
size: {
small: 10,
},
})
```
After you pass that into `createTamagui`, you can access your tokens from anywhere using `getTokens`:
```tsx
import { getTokens } from '@tamagui/core'
getTokens().size.small
```
or
```tsx
getTokens().size['small']
```
`getTokens` takes no options in v3. Tokens are keyed by their unprefixed name,
so `size.small` is the only spelling you need. The `prefixed` option that used to
return a `$`-keyed copy is removed.
`getTokens()` returns `Variable` objects, which Tamagui uses to manage values and CSS variable names:
```tsx
getTokens().size.small.val // returns 10
getTokens().size.small.variable // returns something like (--size-small), which matches the CSS rule inserted
```
Tamagui has some helpers that make working with variables easier, which are documented in [Exports](/docs/core/exports), namely [`getVariable`](/docs/core/exports#getvariable) which will return the CSS variable on web, but raw value on native, and `getVariableValue` which always returns the raw value.
## v3 token behavior
The default v3 configs do not export `true` token keys. Component defaults still
resolve to the default size, and that default size is `4`.
```tsx
// before v3
// v3
```
`@tamagui/get-token` also no longer performs runtime token stepping. `getSize`,
`getSpace`, and `getRadius` resolve the same key you pass in. The old
`stepTokenUpOrDown`, `getTokenRelative`, and options such as `shift`, `bounds`,
and `excludeHalfSteps` are removed.
```tsx
// before v3
const padding = getSize(size, { shift: -2 })
// v3
const padding = getVariableValue(getSize(size)) * 0.6
```
If you need a smaller or larger named token, choose that token explicitly. If
you need proportional sizing, multiply the resolved numeric value.
### Color tokens as fallback values for themes
Color tokens are available as fallback values when you access a theme. So when you `useTheme()` and then access a value that isn't in the theme, it will check for a `tokens.color` with the matching name.
Think of it this way:
- Tokens are static and are your base values.
- Themes are dynamic, they can change in your React tree, and live above tokens.
You can skip themes at first and build with style props and tokens alone, adding `light` and `dark` themes only when you need them. Avoid nested themes until you need them.
## Using tokens with components
When using `styled` or any Tamagui component like `View`, you can access tokens directly. Just like with `useTheme`, it will first look for a theme value that matches, and if not it will fall back to a token.
Tokens apply automatically to matching properties. `size` tokens apply to width and height, `radius` to borderRadius:
## Specific tokens
You can also define custom token categories of your own:
```tsx
const tokens = createTokens({
// ...other tokens
icon: {
small: 16,
medium: 24,
large: 32,
},
})
```
And then access them using the "specific tokens" syntax:
```tsx
export default () => (
)
```
This, like all token values, works the same with `styled`:
```tsx
import { styled, View } from '@tamagui/core'
export const MyView = styled(View, {
width: 'icon.small',
})
```
When creating custom tokens, you can use the `px` helper to ensure values get proper pixel units on web while remaining as raw numbers on native:
```tsx
import { createTokens, px } from '@tamagui/core'
const tokens = createTokens({
customSize: {
small: px(100), // → "100px" on web, 100 on native
medium: px(200),
large: px(300),
},
opacity: {
low: 0.25, // → 0.25 (unitless on both platforms)
medium: 0.5,
high: 0.75,
},
})
```
The predefined token categories like `size`, `space`, and `radius` automatically add
pixel units where appropriate, so you don't need to use the `px` helper for them.
## core/use-media
---
title: useMedia
description: Respond to different screen sizes
---
`useMedia` reads the same named media rules that style props use for
responsive conditions.
Define those rules in the media object of your `tamagui.config.ts`:
```tsx line=2-6
export default createTamagui({
media: {
xs: { maxWidth: 660 },
gtXs: { minWidth: 660 + 1 },
sm: { maxWidth: 860 },
gtSm: { minWidth: 860 + 1 },
md: { maxWidth: 980 },
gtMd: { minWidth: 980 + 1 },
lg: { maxWidth: 1120 },
gtLg: { minWidth: 1120 + 1 },
short: { maxHeight: 820 },
tall: { minHeight: 820 },
hoverNone: { hover: 'none' },
touch: { pointer: 'coarse' },
},
})
```
Name them as you like. We use `sm`, `md`, `lg`, and `gtSm`, where `gt` means
"greater than".
Order matters: items defined further down override items before them. Here
`xs` is the weakest and each entry overrides the ones above it.
Tamagui converts each entry to a media query, turning camelCase into
hyphen-case and wrapping it in `@media()`.
### Inline props
Use media names as typed object keys or string modifiers on the style prop:
StringTyped
```tsx
import { useState } from 'react'
import { Button, XStack } from 'tamagui' // note: design system can use @tamagui/core
export default () => {
const [x, setX] = useState(0)
return (
0.5 ? 'green' : 'yellow',
}}
>
)
}
```
```tsx
import { useState } from 'react'
import { Button, XStack } from 'tamagui' // note: design system can use @tamagui/core
export default () => {
const [x, setX] = useState(0)
return (
0.5 ? 'green' : 'yellow'}`}
>
)
}
```
In this example we are doing mobile-first design, where clauses override the base as the viewport gets wider. The ternary payload in `gtMd:` is extractable with Tamagui and still outputs simple CSS.
### Hooks
```tsx
import { Button, XStack, useMedia } from 'tamagui' // note: design system can use @tamagui/core
export default () => {
const media = useMedia()
return (
)
}
```
As long as **all** of your usages of useMedia are extractable, Tamagui will actually generate your CSS and then fully remove the hook from the output code. You can check this by adding `// debug` to the top of your component.
You can also use `useMedia` for logic outside of styles. If an expression
cannot be extracted, Tamagui falls back to runtime execution.
Tamagui tracks which keys are accessed, and only updates if the media query matching that key changes.
#### Limitations
The `useMedia` hook uses proxies so it can track which keys you are accessing and only re-render components that need re-rendering based on those keys. The Tamagui compiler also understands straightforward uses of the hook during compile-time and can remove it entirely when targeting the web, in favor of CSS.
- The proxied object returned by `useMedia` is not iterable, and does not support `in` checks or `Object.keys`.
- Write `const media = useMedia()` and access keys directly, like `media.sm`, for best results with the compiler. The compiler supports destructuring without renaming.
## core/server-rendering
---
title: Server Rendering
description: Advanced SSR, hydration, and client-only rendering in Tamagui
---
Tamagui renders responsive styles and animation state on the server so the
first client frame can hydrate without flickering.
## How it works
Tamagui renders in three phases:
1. **Server**: Renders with proper CSS media queries for everything, including components using spring animation drivers (which normally require hard-coded values)
2. **Client (first render)**: Renders with CSS to match the server output perfectly
3. **Client (after hydration)**: Seamlessly swaps to springs and animations
Hydration matches the server output without flickering, even for responsive layouts with animations.
## Opting out
You can opt out of SSR entirely or for specific parts of your app.
### Wholesale: disableSSR
For single-page applications that don't need SSR, disable it entirely in your config:
```tsx
import { createTamagui } from 'tamagui'
export const config = createTamagui({
// ... other config
settings: {
disableSSR: true, // Automatically wraps app with
},
})
```
When `disableSSR` is true:
- Media queries immediately use actual values
- No double render occurs
- Your entire app is wrapped with ``
### Partial: ClientOnly component
For fine-grained control, wrap specific parts of your tree:
```tsx
import { ClientOnly } from '@tamagui/use-did-finish-ssr'
function MyComponent() {
return (
{/* Server-rendered for SEO */}
{/* Client-only for complex interactions */}
)
}
```
## Configuration component
The `Configuration` component is essentially a shorthand for `ClientOnly`. It accepts [configuration settings](/docs/core/configuration) like `disableSSR`:
```tsx
import { Configuration } from 'tamagui'
function MyApp() {
return (
{/* Everything here renders client-only */}
)
}
```
## useMedia and SSR
The `useMedia` hook automatically leverages Tamagui's smart SSR rendering. During server render, it returns appropriate defaults that match CSS media queries, then updates after hydration without flickering.
```tsx
import { useMedia } from 'tamagui'
function ResponsiveComponent() {
const media = useMedia() // Automatically handles SSR correctly
return media.gtSm ? :
}
```
## SSR hooks
### useDidFinishSSR
Returns `true` when hydration is complete:
```tsx
import { useDidFinishSSR } from '@tamagui/use-did-finish-ssr'
function MyComponent() {
const isClient = useDidFinishSSR()
return isClient ? :
}
```
**Behavior:**
- Server/before hydration: `false`
- After hydration: `true`
- Inside ``: always `true`
### useIsClientOnly
Checks if you're in a client-only context:
```tsx
import { useIsClientOnly } from '@tamagui/use-did-finish-ssr'
function MyComponent() {
const isClientOnly = useIsClientOnly()
if (isClientOnly) {
// Safe to use browser APIs
return
}
return
}
```
### useClientValue
Returns `undefined` during SSR, your value after hydration:
```tsx
import { useClientValue } from '@tamagui/use-did-finish-ssr'
function WindowInfo() {
const width = useClientValue(() => window.innerWidth)
return {width ? `${width}px` : 'Loading...'}
}
```
## Best practices
### When to use disableSSR
**Use it for:**
- Single-page applications
- Client-only web apps
- Maximum performance without SSR overhead
**Avoid for:**
- SEO-critical content
- Server-rendered frameworks (Next.js SSR/SSG)
- Fast initial page loads
### When to use ClientOnly
**Use it for:**
- Expensive client-only components (charts, editors)
- Browser API dependencies
- Non-critical UI that can load after initial render
**Example:**
```tsx
function ProductPage({ product }) {
return (
{/* SEO-critical content */}
{/* Defer complex UI */}
)
}
```
### When to use hooks
- `useDidFinishSSR`: Conditional rendering based on hydration state
- `useIsClientOnly`: Checking client-only context for browser APIs
- `useClientValue`: Shorthand for client-only values
## Package reference
```tsx
import {
ClientOnly, // Component for client-only subtrees
useDidFinishSSR, // Hook for hydration state
useIsClientOnly, // Hook for client-only context
useClientValue, // Hook for client-only values
} from '@tamagui/use-did-finish-ssr'
```
## See also
- [Configuration](/docs/core/configuration): learn more about settings
- [useMedia](/docs/core/use-media): responsive media queries
- [Themes](/docs/intro/themes): theme system and context
## core/animations-react-native
---
title: React Native Driver
description: Spring-based animations using React Native Animated
---
This native-only driver connects Tamagui to [React Native's Animated
library](https://reactnative.dev/docs/animated).
Web applications should use the CSS, Motion, or Reanimated driver.
## Installation
```bash
yarn add @tamagui/animations-react-native
```
## Configuration
Add it to your [Tamagui config](/docs/core/configuration):
```tsx
import { createAnimations } from '@tamagui/animations-react-native'
import { createTamagui } from 'tamagui'
export default createTamagui({
animations: createAnimations({
fast: { duration: 160, bounce: 0.25 },
medium: { duration: 300, bounce: 0.15 },
slow: { duration: 450, bounce: 0.1 },
}),
// ...
})
```
The config format is the same for all four drivers, so a name means the same
motion whichever driver renders it.
## Animation configuration
React Native animations are springs, written as a duration and a bounce:
### Spring parameters
- **`duration`**: the spring's undamped period in milliseconds, defining how fast the animation feels. It is not a fixed cutoff: a bouncy spring continues oscillating past it.
- **`bounce`**: 0 is critically damped, values toward 1 oscillate, and negative values produce an overdamped spring.
```tsx
createAnimations({
bouncy: { duration: 400, bounce: 0.5 },
quick: { duration: 220, bounce: 0.3 },
})
```
`stiffness`, `damping`, and `mass` still work for a config already tuned against
them, and `velocity`, `overshootClamping`, and the rest thresholds pass through
to `Animated.spring`.
### Delay
```tsx
```
The delay is specified in milliseconds and uses `Animated.delay()` internally to
sequence animations.
## When to use
**Advantages on native:**
- **Built-in**: no additional external animation library needed
- **Spring physics**: natural, physics-based motion
- **Reliable**: uses core React Native Animated
**Limitations:**
- Less advanced than Reanimated
- Not available on web
## Platform-specific configuration
You can use different drivers on web vs native using platform-specific file
extensions:
```tsx
// animations.ts (web)
import { createAnimations } from '@tamagui/animations-css'
export const animations = createAnimations({
quick: { duration: 220, bounce: 0.3 },
})
```
```tsx
// animations.native.ts (native)
import { createAnimations } from '@tamagui/animations-react-native'
export const animations = createAnimations({
quick: { duration: 220, bounce: 0.3 },
})
```
Then import without the extension:
```tsx
import { animations } from './animations'
```
Your bundler will automatically pick the right file based on the platform.
## Example
```tsx
import { createAnimations } from '@tamagui/animations-react-native'
import { YStack, createTamagui } from 'tamagui'
const animations = createAnimations({
bouncy: { duration: 400, bounce: 0.5 },
quick: { duration: 220, bounce: 0.3 },
})
export default createTamagui({
animations,
// ... rest of config
})
// Usage in components
export const MyComponent = () => (
)
```
## See also
- [Animations Introduction](/docs/core/animations)
- [CSS Driver](/docs/core/animations-css)
- [Motion Driver](/docs/core/animations-motion)
- [Reanimated Driver](/docs/core/animations-reanimated)
## core/fonts
---
title: Fonts
description: Define font families with createFont and load custom fonts on native.
---
Fonts are the one token category with structure. Each family carries its own
size, line height, weight, and letter spacing scales.
Create one with `createFont` and pass it to `createTamagui` under `fonts`:
```tsx fileName="tamagui.config.ts"
import { createFont, createTamagui, isWeb } from 'tamagui'
const interFont = createFont({
family: isWeb ? 'Inter, Helvetica, Arial, sans-serif' : 'Inter',
size: {
1: 12,
2: 14,
3: 15,
// ...
},
lineHeight: {
1: 17,
2: 22,
3: 25,
// ...
},
weight: {
4: '300',
6: '600',
},
letterSpacing: {
4: 0,
8: -1,
},
// android needs the weight mapped to the font file's family name
// find it with: otfinfo --family Inter.ttf
face: {
400: { normal: 'Inter', italic: 'Inter-Italic' },
500: { normal: 'InterBold', italic: 'InterBold-Italic' },
},
})
export const config = createTamagui({
fonts: {
heading: interFont,
body: interFont,
},
// ...
})
```
Keys can be any string. The `tamagui` components expect number keys `1` through `10`
on `size`, so [headings](/ui/headings) and sized text resolve automatically.
`createFont` fills gaps for you. When `lineHeight`, `weight`, or `letterSpacing`
define fewer keys than `size`, each missing key carries the previous value forward,
and keys before the first one you define take that first value:
```tsx
const systemFont = createFont({
family: isWeb ? 'Helvetica, Arial, sans-serif' : 'System',
size: { 1: 12, 2: 14, 3: 15 },
lineHeight: { 2: 22 }, // 1 becomes 22
weight: { 1: '300', 3: '600' }, // 2 becomes 300
letterSpacing: { 1: 0, 2: -1 }, // 3 becomes -1
})
```
Set `settings.defaultFont` to the key of the family that text should use when no
`fontFamily` is given. See [Settings](/docs/core/settings).
## Face
On Android, React Native picks a font file by name and ignores `fontWeight`. The
`face` map tells Tamagui which file name to use for each weight and style. Without
it, every weight renders with the same file. iOS and web ignore `face`.
## Loading fonts on native
React Native has to load custom font files before it can use them. Tamagui does not
handle loading. With Expo, use `useFonts` from `expo-font` and gate rendering on the
result. The [Expo guide](/docs/guides/expo#loading-fonts) walks through both
`@tamagui/font-inter` and `@expo-google-fonts`. Without Expo, follow the React Native
docs for linking font assets.
The name you load must match the name used in `family` and in `face`:
```tsx fileName="App.tsx"
import { useFonts } from 'expo-font'
function App() {
const [loaded] = useFonts({
Inter: require('@tamagui/font-inter/otf/Inter-Medium.otf'),
InterBold: require('@tamagui/font-inter/otf/Inter-Bold.otf'),
})
if (!loaded) {
return null
}
return
}
```
## Using font tokens
Text components read a family with `fontFamily`. Style props such as `fontSize`
and `lineHeight` accept a key from that family's scale. `SizableText`, `Paragraph`,
and the headings take a `size` prop that picks `fontSize`, `lineHeight`, `weight`,
and `letterSpacing` from the family together:
```tsx
Hello world
Hello world
```
## Per-language fonts
A font name with a `_` suffix is a language variant of the base family. Define
`body_cn` next to `body` and load both font files:
```tsx
import { createFont, createTamagui } from 'tamagui'
export default createTamagui({
fonts: {
body: createFont({
family: 'Inter, Helvetica, Arial, sans-serif',
// ...
}),
body_cn: createFont({
family: 'Inter-CN',
// ...
}),
},
})
```
`FontLanguage` then switches a family for a whole subtree. Every component
reading `fontFamily="body"` inside it picks up the variant, with no prop
changes on the text itself:
```tsx
import { FontLanguage, html } from 'tamagui'
export default () => (
你好
)
```
The suffix name is yours to choose. The reserved value `default` resets a
family to its base font:
```tsx
你好Hello
```
On web, `FontLanguage` swaps a CSS variable, so changing language does not
re-render the text.
## core/variants
---
title: Variants
description: Simple typed prop styles through styled()
---
Variants turn a small set of typed component props into reusable groups of
styles.
Variants expand a single prop into a group of styles. Example:
```tsx
import { type SizeTokens, View, styled } from 'tamagui' // or '@tamagui/core'
export const Circle = styled(View, {
borderRadius: 100_000_000,
variants: {
pin: {
top: {
position: 'absolute',
top: 0,
},
},
centered: {
true: {
alignItems: 'center',
justifyContent: 'center',
},
},
size: styled.dynamic((size, { tokens }) => ({
width: tokens.size[size] ?? size,
height: tokens.size[size] ?? size,
})),
} as const,
})
```
Notice the `as const` on the variant definition object. This is necessary to please
TypeScript until it gains the ability to infer constant objects. If left out, your types
may break.
Usage:
```tsx
```
This component uses a few different types of variants, expanded on below.
## Why variants?
Sharing plain style objects means typing props by hand and gluing them together
with arbitrary logic inside a functional component. The types may not map to
the actual output, and a compiler cannot optimize what it cannot see.
Variants force you out of the React render function, which means no hooks and a
much clearer contract: a dynamic takes one value and the style environment, and
outputs a statically-shaped group of styles. Sibling-prop logic belongs in a
component `.resolve` callback.
Because variants work with the styled function, they nest without adding depth
to your render tree. `styled(styled())` is a single React component that the
compiler can optimize and flatten.
## Variants
### Typed variants
#### `true` or `false`
The special keys `true` and `false` will map to a boolean. So the `centered`
prop will be typed to accept true or false, and when true it will apply its
styles.
```tsx
import { View, styled } from 'tamagui' // or '@tamagui/core'
export const MyView = styled(View, {
variants: {
selectable: {
true: {
userSelect: 'auto',
},
false: {
userSelect: 'none',
},
},
} as const,
})
```
#### String, boolean, and number domains
For an open-ended value domain, declare the prop type on `styled.dynamic`.
There is no runtime type-key lookup:
```tsx
import { View, styled } from 'tamagui' // or '@tamagui/core'
export const ColorfulView = styled(View, {
variants: {
color: styled.dynamic((color) => ({
color,
borderColor: color,
})),
} as const,
})
```
Use an exact-key object when the domain is finite, or one dynamic when it is
parametric. A key such as `string`, `number`, or `Size` is otherwise just an
exact literal key.
### Dynamic token variants
When you write variants, you have to be explicit so TypeScript and the runtime
know exactly which props you accept. This can be especially cumbersome when you
want to "gather" all the values of a specific token scale. For example, without
a dynamic, if you wanted to have a `pad` property that accepted all the
keys from `tokens.size`, you'd have to write this:
```tsx
// in your tamagui.config.ts:
const tokens = createTokens({
size: {
sm: 10,
md: 15,
lg: 25,
// ...
}
// ... see configuration docs for required tokens
})
export default createTamagui({
tokens
})
// somewhere in your app:
const MyButton = styled(View, {
variants: {
pad: {
sm: {
padding: tokens.size.sm,
},
md: {
padding: tokens.size.md,
},
lg: {
padding: tokens.size.lg,
},
// ...
}
} as const
})
// now you can
```
This is verbose, and every token change means updating every component. With a
dynamic instead:
```tsx
// in your tamagui.config.ts:
const tokens = createTokens({
size: {
sm: 10,
md: 15,
lg: 25,
// ...
}
// ... see configuration docs for required tokens
})
export default createTamagui({
tokens
})
// somewhere in your app:
const MyButton = styled(View, {
variants: {
pad: styled.dynamic((val, { tokens }) => ({
padding: tokens.size[val]
}))
} as const
})
// now you can
```
Dynamic variants save you from hardcoding every token key. The generic
parameter is the accepted prop type, so one callback can cover a token scale,
raw values, or a union without runtime type-key matching:
```tsx
const Square = styled(View, {
variants: {
size: styled.dynamic((val, { tokens }) => {
const value = tokens.size[val] ?? val
return { width: value, height: value }
}),
} as const,
})
// both work:
```
#### Environment passed to dynamic variants
The second argument contains the current tokens, theme, and fonts. It never
contains component props. Use `.resolve((props, env) => ...)` when styles depend
on sibling props.
```tsx
const SizableText = styled(Text, {
variants: {
size: styled.dynamic((size, { tokens, font }) => ({
fontSize: font?.size[size],
lineHeight: font?.lineHeight[size],
height: tokens.size[size] ?? size,
})),
} as const,
})
```
Usage:
```tsx
Hello world
```
The second argument is always
`{ theme, tokens, fonts, font, fontFamily }`.
#### Bare dynamic declarations
When variant styles depend on sibling props, declare the prop type with bare `styled.dynamic()` and apply the styles in a component `.resolve` callback:
```tsx
import { View, styled } from 'tamagui' // or '@tamagui/core'
export const AlertBox = styled(View, {
variants: {
tone: styled.dynamic<'neutral' | 'critical'>(),
},
}).resolve((props, env) => ({
backgroundColor: props.tone === 'critical' ? env.theme['red-10'] : undefined,
opacity: props.disabled ? 0.5 : undefined,
}))
```
Bare dynamic variants declare the accepted prop type and mark the prop as consumed. It is not forwarded to the underlying DOM element or native view.
#### Dynamic value domains
Use the dynamic's generic parameter to declare the whole accepted domain, then
branch on the value inside the callback when needed:
```tsx
import { View, styled } from 'tamagui' // or '@tamagui/core'
export const ColorfulView = styled(View, {
variants: {
colorful: styled.dynamic((val) => ({
color: val === true ? 'red' : val,
})),
} as const,
})
```
#### Dynamic variants
If you need more complex types, use `styled.dynamic` to brand the value-to-style
function and declare the prop type:
```tsx
import { View, styled } from 'tamagui' // or '@tamagui/core'
export const MyView = styled(View, {
variants: {
doubleMargin: styled.dynamic((val) => ({
margin: val * 2,
})),
} as const,
})
```
The callback is invoked once per active flat-clause payload, so responsive and
stateful values keep the same behavior as exact variants.
### defaultVariants
Sometimes you'd like to set a default value for a variant you've just set on
your styled() component. Due to the way Typescript types parse from left to
right, we can't properly type variants directly on the object you define them
on.
The `defaultVariants` option allows you to set these, properly typed:
```tsx
const Square = styled(View, {
variants: {
size: styled.dynamic((size, { tokens }) => ({
// size === 'lg'
// tokens.size.lg === 25
width: tokens.size[size] ?? size,
height: tokens.size[size] ?? size,
})),
} as const,
// will get size '10' from size tokens automatically
defaultVariants: {
size: '10',
},
})
```
### Variants and flat clauses
Variant branches can use the same state and media clauses as any other style value,
in string or object form:
```tsx
const SizedText = styled(Text, {
variants: {
size: {
sm: { fontSize: 'sm' },
md: {
fontSize: 'sm md:base xl:lg',
// equivalent:
// fontSize: { default: 'sm', md: 'base', xl: 'lg' },
},
},
} as const,
})
```
The variant prop itself accepts conditions too, so a variant can change per
state or breakpoint:
```tsx
```
### Variants and parent variants
Styled components can access their parent components variants, even in their
variants:
```tsx
const ColorfulText = styled(Text, {
variants: {
colored: {
true: {
color: 'color',
},
},
large: {
true: {
fontSize: '8',
},
},
} as const,
})
const MyParagraph = styled(ColorfulText, {
colored: true,
variants: {
hero: {
true: {
large: true,
},
},
} as const,
})
```
## core/cli
---
title: Tamagui CLI
description: Command-line tools for building, checking, and managing Tamagui projects
---
The Tamagui CLI provides a suite of command-line tools for optimizing components,
managing dependencies, adding fonts and icons, and more.
**Just starting out?** Skim this section, the CLI is not necessary to get going.
## Installation
Install the CLI as a development dependency:
```bash
yarn add -D @tamagui/cli
```
## Commands
### build
Pre-compile Tamagui components in-place for production builds. This is useful
for bundlers that don't have a Tamagui plugin yet (like Turbopack) or when you
want a simple setup that works with any bundler.
```bash
# Build all components in a directory (web + native by default)
npx tamagui build ./src
# Build for web only
npx tamagui build --target web ./src
# Build for native only
npx tamagui build --target native ./src
# Build a specific file
npx tamagui build ./src/components/MyComponent.tsx
# Include/exclude patterns
npx tamagui build --include "components/**" --exclude "**/*.test.tsx" ./src
# Output to a separate directory (source files unchanged)
npx tamagui build --output ./dist ./src
# Create platform-specific files next to source files (.web.tsx or .native.tsx)
npx tamagui build --target native --output-around ./src
# Preview changes without writing files
npx tamagui build --dry-run ./src
```
**Flags:**
- `--target `: Target platform: `web`, `native`, or `both` (default: `both`)
- `--include `: Glob pattern to include files
- `--exclude `: Glob pattern to exclude files
- `--output `: Output directory for optimized files (preserves directory structure). When specified, source files are not modified
- `--output-around`: Create platform-specific files (`.web.tsx` or `.native.tsx`) next to source files instead of modifying them. Errors if the file already exists
- `--expect-optimizations `: Fail if fewer than this many components are optimized (useful for CI)
- `--dry-run`: Preview what would be optimized without writing any files
- `--debug`: Enable debug output
- `--verbose`: Enable verbose debug output
**Platform-Specific File Handling:**
The CLI automatically handles platform-specific files:
- Files with `.web.tsx` or `.ios.tsx` extensions are optimized for web only
- Files with `.native.tsx` or `.android.tsx` extensions are optimized for native
only
- Base files (`.tsx`) without platform-specific versions are optimized for all
platforms
- If both `.web.tsx` and `.native.tsx` exist, the base `.tsx` file is skipped
**Configuration:**
Create a `tamagui.build.ts` config file in your project root:
```ts
import type { TamaguiBuildOptions } from 'tamagui'
export default {
config: './tamagui.config.ts',
components: ['tamagui'],
importsWhitelist: ['constants.js', 'colors.js'],
outputCSS: './public/tamagui.generated.css',
} satisfies TamaguiBuildOptions
```
**Integration Examples:**
The CLI can wrap your build command using `--`, which optimizes files beforehand
and automatically restores them after:
```json
{
"scripts": {
"build": "tamagui build --target web ./src -- next build"
}
}
```
The `--` separator tells the CLI to run `next build` after optimization, then
restore your source files automatically. This is the recommended approach as it
keeps your source files unchanged.
Alternatively, run `tamagui build` separately (files will remain modified):
```json
{
"scripts": {
"build": "tamagui build --target web ./src && next build"
}
}
```
**Important:** Without the `--` wrapper, files are modified in-place and not
restored. Only use this approach if you're building in a CI environment where
the files are discarded anyway.
Alternatively, use `--output` to write optimized files to a separate directory:
```json
{
"scripts": {
"build": "tamagui build --target web --output ./dist ./src && next build"
}
}
```
With `--output`, source files are never modified and no restoration is needed.
For more details, see the
[Compiler Installation guide](/docs/intro/compiler-install#cli-based-in-place-compilation).
### check
Check flat style values, inconsistent versions, duplicate installs, lockfile
issues, and missing config:
```bash
npx tamagui check
```
**Flags:**
- `--strict`: Validate conditional style payloads against the project config too
- `--styles-only`: Skip the dependency checks
- `--deps-only`: Skip the style checks
- `--debug`: Enable debug output
- `--verbose`: Enable verbose debug output
The command exits non-zero when it finds diagnostics, so it works as a CI gate.
### generate
Build your entire Tamagui configuration and output CSS. This is useful for
pre-generating your design system's CSS and validating your configuration.
```bash
npx tamagui generate
```
**Flags:**
- `--debug`: Enable debug output
- `--verbose`: Enable verbose debug output
**What it does:**
1. Loads and validates your Tamagui configuration
2. Generates CSS for all your tokens, themes, and components
3. Outputs to the `.tamagui` directory
4. Also generates an LLM-friendly prompt file at `.tamagui/prompt.md`
### generate-css
Generate the `tamagui.generated.css` file from your configuration. Useful for build
pipelines or when you need to regenerate CSS without running a full build.
```bash
npx tamagui generate-css
```
**Flags:**
- `--output `: Custom output path (default: `tamagui.generated.css`)
- `--debug`: Enable debug output
- `--verbose`: Enable verbose debug output
**Example:**
```bash
npx tamagui generate-css --output ./public/styles/tamagui.generated.css
```
### generate-themes
Pre-build your theme configuration for faster runtime performance. This
generates optimized theme objects from your theme definitions.
```bash
npx tamagui generate-themes
```
**Example:**
```bash
npx tamagui generate-themes ./themes/input.ts ./themes/generated.ts
```
**Flags:**
- `--debug`: Enable debug output
- `--verbose`: Enable verbose debug output
**Use case:** If you have complex theme generation logic, this command
pre-computes your themes at build time rather than runtime.
### icons
Generate typed, themed SVG icon components for only the icons you use. Each
icon becomes one file, and `index.ts` in the output folder exports all of them.
Running it again adds to the folder and rewrites the index.
```bash
# lucide (the default source)
npx @tamagui/cli icons add Search ChevronDown X
# phosphor, with a weight: regular, bold, duotone, fill, light, thin
npx @tamagui/cli icons add Copy --from phosphor --weight bold
# heroicons, with a variant: outline, solid, mini, micro
npx @tamagui/cli icons add Clipboard --from heroicons --variant solid
# your own svg files, looked up as .svg
npx @tamagui/cli icons add Logo --from ./assets/icons
```
**Flags:**
- `--from`: `lucide` (default), `heroicons`, `phosphor`, or a folder of svg files
- `--weight`: Phosphor weight
- `--variant`: Heroicons variant
- `--out`: Output folder (default: `components/icons`)
Names are PascalCase (`ChevronDown`) or the source's kebab-case file name
(`chevron-down`). `Name=source-name` keeps your component name while drawing a
different source icon, so a set can move between libraries without changing
its imports: `icons add Search=magnifying-glass --from phosphor`. Library versions are pinned per CLI release. The generated
components need `react-native-svg` and `@tamagui/helpers-icon`. See
[Lucide Icons](/ui/lucide-icons) for usage.
### add
**Tamagui Pro**: This command is available exclusively to Tamagui Pro members.
Add pre-configured fonts and icons from Tamagui's curated collections. This
includes Google Fonts and Iconify icon packs.
```bash
# Add a font
npx tamagui add font [packages-path]
# Add an icon pack
npx tamagui add icon [packages-path]
```
**Flags:**
- `--debug`: Enable debug output
- `--verbose`: Enable verbose debug output
**Interactive selection:**
The command will present an interactive menu where you can search and select
from available fonts or icons:
- **Fonts**: Browse Google Fonts with weight, style, and subset information
- **Icons**: Browse Iconify collections with icon counts and license information
**Default path:** If you don't provide a path, packages are installed to
`./packages` in your current directory.
**Requirements:** This feature requires
[Tamagui Pro](https://tamagui.dev/takeout) membership for access to the font and
icon repositories.
### migrate
Print an agent brief for migrating an existing app to v3.
```bash
npx tamagui migrate --from v2
npx tamagui migrate --from v1
```
`--from v1` sequences the v1-to-v2 pass ahead of the v2-to-v3 one. The brief is
the checklist of record for the upgrade: dependency updates, the config and theme
moves, the flat-values codemod, the Sheet codemod, and every deprecated API with
its before and after. The prose version is the
[upgrade guide](/docs/guides/how-to-upgrade).
### setup
Print an agent brief for adding Tamagui to a project that does not use it yet.
```bash
npx tamagui setup
```
It covers the version baseline, a pinned install, the config and provider, the
right bundler adapter, and the flat-value style grammar, and it ends by making
the agent verify with `tamagui check` and a real run rather than a typecheck.
Pair it with [migrate](#migrate) for projects already on v1 or v2, or copy the setup prompt directly:
### upgrade
Upgrade all Tamagui packages in your workspace to the latest version:
```bash
npx tamagui upgrade
```
**Flags:**
- `--from `: Lower bound of the upgrade range
- `--to `: Upper bound of the upgrade range
- `--dry-run`: Preview changes without writing files
- `--changelog-only`: Print the changelog without changing files
- `--debug`: Enable debug output
### to-tailwind
Convert Tamagui JSX props in files or globs to Tailwind `className` syntax:
```bash
npx tamagui to-tailwind ./src
```
**Flags:**
- `--write`: Write changes to files (default prints a diff)
- `--config `: Resolve tokens against your Tamagui config (required with `--write`, or pass `--use-default-config`)
- `--rename-dom`: Rename `View` to `div` and other DOM equivalents
### generate-prompt
Generate an LLM-friendly markdown file documenting your design system for AI
assistants.
```bash
npx tamagui generate-prompt
```
**Flags:**
- `--output `: Custom output path (default: `.tamagui/prompt.md`)
- `--debug`: Enable debug output
**What it includes:**
- All your tokens (colors, sizes, space, etc.)
- Theme definitions and variants
- Component configurations
- Font families and configurations
- Media queries and breakpoints
**Use case:** Share this file with AI assistants like Claude or ChatGPT to get
better suggestions that align with your design system.
## Global flags
All commands support these flags:
- `--help`: Show help for the command
- `--version`: Show CLI version
- `--debug`: Enable debug output
- `--verbose`: Enable verbose debug output (more detailed than `--debug`)
## Examples
### Production build pipeline
```json
{
"scripts": {
"build": "tamagui check && tamagui build --target web ./src -- next build"
}
}
```
### CI with optimization verification
```json
{
"scripts": {
"build": "tamagui build --target web --expect-optimizations 10 ./src -- next build"
}
}
```
This fails the build if fewer than 10 components are optimized, helping catch
configuration issues in CI.
### Cross-platform mobile app
```json
{
"scripts": {
"build:ios": "tamagui build --target native ./src -- eas build --platform ios",
"build:android": "tamagui build --target native ./src -- eas build --platform android",
"build:web": "tamagui build --target web ./src -- vite build"
}
}
```
## Troubleshooting
### Build command fails with "cannot find module"
Make sure you have a `tamagui.build.ts` config file that correctly points to
your configuration:
```ts
export default {
config: './tamagui.config.ts', // Verify this path is correct
components: ['tamagui'],
}
```
### Check command reports false positives
The check command is strict about version consistency. If you have a specific
reason for version mismatches (like testing), you can document them in your
README.
### Add command shows "Repository not found"
The `add` command requires Tamagui Takeout access. Visit
[tamagui.dev/takeout](https://tamagui.dev/takeout) to learn more.
## core/configuration
---
title: Configuration
description: Set up tokens, themes, fonts, media queries, shorthands, settings, and sizing.
---
Configuration connects your tokens, themes, media queries, animations,
shorthands, settings, and sizing to every Tamagui component.
Start with [`@tamagui/config/v6`](/docs/core/config-v6) for default tokens,
Tailwind-aligned shorthands, and themes.
Create a `tamagui.config.ts` file:
```bash
npm install @tamagui/config
```
```tsx fileName="tamagui.config.ts"
import { defaultConfig } from '@tamagui/config/v6'
import { createTamagui } from 'tamagui'
export const config = createTamagui({
...defaultConfig,
media: {
...defaultConfig.media,
// add your own media queries here, if wanted
},
})
type OurConfig = typeof config
declare module 'tamagui' {
interface TamaguiCustomConfig extends OurConfig {}
}
```
Pass the `config` export to a [`TamaguiProvider`](/docs/core/tamagui-provider) at
the root of your app:
```tsx
import { TamaguiProvider, html } from 'tamagui'
import { config } from './tamagui.config.ts'
export default () => (
)
```
That is a working setup. Import `tamagui.config.ts` once, near the root. Other files
reach the config through style props and hooks like `useMedia` and `useTheme`.
This guide imports from `tamagui`. If you only use the style library, swap the
imports for `@tamagui/core`, which is a strict subset.
## The shape of createTamagui
`createTamagui` takes one object. Every key is optional. Here is a complete config
written from scratch, without the v6 preset, to show each concern in one place:
```tsx fileName="tamagui.config.ts"
import { createFont, createTamagui, createTokens, isWeb } from 'tamagui'
// fonts group size, lineHeight, weight, and letterSpacing per family.
// createFont fills any keys you leave out of lineHeight, weight, or letterSpacing.
const systemFont = createFont({
family: isWeb ? 'Helvetica, Arial, sans-serif' : 'System',
size: {
1: 12,
2: 14,
3: 15,
},
lineHeight: {
// numeric config values are absolute pixels, so 1 resolves to 22
2: 22,
},
weight: {
1: '300',
3: '600',
},
letterSpacing: {
1: 0,
2: -1,
},
// native only: picks the font file by weight and style
face: {
300: { normal: 'InterLight', italic: 'InterItalic' },
600: { normal: 'InterBold' },
},
})
// tokens work like CSS variables: set once, available everywhere.
// keys can be anything. the tamagui components expect 1 through 10.
const size = {
0: 0,
1: 5,
2: 10,
// ...
}
export const tokens = createTokens({
size,
space: { ...size, '-1': -5, '-2': -10 },
radius: { 0: 0, 1: 3 },
zIndex: { 0: 0, 1: 100, 2: 200 },
color: {
white: '#fff',
black: '#000',
},
})
const config = createTamagui({
fonts: {
heading: systemFont,
body: systemFont,
},
tokens,
// themes can change anywhere in the tree with .
// dark_blue is a sub-theme of dark, reached with inside it.
themes: {
light: {
bg: '#f2f2f2',
color: tokens.color.black,
},
dark: {
bg: '#111',
color: tokens.color.white,
},
dark_blue: {
bg: 'darkblue',
color: tokens.color.white,
},
},
// media queries become style prop keys, useMedia keys, and group style keys
media: {
sm: { maxWidth: 860 },
gtSm: { minWidth: 860 + 1 },
short: { maxHeight: 820 },
hoverable: { hover: 'hover' },
touchable: { pointer: 'coarse' },
},
// becomes
// as const keeps the keys literal for onlyAllowShorthands
shorthands: {
px: 'paddingHorizontal',
f: 'flex',
m: 'margin',
w: 'width',
} as const,
settings: {
disableSSR: true,
allowedStyleValues: 'somewhat-strict-web',
},
})
type AppConfig = typeof config
// if you publish your own design system, put its package name here instead of tamagui
declare module 'tamagui' {
interface TamaguiCustomConfig extends AppConfig {}
// optional: types for named group styling modifiers such as group-hover/card:
interface TypeOverride {
groupNames(): 'card' | 'header' | 'sidebar'
}
}
export default config
```
If using the compiler, your tamagui.config.ts is parsed at build-time. For this reason,
we recommend keeping it relatively simple. Avoid importing heavy dependencies.
## Where each concern lives
Each key of `createTamagui` has its own page. The sections below say what the key
does and where to read more.
### TamaguiProvider
`TamaguiProvider` mounts the config at the root of your app. It also owns how the
generated CSS reaches the page in production, and the option to drop theme JS from
the client bundle. See [TamaguiProvider](/docs/core/tamagui-provider).
### Tokens
Tokens are static values with a name. They compile to CSS variables on web and are
available to every style prop. Create them with `createTokens` and pass them under
`tokens`. The categories `size`, `space`, `radius`, `zIndex`, and `color` map to
specific style properties, and you can add any category of your own. See
[Tokens](/docs/core/tokens) for how tokens resolve, custom categories, and the `px`
helper.
#### Font tokens
Fonts are tokens with structure. Each family carries its own size, line height,
weight, and letter spacing scales, created with `createFont`. Android needs the
`face` map to select font files by weight. See [Fonts](/docs/core/fonts).
### Themes
Themes sit one level above tokens. They hold values that change per subtree, most
often colors, and components read them by key. Sub-themes such as `dark_blue` fall
back to their parent. Custom `variables` merge into every base theme and can be
redefined for any subtree. See [Defining themes](/docs/core/theme#defining-themes)
for the config shape, [Themes](/docs/intro/themes) for usage, and
[Creating themes](/docs/guides/theme-builder) for generating full theme suites.
### Media
The `media` object defines named media queries. Each name becomes a style prop key,
a `useMedia` key, and a group style key. Order matters. Later entries override
earlier ones. See [useMedia](/docs/core/use-media).
### Animations
Tamagui ships four animation drivers: CSS, React Native Animated, Reanimated, and
Motion. Pick one per platform and pass it under `animations`. See
[Animations](/docs/core/animations) for the API and
[Choosing an animation driver](/docs/core/animation-drivers) for setup and
per-platform swapping.
### Shorthands
Shorthands map short prop names to style properties, so `px` can mean
`paddingHorizontal`. Config v6 ships Tailwind-aligned names. See
[Shorthands](/docs/core/shorthands).
### Settings
Settings control type strictness, SSR behavior, theme class placement, and
performance tradeoffs. A few more behaviors are set with environment variables. See
[Settings](/docs/core/settings).
### Sizing
Sizing is the shared control ladder: named rungs from `xs` to `xl` that every
sized control reads, with geometry derived from your font and space scales.
Spread `defaultSizing` and add rungs to grow the ladder. See
[Sizing](/docs/core/sizing).
## core/config-v6
---
title: 'Config v6'
description: The v3 default config with Tailwind-aligned scales and recipe-tree themes
---
Config v6 is the default configuration for Tamagui v3. Its spacing, sizing,
radius, and type scales align with Tailwind CSS, while its themes use adaptive
11-shade ramps and composable relative levels.
```bash
npm install @tamagui/config
```
```tsx fileName="tamagui.config.ts"
import { defaultConfig } from '@tamagui/config/v6'
import { createTamagui } from 'tamagui'
export const config = createTamagui(defaultConfig)
```
## Defaults
- `space` and `size` use Tailwind's 4px scale: `1` is 4, `4` is 16, and `8`
is 32. Half steps, `px`, negative space tokens, and the larger Tailwind keys
are included.
- Radius tokens include Tailwind names from `xs` through `4xl` and `full`.
- Font size and line height include Tailwind names from `xs` through `9xl`.
Their existing numeric font-config line heights remain absolute pixels.
- `w` and `h` are width and height shorthands.
- `tokens.color` contains the full Tailwind palette with names such as
`blue-500` and `slate-200`.
- Themes provide adaptive `color-1` through `color-11`, semantic generics,
relative `level2` through `level4`, `inverse`, `accent`, `brand`, `red`,
`yellow`, and `green`.
- Adaptive ramp numbers are steps relative to the active theme's own scheme.
They are not Tailwind's absolute `50` through `950` shades. Only the spelling
changed when these keys became hyphenated.
- Shadows are per-theme values named `shadow-1` through `shadow-7`, as in v5.
Dark themes use stronger shadows than light themes, and
`boxShadow="0 8px 24px shadow-4"` reads `shadow-4` from the active theme.
- Z-index has no configured token scale. Bare values resolve a matching token
only when an application adds one. Otherwise, the literal unitless value is
preserved.
The config does not choose a global component size. Each styled control owns
its own `size` variant inline and defaults to its `md` row.
This font-config behavior is distinct from v3 style authoring. A numeric
Tamagui `lineHeight` prop is a ratio on web and native. Font configuration uses
a numeric string such as `"1.5"` for a ratio and `"24px"` for an explicit
absolute length. No value changes meaning based on its magnitude.
## Package entries
- `@tamagui/config/v6` contains the complete static default config.
- `@tamagui/config/v6-base` exports the color-independent scales, fonts, media,
settings, and `createV6Config(colors)`.
- `@tamagui/config/v6-builder` re-exports the recipe authoring API from
`@tamagui/themes/builder`.
The static `v6` entry imports generated themes. It does not run theme generation
in the browser.
## Animations
No animation driver is bundled. Import exactly one driver from its neutral
entry and add it to the config:
```tsx fileName="tamagui.config.ts"
import { animationsCSS } from '@tamagui/config/animations-css'
import { defaultConfig } from '@tamagui/config/v6'
import { createTamagui } from 'tamagui'
export const config = createTamagui({
...defaultConfig,
animations: animationsCSS,
})
```
The other entries are `animations-rn`, `animations-reanimated`, and
`animations-motion`. See [Choosing an Animation Driver](/docs/core/animation-drivers)
for their trade-offs.
## Tailwind frontend
Use the separate Tailwind component frontend when you want `className`
authoring:
```tsx
import { Text, View, styled } from '@tamagui/tailwind'
export function Notice() {
return
}
```
Regular components imported from `tamagui` continue to use Tamagui style props.
The shared v6 color tokens make absolute utilities such as `bg-blue-500`
resolve to the same values on web and native.
Tailwind leading utilities preserve the same distinction on both platforms.
Spacing-scale leading and px arbitraries stay absolute, while unitless
arbitraries are ratios:
```tsx
{/* 32px */}
{/* 24px */}
{/* 1.25 times the font size */}
```
Read [v6 Colors](/docs/core/config-v6-colors) to customize the palette and
[Creating Themes](/docs/guides/theme-builder) to customize the tree and scales.
## core/style-pieces
---
title: style()
description: Statically-shaped style fragments created with style()
---
`style()` creates statically shaped style fragments for the `style` prop and
piece-typed component props.
```tsx
import { html, style } from 'tamagui' // or '@tamagui/core'
// define at module scope
const cardActive = style({
backgroundColor: 'background-press',
borderColor: 'border-color-hover',
opacity: 0.9,
})
export function Card({ active }: { active?: boolean }) {
return
}
```
## Module-scope definition
Call `style()` at module scope so its CSS rules are resolved and inserted once during module evaluation.
If `style()` is called during a component render, Tamagui emits a development warning:
```
[tamagui] style() was called during render. Define style pieces at module scope so their rules compile once.
```
For dynamic styles that depend on props, use `styled.dynamic` or a component `.resolve` callback instead.
## The style prop
Tamagui's `style` prop accepts a `StylePiece`, a plain style object, or an array containing both:
```tsx
const baseStyle = style({ padding: 16, borderRadius: 8 })
const activeStyle = style({ backgroundColor: 'background-press' })
// single piece
// array with conditional overrides (last wins)
```
Array elements evaluate in order. Later pieces or style objects override earlier ones per property, following Tamagui's standard style precedence.
## Static shape rule
The object passed to `style()` must have statically known property keys:
```tsx
// good: static keys, scalar values, and flat clauses
const goodPiece = style({
backgroundColor: 'background',
padding: 16,
opacity: '1 hover:0.8',
shadowOffset: { width: 0, height: 2 },
})
// avoid: spreads and computed keys
const dynamicKey = 'padding'
const badPiece = style({
[dynamicKey]: 16,
...someObject,
})
```
Rules for `style()` definitions:
- Static property keys only.
- No object spreads into the definition literal.
- No computed keys.
- Structured leaf values (such as `shadowOffset`) are allowed.
- Flat clause strings (such as `'1 hover:0.8'`) are allowed.
Non-conforming pieces with dynamic spreads or computed keys deopt compiler extraction for that piece. The runtime remains unaffected and interprets the styles in JavaScript.
## Native resolution
On web, `style()` compiles each property to atomic CSS classes at module evaluation.
On React Native and JavaScript drivers, the piece preserves its underlying style object. When rendered, Tamagui resolves the style object against the active theme and caches the resolved styles per `(piece, theme)` pair.
## Piece-typed component props
Several Tamagui components accept a `StylePiece` on dedicated style props:
- `activeStyle` on `Checkbox`, `Toggle`, `ToggleGroup.Item`, and `Tabs.Tab`.
- `contentContainerStyle` on `ScrollView`.
```tsx
import { Checkbox, ScrollView, style } from 'tamagui'
const activeCheckboxStyle = style({
backgroundColor: 'background-press',
borderColor: 'border-color',
})
const scrollContainer = style({
padding: 20,
gap: 12,
})
export function Example() {
return (
)
}
```
These props replace the legacy `accept: 'style'` configuration option. Components forward the piece directly into their frame's `style` prop or resolve it against the active theme on native.
## core/animate-presence
---
title: AnimatePresence
description: Animate components as they mount and unmount
---
AnimatePresence animates direct children before they unmount. It works with
every animation driver in Tamagui.
It is inspired by and forked from
[Framer Motion](https://github.com/framer/motion).
To use with `@tamagui/core`, install and import `@tamagui/animate-presence`. It's already bundled and exported from `tamagui`.
## Basic usage
Use `enter` and `exit` object keys or string clauses to define how each
property animates in and out:
StringTyped
```tsx
import { AnimatePresence, html } from 'tamagui'
export const MyComponent = ({ isVisible }) => (
{isVisible && (
)}
)
```
```tsx
import { AnimatePresence, html } from 'tamagui'
export const MyComponent = ({ isVisible }) => (
{isVisible && (
)}
)
```
Note you don't even need to set `opacity` on the base style. Tamagui knows to normalize
styles like opacity and scale to 1 (and y to 0) if it's not defined on the base styles
but has an `enter:` or `exit:` clause.
Animated child components must each have a unique key prop so AnimatePresence can track
their presence in the tree.
## Enter/exit transitions
Set `enter` and `exit` transitions when mount and unmount should differ:
```tsx
import { AnimatePresence, html } from 'tamagui'
export default ({ show }) => (
{show && (
)}
)
```
`enter` and `exit` each take a whole transition value, so they accept a preset
name, a CSS transition string, or an object. The rest of the transition is what
property changes use while the element is mounted:
```tsx
// enter slowly, exit quickly, property changes use bouncy
```
Because they are whole transition values, they can carry a delay and their own
per-property entries:
```tsx
// enter with lazy but move x slowly, exit with quick after 200ms
```
An `enter` or `exit` value replaces the base while it applies rather than
merging into it, the same way a later CSS `transition` declaration replaces an
earlier one.
This works with all four animation drivers (CSS, React Native, Reanimated, Motion).
## The `custom` prop
```tsx hero template=AnimationsPresence
```
The `custom` prop updates a child's variant before its exit animation runs, so it can leave in a different direction than it entered:
```tsx
import { AnimatePresence } from '@tamagui/animate-presence'
import { ArrowLeft, ArrowRight } from './components/icons' // your generated icons, see Lucide Icons
import { useState } from 'react'
import { Button, Image, XStack, YStack, styled } from 'tamagui'
const GalleryItem = styled(YStack, {
zIndex: 1,
x: 0,
opacity: 1,
position: 'absolute',
inset: 0,
variants: {
// 1 = right, 0 = nowhere, -1 = left
going: styled.dynamic((going) => ({
x: `0 enter:${going > 0 ? '1000px' : '-1000px'} exit:${
going < 0 ? '1000px' : '-1000px'
}`,
opacity: '1 enter:0 exit:0',
zIndex: '1 exit:0',
})),
} as const,
})
const photos = [
'https://picsum.photos/500/300',
'https://picsum.photos/501/300',
'https://picsum.photos/502/300',
]
const wrap = (min: number, max: number, v: number) => {
const rangeSize = max - min
return ((((v - min) % rangeSize) + rangeSize) % rangeSize) + min
}
export function Demo() {
const [[page, going], setPage] = useState([0, 0])
const imageIndex = wrap(0, photos.length, page)
const paginate = (going: number) => {
setPage([page + going, going])
}
return (
)
}
```
## API reference
### AnimatePresence Props
- **`children`**: one or more Tamagui components with unique `key` props
- **`initial`**: if `false`, children entering on initial mount will not animate (default: `true`)
- **`custom`**: pass data to children's variants for dynamic exit animations
- **`mode`**: determines how entering and exiting children render. Use `"wait"` to render one child at a time.
- **`onExitComplete`**: callback fired when all exiting children have finished animating
### Component Props
When used inside AnimatePresence:
- **`enter:` clauses**: values to animate from when mounting
- **`exit:` clauses**: values to animate to when unmounting
- **`transition`**: animation configuration (supports `{ enter, exit }` syntax)
- **`key`**: required unique identifier for AnimatePresence tracking
## See also
- [Animations Overview](/docs/core/animations)
- [Animation Drivers](/docs/core/animation-drivers)
## core/animations-reanimated
---
title: Reanimated Driver
description: Advanced off-thread animations with Reanimated
---
[Reanimated](https://docs.swmansion.com/react-native-reanimated/) provides
spring and timing animations for React Native and React Native Web, with
off-thread execution on native.
## Installation
```bash
yarn add @tamagui/animations-reanimated react-native-reanimated
```
Follow the
[Reanimated installation guide](https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/getting-started/)
to complete setup for your platform.
## Configuration
Add your animations to your [Tamagui config](/docs/core/configuration):
```tsx
import { createAnimations } from '@tamagui/animations-reanimated'
import { createTamagui } from 'tamagui'
export default createTamagui({
animations: createAnimations({
fast: { duration: 160, bounce: 0.25 },
medium: { duration: 300, bounce: 0.15 },
slow: { duration: 450, bounce: 0.1 },
}),
// ...
})
```
The config format is the same for all four drivers, so a name means the same
motion whichever driver renders it.
## How it works
At runtime, this driver parses animatable style properties and hands them over
to Reanimated using worklets. Animations run on the UI thread for smooth 60fps
performance.
## Animation configuration
Reanimated animations support both spring and timing (tween) animations:
### Spring animations
```tsx
{
duration: 400, // the spring's undamped period, in milliseconds
bounce: 0.5, // 0 is critically damped, toward 1 is loose, negative is sluggish
}
```
`stiffness`, `damping`, and `mass` still work for a config already tuned against
them, and Reanimated's `velocity`, `overshootClamping`, and rest thresholds pass
through.
### Timing animations
```tsx
{
type: 'timing',
duration: 300, // Duration in milliseconds
easing: 'ease-out', // any css timing function
}
```
A plain CSS string means the same thing: `'300ms ease-out'`.
### Delay
```tsx
```
The delay is specified in milliseconds.
## When to use
**Advantages:**
- **Off-thread animations**: runs on UI thread for 60fps performance
- **Cross-platform**: works on web and React Native
- **Advanced features**: worklets, gesture integration, shared values
- **React Native optimized**: highest performance on native platforms
**Considerations:**
- Larger bundle size
- Requires Reanimated setup and configuration
- More complex than simpler drivers
- Web-only apps might prefer Motion or CSS drivers
## Platform-specific configuration
You can use Reanimated on native and a lighter driver on web:
```tsx
// animations.native.ts
import { createAnimations } from '@tamagui/animations-reanimated'
export const animations = createAnimations({
bouncy: { duration: 400, bounce: 0.5 },
})
```
```tsx
// animations.ts (web)
import { createAnimations } from '@tamagui/animations-motion'
export const animations = createAnimations({
bouncy: { duration: 400, bounce: 0.5 },
})
```
Then import without the extension:
```tsx
import { animations } from './animations'
```
## Example
```tsx
import { createAnimations } from '@tamagui/animations-reanimated'
import { YStack, createTamagui } from 'tamagui'
const animations = createAnimations({
bouncy: { duration: 400, bounce: 0.5 },
quick: { duration: 220, bounce: 0.3 },
})
export default createTamagui({
animations,
// ... rest of config
})
// Usage in components
export const MyComponent = () => (
)
```
## Zero re-renders
The Reanimated driver works with Tamagui to bypass all internal style changes
and hand off directly to Reanimated worklets.
## Advanced features
Reanimated provides advanced features:
- **Gesture integration**: animate based on gesture values
- **Shared values**: share animated values between components
- **Worklets**: JavaScript functions that run on the UI thread
- **Custom animations**: full control over animation curves
See the
[Reanimated documentation](https://docs.swmansion.com/react-native-reanimated/)
for more details.
## See also
- [Animations Introduction](/docs/core/animations)
- [CSS Driver](/docs/core/animations-css)
- [React Native Driver](/docs/core/animations-react-native)
- [Motion Driver](/docs/core/animations-motion)
- [Reanimated Documentation](https://docs.swmansion.com/react-native-reanimated/)
## core/shorthands
---
title: Shorthands
description: Map short prop names to any style property.
---
Shorthands give frequently used style props shorter names, such as `px` for
`paddingHorizontal`.
Define them on `createTamagui` under `shorthands`. Each key maps to one style
property:
```tsx fileName="tamagui.config.ts"
// as const keeps the keys literal so types flow through
const shorthands = {
ac: 'alignContent',
ai: 'alignItems',
als: 'alignSelf',
bg: 'backgroundColor',
br: 'borderRadius',
f: 'flex',
m: 'margin',
px: 'paddingHorizontal',
w: 'width',
// ...
} as const
export const config = createTamagui({
shorthands,
// ...
})
```
Shorthands work everywhere a style prop works: inline props, `styled`, variants,
media and pseudo clauses, and the compiler flattens them the same way it flattens
the long form.
```tsx
```
[`@tamagui/config/v6`](/docs/core/config-v6) ships with Tailwind-aligned names such
as `w`, `h`, `p`, `m`, `px`, `rounded`, `items`, and `justify`. Spread
`defaultConfig.shorthands` to extend them.
## Only allowing shorthands
Set `settings.onlyAllowShorthands` to remove the long-form prop types for every
property that has a shorthand. Autocomplete then shows one spelling per property:
```tsx fileName="tamagui.config.ts"
export const config = createTamagui({
shorthands,
settings: {
onlyAllowShorthands: true,
},
})
```
This is type-level only. The runtime still accepts the long form.
### Upgrading to 3.1
Version 3.1 repairs declarations that could allow long-form props through styled
components, compound components, and wrappers even when `onlyAllowShorthands` was
enabled. Code that relied on that regression can now report a type error. Use the
configured shorthand instead:
```tsx
// before, with an items shorthand configured for alignItems
// after
```
Setting `onlyAllowShorthands: false` continues to allow both forms. Plain style
objects such as `contentContainerStyle` use React Native style property names,
for example `paddingBottom`, rather than Tamagui shorthands.
## core/config-v6-colors
---
title: Colors
description: Use the default Tailwind colors or generate v3 themes from your own palette
---
Config v6 keeps layout scales separate from color. The default color pack is
static and Tailwind-based. A custom pack is a `tokens.color` record and themes
generated from the same token names.
## Default colors
`@tamagui/config/v6` includes Tailwind's `50` through `950` shades as absolute
tokens and recipe-generated adaptive themes:
```tsx fileName="tamagui.config.ts"
import { defaultConfig } from '@tamagui/config/v6'
import { createTamagui } from 'tamagui'
export const config = createTamagui(defaultConfig)
```
```tsx
```
`blue-500` is absolute. `color-2` and `color-11` adapt to the active scheme and
palette. The adaptive ramp is `color-1` through `color-11`, and a scale added
through `scales` gives twelve. Either way the number is a step relative to the
active theme's own scheme, not a Tailwind shade such as `50` or `950`. Only the
spelling changed when the ramp became hyphenated.
## Color packs
`createV6Config` composes the aligned config with any color pack:
```tsx
type V6Colors = {
themes: Record>
colorTokens?: Record
}
```
```tsx fileName="tamagui.config.ts"
import { createV6Config } from '@tamagui/config/v6-base'
import { createTamagui } from 'tamagui'
import { themes, tokens } from './themes'
export const config = createTamagui(
createV6Config({ themes, colorTokens: tokens.color })
)
```
## Add color scales
The `scales` option adds 12-step scales to the base `light` and `dark` themes.
Each entry gives 12 light values and 12 dark values, faintest to strongest, and
becomes theme keys `name-1` through `name-12` that adapt to the active scheme.
Subthemes reach them through parent fallback.
`@tamagui/themes/v5-color-scales` packs the classic v5 scales (`gray`, `blue`,
`red`, `yellow`, `green`, `orange`, `pink`, `purple`, `teal`, `neutral`,
`black`, `white`), so keeping the old colors while moving to v6 is two lines:
```tsx fileName="tamagui.config.ts"
import { colors, createV6Config } from '@tamagui/config/v6'
import { v5ColorScales } from '@tamagui/themes/v5-color-scales'
import { createTamagui } from 'tamagui'
export const config = createTamagui(
createV6Config({
...colors,
scales: {
...v5ColorScales,
brand: {
light: ['#f0f9ff', /* ... */ '#082f49'],
dark: ['#082f49', /* ... */ '#f0f9ff'],
},
},
})
)
```
```tsx
```
These are theme values, so they also resolve inside flat values like
`backgroundImage="linear-gradient(90deg, red-10, orange-11)"` and take opacity
suffixes (`red-10/50`).
## Shadows
Shadows are theme values again, as they were in v5. Each theme owns `shadow-1`
through `shadow-7`, so dark themes can use stronger shadows than light themes.
`boxShadow="0 8px 24px shadow-4"` resolves `shadow-4` from the active theme
instead of reading an absolute color token, and `shadow-color` is the theme's
`shadow-3`.
## Add a custom palette
This example keeps all default tokens and scales, then adds an `ocean` palette
and child theme:
```tsx fileName="themes.ts"
import {
createThemes,
fromShades,
levels,
ramp,
scales,
shades,
tokens as defaultTokens,
type GetThemeContext,
type Level,
type Scheme,
type Treatment,
} from '@tamagui/themes/builder'
const ocean = [
'#effcff', '#dff8ff', '#b8efff', '#7cdef9', '#35c4ed', '#0ba6d1',
'#0784aa', '#086a89', '#0b576f', '#0b495e', '#07303f',
] as const
export const tokens = {
color: {
...defaultTokens.color,
...Object.fromEntries(
shades.map((shade, index) => [`ocean-${shade}`, ocean[index]])
),
},
}
type Recipe = {
scheme: Scheme
palette: string
treatment?: Treatment
level?: Level
}
export const tree = {
light: { scheme: 'light', palette: 'gray' },
dark: { scheme: 'dark', palette: 'gray' },
children: {
...levels(),
ocean: {
palette: 'ocean',
treatment: 'tint',
children: levels(),
},
},
} as const
function getTheme({ recipe }: GetThemeContext) {
const scale =
scales[recipe.treatment ?? 'normal'][recipe.scheme][recipe.level ?? 1]
return {
...ramp(recipe.palette, recipe.scheme, scale),
...fromShades(recipe.palette, scale),
}
}
export const themes = createThemes(tokens, tree, { getTheme })
```
Now `theme="ocean"` uses the custom palette, and nested `theme="level2"`
keeps that palette while raising its semantic background and border values.
## Replace the default scales
The default `normal`, `bold`, and `tint` scales are plain objects. Copy or extend
them when your palette needs different contrast:
```tsx
const scales = {
normal: myNormalScales,
bold: myBoldScales,
tint: myTintScales,
outline: myOutlineScales,
}
```
The generator only sees the recipe fields and the record returned by your
resolver. Treatment names and shade choices are entirely yours.
## Generate once for production
Export the runtime result from an input file:
```tsx fileName="themes.input.ts"
export { themes } from './themes'
```
Then emit a flat module:
```bash
npx tamagui generate-themes ./themes.input.ts ./src/themes.generated.ts
```
Import `themes.generated.ts` from the config. The generated module retains the
full inferred theme name and key types without shipping the authoring tree.
The visual [Theme Studio](/theme) exports this same v3 scales-and-tree format.
For the complete API, read [Creating Themes](/docs/guides/theme-builder).
## core/sizing
---
title: Sizing
description: The shared control size ladder, from named rungs to custom sizes
---
Every control that takes `size` reads from one shared ladder: five named
rungs (`xs` through `xl`) in your config's `sizing` key. A rung is token
keys plus geometry derived from them, so retuning your type or space scale
moves every control together.
## A rung
Each entry of `sizing.sizes` is a `SizeRecipe`: six token keys, never pixels.
## Derived values
`resolveSizing` turns a rung into style-ready values: the six keys pass
through to the style system, and three px numbers derive from the active font
and the space scale.
- `height`: the text line box plus vertical padding, before any border. A
button frame adds its 1px border on each side.
- `icon`: the icon px for the rung: the font size rounded up to a multiple
of 4.
- `square`: the checkbox box, radio circle, and switch track height: 1.4
times the control font size, rounded.
## Custom sizes
Spread `defaultSizing` and add rungs. New names are accepted everywhere a
size is, typed as `ComponentSize`, with no component changes.
```tsx fileName="tamagui.config.ts"
import { defaultConfig, defaultSizing } from '@tamagui/config/v6'
export const config = createTamagui({
...defaultConfig,
sizing: {
...defaultSizing,
sizes: {
...defaultSizing.sizes,
xxl: {
fontSize: 'xl',
controlFontSize: '2xl',
paddingInline: '10',
paddingBlock: '3',
gap: '3',
radius: 'xl',
},
},
},
})
```
`sizing.default` names the rung `true` (and an absent size) resolves to.
## Missing tokens
A rung that names a token your config does not have throws in development,
naming the size and the missing reference:
```text
size "xxl" references a missing token (fontSize "xl", controlFontSize "2xl", paddingBlock "3")
```
An unknown size name throws with the valid names listed. In production both
degrade to the default rung. Prefer the error: it means a rung points at a
scale your config never defined.
## core/compiler-tiers
---
title: Compiler tiers
description: How Tamagui moves from runtime components to extracted host output
---
Tamagui compilation is a ladder. An app can keep the full runtime, move
configuration-static CSS into a build artifact, or enforce a web entry with
no Tamagui component runtime.
Inside an ordinary compiled app, each component is handled independently. One
dynamic component does not turn extraction off for the rest of the file.
## The build ladder
| Tier | What the build emits | What remains at runtime |
| --- | --- | --- |
| Ordinary compiled Tamagui | Extracted CSS and flattened host elements wherever analysis succeeds | Provider, config, style engine, theme and media state, animation driver, and component runtime for residual sites |
| Compiled global CSS | Ordinary compiled output plus the `outputCSS` artifact owned by the integration | Residual components and dynamic theme or media behavior remain, while configuration-static CSS generation is removed |
| Strict zero-runtime | Lowered React host JSX plus one generated CSS artifact | No Tamagui renderer, provider, style engine, config parser, theme state, media state, or component animation runtime in the zero entry |
The compiler is optional for ordinary Tamagui. Without it, components use the
runtime path. Strict zero-runtime is an experimental web-only contract and
turns every residual Tamagui site into a build error. See
[Zero-runtime mode](/docs/guides/zero-runtime).
## Candidate outcomes
The compiler records five counters for every module:
| Counter | Meaning |
| --- | --- |
| `found` | A JSX or component-call candidate resolved to a compiler-known component |
| `lowered` | The compiler safely committed edits and any extracted CSS for that candidate |
| `flattened` | A lowered candidate became a platform host element |
| `styled` | A successfully lowered candidate came from an app-local `styled()` definition |
| `bailed` | The candidate stayed on its runtime path because analysis or the component contract prevented lowering |
`styled` describes where a lowered candidate came from, so it is not a separate
result alongside `flattened`. A partially lowered candidate is `lowered` without
being `flattened`.
### Flattened
A component can flatten when its static contract accepts `className`, it is not
a behavior HOC marked `neverFlatten`, and it does not provide a styled context.
The compiler can then replace it with one host element. On web the output may
contain extracted classes plus an inline `style` object for values that must
stay dynamic.
```tsx
const tone = selected ? 'red' : 'blue'
// static layout and spacing can extract while the dynamic color remains a
// narrow host style value
```
Conditional expressions whose branches are both static can also lower as
conditional classes. The JavaScript condition remains, while both style
branches are compiled.
### Partially lowered
Partial extraction lets safe static work leave the runtime even when one style
value remains dynamic. Only properties the host can receive directly qualify.
If a dynamic value needs Tamagui theme, media, interaction, event, or component
behavior, the candidate stays on the runtime path.
Set `disablePartialExtraction: true` in `tamagui.build.ts` when you need dynamic
candidates to remain completely runtime-owned:
```ts fileName="tamagui.build.ts"
import type { TamaguiBuildOptions } from 'tamagui'
export default {
config: './tamagui.config.ts',
components: ['tamagui'],
disablePartialExtraction: true,
} satisfies TamaguiBuildOptions
```
### Runtime bailout
A candidate bails out when the compiler cannot prove a complete, safe rewrite.
Common causes include an unresolved component target, a dynamic style spread,
runtime event mapping, overlapping source edits, or a component whose static
contract requires its runtime wrapper.
A bailout is valid in ordinary compiled Tamagui. The component renders through
the same runtime that an uncompiled app uses. In strict zero-runtime, the same
residual site is a contract violation because that entry has no component
runtime to receive it.
## What you control
Authoring and build configuration influence the result:
- Put packages whose components should be evaluated in `components`.
- Keep component targets literal and style values statically analyzable when
you want flattening.
- Use flat CSS conditions for theme, media, interaction, group, and container
behavior that should extract.
- Set `disableOptimization` on one element to keep that element on the runtime
path.
- Set `disableExtraction` for a file set or the whole build to use runtime
components, commonly during development.
- Set `disablePartialExtraction` to prevent the compiler from splitting a
dynamic candidate between extracted and host-owned styles.
- Set `outputCSS` and load that artifact to enter the compiled-global-CSS tier.
- Set `experimental.zeroRuntime` to audit or enforce the strict tier.
There is no per-component switch that forces a component to flatten. The
component's static contract and the actual authored values decide whether a
safe host replacement exists.
## Inspect the result
The Vite integration can print the compiler counters and bailout codes:
```bash
TAMAGUI_COMPILER_STATS=1 vite build
```
Use verbose mode for per-module counters:
```bash
TAMAGUI_COMPILER_STATS=verbose vite build
```
Write the same report as JSON by setting a project-relative output path:
```bash
TAMAGUI_COMPILER_STATS_FILE=.tamagui/compiler-stats.json vite build
```
The report includes totals, partial and flattened counts, bailout codes,
grouped reasons, and per-module diagnostics. These counters describe compiler
handling. They do not by themselves measure bundle size or runtime speed.
For one element, set `debug="verbose"`. In development, the existing verbose
console group includes a `receipt` with the element's `lowered`, `flattened`,
`styled`, or `bailed` tiers, each authored style prop, and the reason it was
emitted, resolved at runtime, or dropped. A fully flattened element prints the
same receipt when it renders. Receipt code is omitted from production output.
## core/style-props
---
title: Style props
description: Every style property Tamagui accepts, and how each renders per platform
---
Every styled Tamagui component accepts a universal, web-aligned style API
with cross-platform additions that render cleanly on both web and native.
The full types are exported as `ViewStyle` and `TextStyle`. For the base set,
see the React Native docs (v3 targets React Native 0.82):
- [React Native View style props](https://reactnative.dev/docs/view-style-props)
- [React Native Text style props](https://reactnative.dev/docs/text-style-props)
Every value may also be a token, a theme key, or a
[conditional value](/docs/intro/styles#conditional-values).
## Cross-platform additions
These render on web and native.
| Prop | Value | Notes |
| --- | --- | --- |
| `boxShadow` | string, object, or array | Accepts theme colors inside the string, such as `"0 0 10px shadow-color"`. |
| `filter` | CSS filter string | `brightness` and `opacity` work everywhere, while `blur`, `contrast`, and the rest need Android 12+. |
| `backgroundImage` | `linear-gradient()` or `radial-gradient()` | Accepts tokens and theme colors. `url()` is web only. Use `ImageBackground` on native. |
| `border` | `"1px solid border-color"` | Expands to width, style, and color per side on native. Native supports one border for all sides. |
| `outline` | `"2px solid outline-color"` | Expands to `outlineWidth`, `outlineStyle`, and `outlineColor` on native. |
| `outlineColor`, `outlineWidth`, `outlineOffset`, `outlineStyle` | color, space, space, `solid`, `dotted`, or `dashed` | |
| `textShadow` | `"2px 2px 4px shadow-color"` | Multiple shadows are web only. |
| `cursor` | CSS cursor | Web, and iOS 17+ for trackpad, stylus, and gaze. |
| `position` | `absolute`, `relative`, `fixed`, `static`, `sticky` | `fixed` becomes `absolute` on native. |
| `display` | `flex`, `none`, `contents`, and the CSS values | |
| `mixBlendMode` | `normal`, `multiply`, `screen`, `overlay`, and the rest | |
| `isolation` | `auto` or `isolate` | |
| `boxSizing` | `border-box` or `content-box` | |
### Flat transforms
Each of these is a top-level prop that folds into the `transform` array:
| Prop | Value |
| --- | --- |
| `x`, `y` | number |
| `scale`, `scaleX`, `scaleY` | number |
| `rotate`, `rotateX`, `rotateY`, `rotateZ` | string such as `"45deg"` |
| `skewX`, `skewY` | string |
| `perspective` | number |
| `matrix` | number array |
## Web-only props
These are typed and accepted everywhere, and render only on web:
| Group | Props |
| --- | --- |
| Background | `background`, `backgroundAttachment`, `backgroundBlendMode`, `backgroundClip`, `backgroundOrigin`, `backgroundPosition`, `backgroundRepeat`, `backgroundSize` |
| Borders | `borderTop`, `borderRight`, `borderBottom`, `borderLeft`, `borderImage` |
| Masks | `mask`, `maskImage`, `maskBorder`, `maskBorderMode`, `maskBorderOutset`, `maskBorderRepeat`, `maskBorderSlice`, `maskBorderSource`, `maskBorderWidth`, `maskClip`, `maskComposite`, `maskMode`, `maskOrigin`, `maskPosition`, `maskRepeat`, `maskSize`, `maskType` |
| Layout | `float`, `overflowBlock`, `overflowInline`, `contain`, `transformStyle` |
| Effects | `backdropFilter`, `clipPath` |
| Text and input | `caretColor`, `textEmphasis`, `touchAction` |
## Text props
Text components accept the view props above plus the React Native text
styles: `color`, `fontFamily`, `fontSize`, `fontStyle`, `fontWeight`,
`letterSpacing`, `lineHeight`, `textAlign`, `textDecorationColor`,
`textDecorationLine`, `textDecorationStyle`, `textShadowColor`,
`textShadowOffset`, `textShadowRadius`, and `textTransform`.
### Font size values
v3 separates raw numbers from exact pixel strings:
```tsx
Platform line heightExact px size
```
`fontSize={17}` is a platform value and keeps the platform-default line
height. `fontSize="17px"` passes through as CSS on web and parses to a number
on native. The v5 config pins its font `size` and `lineHeight` scales to px
strings so v2 output is preserved.
## Variables inside shorthand strings
`boxShadow`, `backgroundImage`, `filter`, `border`, `outline`, and
`textShadow` resolve tokens and theme keys inside the string on both platforms.
`borderTop` and friends and `background` do the same on web only.
```tsx
// web: boxShadow: "0 0 10px var(--t-shadow-shadow-color)"
// native: boxShadow: "0 0 10px rgba(0,0,0,0.2)"
```
### border
```tsx
```
On web the shorthand passes straight through. On native it expands to
`borderTopWidth`, `borderRightWidth`, `borderBottomWidth`, `borderLeftWidth`,
`borderStyle`, and the four `border*Color` props. Native supports a single
border for all sides, so use the per-side props for anything else.
### outline
```tsx
```
On native this expands to `outlineWidth`, `outlineStyle`, and `outlineColor`.
### backgroundImage
```tsx
```
`linear-gradient()` and `radial-gradient()` work on both platforms. `url()`
is web only, so use `ImageBackground` for images on native.
### textShadow
```tsx
Shadowed
// multiple shadows, web only
Glowing
```
The individual `textShadowColor`, `textShadowOffset`, and `textShadowRadius`
props also work. Native renders one text shadow.
## rem units
`rem` works on both platforms. Browsers handle it natively, and native
converts it to pixels using `PixelRatio.getFontScale()`, so it follows the
user's font size preference:
```tsx
```
The base font size defaults to 16 and is set with
`settings.remBaseFontSize`. On native, `1rem` with a base of 16 equals
`16 * PixelRatio.getFontScale()` pixels.
## core/surfaces
---
title: Surfaces & Levels
description: Re-bind semantic theme values relative to the current subtree
---
Surfaces group UI elements into visual containers, and levels adjust the
contrast of semantic color tokens relative to their parent container.
## What they are
A **surface** is a container in your layout, such as a panel, card, dialog, or
toolbar, with its own background and border.
A **level** is a relative theme step (`level2`, `level3`, `level4`). It shifts
semantic values like `background`, `border-color`, and `color` one or more
steps from the surrounding theme. In v3, levels are how surfaces get their
contrast. There is no separate surface theme system.
Use a level when a container should stand out from what's around it. Components
inside it read the shifted values, so they adapt without extra color props:
```tsx
import { Button, Text, Theme, YStack } from 'tamagui'
export function Card() {
return (
Level 2 card contentSave
)
}
```
The card resolves `background` and `border-color` at level 2. `Button` reads its
colors relative to that, so it still stands out against the card.
## Two color vocabularies
Keep adaptive scale values and semantic styling values separate:
- `color-1` through `color-11` are the scheme-relative palette ramp. These are
theme steps rather than absolute color shades.
- `background`, `background-hover`, `background-press`, `border-color`,
`color`, and other generics describe a component's role.
Component skins should normally use the generics. A level re-binds those keys once for the whole subtree, so every skin below it responds without needing its own color calculations.
## Levels are relative sub-themes
Use `level2`, `level3`, or `level4` to raise a subtree from its current level:
```tsx
Raised again by the Button skin
```
The level name represents an operation rather than an absolute destination. From the root, `theme="level2"` resolves to absolute level 2. Inside that subtree, another `theme="level2"` resolves to absolute level 3. `theme="level3"` moves two steps from the current context. Values clamp at level 4.
The generated path records the nesting, such as `light_level2_level2`. Saturated paths are aliases of an existing map, so they do not duplicate CSS declarations.
Color themes keep their palette while levels compose:
```tsx
Still red
```
## Surface is a copied panel fixture
For layout surfaces, copy the [Surface](/ui/surface) fixture. It is a `YStack`
with a level prop and composable `filled`, `outlined`, `elevated`, `rounded`,
and `interactive` facets. Nothing is enabled by default:
```tsx
```
The `level={2}` prop creates a `level2` theme boundary. The facets read the
semantic generics, so changing the level or surrounding color theme restyles
them together.
Component skins do not extend Surface. Card, Select content, ListItem, and other
skins use the same generic values and choose their own default level.
## Inline theme values for one-off overrides
Set the value with [`ThemeUpdate`](/docs/core/theme#theme-updates) when a patch belongs in exactly one place:
```tsx
{/* this subtree receives the patch */}
```
Create a named theme when the same patch appears in multiple places. Set it
inline with `ThemeUpdate` when it is local to one component.
## Custom levels
The defaults are ordinary data in `@tamagui/themes/builder`. Change a level by
editing or replacing its scale:
```tsx
import { raise, scales } from '@tamagui/themes/builder'
const level3 = {
...raise(scales.normal.light[1], 2),
'border-color': 400,
}
```
`levels(max)` creates the recursive tree definitions. It works with custom
recipes because it only reads and updates the optional `level` field.
## V2 surface migration
In Tamagui v2, surface names represented fixed templates. Replace them with relative levels in v3:
| V2 theme | V3 theme |
| --- | --- |
| `surface1` | `level2` |
| `surface2` | `level3` |
| `surface3` | `level4` |
| `surface4` | `level4` |
`surface4` clamps because the default v3 scale has four absolute levels. If the old contrast was important, add another level to your own scale and tree.
## Summary
- Style reusable skins against semantic generics.
- Use `theme="level2"` for the common one-step relative boundary.
- Nest levels freely because the recipe tree tracks the current absolute level.
- Set the value on `ThemeUpdate` for a one-off patch.
- Use the copied Surface fixture for layout chrome.
- Customize scales and `levels()` in normal application code.
## core/tailwind
---
title: Tamagui Tailwind
description: Tailwind CSS utility authoring for Tamagui web and native
---
Tamagui Tailwind brings utility classes in `className` to web, iOS, and
Android through the Tamagui compiler.
Tailwind CSS is a trademark of Tailwind Labs Inc. Tamagui is not affiliated with or endorsed by Tailwind Labs.
## Installation
Install `@tamagui/tailwind` and `@tamagui/config`:
```bash
npm install @tamagui/tailwind @tamagui/config
```
## Basic usage
Import components directly from `@tamagui/tailwind`:
```tsx
import { Text, View } from '@tamagui/tailwind'
export function Card() {
return (
Tamagui Tailwind
)
}
```
The styling frontend is selected by import path. There is no global configuration toggle.
## Semantic elements
`@tamagui/tailwind` also exports `html`, the same set of semantic elements as `html` from `tamagui`, built on the Tailwind frontend. Same tags, same element defaults, and a class string rather than style props as the styling input:
```tsx
import { html } from '@tamagui/tailwind'
export function Cta() {
return (
Ship it
)
}
```
Every tag renders a real element on web and its React Native equivalent on native, exactly as the `tamagui` `html` does. It composes with `styled()` too:
```tsx
import { html, styled } from '@tamagui/tailwind'
export const Button = styled(html.button, 'px-4 py-2 rounded-lg', {
variants: {
tone: {
solid: 'bg-blue-500 hover:bg-blue-600',
ghost: 'hover:bg-blue-50',
},
},
})
```
## Vite integration
Applications authoring with `@tamagui/tailwind` use the Vite plugin from `@tamagui/tailwind/vite`:
```ts
// vite.config.ts
import { tamaguiPlugin } from '@tamagui/tailwind/vite'
import { defineConfig } from 'vite'
export default defineConfig({
plugins: [
tamaguiPlugin({
config: './tamagui.config.ts',
components: ['@tamagui/tailwind'],
}),
],
})
```
The plugin wraps the core Tamagui compiler and scans JSX sources for utility candidates.
## Web and native resolution
Tamagui Tailwind components render through the same core engine as standard Tamagui components:
- **On web:** The compiler extracts claimed utilities into atomic CSS classes at build time.
- **On native:** The shared renderer resolves the utility classes into React Native styles at runtime.
Both platforms share the same config, design tokens, themes, media queries, accessibility props, and animation drivers.
## Modifiers
Tamagui Tailwind supports standard modifiers:
### Pseudo states
```tsx
```
Supported state modifiers include `hover:`, `press:`, `focus:`, `focus-visible:`, and `disabled:`.
### Breakpoints
```tsx
```
Breakpoint prefixes (e.g. `sm:`, `md:`, `lg:`, `xl:`, `max-sm:`) map to the media query definitions in your Tamagui config.
### Platform modifiers
```tsx
```
Use `web:`, `native:`, `ios:`, and `android:` to apply platform-specific utilities.
### Group and container queries
```tsx
```
Mark ancestors with `group` or `@container` / `container`, then apply `group-hover:` or `@sm:` on children.
## Arbitrary values
Pass arbitrary values inside square brackets:
```tsx
```
Tamagui normalizes arbitrary values for cross-platform safety:
- Unitless numbers (such as `z-[400]`) and pixel lengths (such as `p-[18px]`, `text-[14px]`) resolve to numeric values on native. React Native requires numbers for layout and typography props.
- CSS units and functions (such as `%`, `rem`, `calc()`, `var()`, hex colors) stay strings.
## Tokens and color opacity
Utilities for color, radius, size, and spacing resolve against your Tamagui config tokens:
- `bg-blue-500` resolves to the `blue-500` color token.
- `rounded-lg` resolves to the `lg` radius token.
- `p-4` resolves to the `4` space token.
Color utilities support opacity modifiers:
```tsx
```
The opacity suffix lowers to CSS `color-mix` on web and RGBA colors on native.
## Class-first styled()
Use `styled()` from `@tamagui/tailwind` to create reusable components with class-based variants:
```tsx
import { View, styled } from '@tamagui/tailwind'
export const Button = styled(View, 'px-4 py-2 rounded-md font-medium', {
variants: {
variant: {
primary: 'bg-blue-500 hover:bg-blue-600 text-white',
secondary: 'bg-slate-200 hover:bg-slate-300 text-slate-800',
},
size: {
small: 'text-sm py-1 px-2',
large: 'text-lg py-3 px-6',
},
},
defaultVariants: {
variant: 'primary',
},
})
```
Variant values are class strings. The resulting component accepts `className` and its declared variant props.
## Mixing with Tamagui components
You can mix `@tamagui/tailwind` components and standard `tamagui` components within the same application:
```tsx
import { View as TamaguiView } from 'tamagui'
import { Text as TailwindText, View as TailwindView } from '@tamagui/tailwind'
export function MixedTree() {
return (
Mixed Tree
)
}
```
Each component retains its authoring syntax. Both resolve through the same runtime and theme context.
## Utility coverage
Claimed utilities are parsed by `@tamagui/style-grammar`. They compile to atomic CSS on web and resolve to React Native styles on iOS and Android.
### Claimed cross-platform utilities
- **Sizing:** `w-*`, `h-*`, `size-*` (width and height), `min-w-*`, `max-w-*`, `min-h-*`, `max-h-*`, fractions (`w-1/2`), keywords (`full`, `auto`, `screen`, `min`, `max`, `fit`).
- **Spacing:** `p`, `pt`, `pr`, `pb`, `pl`, `px`, `py`, `m`, `mt`, `mr`, `mb`, `ml`, `mx`, `my`, `gap-*`, `gap-x-*`, `gap-y-*`.
- **Flex and Alignment:** `flex`, `flex-1`, `flex-auto`, `flex-initial`, `flex-none`, `flex-row`, `flex-col`, `flex-wrap`, `flex-wrap-reverse`, `grow`, `shrink`, `items-*`, `justify-*`, `content-*`, `self-*`.
- **Typography:** `text-sm` (font size tokens), `text-white` (palette and theme colors), `text-center`/`left`/`right`/`justify`/`start`/`end` (alignment), `text-[14px]` (arbitrary size), `text-[#fff]` (arbitrary color), `font-bold` and named weights, `font-sans`/`serif`/`mono` and configured font families, `leading-*` (line height), `tracking-*` (letter spacing), `italic`/`not-italic`, `uppercase`/`lowercase`/`capitalize`/`normal-case`, `underline`/`line-through`/`no-underline`.
- **Color:** `bg-*`, `color-*`, `text-*` (colors), `border-*` (color), opacity suffix `/50` (such as `bg-blue-500/50`).
- **Radius:** `rounded-*`, `rounded-t/r/b/l-*`, `rounded-tl/tr/bl/br-*` (including `xl`, `lg`, `full`).
- **Borders:** `border` (1px default), `border-4`, `border-t/r/b/l-*`, `border-x`/`border-y` (width and color).
- **Positioning:** `relative`, `absolute`, `fixed`, `sticky`, `inset-*`, `inset-x-*`, `inset-y-*`, `top-*`, `right-*`, `bottom-*`, `left-*`, `z-*`.
- **Transforms & Effects:** `opacity-*`, `overflow-*`, `object-fit`, `pointer-events-*`, `rotate-*`, `scale-*`, `translate-x-*`, `translate-y-*`, `aspect-*`, `shadow-[…]` (arbitrary shadow), group and container markers (`group`, `container="card"`, `@container`).
`text-*` utilities resolve in strict order: configured `fontSize` token first, then alignment keywords, then configured palette color.
### Web-only passthrough
Classes that do not have a React Native style equivalent pass through to `className` on web. On native, unrecognized classes are dropped:
- **CSS Grid & Columns:** `grid-*`, `col-*`, `row-*`, `auto-cols-*`, `auto-rows-*`, `columns-*`, `break-*`
- **Filters & Blurs:** `backdrop-*`, `blur-*`, `brightness-*`, `contrast-*`, `grayscale-*`, `drop-shadow-*`
- **Gradients & Blends:** `from-*`, `via-*`, `to-*`, `bg-gradient-*`, `mix-blend-*`
- **Child & Sibling Selectors:** `space-x-*`, `space-y-*`, `divide-x-*`, `divide-y-*`
- **Rings & Outlines:** `ring-*`, `ring-offset-*`, `outline-*` (use `shadow-[…]` for cross-platform shadow)
- **Web Chrome & Pseudo Elements:** `cursor-*`, `select-*`, `appearance-*`, `resize-*`, `placeholder-*`, `caret-*`
- **Arbitrary Selectors:** `peer-*`, `has-*`, `data-*`, `aria-*`, `[&>*]`
- **Keyframe Animations:** `animate-*` (use Tamagui's `transition` prop instead)
## core/view-and-text
---
title: View & Text
description: View and Text with the full Tamagui style API
---
Tamagui's `View` and `Text` match the React Native primitives and accept the
full set of Tamagui style props.
They are the default building blocks. `View` is a flex column on every platform,
so a layout looks the same on web and native. On web they render a `div` and a
`span`. For semantic tags, set [`render`](/docs/core/styled#render), or see the
[HTML elements](/docs/core/html-primitives), which are in early development.
## Usage
You can use them directly:
```tsx
import { View, Text } from 'tamagui' // or '@tamagui/core'
export default () => (
Hello
)
```
Inline styles and [shorthands](/docs/core/shorthands) compile away, so prefer them for one-off styles:
```tsx
import { View, Text } from 'tamagui' // or '@tamagui/core'
export default () => (
Hello
)
```
## With styled()
You can also use them [with styled](/docs/core/styled) to create your own lower level views that are meant to be re-usable:
```tsx
import { type SizeTokens, View, styled } from 'tamagui' // or '@tamagui/core'
export const Circle = styled(View, {
borderRadius: 100_000_000,
variants: {
pin: {
top: {
position: 'absolute',
top: 0,
},
},
centered: {
true: {
alignItems: 'center',
justifyContent: 'center',
},
},
size: styled.dynamic((size, { tokens }) => ({
width: tokens.size[size] ?? size,
height: tokens.size[size] ?? size,
})),
} as const,
})
```
Both inline styles and `styled()` are compiler-optimized, so pick per use case.
## Props
Tamagui supports a superset of the React Native props. Start with:
- [View](https://reactnative.dev/docs/view-style-props), or
- [Text](https://reactnative.dev/docs/text-style-props)
From there, we add [style props](/docs/intro/styles) directly onto the same
object.
Finally, there are a few non-style props Tamagui adds:
### Event props
All Tamagui View-based components have Pressable-like functionality built in. You don't need to wrap in `Pressable` or `TouchableOpacity` - add event handlers directly:
```tsx
console.log('pressed')}
onPointerEnter={() => console.log('hovered')}
opacity="press:0.8"
backgroundColor="hover:background-hover"
/>
```
void',
description: (
Called when a press is released. Works on both web and native.
),
},
{
name: 'onPressIn',
required: false,
type: '(e: GestureResponderEvent) => void',
description: Called immediately when a press starts.,
},
{
name: 'onPressOut',
required: false,
type: '(e: GestureResponderEvent) => void',
description: Called when a press is released or cancelled.,
},
{
name: 'onLongPress',
required: false,
type: '(e: GestureResponderEvent) => void',
description: (
Called when a press is held for longer than delayLongPress (default
500ms).
),
},
{
name: 'onFocus',
required: false,
type: '(e: FocusEvent) => void',
description: Called when the element receives focus.,
},
{
name: 'onBlur',
required: false,
type: '(e: FocusEvent) => void',
description: Called when the element loses focus.,
},
{
name: 'delayPressIn',
required: false,
type: 'number',
description: (
Duration in ms to wait before calling onPressIn.
),
},
{
name: 'delayPressOut',
required: false,
type: 'number',
description: (
Duration in ms to wait before calling onPressOut after press is
released.
),
},
{
name: 'delayLongPress',
required: false,
type: 'number',
description: (
Duration in ms to wait before calling onLongPress. Default is
500ms.
),
},
{
name: 'minPressDuration',
required: false,
type: 'number',
description: (
Minimum duration in ms that a press must be held before onPressOut{' '}
is called.
),
},
{
name: 'cancelable',
required: false,
type: 'boolean',
description: (
When true, allows the press to be cancelled by scrolling or other gestures.
),
},
{
name: 'disabled',
required: false,
type: 'boolean',
description: Disables all press and hover interactions on the element.,
},
{
name: 'hitSlop',
required: false,
type: `number | Insets`,
description: (
Extends the pressable area beyond the element bounds. Accepts a number (applies
to all sides) or an object with top, bottom,{' '}
left, right.
),
},
]}
/>
Use these events alongside `press:`, `hover:`, and `focus:` clauses to create interactive components. See the [Style API](/docs/intro/styles) for interaction styling.
### Other props
JSX.Element)`,
description: (
Control which element or component is rendered. Accepts an HTML tag name (e.g., "button", "a"),
a JSX element, or a render function receiving props and component state. String render props are compiler-optimized.
See styled() render documentation for details.
),
},
{
name: 'transition',
required: false,
type: 'string | object',
description: (
A CSS transition string ("200ms ease-out"), a preset name from your
createTamagui configuration ("bouncy"), or an object for per-property
control ({`{ preset: 'bouncy', opacity: '100ms' }`}). See the{' '}
Animations documentation.
),
},
{
name: 'theme',
required: false,
type: 'string',
description: (
Apply a theme or sub-theme, such as theme="inverse", to
this component and the components below it.
),
},
{
name: 'themeShallow',
required: false,
type: 'boolean',
description: (
Used in combination with the theme prop, it prevents the theme from passing to children.
),
},
{
name: 'forceStyle',
required: false,
type: `'hover' | 'press' | 'focus' | 'focusVisible' | 'focusWithin'`,
description: (
Forces the pseudo style state to be on.
),
},
{
name: 'group',
required: false,
type: `boolean | string`,
description: (
Marks this component as a group for use in styling children based on parents named group.
),
},
{
name: 'className',
required: false,
type: 'string',
description: (
Adds a className. In Tailwind mode, supported utility classes compile to
Tamagui styles on web and native. Unknown classes pass through on web.
),
},
{
name: 'disableClassName',
required: false,
type: 'boolean',
description: (
Web Only: Disables className output of styles, instead using only inline styles.
),
},
{
name: 'debug',
required: false,
type: `boolean | 'verbose' | 'break'`,
description: (
When set Tamagui will output a variety of helpful information on how it parsed and
applied styling. Verbose will output more information, and break will hit a debugger at
the start of render in development mode.
),
},
{
name: 'untilMeasured',
required: false,
type: `'hide' | 'show'`,
description: (
Works only alongside group, when children of the group are using container based sizing on native you can hide them until parent is measured.
),
},
{
name: 'disableOptimization',
required: false,
type: 'boolean',
description: (
Disable all compiler optimization.
),
},
{
name: 'tabIndex',
required: false,
type: `string | number`,
description: (
Used for controlling keyboard and assistive device focus. This replaces the old focusable prop.
),
},
{
name: 'role',
required: false,
type: 'Role',
description: (
Indicates to accessibility services to treat UI component like a specific role.
),
},
{
name: 'asChild',
required: false,
type: `boolean | 'except-style' | 'except-style-web' | 'web'`,
description: (
When true, Tamagui expects a single child element. Instead of rendering its own element, it will pass all props to that child, merging together any event handling props. When "except-style", the same behavior except Tamagui won't pass styles down from the parent, only non-style props.
),
},
{
name: 'passThrough',
required: false,
type: 'boolean',
description: (
Advanced use only. React will "re-parent" components when it's internal tree structure changes.
When making UI that adapts to another look, for better performance and avoiding losing user state (like focus, or uncontrolled text input), you can use passThrough rather than conditionally rendering a wrapping component.
),
},
]} />
## Event handlers
Tamagui supports all React Native events on all platforms. On web, all standard DOM event handlers work too:
### Cross-platform events
- `onPress`, `onLongPress`, `onPressIn`, `onPressOut` - Touch/press events that work on both native and web
**Pointer Events** - Modern events that unify mouse, touch, and pen input. On native, these are mapped to touch events with a normalized event shape including `clientX/Y`, `pointerId`, and `pointerType`:
- `onPointerDown`, `onPointerUp`, `onPointerMove`
- `onPointerEnter`, `onPointerLeave`, `onPointerCancel`
On native, `e.target.setPointerCapture(pointerId)` and `releasePointerCapture(pointerId)` are supported for drag scenarios where you need to receive move events outside the element bounds.
### Web-only events
The following events are available on web and automatically ignored on native:
**Mouse events**
- `onClick`, `onDoubleClick`, `onContextMenu`
- `onMouseEnter`, `onMouseLeave`, `onMouseMove`, `onMouseOver`, `onMouseOut`
- `onMouseDown`, `onMouseUp`
- `onWheel`
**Keyboard events**
- `onKeyDown`, `onKeyUp`, `onKeyPress`
**Drag and drop**
- `onDrag`, `onDragStart`, `onDragEnd`
- `onDragEnter`, `onDragLeave`, `onDragOver`
- `onDrop`
**Input events**
- `onChange`, `onInput`, `onBeforeInput`
**Scroll events**
- `onScroll`
**Clipboard events**
- `onCopy`, `onCut`, `onPaste`
**Focus events**
- `onFocus`, `onBlur`
All event handlers receive the standard React synthetic event as their argument.
## core/config-v5
---
title: 'Config v5 compatibility'
description: Preserve Tamagui v2 token and theme values while migrating to v3
---
Config v5 preserves Tamagui v2 design values while an app adopts the v3 APIs
and runtime.
Tamagui v3 defaults to Config v6. Use this frozen compatibility pack when an
existing application's layout and visual design still depend on v5 values.
The dynamic v5 theme builders are not included in v3.
The v5 entry preserves the familiar independent size and space scales, the
12-step adaptive color ramp, surface theme names, component themes, fonts, and
shorthands:
```tsx fileName="tamagui.config.ts"
import { animationsCSS } from '@tamagui/config/animations-css'
import { defaultConfig } from '@tamagui/config/v5'
import { createTamagui } from 'tamagui'
export const config = createTamagui({
...defaultConfig,
animations: animationsCSS,
})
```
The subtle pack exports its muted generated themes separately, matching its v2
API:
```tsx fileName="tamagui.config.ts"
import { animationsCSS } from '@tamagui/config/animations-css'
import { defaultConfig, themes } from '@tamagui/config/v5-subtle'
import { createTamagui } from 'tamagui'
export const config = createTamagui({
...defaultConfig,
themes,
animations: animationsCSS,
})
```
The static theme data is also available from `@tamagui/themes/v5`,
`@tamagui/themes/v5-subtle`, and `@tamagui/themes/v5-tokens`. Legacy animation
imports at `@tamagui/config/v5-css`, `/v5-rn`, `/v5-reanimated`, and `/v5-motion`
remain aliases to their maintained unversioned entries.
The compatibility lane does not restore `@tamagui/theme-builder`,
`@tamagui/themes/v5-builder`, or `@tamagui/themes/v5-subtle-builder`. Those
builders carried a second theme-generation architecture. New custom themes
should use [`@tamagui/themes/builder`](/docs/guides/theme-builder).
Read the [v3 upgrade guide](/docs/guides/how-to-upgrade#theme-migration) when
you are ready to move to the v6 11-step color ramp and level-based surfaces.
## core/animations
---
title: Animations
description: Swap out animation drivers per-platform or at runtime
---
Tamagui animates style props through swappable drivers, with the same
component API across web and native.
```tsx hero template=Animations
```
Add animations to Tamagui with an animation driver. Animation drivers are designed to be swappable, so you can use lightweight CSS animations or web-focused libraries on the web, while using Reanimated on native, without changing component code outside of configuration.
For this guide, we'll use the React Native driver as an example, but you can choose from several [Animation Drivers](/docs/core/animation-drivers). For mount and unmount animations, see [AnimatePresence](/docs/core/animate-presence).
## Installation
```bash
yarn add @tamagui/animations-react-native
```
Then add it to your [Tamagui config](/docs/core/configuration):
```tsx
import { createAnimations } from '@tamagui/animations-react-native'
import { createTamagui } from 'tamagui'
export default createTamagui({
animations: createAnimations({
bouncy: { duration: 400, bounce: 0.5 },
lazy: { duration: 500, bounce: -0.2 },
quick: { duration: 220, bounce: 0.3 },
}),
// ...
})
```
A config entry is `{ duration, bounce }`, a CSS timing string, or
`{ type: 'timing', duration, easing }`. `stiffness`, `damping`, and `mass` still
work for a config already tuned against them. The same config resolves
to the same motion on all four drivers, so you can swap drivers without
retuning.
`@tamagui/config` ships this table for every driver, so you only need
`createAnimations` when you want names of your own:
```tsx
import { animationsCSS } from '@tamagui/config/animations-css'
```
## Usage
The `transition` prop accepts the name of an animation you've configured. By default, animations will
apply to all animatable styles, similar to setting `all` in a CSS `transition`.
Use a `hover` object key or a `hover:` string clause to animate a state value:
StringTyped
```tsx
```
```tsx
```
Here is an interactive example:
```tsx hero template=AnimationsHover
```
### The transition prop rules
If you add a `transition` prop, you must always keep the prop. If you need the animation to be disabled,
pass false, null or even undefined if it suits you.
The spring-based animation drivers have expensive hooks that would degrade runtime
performance if present on every component. As a workaround, the animation hooks
are called conditionally based on whether the `transition` key is present in the props object.
So, `` rather than
``.
If you'd like to remove or add a transition prop after a component has already rendered, you'd have
to change the key.
### Enter clauses
Setting an `enter` object key or an `enter:` string clause tells a property to
start at that value, then animate to its base value after mount:
StringTyped
```tsx
```
```tsx
```
```tsx hero template=AnimationsEnter
```
### The transition value
`transition` takes a CSS transition string or an object. Both forms mean the
same thing:
```tsx
// a css transition, exactly as css writes it
// a configured preset name, anywhere a timing goes
// a spring, inline
// nothing transitions
```
The object form takes the same values under names:
```tsx
```
The settings are `preset`, `duration`, `bounce`, `easing`, `delay`, `behavior`,
`properties`, `spring`, `enter`, and `exit`. Every other key names a style
property and gets its own timing. An unrecognized key is an error rather than a
silent no-op.
### Granular animations
A per-property key wins for that property, the same way a later entry wins in a
CSS transition list. These two are the same transition:
```tsx
```
Per-property values take a preset name, a CSS timing, or an object:
```tsx
```
Entries do not inherit from the base. `{ duration: 200, opacity: {} }` gives
`opacity` no timing of its own, the same as `transition: all 200ms, opacity` in
CSS. Name what you want on each entry, or use `preset` for a shared starting
point.
### Springs
A spring is `duration` plus `bounce`:
```tsx
```
`duration` is the spring's period, which is what "how fast does this feel"
means. It is not a stopwatch: a bouncy spring keeps ringing past it, which is
the point of a bouncy spring. `bounce` is `0` for critically damped (fast, no
overshoot), goes up toward `1` for loose and oscillating, and goes negative for
sluggish. A value outside `-1` to `1` is an error.
On the web this compiles to a `linear()` easing that traces the real spring
curve, overshoot included, with no JavaScript running.
When you need the physics directly, `spring` is the escape hatch:
```tsx
```
It takes `stiffness`, `damping`, `mass`, `velocity`, `overshootClamping`,
`restDisplacementThreshold`, and `restSpeedThreshold`. Tamagui derives the
`duration`/`bounce` pair from `stiffness` and `mass`, so the same motion reaches
the CSS driver too. It is a projection of duration and bounce, not a second API.
### Presets
A preset is a name from your config's `animations`. Any name works where a
timing goes, and `preset` in the object form takes overrides:
```tsx
```
Overriding `duration` or `bounce` re-solves the spring, so the preset keeps
whichever of the two you did not name and drops the `stiffness`/`damping` it was
written with, since those are what you just replaced. `mass` carries over: it
describes the object rather than the curve. Naming `stiffness` or `damping`
yourself still wins outright, and a preset used without a `duration` or `bounce`
override runs exactly the numbers your config gave it.
The shipped presets are the same springs on every driver: `quickest`, `quicker`,
`quick`, `medium`, `slow`, `slowest`, `lazy`, `superLazy`, `bouncy`,
`superBouncy`, plus `quickestLessBouncy`, `quickerLessBouncy`, and
`quickLessBouncy`. Keep your own table small. A duration is CSS now, so
`transition="200ms"` needs nothing configured, and a name is worth having only
when it means something a duration cannot say.
### Delay
`delay` is the second time in the CSS string, or the `delay` key:
```tsx
import { Square, XStack } from 'tamagui'
export default () => (
{[0, 1, 2, 3].map((i) => (
))}
)
```
Each square starts 100ms after the previous one, on enter, exit, and updates.
### Enter/exit transitions
`enter` and `exit` set the transition to use while mounting and unmounting.
Set them when elements should enter slowly and leave quickly:
```tsx
import { AnimatePresence, html } from 'tamagui'
export default ({ show }) => (
{show && (
)}
)
```
Each takes a whole transition value, so they can carry their own properties and
per-property timings, and the base still applies to changes while mounted:
```tsx
// enter slowly, exit quickly, changes while mounted use 200ms
```
An `enter` or `exit` list replaces the base while it applies rather than merging
into it, because the two never run at the same time.
This works with all four animation drivers (CSS, React Native, Reanimated,
Motion).
### Pseudo-style transitions
Specify different transitions for entering and exiting pseudo states like
hover, press, and focus. Entering a state uses that state's transition, and
exiting uses the base transition:
```tsx
import { Square } from 'tamagui'
export default () => (
)
```
In this example:
- **Hover enter**: Uses the 200ms transition (snaps quickly to hover state)
- **Hover exit**: Uses the 1000ms base transition (fades slowly back)
This works with all pseudo styles:
```tsx
```
And with group pseudo states:
```tsx
```
Priority follows CSS specificity intuition: press > hover > focus. This works with all animation drivers.
### Limiting which properties transition
Name the properties in the transition itself, the same way CSS does. In string
form the property leads the value, and in object form it is the `properties` key:
```tsx
// only transform transitions, everything else jumps
// nothing transitions
```
This replaces v2's `animateOnly` prop, which was the same idea spelled as a
separate array. `properties` reaches every driver, so a list means the same
thing on CSS, Motion, React Native, and Reanimated.
Naming `transform` covers every transform part, so `x`, `y`, `scale`,
`scaleX`/`scaleY`, `rotate` and the rest all transition together. Naming one
part covers only that part:
```tsx
// x, scale and rotate all transition
// only x transitions; scale jumps
```
## The onTransition lifecycle
Every animated component accepts `onTransition`, a typed callback that fires at
the start and end of a transition. It replaces the untyped, enter-only
`onDidAnimate` from v2.
```tsx
type TransitionEvent = {
phase: 'start' | 'end'
cause: 'enter' | 'exit' | 'update' // update = style change while mounted
finished?: boolean // on 'end': false when interrupted
}
{ ... }} />
```
`cause` is `enter` when the component mounts into an [AnimatePresence](/docs/core/animate-presence),
`exit` when it unmounts, and `update` for any style change while it stays
mounted. On the `end` phase, `finished` is `false` when the transition was
interrupted, for example an exit canceled by a re-enter, or an update
superseded by another before it settled.
```tsx
import { html } from 'tamagui'
export default ({ on }) => (
{
if (e.phase === 'end' && e.finished) {
// the style change finished without being interrupted
}
}}
/>
)
```
A driver may coalesce a multi-property update into one start/end pair per batch,
but it never skips the end event. Exit `end` fires before or with presence
removal, so you can observe exit completion without reaching into presence
internals. There is a single callback rather than separate
`onTransitionStart`/`onTransitionEnd` props because those names collide with the
React DOM event props on web.
## Imperative animation hooks
The same animated-number primitives the drivers use internally are exported from
`tamagui` (and `@tamagui/core`). Use them to drive an effect from a value you
control instead of from style props, for example a drag-linked overlay fade.
```tsx
import {
useAnimationDriver, // resolved driver from config, throws helpfully on the stub
useAnimatedNumber, // (initial: number) => UniversalAnimatedNumber
useAnimatedNumberStyle, // (value, worklet getStyle) => a style object
useAnimatedNumbersStyle, // multiple values, one style
useAnimatedNumberReaction, // observe the value on the JS thread
} from 'tamagui'
```
Each hook is a thin delegation to the configured driver, so first-party code and
your code exercise the same surface. They throw a helpful error if no
`animations` config is set or if the configured CSS driver came from the core
`@tamagui/animations-css` entry. Import `createAnimations` from
`@tamagui/animations-css/extras` when using these hooks, `Sheet`, or draggable
`Toast` with the CSS driver. The resolved driver must not change identity
mid-lifecycle, which is only a concern with `animatedBy` multi-driver setups.
`useAnimatedNumberStyle` takes a value and a worklet that maps it to a style
object. Spread that style onto the driver's animated view. On the CSS driver, a
plain Tamagui view with a `transition` prop interpolates the change.
```tsx
import { useRef } from 'react'
import type { Animated } from 'react-native'
import {
Button,
View as TamaguiView,
YStack,
useAnimatedNumber,
useAnimatedNumberStyle,
useAnimationDriver,
} from 'tamagui'
export function Toggle() {
const driver = useAnimationDriver()
// the driver's animated view consumes the returned style; on the css driver
// there is no driver View, so fall back to a Tamagui view + transition prop
const AnimatedView = (driver.View ?? TamaguiView) as typeof Animated.View
const x = useAnimatedNumber(0)
const openRef = useRef(false)
const style = useAnimatedNumberStyle(x, (value: number) => {
'worklet'
return { transform: [{ translateX: value }] }
})
const toggle = () => {
openRef.current = !openRef.current
x.setValue(openRef.current ? 200 : 0, { type: 'spring', damping: 20, stiffness: 90 })
}
return (
Toggle
)
}
```
`setValue(next, config, onFinished)` accepts an optional completion callback that
fires once, with the value settled on the target. `useAnimatedNumberReaction`
observes intermediate values on the JS thread.
`UniversalAnimatedNumber`, `AnimatedNumberStrategy`, and the hook types are
public API.
## What to know when animating
### Driver completion
Every driver reports real completion. The CSS driver drives its animated numbers
with a `requestAnimationFrame` ticker and fires completion once when the value
settles, so `onTransition` end events and `setValue` completion callbacks are
accurate on web without an estimated-duration timer. Web completion detection
uses the element's running animations rather than a guessed duration.
### Conditional animations and HMR
The animation hooks are heavy, which initially meant we either had to choose
great performance or animations. We settled on a trade-off: we track if the
`transition` prop is set, and if so, we enable the hook. If it is ever set, even
just once, then the hooks will continue to run for the remainder of the
component lifecycle. This means if you ever plan to animate a component you
should keep `transition` always set on the component props. You can disable it
like so:
```tsx
```
Note that because of this constraint, adding the `transition` prop to a mounted
component during dev HMR can error. Saving again or reloading clears it.
## See also
- [AnimatePresence](/docs/core/animate-presence): mount and unmount animations
- [Animation Drivers](/docs/core/animation-drivers): choose and configure animation drivers
## core/use-theme
---
title: useTheme
description: Read the current theme as typed values
---
`useTheme` reads the current theme as a typed object whose values update with
the surrounding theme context.
Tamagui themes operate much like
[CSS variables](https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties),
so [they can nest and override each other contextually](/docs/intro/themes).
The returned object proxies up through every parent theme to your tokens, just
like CSS variables at runtime.
Example:
```tsx
import { YStack, useTheme } from 'tamagui'
const App = () => {
const theme = useTheme()
return
}
```
The `useTheme` hook returns your Theme turned into a `ThemeParsed`, which means
it turns all values into a `Variable`:
```tsx
{
background: {
val: '#000',
variable: 'var(--background)',
name: 'background',
isVar: true,
},
color: {
val: '#fff',
variable: 'var(--color)',
name: 'color',
isVar: true,
},
}
```
The `useTheme` hook adds a `.get()` helper function on each Variable for
performance. On web, `.get()` returns the `.variable` property and avoids
re-rendering on value changes, because CSS custom properties handle updates
directly in the browser. On native, `.get()` returns `.val` and re-renders on
change. When the [`fastSchemeChange` setting](/docs/core/settings)
is enabled, `get()` returns a [DynamicColorIOS](https://reactnative.dev/docs/dynamiccolorios)
value on iOS to avoid re-renders there as well. Passing `get('web')` restricts
that optimization to web.
```tsx
import { useTheme } from '@tamagui/core'
import { SomeExternalComponent } from 'some-external-component'
const App = () => {
const theme = useTheme()
// on the web this is something like var(--background) and will avoid re-renders
// on native it will be something like #fff and will re-render
const background = theme.background.get()
// if you needed to access it in a way that always returns the raw value
const backgroundValue = theme.background.val
return (
)
}
```
You can mix and match `useMedia` and `useTheme`, and the compiler understands
most basic usages, even with nested logic or constants within the file or
imports [whitelisted in your build setup](/docs/intro/compiler-install):
```tsx
import { YStack, useMedia, useTheme } from 'tamagui'
const App = () => {
const theme = useTheme()
const media = useMedia()
return (
)
}
```
This will compile on the web to:
```tsx
const _cn =
' _alignItems-1oszu61 _boxSizing-deolkf _display-6koalj _flexBasis-1mlwlqe _flexDirection-eqz5dr _flexShrink-1q142lx _transform-_sm_1exagq _transform-_sm0_1wpzndr _backgroundColor-_lg_no4z4g _backgroundColor-_lg0_1qoifqd _transform-_xl_gqa6p0'
import { YStack, useMedia, useTheme } from 'tamagui'
const App = () => {
return
}
```
And the following CSS:
```css
._alignItems-1oszu61 {
-ms-flex-align: stretch;
-webkit-align-items: stretch;
-webkit-box-align: stretch;
align-items: stretch;
}
._boxSizing-deolkf {
box-sizing: border-box;
}
._display-6koalj {
display: -webkit-box;
display: -moz-box;
display: -ms-flexbox;
display: -webkit-flex;
display: flex;
}
._flexBasis-1mlwlqe {
-ms-flex-preferred-size: auto;
-webkit-flex-basis: auto;
flex-basis: auto;
}
._flexDirection-eqz5dr {
-ms-flex-direction: column;
-webkit-box-direction: normal;
-webkit-box-orient: vertical;
-webkit-flex-direction: column;
flex-direction: column;
}
._flexShrink-1q142lx {
-ms-flex-negative: 0;
-webkit-flex-shrink: 0;
flex-shrink: 0;
}
@media (max-width: 860px) {
:root:root ._transform-_sm_1exagq {
-webkit-transform: translateY(10px);
transform: translateY(10px);
}
}
@media not all and (max-width: 860px) {
:root:root ._transform-_sm0_1wpzndr {
-webkit-transform: translateY(0px);
transform: translateY(0px);
}
}
@media (min-width: 1120px) {
:root:root:root ._backgroundColor-_lg_no4z4g {
background-color: var(--red);
}
}
@media not all and (min-width: 1120px) {
:root:root:root ._backgroundColor-_lg0_1qoifqd {
background-color: var(--blue);
}
}
@media (min-width: 1280px) {
:root:root:root:root ._transform-_xl_gqa6p0 {
-webkit-transform: translateY(var(--space2));
transform: translateY(var(--space2));
}
}
```
## Using outside of styling
You can `useTheme()` (and `useMedia()`) at runtime. Like useMedia, useTheme will
only re-render when it has to, and often you can skip re-renders altogether by
either passing the values to a Tamagui styled component, or by using the helper
`getVariable`:
### getVariable
If you access `theme.bg.val` in your render function, the component will only
re-render when theme.bg changes.
```tsx
import { useTheme, html } from '@tamagui/core'
export default () => {
const theme = useTheme()
// access the value
console.log(theme.bg.val)
return
}
```
## Reading another theme
`useTheme` takes no arguments and reads the ambient theme. To read values under
a different theme, wrap the reader in `` and call `useTheme`
inside it.
## components/tamagui-image/1.0.0
---
title: Image
description: Web compatible and super light image component with Tamagui style props.
name: html
component: Image
package: image-next
demoName: WebNativeImageDemo
---
```tsx hero template=WebNativeImage
```
## Installation
Image is already installed in `tamagui`, or you can install it independently:
```bash
npm install @tamagui/image
```
## Usage
```tsx
export default () =>
```
## API Reference
### Image
[Tamagui props](/docs/intro/props) +
[Web img props](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img) +
[React Native Image props](https://necolas.github.io/react-native-web/docs/image/).
All web [img](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img) props are
supported on web, and all native
[Image](https://necolas.github.io/react-native-web/docs/image/) props are supported on
native. on native we are still using web img APIs, but not all web img props are
supported. like `decoding`
#### Some common props
## components/tamagui-image/2.0.0
---
title: Image
description: Lightweight web-compatible image with Tamagui style props
name: tamagui-image
component: Image
package: image
demoName: WebNativeImageDemo
---
This lightweight Image component renders on web and native with Tamagui style
props and no react-native-web dependency.
```tsx hero template=WebNativeImage
```
## Installation
Image is already installed in `tamagui`, or you can install it independently:
```bash
npm install @tamagui/image
```
## Usage
```tsx
export default () =>
```
## API reference
### Image
[Tamagui props](/docs/intro/props) +
[Web img props](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img) +
[React Native Image props](https://necolas.github.io/react-native-web/docs/image/).
All web [img](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img)
props are supported on web, and all native
[Image](https://reactnative.dev/docs/image) props are supported on native. Not
every web img prop (like `decoding`) is supported on native.
#### Some common props
## components/sheet/1.21.0
---
title: Sheet
description: A simple sheet component
name: sheet
component: Sheet
package: sheet
demoName: Sheet
---
# Sheet
A bottom sheet that slides up
```tsx hero template=Sheet
```
### Anatomy
```tsx
import { Sheet } from 'tamagui' // or '@tamagui/sheet'
export default () => (
{/* ...inner contents */}
)
```
### API
#### <Sheet />
Contains every component for the sheet.
void',
description: `Called on change open, controlled or uncontrolled.`,
},
{
name: 'position',
type: 'number',
description: `Controlled position, set to an index of snapPoints.`,
},
{
name: 'defaultPosition',
type: 'number',
description: `Uncontrolled default position on mount.`,
},
{
name: 'snapPoints',
type: 'number[]',
default: `[80, 10]`,
description: `Array of numbers, 0-100 that corresponds to % of the screen it should take up. Should go from most visible to least visible in order. Use "open" prop for fully closed.`,
},
{
name: 'onPositionChange',
type: '(position: number) => void',
description: `Called on change position, controlled or uncontrolled.`,
},
{
name: 'dismissOnOverlayPress',
type: 'boolean',
default: 'true',
description: `Controls tapping on the overlay to close, defaults to true.`,
},
{
name: 'animationConfig',
type: 'Animated.SpringAnimationConfig',
default: 'true',
description: `Customize the spring used, passed to react-native Animated.spring().`,
},
{
name: 'native',
type: 'boolean | "ios"[]',
description: `(iOS only) Render to a native sheet, must install native dependency first.`,
},
{
name: 'disableDrag',
type: 'boolean',
description: `Disables all touch events to drag the sheet.`,
},
{
name: 'modal',
type: 'boolean',
description: `Renders sheet into the root of your app instead of inline.`,
},
{
name: 'dismissOnSnapToBottom',
type: 'boolean',
description: `Adds a snap point to the end of your snap points set to "0", that when snapped to will set open to false (uncontrolled) and call onOpenChange with false (controlled).`,
},
{
name: 'forceRemoveScrollEnabled',
type: 'boolean',
default: 'false',
description: `By default. Tamagui uses react-remove-scroll to prevent anything outside the sheet scrolling. This can cause some issues so you can override the behavior with this prop (either true or false).`,
},
{
name: 'portalProps',
type: 'Object',
description: `YStack props that can be passed to the Portal that sheet uses when in modal mode.`,
},
{
name: 'moveOnKeyboardChange',
type: 'boolean',
default: 'false',
description:
'Native-only flag that will make the sheet move up when the mobile keyboard opens so the focused input remains visible.',
},
]}
/>
#### <Overlay />
Displays behind Frame. Extends [YStack](/docs/components/stacks).
#### <Frame />
Contains the content. Extends [YStack](/docs/components/stacks).
#### <Handle />
Shows a handle above the frame by default, on tap it will cycle between `snapPoints` but this can be overridden with `onPress`.
Extends [XStack](/docs/components/stacks).
#### <Scrollview />
Allows scrolling within Sheet. Extends [Scrollview](/docs/components/scroll-view).
### Notes
For Android you need to manually re-propagate any context when using `modal`. This is because React Native doesn't support portals yet.
## components/sheet/1.0.0
---
title: Sheet
description: A simple sheet component
name: sheet
component: Sheet
package: sheet
demoName: Sheet
---
# Sheet
A bottom sheet that slides up.
```tsx hero template=Sheet
```
### Anatomy
```tsx
import { Sheet } from 'tamagui' // or '@tamagui/sheet'
export default () => (
{/* ...inner contents */}
)
```
### API
#### <Sheet />
Contains every component for the sheet.
void',
description: `Called on change open, controlled or uncontrolled.`,
},
{
name: 'position',
type: 'number',
description: `Controlled position, set to an index of snapPoints.`,
},
{
name: 'defaultPosition',
type: 'number',
description: `Uncontrolled default position on mount.`,
},
{
name: 'snapPoints',
type: 'number[]',
default: `[80, 10]`,
description: `Array of numbers, 0-100 that corresponds to % of the screen it should take up. Should go from most visible to least visible in order. Use "open" prop for fully closed.`,
},
{
name: 'onPositionChange',
type: '(position: number) => void',
description: `Called on change position, controlled or uncontrolled.`,
},
{
name: 'dismissOnOverlayPress',
type: 'boolean',
default: 'true',
description: `Controls tapping on the overlay to close, defaults to true.`,
},
{
name: 'animationConfig',
type: 'Animated.SpringAnimationConfig',
default: 'true',
description: `Customize the spring used, passed to react-native Animated.spring().`,
},
{
name: 'disableDrag',
type: 'boolean',
description: `Disables all touch events to drag the sheet.`,
},
{
name: 'modal',
type: 'boolean',
description: `Renders sheet into the root of your app instead of inline.`,
},
{
name: 'dismissOnSnapToBottom',
type: 'boolean',
description: `Adds a snap point to the end of your snap points set to "0", that when snapped to will set open to false (uncontrolled) and call onOpenChange with false (controlled).`,
},
{
name: 'forceRemoveScrollEnabled',
type: 'boolean',
default: 'false',
description: `By default. Tamagui uses react-remove-scroll to prevent anything outside the sheet scrolling. This can cause some issues so you can override the behavior with this prop (either true or false).`,
},
{
name: 'portalProps',
type: 'Object',
description: `YStack props that can be passed to the Portal that sheet uses when in modal mode.`,
},
]}
/>
#### <Overlay />
Displays behind Frame. Extends [YStack](/docs/components/stacks).
#### <Frame />
Contains the content. Extends [YStack](/docs/components/stacks).
#### <Handle />
Shows a handle above the frame by default, on tap it will cycle between `snapPoints` but this can be overridden with `onPress`.
Extends [XStack](/docs/components/stacks).
#### <Scrollview />
Allows scrolling within Sheet. Extends [Scrollview](/docs/components/scroll-view).
### Notes
For Android you need to manually re-propagate any context when using `modal`. This is because React Native doesn't support portals yet.
## components/sheet/1.116.0
---
title: Sheet
description: A simple sheet component
name: sheet
component: Sheet
package: sheet
demoName: Sheet
---
# Sheet
A bottom sheet that slides up
```tsx hero template=Sheet
```
## Installation
Sheet is already installed in `tamagui`, or you can install it independently:
```bash
npm install @tamagui/sheet
```
### PortalProvider
When rendering into root of app instead of inline, you'll first need to install the `@tamagui/portal` package:
```bash
npm install @tamagui/portal
```
Then add `PortalProvider` to the root of your app:
```tsx fileName="App.tsx"
import { PortalProvider } from '@tamagui/portal'
import YourApp from './components/YourApp'
function App() {
return (
)
}
export default App
```
## Anatomy
```tsx
import { Sheet } from 'tamagui' // or '@tamagui/sheet'
export default () => (
{/* ...inner contents */}
)
```
## Snap points
By default, snap points are treated as percentages.
```tsx
// 85% and 50%
```
The behavior of snap points can be changed by setting the `snapPointsMode` prop to any of these values:
- **percent** (default) - Snap points are percentages of the parent container or screen as numbers
- **constant** - Snap points are raw pixel values as numbers
- **fit** - The sheet is constrained to the content's natural height without the `snapPoints` prop
- **mixed** - Snap points can be either numbers (pixels), percentages as strings (ex: `"50%"`), or `"fit"` for fit behavior
Snap points should be ordered from largest to smallest (most visible to least visible). When using `mixed` mode with the `"fit"` as a snap point, it must be the first and largest snap point.
## Unstyled
Adding the `unstyled` prop to your Handle, Overlay or Frame will turn off the default styles allowing you to customize without having to override any of the built-in styling.
## Headless with `createSheet`
Using the `createSheet` export, you can create a fully custom sheet without using any of the default styles. This is similar to `unstyled`, but it lets you also control the `open` variant.
Here's an example:
```tsx
import { View, styled } from '@tamagui/core'
import { createSheet } from '@tamagui/sheet'
const Handle = styled(View, {
variants: {
open: {
true: {
opacity: 0.35,
},
false: {
opacity: 0.5,
},
},
} as const,
})
const Overlay = styled(View, {
variants: {
open: {
true: {
opacity: 1,
pointerEvents: 'auto',
},
false: {
opacity: 0,
pointerEvents: 'none',
},
},
} as const,
})
const Frame = styled(View, {
backgroundColor: 'background',
// can add open variant as well
})
export const Sheet = createSheet({
Frame,
Handle,
Overlay,
})
```
## Native support
Sheets now support rendering to a native iOS sheet, while still rendering any of your React Native content inside of them.
Because Metro doesn't support conditional imports and we don't want to make `tamagui` enforce installing native dependencies in order to get started, there's an install step.
As of the time of writing, we are using the new `3.0.x` branch which is in beta. Until ready, it does require a bit more setup.
```sh
yarn add react-native-ios-modal@3.0.0-5 react-native-ios-utilities@next @dominicstop/ts-event-emitter
```
Then, rebuild your native iOS app so it picks up the new native dependencies. This is done either through Expo or plain React Native.
Finally, set it up:
```tsx
import { Sheet, setupNativeSheet } from '@tamagui/sheet'
import * as NativeModal from 'react-native-ios-modal'
setupNativeSheet('ios', NativeModal)
// now you can use the `native` prop:
export default {/* ... the rest of your sheet */}
```
## API Reference
### Sheet
Contains every component for the sheet.
void',
description: `Called on change open, controlled or uncontrolled.`,
},
{
name: 'position',
type: 'number',
description: `Controlled position, set to an index of snapPoints.`,
},
{
name: 'defaultPosition',
type: 'number',
description: `Uncontrolled default position on mount.`,
},
{
name: 'snapPoints',
type: '(number | string)[] | undefined',
default: `[80]`,
description: `Array of values representing different sizes for the sheet to snap to. Not used in 'fit' mode. See docs above for usage information.`,
},
{
name: 'snapPointsMode',
type: '"percent" | "constant" | "fit" | "mixed"',
default: '"percent"',
description: `Alters the behavior of the 'snapPoints' prop. See docs above for usage information.`,
},
{
name: 'onPositionChange',
type: '(position: number) => void',
description: `Called on change position, controlled or uncontrolled.`,
},
{
name: 'dismissOnOverlayPress',
type: 'boolean',
default: 'true',
description: `Controls tapping on the overlay to close, defaults to true.`,
},
{
name: 'animationConfig',
type: 'Animated.SpringAnimationConfig',
default: 'true',
description: `Customize the spring used, passed to react-native Animated.spring().`,
},
{
name: 'native',
type: 'boolean | "ios"[]',
description: `(iOS only) Render to a native sheet, must install native dependency first.`,
},
{
name: 'disableDrag',
type: 'boolean',
description: `Disables all touch events to drag the sheet.`,
},
{
name: 'modal',
type: 'boolean',
description: `Renders sheet into the root of your app instead of inline.`,
},
{
name: 'dismissOnSnapToBottom',
type: 'boolean',
description: `Adds a snap point to the end of your snap points set to "0", that when snapped to will set open to false (uncontrolled) and call onOpenChange with false (controlled).`,
},
{
name: 'forceRemoveScrollEnabled',
type: 'boolean',
default: 'false',
description: `By default. Tamagui uses react-remove-scroll to prevent anything outside the sheet scrolling. This can cause some issues so you can override the behavior with this prop (either true or false).`,
},
{
name: 'portalProps',
type: 'Object',
description: `YStack props that can be passed to the Portal that sheet uses when in modal mode.`,
},
{
name: 'moveOnKeyboardChange',
type: 'boolean',
default: 'false',
description:
'Native-only flag that will make the sheet move up when the mobile keyboard opens so the focused input remains visible.',
},
{
name: 'unmountChildrenWhenHidden',
type: 'boolean',
default: 'false',
description:
'Flag to enable unmounting the children after the exit animation has completed.',
},
]}
/>
If using `modal={true}` (which is `true` by default), refer to the [PortalProvider
installation](/ui/sheet/1.59.0#portalprovider) for more information.
### Sheet.Overlay
Displays behind Frame. Extends [YStack](/docs/components/stacks).
### Sheet.Frame
Contains the content. Extends [YStack](/docs/components/stacks).
### Sheet.Handle
Shows a handle above the frame by default, on tap it will cycle between `snapPoints` but this can be overridden with `onPress`.
Extends [XStack](/docs/components/stacks).
### Sheet.ScrollView
Allows scrolling within Sheet. Extends [ScrollView](/docs/components/scroll-view).
#### useSheet
Use this to control the sheet programatically.
void',
description: `Control the position of the sheet.`,
},
]}
/>
## Notes
For Android you need to manually re-propagate any context when using `modal`. This is because React Native doesn't support portals yet.
## components/sheet/1.27.0
---
title: Sheet
description: A simple sheet component
name: sheet
component: Sheet
package: sheet
demoName: Sheet
---
# Sheet
A bottom sheet that slides up
```tsx hero template=Sheet
```
## Installation
Sheet is already installed in `tamagui`, or you can install it independently:
```bash
npm install @tamagui/sheet
```
## Anatomy
```tsx
import { Sheet } from 'tamagui' // or '@tamagui/sheet'
export default () => (
{/* ...inner contents */}
)
```
## Unstyled
Adding the `unstyled` prop to your Handle, Overlay or Frame will turn off the default styles allowing you to customize without having to override any of the built-in styling.
## Headless with `createSheet`
Using the `createSheet` export, you can create a fully custom sheet without using any of the default styles. This is similar to `unstyled`, but it lets you also control the `open` variant.
Here's an example:
```tsx
import { View, styled } from '@tamagui/core'
import { createSheet } from '@tamagui/sheet'
const Handle = styled(View, {
variants: {
open: {
true: {
opacity: 0.35,
},
false: {
opacity: 0.5,
},
},
} as const,
})
const Overlay = styled(View, {
variants: {
open: {
true: {
opacity: 1,
pointerEvents: 'auto',
},
false: {
opacity: 0,
pointerEvents: 'none',
},
},
} as const,
})
const Frame = styled(View, {
backgroundColor: 'background',
// can add open variant as well
})
export const Sheet = createSheet({
Frame,
Handle,
Overlay,
})
```
## Native support
Sheets now support rendering to a native iOS sheet, while still rendering any of your React Native content inside of them.
Because Metro doesn't support conditional imports and we don't want to make `tamagui` enforce installing native dependencies in order to get started, there's an install step:
```sh
yarn add react-native-ios-modal
pod install
# rebuild your app (expo ios, or use react-native cli)
```
And set it up as follows:
```tsx
import { Sheet, setupNativeSheet } from '@tamagui/sheet'
import { ModalView } from 'react-native-ios-modal'
setupNativeSheet('ios', ModalView)
export default (
{/* The rest of your sheet views, see Anatomy, example and props API */}
)
```
## API Reference
### Sheet
Contains every component for the sheet.
void',
description: `Called on change open, controlled or uncontrolled.`,
},
{
name: 'position',
type: 'number',
description: `Controlled position, set to an index of snapPoints.`,
},
{
name: 'defaultPosition',
type: 'number',
description: `Uncontrolled default position on mount.`,
},
{
name: 'snapPoints',
type: 'number[]',
default: `[80, 10]`,
description: `Array of numbers, 0-100 that corresponds to % of the screen it should take up. Should go from most visible to least visible in order. Use "open" prop for fully closed.`,
},
{
name: 'onPositionChange',
type: '(position: number) => void',
description: `Called on change position, controlled or uncontrolled.`,
},
{
name: 'dismissOnOverlayPress',
type: 'boolean',
default: 'true',
description: `Controls tapping on the overlay to close, defaults to true.`,
},
{
name: 'animationConfig',
type: 'Animated.SpringAnimationConfig',
default: 'true',
description: `Customize the spring used, passed to react-native Animated.spring().`,
},
{
name: 'native',
type: 'boolean | "ios"[]',
description: `(iOS only) Render to a native sheet, must install native dependency first.`,
},
{
name: 'disableDrag',
type: 'boolean',
description: `Disables all touch events to drag the sheet.`,
},
{
name: 'modal',
type: 'boolean',
description: `Renders sheet into the root of your app instead of inline.`,
},
{
name: 'dismissOnSnapToBottom',
type: 'boolean',
description: `Adds a snap point to the end of your snap points set to "0", that when snapped to will set open to false (uncontrolled) and call onOpenChange with false (controlled).`,
},
{
name: 'forceRemoveScrollEnabled',
type: 'boolean',
default: 'false',
description: `By default. Tamagui uses react-remove-scroll to prevent anything outside the sheet scrolling. This can cause some issues so you can override the behavior with this prop (either true or false).`,
},
{
name: 'portalProps',
type: 'Object',
description: `YStack props that can be passed to the Portal that sheet uses when in modal mode.`,
},
{
name: 'moveOnKeyboardChange',
type: 'boolean',
default: 'false',
description:
'Native-only flag that will make the sheet move up when the mobile keyboard opens so the focused input remains visible.',
},
]}
/>
### Sheet.Overlay
Displays behind Frame. Extends [YStack](/docs/components/stacks).
### Sheet.Frame
Contains the content. Extends [YStack](/docs/components/stacks).
### Sheet.Handle
Shows a handle above the frame by default, on tap it will cycle between `snapPoints` but this can be overridden with `onPress`.
Extends [XStack](/docs/components/stacks).
### Sheet.ScrollView
Allows scrolling within Sheet. Extends [Scrollview](/docs/components/scroll-view).
#### useSheet
Use this to control the sheet programatically.
void',
description: `Control the position of the sheet.`,
},
]}
/>
## Notes
For Android you need to manually re-propagate any context when using `modal`. This is because React Native doesn't support portals yet.
## components/sheet/1.9.18
---
title: Sheet
description: A simple sheet component
name: sheet
component: Sheet
package: sheet
demoName: Sheet
---
# Sheet
A bottom sheet that slides up.
```tsx hero template=Sheet
```
### Anatomy
```tsx
import { Sheet } from 'tamagui' // or '@tamagui/sheet'
export default () => (
{/* ...inner contents */}
)
```
### API
#### <Sheet />
Contains every component for the sheet.
void',
description: `Called on change open, controlled or uncontrolled.`,
},
{
name: 'position',
type: 'number',
description: `Controlled position, set to an index of snapPoints.`,
},
{
name: 'defaultPosition',
type: 'number',
description: `Uncontrolled default position on mount.`,
},
{
name: 'snapPoints',
type: 'number[]',
default: `[80, 10]`,
description: `Array of numbers, 0-100 that corresponds to % of the screen it should take up. Should go from most visible to least visible in order. Use "open" prop for fully closed.`,
},
{
name: 'onPositionChange',
type: '(position: number) => void',
description: `Called on change position, controlled or uncontrolled.`,
},
{
name: 'dismissOnOverlayPress',
type: 'boolean',
default: 'true',
description: `Controls tapping on the overlay to close, defaults to true.`,
},
{
name: 'animationConfig',
type: 'Animated.SpringAnimationConfig',
default: 'true',
description: `Customize the spring used, passed to react-native Animated.spring().`,
},
{
name: 'disableDrag',
type: 'boolean',
description: `Disables all touch events to drag the sheet.`,
},
{
name: 'modal',
type: 'boolean',
description: `Renders sheet into the root of your app instead of inline.`,
},
{
name: 'dismissOnSnapToBottom',
type: 'boolean',
description: `Adds a snap point to the end of your snap points set to "0", that when snapped to will set open to false (uncontrolled) and call onOpenChange with false (controlled).`,
},
{
name: 'forceRemoveScrollEnabled',
type: 'boolean',
default: 'false',
description: `By default. Tamagui uses react-remove-scroll to prevent anything outside the sheet scrolling. This can cause some issues so you can override the behavior with this prop (either true or false).`,
},
{
name: 'portalProps',
type: 'Object',
description: `YStack props that can be passed to the Portal that sheet uses when in modal mode.`,
},
{
name: 'moveOnKeyboardChange',
type: 'boolean',
default: 'false',
description:
'Native-only flag that will make the sheet move up when the mobile keyboard opens so the focused input remains visible.',
},
]}
/>
#### <Overlay />
Displays behind Frame. Extends [YStack](/docs/components/stacks).
#### <Frame />
Contains the content. Extends [YStack](/docs/components/stacks).
#### <Handle />
Shows a handle above the frame by default, on tap it will cycle between `snapPoints` but this can be overridden with `onPress`.
Extends [XStack](/docs/components/stacks).
#### <Scrollview />
Allows scrolling within Sheet. Extends [Scrollview](/docs/components/scroll-view).
### Notes
For Android you need to manually re-propagate any context when using `modal`. This is because React Native doesn't support portals yet.
## components/sheet/1.59.0
---
title: Sheet
description: A simple sheet component
name: sheet
component: Sheet
package: sheet
demoName: Sheet
---
# Sheet
A bottom sheet that slides up
```tsx hero template=Sheet
```
## Installation
Sheet is already installed in `tamagui`, or you can install it independently:
```bash
npm install @tamagui/sheet
```
### PortalProvider
When rendering into root of app instead of inline, you'll first need to install the `@tamagui/portal` package:
```bash
npm install @tamagui/portal
```
Then add `PortalProvider` to the root of your app:
```tsx fileName="App.tsx"
import { PortalProvider } from '@tamagui/portal'
import YourApp from './components/YourApp'
function App() {
return (
)
}
export default App
```
## Anatomy
```tsx
import { Sheet } from 'tamagui' // or '@tamagui/sheet'
export default () => (
{/* ...inner contents */}
)
```
## Snap points
By default, snap points are treated as percentages.
```tsx
// 85% and 50%
```
The behavior of snap points can be changed by setting the `snapPointsMode` prop to any of these values:
- **percent** (default) - Snap points are percentages of the parent container or screen as numbers
- **constant** - Snap points are raw pixel values as numbers
- **fit** - The sheet is constrained to the content's natural height without the `snapPoints` prop
- **mixed** - Snap points can be either numbers (pixels), percentages as strings (ex: `"50%"`), or `"fit"` for fit behavior
Snap points should be ordered from largest to smallest (most visible to least visible). When using `mixed` mode with the `"fit"` as a snap point, it must be the first and largest snap point.
## Unstyled
Adding the `unstyled` prop to your Handle, Overlay or Frame will turn off the default styles allowing you to customize without having to override any of the built-in styling.
## Headless with `createSheet`
Using the `createSheet` export, you can create a fully custom sheet without using any of the default styles. This is similar to `unstyled`, but it lets you also control the `open` variant.
Here's an example:
```tsx
import { View, styled } from '@tamagui/core'
import { createSheet } from '@tamagui/sheet'
const Handle = styled(View, {
variants: {
open: {
true: {
opacity: 0.35,
},
false: {
opacity: 0.5,
},
},
} as const,
})
const Overlay = styled(View, {
variants: {
open: {
true: {
opacity: 1,
pointerEvents: 'auto',
},
false: {
opacity: 0,
pointerEvents: 'none',
},
},
} as const,
})
const Frame = styled(View, {
backgroundColor: 'background',
// can add open variant as well
})
export const Sheet = createSheet({
Frame,
Handle,
Overlay,
})
```
## Native support
Sheets now support rendering to a native iOS sheet, while still rendering any of your React Native content inside of them.
Because Metro doesn't support conditional imports and we don't want to make `tamagui` enforce installing native dependencies in order to get started, there's an install step:
```sh
yarn add react-native-ios-modal
pod install
# rebuild your app (expo ios, or use react-native cli)
```
And set it up as follows:
```tsx
import { Sheet, setupNativeSheet } from '@tamagui/sheet'
import { ModalView } from 'react-native-ios-modal'
setupNativeSheet('ios', ModalView)
export default (
{/* The rest of your sheet views, see Anatomy, example and props API */}
)
```
## API Reference
### Sheet
Contains every component for the sheet.
void',
description: `Called on change open, controlled or uncontrolled.`,
},
{
name: 'position',
type: 'number',
description: `Controlled position, set to an index of snapPoints.`,
},
{
name: 'defaultPosition',
type: 'number',
description: `Uncontrolled default position on mount.`,
},
{
name: 'snapPoints',
type: '(number | string)[] | undefined',
default: `[80]`,
description: `Array of values representing different sizes for the sheet to snap to. Not used in 'fit' mode. See docs above for usage information.`,
},
{
name: 'snapPointsMode',
type: '"percent" | "constant" | "fit" | "mixed"',
default: '"percent"',
description: `Alters the behavior of the 'snapPoints' prop. See docs above for usage information.`,
},
{
name: 'onPositionChange',
type: '(position: number) => void',
description: `Called on change position, controlled or uncontrolled.`,
},
{
name: 'dismissOnOverlayPress',
type: 'boolean',
default: 'true',
description: `Controls tapping on the overlay to close, defaults to true.`,
},
{
name: 'animationConfig',
type: 'Animated.SpringAnimationConfig',
default: 'true',
description: `Customize the spring used, passed to react-native Animated.spring().`,
},
{
name: 'native',
type: 'boolean | "ios"[]',
description: `(iOS only) Render to a native sheet, must install native dependency first.`,
},
{
name: 'disableDrag',
type: 'boolean',
description: `Disables all touch events to drag the sheet.`,
},
{
name: 'modal',
type: 'boolean',
description: `Renders sheet into the root of your app instead of inline.`,
},
{
name: 'dismissOnSnapToBottom',
type: 'boolean',
description: `Adds a snap point to the end of your snap points set to "0", that when snapped to will set open to false (uncontrolled) and call onOpenChange with false (controlled).`,
},
{
name: 'forceRemoveScrollEnabled',
type: 'boolean',
default: 'false',
description: `By default. Tamagui uses react-remove-scroll to prevent anything outside the sheet scrolling. This can cause some issues so you can override the behavior with this prop (either true or false).`,
},
{
name: 'portalProps',
type: 'Object',
description: `YStack props that can be passed to the Portal that sheet uses when in modal mode.`,
},
{
name: 'moveOnKeyboardChange',
type: 'boolean',
default: 'false',
description:
'Native-only flag that will make the sheet move up when the mobile keyboard opens so the focused input remains visible.',
},
{
name: 'unmountChildrenWhenHidden',
type: 'boolean',
default: 'false',
description:
'Flag to enable unmounting the children after the exit animation has completed.',
},
]}
/>
If using `modal={true}` (which is `true` by default), refer to the [PortalProvider
installation](/ui/sheet/1.59.0#portalprovider) for more information.
### Sheet.Overlay
Displays behind Frame. Extends [YStack](/docs/components/stacks).
### Sheet.Frame
Contains the content. Extends [YStack](/docs/components/stacks).
### Sheet.Handle
Shows a handle above the frame by default, on tap it will cycle between `snapPoints` but this can be overridden with `onPress`.
Extends [XStack](/docs/components/stacks).
### Sheet.ScrollView
Allows scrolling within Sheet. Extends [ScrollView](/docs/components/scroll-view).
#### useSheet
Use this to control the sheet programatically.
void',
description: `Control the position of the sheet.`,
},
]}
/>
## Notes
For Android you need to manually re-propagate any context when using `modal`. This is because React Native doesn't support portals yet.
## components/sheet/2.0.0
---
title: Sheet
description: A bottom sheet that animates
name: sheet
component: Sheet
package: sheet
demoName: Sheet
---
```tsx hero template=Sheet
```
Sheet is a bottom panel that slides up from the bottom of the screen, commonly used for mobile-friendly dialogs and action menus. It supports drag-to-dismiss, multiple snap points, and [automatically stacks](/ui/z-index) above other content.
## Installation
Sheet is already installed in `tamagui`, or you can install it independently:
```bash
npm install @tamagui/sheet
```
For native apps, we recommend [setting up native
portals](/docs/components/portal#native-portal-setup-recommended) to preserve React
context inside Sheet content.
## Anatomy
```tsx
import { Sheet } from 'tamagui' // or '@tamagui/sheet'
export default () => (
{/* ...inner contents */}
)
```
## API Reference
### Sheet
Contains every component for the sheet.
void',
description: `Called on change open, controlled or uncontrolled.`,
},
{
name: 'position',
type: 'number',
description: `Controlled position, set to an index of snapPoints.`,
},
{
name: 'defaultPosition',
type: 'number',
description: `Uncontrolled default position on mount.`,
},
{
name: 'snapPoints',
type: 'number[]',
default: `[80, 10]`,
description: `Array of numbers, 0-100 that corresponds to % of the screen it should take up. Should go from most visible to least visible in order. Use "open" prop for fully closed.`,
},
{
name: 'onPositionChange',
type: '(position: number) => void',
description: `Called on change position, controlled or uncontrolled.`,
},
{
name: 'dismissOnOverlayPress',
type: 'boolean',
default: 'true',
description: `Controls tapping on the overlay to close, defaults to true.`,
},
{
name: 'animationConfig',
type: 'Animated.SpringAnimationConfig',
default: 'true',
description: `Customize the spring used, passed to react-native Animated.spring().`,
},
{
name: 'native',
type: 'boolean | "ios"[]',
description: `(iOS only) Render to a native sheet, must install native dependency first.`,
},
{
name: 'disableDrag',
type: 'boolean',
description: `Disables all touch events to drag the sheet.`,
},
{
name: 'modal',
type: 'boolean',
description: `Renders sheet into the root of your app instead of inline.`,
},
{
name: 'dismissOnSnapToBottom',
type: 'boolean',
description: `Adds a snap point to the end of your snap points set to "0", that when snapped to will set open to false (uncontrolled) and call onOpenChange with false (controlled).`,
},
{
name: 'disableRemoveScroll',
type: 'boolean',
default: 'false',
description: `Disables the RemoveScroll behavior that prevents body scrolling while sheet is open. By default, RemoveScroll is enabled when the sheet is open and modal.`,
},
{
name: 'portalProps',
type: 'Object',
description: `YStack props that can be passed to the Portal that sheet uses when in modal mode.`,
},
{
name: 'moveOnKeyboardChange',
type: 'boolean',
default: 'false',
description:
'Native-only flag that will make the sheet move up when the mobile keyboard opens so the focused input remains visible.',
},
{
name: 'preferAdaptParentOpenState',
type: 'boolean',
default: 'false',
description: `By default Sheet will prefer the open prop over a parent component that is controlling it via Adapt. In general if you want to Adapt to a sheet, you'd leave the open prop undefined. If you'd like to have the parent override the prop you've set manually on Sheet, set this to true.`,
},
]}
/>
### Sheet.Overlay
Displays behind Frame. Extends [YStack](/docs/components/stacks).
### Sheet.Frame
Contains the content. Extends [YStack](/docs/components/stacks).
### Sheet.Handle
Shows a handle above the frame by default. On tap, it will cycle between
`snapPoints`, but this can be overridden with `onPress`.
Extends [XStack](/docs/components/stacks).
### Sheet.ScrollView
Allows scrolling within Sheet. Extends
[ScrollView](/docs/components/scroll-view).
## Native Gesture Handler Integration
For the best gesture experience on iOS and Android, Sheet supports optional integration with `react-native-gesture-handler`. This provides:
- **Smooth scroll-to-drag handoffs** - Seamlessly transition between scrolling content and dragging the sheet
- **No gesture conflicts** - Sheet and ScrollView gestures coordinate properly
- **Native-quality feel** - Matches the behavior of system sheets
#### Setup
1. Install `react-native-gesture-handler`:
```bash
npm install react-native-gesture-handler
```
2. Add the setup import to your app entry point (before any Tamagui imports):
```tsx
// App.tsx or index.js
import '@tamagui/native/setup-gesture-handler'
import { GestureHandlerRootView } from 'react-native-gesture-handler'
export default function App() {
return (
{/* Your app */}
)
}
```
That's it! Sheet will automatically detect and use the native gesture handler when available.
#### Using Sheet.ScrollView
When using scrollable content inside a Sheet, use `Sheet.ScrollView` for proper gesture coordination:
```tsx
{/* Scrollable content */}
```
This ensures:
- Scrolling up at the top of content works naturally
- Dragging down when scroll is at top drags the sheet
- Direction changes mid-gesture work smoothly
#### Without Gesture Handler
If you don't set up `react-native-gesture-handler`, Sheet falls back to React Native's built-in `PanResponder`. This works well for basic use cases but has some limitations on iOS where scroll and pan gestures can occasionally conflict.
## Notes
For Android you need to manually re-propagate any context when using `modal`.
This is because React Native doesn't support portals yet.
## Native Support
We've deprecated the `native` prop in favor of using Adapt.
## components/sheet/3.0.0
---
title: Sheet
description: A bottom sheet that animates
name: sheet
component: Sheet
package: sheet
demoName: Sheet
---
Sheet presents draggable content at configurable snap points and can host
adaptive content from other overlay components.
```tsx hero template=Sheet
```
Sheet is a bottom panel for mobile-friendly dialogs and action menus. It supports drag-to-dismiss, multiple snap points, and [automatically stacks](/ui/z-index) above other content.
## Installation
Sheet is already installed in `tamagui`, or you can install it independently:
```bash
npm install @tamagui/sheet
```
For native apps, we recommend [setting up native
portals](/ui/portal#native-portal-setup-recommended) to preserve React
context inside Sheet content.
## Anatomy
```tsx
import { Sheet } from 'tamagui' // or '@tamagui/sheet'
export default () => (
{/* ...inner contents */}
)
```
## API reference
### Sheet
Contains every component for the sheet.
void',
description: `Called on change open, controlled or uncontrolled.`,
},
{
name: 'position',
type: 'number',
description: `Controlled position, set to an index of snapPoints.`,
},
{
name: 'defaultPosition',
type: 'number',
description: `Uncontrolled default position on mount.`,
},
{
name: 'snapPoints',
type: '(string | number)[]',
default: `[80]`,
description: `Array of pixels or percents the sheet moves to when dragged. The first is the topmost and default when first opened. Use "open" prop for fully closed.`,
},
{
name: 'onPositionChange',
type: '(position: number) => void',
description: `Called on change position, controlled or uncontrolled.`,
},
{
name: 'dismissOnOverlayPress',
type: 'boolean',
default: 'true',
description: `Controls tapping on the overlay to close, defaults to true.`,
},
{
name: 'transitionConfig',
type: 'AnimatedNumberStrategy',
description: `Customize the animation strategy for position transitions.`,
},
{
name: 'native',
type: 'boolean | "ios"[]',
description: `(iOS only) Render with the system renderer registered through setupNativeSheet. UIKit does not expose continuous position, so Sheet.useAnimatedPosition() and onTransition are unavailable in this mode.`,
},
{
name: 'disableDrag',
type: 'boolean',
description: `Disables all touch events to drag the sheet.`,
},
{
name: 'modal',
type: 'boolean',
description: `Renders sheet into the root of your app instead of inline.`,
},
{
name: 'dismissOnSnapToBottom',
type: 'boolean',
description: `Adds a snap point to the end of your snap points set to "0", that when snapped to will set open to false (uncontrolled) and call onOpenChange with false (controlled).`,
},
{
name: 'disableRemoveScroll',
type: 'boolean',
default: 'false',
description: `Disables the RemoveScroll behavior that prevents body scrolling while sheet is open. By default, RemoveScroll is enabled when the sheet is open and modal.`,
},
{
name: 'portalProps',
type: 'PortalProps',
description: `Props passed to the Portal that sheet uses when in modal mode.`,
},
{
name: 'moveOnKeyboardChange',
type: 'boolean',
default: 'false',
description:
'Makes the sheet move up when the mobile keyboard opens so the focused input remains visible. Works on native and on mobile web.',
},
{
name: 'preferAdaptParentOpenState',
type: 'boolean',
default: 'false',
description: `By default Sheet will prefer the open prop over a parent component that is controlling it via Adapt. In general if you want to Adapt to a sheet, you'd leave the open prop undefined. If you'd like to have the parent override the prop you've set manually on Sheet, set this to true.`,
},
{
name: 'onTransition',
type: `(e: { phase: 'start' | 'end', cause: 'open' | 'close' | 'snap', position: number, finished?: boolean }) => void`,
description: `Fires at the start and end of the custom sheet's position transition. cause is "open" when moving from closed, "close" when moving off screen, and "snap" when moving between snap points while open. On the end phase, finished is false when the transition was interrupted (e.g. a close canceled by a re-open). position is the resolved translateY target in px from the top of the screen. Replaces the old onAnimationComplete prop. The native iOS system sheet does not expose this lifecycle.`,
},
{
name: 'disableHideWhenClosed',
type: 'boolean',
default: 'false',
description: `By default a fully-closed sheet wrapper is hidden with display: "none". Set this to keep the closed wrapper laid out, for example so native visual effects can initialize below it. pointerEvents still gates interaction while closed.`,
},
{
name: 'unmountChildrenWhenHidden',
type: 'boolean',
default: 'false',
description: `(experimental) Remove the children while the sheet is fully closed to save some rendering cost. Can interfere with animations.`,
},
]}
/>
### Sheet.Overlay
Displays behind the sheet content. Extends [YStack](/ui/stacks).
`Sheet.Overlay` must be a direct child of `Sheet`. It renders outside the animated
content region so it stays fixed while the container moves.
In v3 the sheet no longer fades anything for you. `Sheet.Overlay` has no baked-in
opacity, so you drive its fade yourself. See [Overlay fades](#overlay-fades) for
the two supported patterns.
### Sheet.Container
Contains the sheet content and layout props. Extends
[YStack](/ui/stacks).
### Sheet.Background
The themed sheet surface. Extends [YStack](/ui/stacks).
Place it as the first child of `Sheet.Container`. By default it fills the
container and extends past the bottom edge so spring overshoot does not reveal
the page below the sheet. Put visual surface props like `bg`, `borderRadius`,
and `shadow*` on `Sheet.Background`; keep layout props like
`padding`, `gap`, and `maxHeight` on `Sheet.Container`.
### Sheet.Handle
Shows a handle above the container by default. On tap, it will cycle between
`snapPoints`, but this can be overridden with `onPress`.
Extends [XStack](/ui/stacks).
In v3 the Handle ships no opacity of its own. The idle dim and the fade-in on
open are aesthetics, so they live in the copied skin rather than the behavior
package. Set `opacity` (and an `open`-driven opacity if you want it) on your own
`Sheet.Handle`.
### Sheet.ScrollView
Allows scrolling within Sheet. Extends
[ScrollView](/ui/scroll-view).
## Sheet.useAnimatedPosition
`Sheet.useAnimatedPosition()` returns the sheet's live animated position so you
can drive effects that track the drag on the UI thread. Call it inside a `Sheet`;
it throws with a clear message outside that scope.
```tsx
const { value, screenSize, frameSize, snapOffsets, minY } =
Sheet.useAnimatedPosition()
```
- `value` is the exact `UniversalAnimatedNumber` driving the frame's translateY,
in px from the top of the screen. Feed it to
[useAnimatedNumberStyle](/docs/core/animations#imperative-animation-hooks).
- `screenSize` is the height of the viewport the sheet positions against.
- `frameSize` is the measured height of the sheet frame.
- `snapOffsets` are the resolved translateY positions, in the same order as
`snapPoints`.
- `minY` is the top-most (fully open) position.
These are enough to compute any progress mapping inside a `getStyle` worklet.
There is no separate derived progress value; the recipe lives in docs and the
canonical skin.
`Sheet.useAnimatedPosition()` and the Sheet `onTransition` callback are available
on Tamagui's custom Sheet. The native iOS system sheet selected by the `native`
prop is driven by UIKit, which does not expose a continuous position. Remove the
`native` prop when you need position-linked effects or transition lifecycle events.
## Overlay fades
The sheet does not fade anything for you, so an overlay fade is your code. There
are two supported patterns.
**1. Presence fade** (animates on open and close only). Put enter/exit styles on
`Sheet.Overlay`:
```tsx
```
**2. Drag-linked fade** (tracks the finger). Read the animated position and map
it to opacity with `useAnimatedNumberStyle`:
```tsx
import type { Animated } from 'react-native'
import {
Sheet,
View as TamaguiView,
useAnimationDriver,
useAnimatedNumberStyle,
} from 'tamagui'
// drag-linked overlay fade built entirely on the public hooks:
// Sheet.useAnimatedPosition() gives the live translateY, useAnimatedNumberStyle
// maps it to an opacity worklet. rendered through the driver's animated view so
// it works on every driver (css re-renders + DOM transition, motion/reanimated
// run on their own value).
function DragLinkedBackdrop() {
const animationDriver = useAnimationDriver()
const AnimatedView = (animationDriver.View ?? TamaguiView) as typeof Animated.View
const { value, screenSize } = Sheet.useAnimatedPosition()
const style = useAnimatedNumberStyle(value, (y: number) => {
'worklet'
return { opacity: Math.max(0, 0.6 * (1 - y / screenSize)) }
})
return (
)
}
```
Keep `Sheet.Overlay` as the direct child of `Sheet`, then render the hook-driven
backdrop inside it. The driver's animated view makes the worklet-driven style
apply on every driver.
## Native system renderers
Register a renderer at app startup to use system presentation with ``.
Without a registered renderer, Sheet uses its custom implementation. Tamagui
owns open and position state, context, Adapt, and the lifetime of one content
tree. The renderer owns platform presentation, authored detents, gestures,
keyboard handling, and physical dismissal.
```tsx
import { setupNativeSheet, type NativeSheetRendererProps } from '@tamagui/sheet'
setupNativeSheet('ios', NativeSheetRenderer)
```
`NativeSheetRendererProps` supplies the resolved `open` value, `onOpenChange`,
`position`, `onPositionChange`, and normalized `snapPoints`. A point is
`{ type: 'percent', value: number }`, `{ type: 'height', value: number }`, or
`{ type: 'fit' }`. A dismissal-only bottom point is removed before forwarding.
The other Sheet props, content, and host ref reach the renderer unchanged.
Report interaction requests through `onOpenChange` and `onPositionChange`.
Controlled parents may decline either request; continue presenting their
accepted values. Call `onDismiss` only after the original presentation is
physically removed following an accepted close. A reopen interrupts that
completion. Tamagui then releases Adapt presence and, when requested, unmounts
hidden children. Do not mount a second copy of the content beside the native
presentation.
Render `children` once inside the platform host. `Sheet.Container` measures
content without custom sheet positioning, `Sheet.ScrollView` forwards to the
native React Native ScrollView, and the system supplies the background,
overlay, and grabber. `Sheet.Background`, `Sheet.Overlay`, and `Sheet.Handle`
render nothing in this mode. Their styles and press handlers do not customize
the system chrome. Use the renderer's presentation callbacks for dismissal
policy.
The registration accepts a React component. An imperative implementation keeps
a controller ref inside that component and drives its presentation methods from
the resolved `open` value. Use a layout effect when these commands synchronize
the host's controlled value, so its acknowledgment observes the parent's accepted
state. Forward interaction requests and physical completion separately. Preserve
the native library's content container and lazy mounting behavior, and check
parent-declined close and reopen during dismissal against its actual events.
The previous `setupNativeSheet('ios', modalModule)` object registration is
replaced by this component contract; no modal dependency is built into Tamagui.
Native renderers decide which Sheet options their platform can represent.
Reject unsupported combinations before presenting. A native iOS system sheet
does not expose continuous animated position or custom transition events.
`moveOnKeyboardChange` does not install Tamagui's custom keyboard driver in a
native renderer; its host handles the keyboard. Keep `native` off for custom
sheet animation and gesture behavior.
## Native gesture handler integration
For the best gesture experience on iOS and Android, Sheet supports optional integration with `react-native-gesture-handler`. This provides:
- **Smooth scroll-to-drag handoffs** - transition between scrolling content and dragging the sheet
- **No gesture conflicts** - Sheet and ScrollView gestures coordinate properly
- **Native-quality feel** - matches the behavior of system sheets
#### Setup
1. Install `react-native-gesture-handler`:
```bash
npm install react-native-gesture-handler
```
2. Add the setup import to your app entry point (before any Tamagui imports):
```tsx
// App.tsx or index.js
import '@tamagui/native/setup-gesture-handler'
import { GestureHandlerRootView } from 'react-native-gesture-handler'
export default function App() {
return (
{/* Your app */}
)
}
```
That's it! Sheet will automatically detect and use the native gesture handler when available.
#### Using Sheet.ScrollView
When using scrollable content inside a Sheet, use `Sheet.ScrollView` for proper gesture coordination:
```tsx
{/* Scrollable content */}
```
This ensures:
- Scrolling up at the top of content works naturally
- Dragging down when scroll is at top drags the sheet
- Direction changes mid-gesture work smoothly
#### Without gesture handler
If you don't set up `react-native-gesture-handler`, Sheet falls back to React Native's built-in `PanResponder`. This works well for basic use cases but has some limitations on iOS where scroll and pan gestures can occasionally conflict.
## Notes
A fully-closed sheet is hidden with `display: 'none'` rather than by fading to
transparent. Use [`disableHideWhenClosed`](#sheet) if you need the closed
wrapper to stay laid out. `pointerEvents` still gates interaction while closed,
and `unmountChildrenWhenHidden` keys off the same closed state.
For Android you need to manually re-propagate any context when using `modal`.
This is because React Native doesn't support portals yet.
## components/sheet/1.123.18
---
title: Sheet
description: A bottom sheet that animates.
name: sheet
component: Sheet
package: sheet
demoName: Sheet
---
```tsx hero template=Sheet
```
## Installation
Sheet is already installed in `tamagui`, or you can install it independently:
```bash
npm install @tamagui/sheet
```
### Anatomy
```tsx
import { Sheet } from 'tamagui' // or '@tamagui/sheet'
export default () => (
{/* ...inner contents */}
)
```
### API
#### <Sheet />
Contains every component for the sheet.
void',
description: `Called on change open, controlled or uncontrolled.`,
},
{
name: 'position',
type: 'number',
description: `Controlled position, set to an index of snapPoints.`,
},
{
name: 'defaultPosition',
type: 'number',
description: `Uncontrolled default position on mount.`,
},
{
name: 'snapPoints',
type: 'number[]',
default: `[80, 10]`,
description: `Array of numbers, 0-100 that corresponds to % of the screen it should take up. Should go from most visible to least visible in order. Use "open" prop for fully closed.`,
},
{
name: 'onPositionChange',
type: '(position: number) => void',
description: `Called on change position, controlled or uncontrolled.`,
},
{
name: 'dismissOnOverlayPress',
type: 'boolean',
default: 'true',
description: `Controls tapping on the overlay to close, defaults to true.`,
},
{
name: 'animationConfig',
type: 'Animated.SpringAnimationConfig',
default: 'true',
description: `Customize the spring used, passed to react-native Animated.spring().`,
},
{
name: 'native',
type: 'boolean | "ios"[]',
description: `(iOS only) Render to a native sheet, must install native dependency first.`,
},
{
name: 'disableDrag',
type: 'boolean',
description: `Disables all touch events to drag the sheet.`,
},
{
name: 'modal',
type: 'boolean',
description: `Renders sheet into the root of your app instead of inline.`,
},
{
name: 'dismissOnSnapToBottom',
type: 'boolean',
description: `Adds a snap point to the end of your snap points set to "0", that when snapped to will set open to false (uncontrolled) and call onOpenChange with false (controlled).`,
},
{
name: 'forceRemoveScrollEnabled',
type: 'boolean',
default: 'false',
description: `By default. Tamagui uses react-remove-scroll to prevent anything outside the sheet scrolling. This can cause some issues so you can override the behavior with this prop (either true or false).`,
},
{
name: 'portalProps',
type: 'Object',
description: `YStack props that can be passed to the Portal that sheet uses when in modal mode.`,
},
{
name: 'moveOnKeyboardChange',
type: 'boolean',
default: 'false',
description:
'Native-only flag that will make the sheet move up when the mobile keyboard opens so the focused input remains visible.',
},
]}
/>
#### <Overlay />
Displays behind Frame. Extends [YStack](/docs/components/stacks).
#### <Frame />
Contains the content. Extends [YStack](/docs/components/stacks).
#### <Handle />
Shows a handle above the frame by default, on tap it will cycle between `snapPoints` but this can be overridden with `onPress`.
Extends [XStack](/docs/components/stacks).
#### <Scrollview />
Allows scrolling within Sheet. Extends [Scrollview](/docs/components/scroll-view).
### Notes
For Android you need to manually re-propagate any context when using `modal`. This is because React Native doesn't support portals yet.
### Native support
We've deprecated the `native` prop in favor of using Adapt.
## components/sheet/1.130.0
---
title: Sheet
description: A bottom sheet that animates.
name: sheet
component: Sheet
package: sheet
demoName: Sheet
---
```tsx hero template=Sheet
```
## Installation
Sheet is already installed in `tamagui`, or you can install it independently:
```bash
npm install @tamagui/sheet
```
### Anatomy
```tsx
import { Sheet } from 'tamagui' // or '@tamagui/sheet'
export default () => (
{/* ...inner contents */}
)
```
### API
#### <Sheet />
Contains every component for the sheet.
void',
description: `Called on change open, controlled or uncontrolled.`,
},
{
name: 'position',
type: 'number',
description: `Controlled position, set to an index of snapPoints.`,
},
{
name: 'defaultPosition',
type: 'number',
description: `Uncontrolled default position on mount.`,
},
{
name: 'snapPoints',
type: 'number[]',
default: `[80, 10]`,
description: `Array of numbers, 0-100 that corresponds to % of the screen it should take up. Should go from most visible to least visible in order. Use "open" prop for fully closed.`,
},
{
name: 'onPositionChange',
type: '(position: number) => void',
description: `Called on change position, controlled or uncontrolled.`,
},
{
name: 'dismissOnOverlayPress',
type: 'boolean',
default: 'true',
description: `Controls tapping on the overlay to close, defaults to true.`,
},
{
name: 'animationConfig',
type: 'Animated.SpringAnimationConfig',
default: 'true',
description: `Customize the spring used, passed to react-native Animated.spring().`,
},
{
name: 'native',
type: 'boolean | "ios"[]',
description: `(iOS only) Render to a native sheet, must install native dependency first.`,
},
{
name: 'disableDrag',
type: 'boolean',
description: `Disables all touch events to drag the sheet.`,
},
{
name: 'modal',
type: 'boolean',
description: `Renders sheet into the root of your app instead of inline.`,
},
{
name: 'dismissOnSnapToBottom',
type: 'boolean',
description: `Adds a snap point to the end of your snap points set to "0", that when snapped to will set open to false (uncontrolled) and call onOpenChange with false (controlled).`,
},
{
name: 'forceRemoveScrollEnabled',
type: 'boolean',
default: 'false',
description: `By default. Tamagui uses react-remove-scroll to prevent anything outside the sheet scrolling. This can cause some issues so you can override the behavior with this prop (either true or false).`,
},
{
name: 'portalProps',
type: 'Object',
description: `YStack props that can be passed to the Portal that sheet uses when in modal mode.`,
},
{
name: 'moveOnKeyboardChange',
type: 'boolean',
default: 'false',
description:
'Native-only flag that will make the sheet move up when the mobile keyboard opens so the focused input remains visible.',
},
{
name: 'preferAdaptParentOpenState',
type: 'boolean',
default: 'false',
description: `By default Sheet will prefer the open prop over a parent component that is controlling it via Adapt. In general if you want to Adapt to a sheet, you'd leave the open prop undefined. If you'd like to have the parent override the prop you've set manually on Sheet, set this to true.`,
},
]}
/>
#### <Overlay />
Displays behind Frame. Extends [YStack](/docs/components/stacks).
#### <Frame />
Contains the content. Extends [YStack](/docs/components/stacks).
#### <Handle />
Shows a handle above the frame by default, on tap it will cycle between
`snapPoints` but this can be overridden with `onPress`.
Extends [XStack](/docs/components/stacks).
#### <ScrollView />
Allows scrolling within Sheet. Extends
[Scrollview](/docs/components/scroll-view).
### Notes
For Android you need to manually re-propagate any context when using `modal`.
This is because React Native doesn't support portals yet.
### Native support
We've deprecated the `native` prop in favor of using Adapt.
## components/card/1.0.0
---
title: Card
description: Sizable, themeable cards with a flexible API.
name: card
component: Card
package: card
demoName: Card
---
```tsx hero template=Card
```
## Installation
Card is already installed in `tamagui`, or you can install it independently:
```bash
npm install @tamagui/card
```
## Anatomy
```tsx
import { Card } from 'tamagui' // or '@tamagui/card'
export default () => (
{/* any other components */}
)
```
## API Reference
### Card
Frame of the card, extends [ThemeableStack props](/docs/components/stacks#themeablestack), adding:
### Card.Header
Extends [ThemeableStack](/docs/components/stacks#themeablestack), adding:
### Card.Footer
Extends [ThemeableStack](/docs/components/stacks#themeablestack), adding:
### Card.Background
Extends [YStack](/docs/components/stacks), set to `position="absolute"` and `inset={0}` with zIndex below Header/Footer.
## components/card/2.0.0
---
title: Card
description: Sizable, themeable cards with a flexible API
name: card
component: Card
package: card
demoName: Card
---
```tsx hero template=Card
```
## Installation
Card is already installed in `tamagui`, or you can install it independently:
```bash
npm install @tamagui/card
```
## Anatomy
```tsx
import { Card } from 'tamagui' // or '@tamagui/card'
export default () => (
{/* any other components */}
)
```
## API Reference
### Card
Frame of the card, extends [ThemeableStack props](/docs/components/stacks#themeablestack), adding:
### Card.Header
Extends [ThemeableStack](/docs/components/stacks#themeablestack), adding:
### Card.Footer
Extends [ThemeableStack](/docs/components/stacks#themeablestack), adding:
### Card.Background
Extends [YStack](/docs/components/stacks), set to `position="absolute"` and `inset={0}` with `zIndex` below Header/Footer.
## components/card/3.0.0
---
title: Card
description: Sizable, themeable cards with a flexible API
name: card
component: Card
package: card
demoName: Card
---
Card composes headers, footers, backgrounds, and content into a sizable,
theme-aware container.
```tsx hero template=Card
```
## Installation
Card is already installed in `tamagui`, or you can install it independently:
```bash
npm install @tamagui/card
```
## Anatomy
```tsx
import { Card } from 'tamagui' // or '@tamagui/card'
export default () => (
{/* any other components */}
)
```
## API reference
### Card
Frame of the card, extends [YStack](/ui/stacks), adding:
### Card.Header
Extends [YStack](/ui/stacks), adding:
### Card.Footer
Extends [YStack](/ui/stacks), adding:
### Card.Background
Extends [YStack](/ui/stacks), set to `position="absolute"` and `inset={0}` with `zIndex` below Header/Footer.
## components/tabs/1.7.0
---
title: Tabs
description: Use in pages to manage sub-pages.
name: tabs
component: Tabs
package: tabs
demoName: Tabs
---
```tsx hero template=Tabs
```
Note: Tabs have landed on v1.7 and not fully ready for runtime. Send us your feedback and we'll address it. We're marking it Beta a such as there may be hopefully minimal breaking changes as we get feedback on the API.
## Usage
```tsx
import { SizableText, Tabs } from 'tamagui'
export default () => (
Tab 1Tab 2
Tab 1
Tab 2
)
```
## API Reference
### Tabs
Root tabs component. Extends `View`. Passing the `size` prop to this component will have effect on the descendants.
void',
description: `A function called when a new tab is selected`,
},
{
name: 'orientation',
type: '"horizontal" | "vertical"',
default: 'horizontal',
description: `The orientation the tabs are layed out`,
},
{
name: 'dir',
type: '"ltr" | "rtl"',
description: `The direction of navigation between toolbar items`,
},
{
name: 'activationMode',
type: '"manual" | "automatic"',
default: 'automatic',
description: `Whether or not a tab is activated automatically or manually. Not applicable on mobile`,
},
]}
/>
### Tabs.List
Container for the trigger buttons. Supports scrolling by extending [Group](/docs/components/group). You can disable passing border radius to children by passing `disablePassBorderRadius`.
### Tabs.Trigger
Extends [Button](/docs/components/button), adding:
void`,
description: `Used for making custom indicators when trigger interacted with`,
},
{
name: 'unstyled',
type: `boolean`,
description: `When true, remove all default tamagui styling`,
},
]}
/>
### Tabs.Content
Where each tab's content will be shown. Extends [ThemeableStack](/docs/components/stacks#themeablestack), adding:
## Examples
### Animations
Here is a demo with more advanced animations using [AnimatePresence](/docs/core/animations#animatepresence-and-exitstyle) and [Trigger](#trigger)'s `onInteraction` prop.
```tsx hero template=TabsAdvanced
```
## components/tabs/2.0.0
---
title: Tabs
description: Use in pages to manage sub-pages
name: tabs
component: Tabs
package: tabs
demoName: Tabs
---
```tsx hero template=Tabs
```
## Installation
Tabs is already installed in `tamagui`, or you can install it independently:
```bash
npm install @tamagui/tabs
```
## Usage
```tsx
import { SizableText, Tabs } from 'tamagui'
export default () => (
Tab 1Tab 2
Tab 1
Tab 2
)
```
### Headless Usage
To use headless tabs without `@tamagui/core`, install `@tamagui/tabs-headless`:
```bash
npm install @tamagui/tabs-headless
```
The `useTabs` hook provides all the state and accessibility props needed to build custom tabs with any styling solution:
```tsx
import { useTabs } from '@tamagui/tabs-headless'
function MyTabs() {
const { tabsProps, listProps, getTabProps, getContentProps, value } = useTabs({
defaultValue: 'tab1',
orientation: 'horizontal',
})
return (
Tab 1Tab 2
{value === 'tab1' &&
Content 1
}
{value === 'tab2' &&
Content 2
}
)
}
```
## API Reference
### Tabs
Root tabs component. Extends View. Passing the `size` prop to this component will affect the descendants.
void',
description: `A function called when a new tab is selected.`,
},
{
name: 'orientation',
type: '"horizontal" | "vertical"',
default: 'horizontal',
description: `The orientation the tabs are laid out in.`,
},
{
name: 'dir',
type: '"ltr" | "rtl"',
description: `The direction of navigation between toolbar items.`,
},
{
name: 'activationMode',
type: '"manual" | "automatic"',
default: 'automatic',
description: `Whether a tab is activated automatically (on focus) or manually (on click/enter). Automatic activation is web-only; native always activates manually.`,
},
{
name: 'size',
type: "'xs' | 'sm' | 'md' | 'lg' | 'xl' | SizeTokens",
default: 'true',
description: `Sets the size of the tab triggers. Passed down via context to all Tabs.Tab children.`,
},
{
name: 'loop',
type: 'boolean',
default: 'true',
description: `Whether keyboard navigation should loop from last to first and vice versa.`,
},
]}
/>
### Tabs.List
Container for the trigger buttons. Supports scrolling by extending [Group](/docs/components/group). You can disable passing border radius to children by passing `disablePassBorderRadius`.
Since Tabs.List extends Group, the same limitation applies: automatic border radius
detection only works when `Tabs.Tab` is a **direct child** of `Tabs.List`. If you wrap
tabs in custom components, see the [Group docs on nested
items](/docs/components/group#custom-components--nested-items) for workarounds.
### Tabs.Tab
Extends [YStack](/ui/stacks), adding:
void`,
description: `Used for making custom indicators when trigger is interacted with.`,
},
{
name: 'disabled',
type: `boolean`,
description: `Whether the tab is disabled.`,
},
{
name: 'unstyled',
type: `boolean`,
description: `When true, removes all default Tamagui styling.`,
},
{
name: 'activeStyle',
type: `StylePiece`,
description: `Styles to apply when the tab is selected. Accepts a style() piece.`,
},
{
name: 'activeTheme',
type: `string | null`,
description: `Theme to apply when the tab is selected. Set to null for no theme change.`,
},
]}
/>
### Tabs.Content
Where each tab's content will be shown. Extends [YStack](/ui/stacks), adding:
## Headless Props
### useTabs Return Value
| Property | Type | Description |
| ----------------- | ----------------------------------------------- | ------------------------------------- |
| `value` | `string` | The currently selected tab value |
| `setValue` | `(value: string) => void` | Function to change the selected tab |
| `direction` | `'ltr' \| 'rtl'` | The resolved text direction |
| `tabsProps` | `object` | Props to spread on the tabs container |
| `listProps` | `object` | Props to spread on the tab list |
| `getTabProps` | `(value: string, disabled?: boolean) => object` | Get props for a tab trigger |
| `getContentProps` | `(value: string) => object` | Get props for a tab content panel |
| `contextValue` | `TabsContextValue` | Context value for component-based API |
### useTab
Hook for individual tab triggers when using the component-based API.
```tsx
const { isSelected, tabProps } = useTab({
value: 'tab1',
disabled: false,
onPress: () => {},
onKeyDown: () => {},
onFocus: () => {},
})
```
### useTabContent
Hook for tab content panels when using the component-based API.
```tsx
const { isSelected, shouldMount, contentProps } = useTabContent({
value: 'tab1',
forceMount: false,
})
```
## Examples
### Animations
Here is a demo with more advanced animations using [AnimatePresence](/docs/core/animations#animatepresence-and-exitstyle) and [Tab](#tabstab)'s `onInteraction` prop.
```tsx hero template=TabsAdvanced
```
## components/tabs/3.0.0
---
title: Tabs
description: Use in pages to manage sub-pages
name: tabs
component: Tabs
package: tabs
demoName: Tabs
---
Tabs organizes related panels behind a controlled or uncontrolled,
keyboard-navigable list of triggers.
```tsx hero template=Tabs
```
## Installation
Tabs is already installed in `tamagui`, or you can install it independently:
```bash
npm install @tamagui/tabs
```
## Usage
```tsx
import { SizableText, Tabs } from 'tamagui'
export default () => (
Tab 1Tab 2
Tab 1
Tab 2
)
```
### Headless usage
To use headless tabs without `@tamagui/core`, install `@tamagui/tabs-headless`:
```bash
npm install @tamagui/tabs-headless
```
The `useTabs` hook provides all the state and accessibility props needed to build custom tabs with any styling solution:
```tsx
import { useTabs } from '@tamagui/tabs-headless'
function MyTabs() {
const { tabsProps, listProps, getTabProps, getContentProps, value } = useTabs({
defaultValue: 'tab1',
orientation: 'horizontal',
})
return (
Tab 1Tab 2
{value === 'tab1' &&
Content 1
}
{value === 'tab2' &&
Content 2
}
)
}
```
## API reference
### Tabs
Root tabs component. Extends View. Passing the `size` prop to this component will affect the descendants.
void',
description: `A function called when a new tab is selected.`,
},
{
name: 'orientation',
type: '"horizontal" | "vertical"',
default: 'horizontal',
description: `The orientation the tabs are laid out in.`,
},
{
name: 'dir',
type: '"ltr" | "rtl"',
description: `The direction of navigation between toolbar items.`,
},
{
name: 'activationMode',
type: '"manual" | "automatic"',
default: 'automatic',
description: `Whether a tab is activated automatically (on focus) or manually (on click/enter). Automatic activation is web-only; native always activates manually.`,
},
{
name: 'size',
type: "'xs' | 'sm' | 'md' | 'lg' | 'xl'",
default: 'md',
description: `Sets the size of the tab triggers. Passed down via context to all Tabs.Tab children.`,
},
{
name: 'loop',
type: 'boolean',
default: 'true',
description: `Whether keyboard navigation should loop from last to first and vice versa.`,
},
]}
/>
### Tabs.List
Container for the trigger buttons. Supports scrolling by extending [Group](/ui/group).
Since Tabs.List extends Group, the same limitation applies: automatic border radius
detection only works when `Tabs.Tab` is a **direct child** of `Tabs.List`. If you wrap
tabs in custom components, see the [Group docs on nested
items](/ui/group#custom-components--nested-items) for workarounds.
### Tabs.Tab
Extends [YStack](/ui/stacks), adding:
void`,
description: `Used for making custom indicators when trigger is interacted with.`,
},
{
name: 'disabled',
type: `boolean`,
description: `Whether the tab is disabled.`,
},
{
name: 'unstyled',
type: `boolean`,
description: `When true, removes all default Tamagui styling.`,
},
{
name: 'activeStyle',
type: `StylePiece`,
description: `Styles to apply when the tab is selected. Accepts a style() piece.`,
},
{
name: 'activeTheme',
type: `string | null`,
description: `Theme to apply when the tab is selected. Set to null for no theme change.`,
},
]}
/>
### Tabs.Content
Where each tab's content will be shown. Extends [YStack](/ui/stacks), adding:
## Headless props
### useTabs return value
| Property | Type | Description |
| ----------------- | ----------------------------------------------- | ------------------------------------- |
| `value` | `string` | The currently selected tab value |
| `setValue` | `(value: string) => void` | Function to change the selected tab |
| `direction` | `'ltr' \| 'rtl'` | The resolved text direction |
| `tabsProps` | `object` | Props to spread on the tabs container |
| `listProps` | `object` | Props to spread on the tab list |
| `getTabProps` | `(value: string, disabled?: boolean) => object` | Get props for a tab trigger |
| `getContentProps` | `(value: string) => object` | Get props for a tab content panel |
| `contextValue` | `TabsContextValue` | Context value for component-based API |
### useTab
Hook for individual tab triggers when using the component-based API.
```tsx
const { isSelected, tabProps } = useTab({
value: 'tab1',
disabled: false,
onPress: () => {},
onKeyDown: () => {},
onFocus: () => {},
})
```
### useTabContent
Hook for tab content panels when using the component-based API.
```tsx
const { isSelected, shouldMount, contentProps } = useTabContent({
value: 'tab1',
forceMount: false,
})
```
## Examples
### Animations
Here is a demo with more advanced animations using [AnimatePresence](/docs/core/animations#enterexit-transitions) and [Tabs.Tab](#tabstab)'s `onInteraction` prop.
```tsx hero template=TabsAdvanced
```
## components/tabs/1.125.35
---
title: Tabs
description: Use in pages to manage sub-pages.
name: tabs
component: Tabs
package: tabs
demoName: Tabs
---
StyledHeadless
```tsx hero template=Tabs
```
Note: Tabs have landed on v1.7 and not fully ready for runtime. Send us your feedback and we'll address it. We're marking it Beta a such as there may be hopefully minimal breaking changes as we get feedback on the API.
## Installation
Tabs is already installed in `tamagui`, or you can install it independently:
```bash
npm install @tamagui/tabs
```
## Usage
```tsx
import { SizableText, Tabs } from 'tamagui'
export default () => (
Tab 1Tab 2
Tab 1
Tab 2
)
```
## API Reference
### Tabs
Root tabs component. Extends `View`. Passing the `size` prop to this component will have effect on the descendants.
void',
description: `A function called when a new tab is selected`,
},
{
name: 'orientation',
type: '"horizontal" | "vertical"',
default: 'horizontal',
description: `The orientation the tabs are laid out.`,
},
{
name: 'dir',
type: '"ltr" | "rtl"',
description: `The direction of navigation between toolbar items`,
},
{
name: 'activationMode',
type: '"manual" | "automatic"',
default: 'automatic',
description: `Whether or not a tab is activated automatically or manually. Not applicable on mobile`,
},
]}
/>
### Tabs.List
Container for the trigger buttons. Supports scrolling by extending [Group](/docs/components/group). You can disable passing border radius to children by passing `disablePassBorderRadius`.
### Tabs.Tab
The clickable tab button that activates its corresponding content. Extends [ThemeableStack](/docs/components/stacks#themeablestack), adding:
void`,
description: `Used for making custom indicators when tab is interacted with. InteractionType is 'select' | 'focus' | 'hover'`,
},
{
name: 'disabled',
type: `boolean`,
description: `When true, prevents user interaction with the tab`,
},
{
name: 'disableActiveTheme',
type: `boolean`,
description: `When true, disables applying the 'active' theme when the tab is selected`,
},
{
name: 'unstyled',
type: `boolean`,
description: `When true, remove all default tamagui styling`,
},
]}
/>
### Tabs.Trigger
Extends [ThemeableStack](/docs/components/stacks#themeablestack), adding:
void`,
description: `Used for making custom indicators when trigger interacted with`,
},
{
name: 'unstyled',
type: `boolean`,
description: `When true, remove all default tamagui styling`,
},
]}
/>
### Tabs.Content
Where each tab's content will be shown. Extends [ThemeableStack](/docs/components/stacks#themeablestack), adding:
## Examples
### Animations
Here is a demo with more advanced animations using [AnimatePresence](/docs/core/animations#animatepresence-and-exitstyle) and [Trigger](#trigger)'s `onInteraction` prop.
```tsx hero template=TabsAdvanced
```
```tsx hero template=TabsCustom
```
### Customization Options
When using `createTabs`, you need to provide three styled components:
- `TabsFrame`: The root container component
- `TabFrame`: The tab trigger component
- `ContentFrame`: The content container component
The created tabs component will maintain all the accessibility features and keyboard navigation while allowing you to have complete control over the visual presentation.
The headless API gives you the foundation to build completely custom tab interfaces while maintaining accessibility and interaction patterns. You can integrate it with your design system and add any custom behaviors you need.
## components/toggle-group/1.10.0
---
title: ToggleGroup
description: Two-state buttons that can be toggled on or off.
name: toggleGroup
component: ToggleGroup
package: toggle-group
demoName: ToggleGroup
---
```tsx hero template=ToggleGroup
```
## Installation
ToggleGroup is already installed in `tamagui`, or you can install it independently:
```bash
npm install @tamagui/toggle-group
```
## Usage
```tsx
import { ToggleGroup } from 'tamagui'
export default () => {
return (
)
}
```
## API Reference
### ToggleGroup
`ToggleGroup` extends the [Group](/docs/components/group) component. You can disable passing border radius to children by passing `disablePassBorderRadius`. plus:
void',
description: `Event handler called when the pressed state of an item changes and type is "multiple".`,
},
{
name: 'loop',
type: 'boolean',
description: `Whether or not to loop over after reaching the end or start of the items. Used mainly for managing keyboard navigation.`,
default: `true`,
},
{
name: 'disableDeactivation',
type: 'boolean',
description: `Won't let the user turn the active item off. Only applied to single toggle group.`,
default: 'false',
},
{
name: 'unstyled',
type: 'boolean',
default: 'false',
description: `When true, remove all default tamagui styling.`,
},
{
name: 'sizeAdjust',
type: 'number',
description: `Adjust the component's size scaling by this number.`,
},
]}
/>
### ToggleGroup.Item
`ToggleGroup.Item` extend View inheriting all the [Tamagui standard props](/docs/intro/props), plus:
When it is active, it will receive an `active` prop set to true. This means you can customize the active styles like so:
```tsx
import { ToggleGroup } from '@tamagui/toggle-group'
import { styled } from 'tamagui'
const MyToggleGroupItem = styled(ToggleGroup.Item, {
variants: {
active: {
true: {
backgroundColor: 'red',
},
},
},
})
```
## components/toggle-group/2.0.0
---
title: ToggleGroup
description: Two-state buttons that can be toggled on or off
name: toggleGroup
component: ToggleGroup
package: toggle-group
demoName: ToggleGroup
---
ToggleGroup arranges two-state buttons into a keyboard-navigable single or
multiple selection control.
```tsx hero template=ToggleGroup
```
## Installation
ToggleGroup is already installed in `tamagui`, or you can install it independently:
```bash
npm install @tamagui/toggle-group
```
## Usage
ToggleGroup handles the toggle state logic. For visual grouping with border radius, compose with `XGroup` or `YGroup`. Use `activeStyle` to customize the active state appearance:
```tsx
import { ToggleGroup, XGroup, style } from 'tamagui'
const activeItemStyle = style({ backgroundColor: 'color-5' })
export default () => {
return (
Foo
Bar
)
}
```
For vertical layouts, use `YGroup`:
```tsx
Top
Bottom
```
## API reference
### ToggleGroup
ToggleGroup manages toggle state and keyboard navigation. It does not render any visible element by default. Use `XGroup`/`YGroup` for visual grouping.
void',
description: `Event handler called when the pressed state of an item changes.`,
},
{
name: 'loop',
type: 'boolean',
description: `Whether or not to loop over after reaching the end or start of the items. Used for keyboard navigation.`,
default: `true`,
},
{
name: 'disableDeactivation',
type: 'boolean',
description: `Won't let the user turn the active item off. Only applies to type="single".`,
default: 'false',
},
{
name: 'rovingFocus',
type: 'boolean',
description: `Enable roving focus keyboard navigation between items.`,
default: 'true',
},
]}
/>
### ToggleGroup.Item
`ToggleGroup.Item` extends View inheriting all the [Tamagui standard props](/docs/intro/props), plus:
### Styling active state
Customize the active/pressed state using `activeStyle`:
```tsx
const greenActiveStyle = style({
backgroundColor: 'green-500',
})
// Usage on ToggleGroup.Item
Left
// Or via styled()
const GreenItem = styled(ToggleGroup.Item, {
activeStyle: greenActiveStyle,
})
```
You can also use `activeTheme` to apply a theme when active:
```tsx
Option 1
```
### useToggleGroupItem
For custom components inside `ToggleGroup.Item` that need to know the active state, use the `useToggleGroupItem` hook:
```tsx
import { useToggleGroupItem, ToggleGroup } from '@tamagui/toggle-group'
function CustomLabel({ children }) {
const { active, color } = useToggleGroupItem()
return {children}
}
// Usage
;Option 1
```
## components/form/1.3.0
---
title: Form
description: A simple form component for native and web.
name: form
component: Form
package: form
demoName: Form
---
```tsx hero template=Forms
```
## Installation
Form is already installed in `tamagui`, or you can install it independently:
```bash
npm install @tamagui/form
```
## Anatomy
```tsx
import { Form } from 'tamagui' // or '@tamagui/form'
export default () => (
)
```
## API Reference
### Form
void',
required: true,
description: `Must use Form.Trigger to ensure onSubmit will callback.`,
},
]}
/>
### Form.Trigger
Wrap this around your submitting element to make the form submit. We recommend using `asChild` to a child element of your choosing for more control.
Accepts [Tamagui Props](/docs/intro/props).
## components/form/2.0.0
---
title: Form
description: A simple form component for native and web
name: form
component: Form
package: form
demoName: Form
---
```tsx hero template=Forms
```
## Installation
Form is already installed in `tamagui`, or you can install it independently:
```bash
npm install @tamagui/form
```
## Anatomy
```tsx
import { Form } from 'tamagui' // or '@tamagui/form'
export default () => (
)
```
## API Reference
### Form
void',
required: true,
description: `Must use \`Form.Trigger\` to ensure \`onSubmit\` will be called.`,
},
]}
/>
### Form.Trigger
Wrap this around your submitting element to make the form submit. We recommend using `asChild` to a child element of your choosing for more control.
Accepts [Tamagui Props](/docs/intro/props).
## components/form/3.0.0
---
title: Form
description: Collect and validate fields on native and web
name: form
component: Form
package: form
demoName: Form
---
Form collects values and coordinates validation, focus, and submission with
the same component tree on web and native.
```tsx hero template=Forms
```
Form coordinates registered [Fields](/ui/field). It validates synchronously,
projects their values by name, focuses the first invalid control, and then calls
`onSubmit(values, details)`.
## Installation
Form is already installed in `tamagui`, or you can install it independently:
```bash
npm install @tamagui/form
```
## Usage
```tsx
import { Button, Field, Form } from 'tamagui'
export function AccountForm() {
return (
Create account
)
}
```
The callback receives values from every registered Field with a `name`:
```ts
{
email: 'ada@example.com',
displayName: 'Ada'
}
```
The second argument is a `TamaguiEventDetails` with reason `submit` for a
native form submission or `trigger-press` for Form.Trigger.
## Validation and focus
Form validates every registered field before calling `onSubmit`. Async
validators are started but never delay the submit attempt. Synchronous
validation and previously committed errors can stop submission.
When a field is invalid, Form focuses the first registered control. Text inputs
are also selected on web. Native focus uses the `@tamagui/focusable` registry.
Set the default validation boundary on Form:
```tsx
```
An individual Field can override it.
## Server errors
Pass an error record keyed by Field name:
```tsx
const [errors, setErrors] = useState({})
```
Server errors participate in invalid styling, accessible error messages, and
first-invalid focus. An error clears when that field is edited. If errors arrive
after a submit, Form focuses the first matching field again.
The same prop is the bridge for React Hook Form or TanStack Form:
```tsx
const {
formState: { errors },
} = useForm()
const fieldErrors = Object.fromEntries(
Object.entries(errors).map(([name, error]) => [name, error?.message])
)
```
## Imperative validation
Use `actionsRef` to validate every field or one field by name:
```tsx
const actionsRef = useRef(null)
actionsRef.current?.validate()
actionsRef.current?.validate('email')
```
## Anatomy
```tsx
```
## Migrating from Form 2
Form 3 changes `onSubmit` from a zero-argument callback to:
```ts
onSubmit(values, details)
```
Callbacks that ignore their arguments continue to work. Code that previously
read values through a second form system can usually read them from `values`
instead.
## API reference
### Form
Accepts [Tamagui Props](/docs/intro/props), plus:
void | Promise',
description: `Called after synchronous validation passes with projected Field values and submit details.`,
},
{
name: 'scope',
type: 'string',
description: `Names this Form so a Form.Trigger rendered elsewhere can target it.`,
},
{
name: 'validationMode',
type: `'onSubmit' | 'onBlur' | 'onChange'`,
default: `'onSubmit'`,
description: `Sets the default validation mode for registered Fields.`,
},
{
name: 'errors',
type: 'Record',
description: `External server or form-library errors keyed by Field name.`,
},
{
name: 'actionsRef',
type: 'React.RefObject',
description: `Provides validate(fieldName?) for imperative validation.`,
},
]}
/>
### Form.Trigger
Submits the nearest scoped Form on web and native. We recommend `asChild` when
you want to use a Button or another pressable component.
Accepts [Tamagui Props](/docs/intro/props), plus:
## components/toast/1.11.3
---
title: Toast
description: A toast component with native features
name: toast
component: Toast
package: toast
demoName: Toast
---
# Toast Use to show feedback to user interactions
```tsx hero template=Toast
```
## Installation
Run the following command:
```
yarn add @tamagui/toast burnt
```
then, rebuild your React Native app. React Native requires sub-dependencies with native dependencies always be hoisted to your apps package.json and Toast relies on the amazing [Burnt](https://github.com/nandorojo/burnt) library by Fernando Rojo to provide its native functionality.
## Usage
To display the toast natively, you should either pass an array of native platforms (`native: ["ios", "web"]`), a single platform or `true` for all platforms.
```tsx
import { Button } from 'tamagui' // or '@tamagui/button'
import { Toast, ToastProvider, useToast } from '@tamagui/toast'
export default () => (
)
const CurrentToast = () => {
const { currentToast } = useToast()
// only show the component if it's present and not handled by native toast
if (!currentToast || currentToast.isHandledNatively) return null
return (
{currentToast.title}{currentToast.message}
)
}
const MyPage = () => {
const { show } = useToast()
return (
show('Done!', { message: 'Form submitted successfully.' })}>
Show Toast
)
}
```
## API
### ToastProvider
Your toasts should be wrapped within a `ToastProvider`. This is usually done at the root of your application.
`,
description: `Options for the burnt package if you're using native toasts on mobile`,
},
{
name: `notificationOptions`,
required: false,
type: `NotificationOptions`,
description: `Options for the notification API if you're using native toasts on web`,
},
]}
/>
### ToastViewport
The portal for toasts to be directed to. Should be used inside [ToastProvider](#toastprovider). Beyond `View` props, adds:
### Toast
Contains the Title, Description, Action and Close component. Should be used inside [ToastProvider](#toastprovider). Extends `View` and adds:
#### Toast.Title
Should be used inside [Toast](#toast). Extends [SizableText](/docs/components/text/1.0.0#sizabletext).
#### Toast.Description
Should be used inside [Toast](#toast). Extends [SizableText](/docs/components/text/1.0.0#sizabletext).
#### Toast.Close
Should be used inside [Toast](#toast). Extends `View`. You can pass `asChild` to this component and use a custom `` inside.
#### Toast.Action
Should be used inside [Toast](#toast). Extends `View`. You can pass `asChild` to this component and use a custom `` inside.
### useToast
Should be used inside [ToastProvider](#toastprovider).
## FAQ
#### How to change the placement of toasts?
Native toasts:
- iOS (burnt): Supports top or bottom placements. Adjustable by passing `from` to `burntOptions`:
```tsx
```
- Android (burnt): Not supported.
- Web (Notification API): Not supported.
Non-native toasts:
You should change the positioning of your [``](#toastviewport). For instance, if you want them to appear from top right:
```tsx
```
Or for bottom center:
```tsx
```
When using multiple toasts, you can change the order of toasts by setting
`flexDirection` to `column` or `column-reverse`. Or even have them stack horizontally
using `row` or `row-reverse`.
#### How to show non-native toasts within safe area on mobile?
Install `react-native-safe-area-context` if you haven't, wrap your app inside ``, and use the safe area insets to position the viewport inside the safe area.
```tsx
import { useSafeAreaInsets } from 'react-native-safe-area-context'
const SafeToastViewport = () => {
const { left, top, right } = useSafeAreaInsets()
return (
)
}
```
#### Can I send toasts to different toast viewports?
Yes, but you will have to name them and then reference the viewport name on the `` component. for instance:
```tsx
const App = () => {
return (
{/* name will be "default" */}
)
}
const MyComponent = () => {
return {/* goes to default viewport */}
}
const MyComponent2 = () => {
return
}
```
#### How can I have more control over toasts?
You will have to opt-out of the native toasts and only use custom ones. Here are some examples:
##### Single Toast
```tsx
export default () => {
const [open, setOpen] = React.useState(false)
const timerRef = React.useRef(0)
React.useEffect(() => {
return () => clearTimeout(timerRef.current)
}, [])
return (
{
setOpen(false)
window.clearTimeout(timerRef.current)
timerRef.current = window.setTimeout(() => {
setOpen(true)
}, 150)
}}
>
Single Toast
Subscribed!We'll be in touch.
)
}
```
##### Multiple Toasts
To use multiple toasts, you should pass `multipleToasts` to your `ToastViewport`.
Otherwise there'll be issues when swipe-dismissing or animating toasts.
```tsx
export default () => {
const [savedCount, setSavedCount] = React.useState(0)
return (
{
setSavedCount((old) => old + 1)
}}
>
Show toast
{[...Array(savedCount)].map((_, index) => (
Subscribed!We'll be in touch.
))}
)
}
```
## components/toast/1.9.1
---
title: Toast
description: A toast component with native features
name: toast
component: Toast
package: toast
demoName: Toast
---
# Toast Use to show feedback to user interactions
```tsx hero template=Toast
```
## Usage
### Imperative API
This API offers a nice imperative interface, support for native toasts with a lot of control.
#### Installation
For native support on mobile, run `yarn add burnt` to add `burnt`, then rebuild your React Native app. React Native requires sub-dependencies with native dependencies always be hoisted to your apps package.json and Toast relies on the amazing [Burnt](https://github.com/nandorojo/burnt) library by Fernando Rojo to provide its native functionality.
Note: `Burnt` [will not work](https://github.com/nandorojo/burnt?tab=readme-ov-file#expo) with Expo Go.
#### Usage
To display the toast natively, you should either pass an array of native platforms (`native: ["ios", "web"]`), a single platform or `true` for all platforms.
```tsx
import { Button } from 'tamagui' // or '@tamagui/button'
import { Toast, ToastImperativeProvider, ToastProvider, useToast } from 'tamagui' // or '@tamagui/toast'
const options = { native: 'mobile' }
export default () => (
)
const CurrentToast = () => {
const { currentToast } = useToast()
// only show the component if it's present and not handled by native toast
if (!currentToast || currentToast.isHandledNatively) return null
return (
{currentToast.title}{currentToast.message}
)
}
const MyPage = () => {
const { show } = useToast()
return (
show('Done!', { message: 'Form submitted successfully.' })}>
Show Toast
)
}
```
### Barebone API
This API does not support native toasts. For native toasts, use the [Imperative
API](#imperative-api).
#### Single Toast
```tsx
export default () => {
const [open, setOpen] = React.useState(false)
const timerRef = React.useRef(0)
React.useEffect(() => {
return () => clearTimeout(timerRef.current)
}, [])
return (
{
setOpen(false)
window.clearTimeout(timerRef.current)
timerRef.current = window.setTimeout(() => {
setOpen(true)
}, 150)
}}
>
Single Toast
Subscribed!We'll be in touch.
)
}
```
#### Multiple Toast
To use multiple toasts, you should pass `multipleToasts` to your `ToastViewport`.
Otherwise there'll be issues when swipe-dismissing or animating toasts.
```tsx
export default () => {
const [savedCount, setSavedCount] = React.useState(0)
return (
{
setSavedCount((old) => old + 1)
}}
>
Show toast
{[...Array(savedCount)].map((_, index) => (
Subscribed!We'll be in touch.
))}
)
}
```
## API
### ToastProvider
Your toasts should be wrapped within a `ToastProvider`. This is usually done at the root of your application.
### ToastViewport
The portal for toasts to be directed to. Should be inside [ToastProvider](#toastprovider). Beyond `View` props, adds:
### Toast
Contains the Title, Description, Action and Close component. Should be inside [ToastProvider](#toastprovider). Extends `View` and adds:
#### Toast.Title
Should be inside [Toast](#toast). Extends [SizableText](/docs/components/text/1.0.0#sizabletext).
#### Toast.Description
Should be inside [Toast](#toast). Extends [SizableText](/docs/components/text/1.0.0#sizabletext).
#### Toast.Close
Should be inside [Toast](#toast). Extends `View`. You can pass `asChild` to this component and use a custom `` inside.
#### Toast.Action
Should be inside [Toast](#toast). Extends `View`. You can pass `asChild` to this component and use a custom `` inside.
### ToastImperativeProvider
Wrap components within this provider to use `useToast()` inside them.
### useToast
Used when using the imperative API.
## FAQ
#### How to change the placement of toasts?
##### Native toasts
- iOS (burnt): Supports top or bottom placements. Adjustable by passing `from` to `burntOptions`:
```tsx
```
- Android (burnt): Not supported.
- Web (Notification API): Not supported.
##### Non-native toasts
You should change the positioning of your [``](#toastviewport). For instance, if you want them to appear from top right:
```tsx
```
Or for bottom center:
```tsx
```
When using multiple toasts, you can change the order of toasts by setting
`flexDirection` to `column` or `column-reverse`. Or even have them stack horizontally
using `row` or `row-reverse`.
#### How to show non-native toasts within safe area on mobile?
Install `react-native-safe-area-context` if you haven't, wrap your app inside ``, and use the safe area insets to position the viewport inside the safe area.
```tsx
import { useSafeAreaInsets } from 'react-native-safe-area-context'
const SafeToastViewport = () => {
const { left, top, right } = useSafeAreaInsets()
return (
)
}
```
#### Can I have multiple viewports?
Yes, but you will have to name them and then reference the viewport name on the `` component. for instance:
```tsx
const App = () => {
return (
// name will be "default"
)
}
const MyComponent = () => {
return // goes to default viewport // ...
}
const MyComponent2 = () => {
return // ...
}
```
## components/toast/1.83.0
---
title: Toast
description: Use to show feedback to user interactions.
name: toast
component: Toast
package: toast
demoName: Toast
---
```tsx hero template=Toast
```
## Installation
Note that `@tamagui/toast` is the only UI package not included by default in `tamagui`. The reason for this is that Metro would force you to install the native `burnt` dependency, and that would mean `tamagui` wouldn't work with Expo Go and would require a native install step no matter if you used Toast or not. This is due to a limitation in Metro.
To install toast:
```bash
yarn add @tamagui/toast burnt
```
Then, if targeting native, rebuild your React Native app. React Native requires sub-dependencies with native dependencies always be hoisted to your apps package.json and Toast relies on the amazing [Burnt](https://github.com/nandorojo/burnt) library by Fernando Rojo to provide its native functionality.
We're open to a refactor that moves the burnt dependency into a sub-path like `@tamagui/toast/burnt` and forces setup through there, that likely fixes the Metro issue and allow us to include it by default.
## Anatomy
```tsx
```
## API Reference
### ToastProvider
Your toasts should be wrapped within a `ToastProvider`. This is usually done at the root of your application.
`,
description: `Options for the burnt package if you're using native toasts on mobile`,
},
{
name: `notificationOptions`,
required: false,
type: `NotificationOptions`,
description: `Options for the notification API if you're using native toasts on web`,
},
]}
/>
### ToastViewport
The portal for toasts to be directed to. Should be used inside [ToastProvider](#toastprovider). Beyond `View` props, adds:
### Toast
Contains the Title, Description, Action and Close component. Should be used inside [ToastProvider](#toastprovider). Extends `View` and adds:
### Toast.Title
Should be used inside [Toast](#toast). Extends [SizableText](/docs/components/text#sizabletext), adding:
### Toast.Description
Should be used inside [Toast](#toast). Extends [SizableText](/docs/components/text#sizabletext), adding:
### Toast.Close
Should be used inside [Toast](#toast). Extends `View`. You can pass `asChild` to this component and use a custom `` inside.
### Toast.Action
Should be used inside [Toast](#toast). Extends `View`. You can pass `asChild` to this component and use a custom `` inside.
### useToastController
Used to control the display of toasts. Should be used inside [ToastProvider](#toastprovider).
### useToastState
Used to render out your toast contents. Should be used inside [ToastProvider](#toastprovider). Doesn't take in anything and returns `ToastData`.
```tsx
const CurrentToast = () => {
const toast = useToastState()
// don't show any toast if no toast is present or it's handled natively
if (!toast || toast.isHandledNatively) {
return null
}
return (
{toast.title}{toast.message}
)
}
```
## Examples
### Position the viewport
To position the viewport on native toasts:
- iOS (burnt): Supports top or bottom placements. Adjustable by passing `from` to `burntOptions`:
```tsx
```
- Android (burnt): Not supported.
- Web (Notification API): Not supported.
To position the viewport on custom toasts:
You should change the positioning of your [``](#toastviewport). For instance, if you want them to appear from top right:
```tsx
```
Or for bottom center:
```tsx
```
When using multiple toasts, you can change the order of toasts by setting
`flexDirection` to `column` or `column-reverse`. Or even have them stack horizontally
using `row` or `row-reverse`.
### Mobile safe area
To show toasts inside device's safe area, install `react-native-safe-area-context` if you haven't, wrap your app inside ``, and then use the safe area insets to position the viewport inside the safe area.
```tsx
import { useSafeAreaInsets } from 'react-native-safe-area-context'
const SafeToastViewport = () => {
const { left, top, right } = useSafeAreaInsets()
return (
)
}
```
### Different viewports
To send toasts to different viewports, you can set up different viewports:
```tsx
const App = () => {
return (
{/* default viewport */}
)
}
```
And then, use the viewport's name on the toasts.
```tsx
const MyComponent = () => {
return // default viewport
}
const MyComponent2 = () => {
return
}
```
### Custom data
Just pass your custom data to the second parameter of the `show()` method.
```ts
const toastController = useToastController()
toastController.show('Title', { myPreset: 'error' }) // or toastController.show("Title", { customData: { myPreset: 'error' } })
```
then, when showing the toast, you can retrieve them like so:
```ts
const toastState = useToastState()
toastState.myPreset // or toastState.customData.myPreset
```
To add TypeScript auto-completion for your custom fields, you can use TS module augmentation:
```ts
declare module '@tamagui/toast' {
interface CustomData {
myPreset: 'error' | 'success' | 'warning'
}
}
```
### Without hooks
You can also use toasts without the hooks.
You can't use native toasts this way.
#### Single Toast
```tsx
export default () => {
const [open, setOpen] = React.useState(false)
const timerRef = React.useRef(0)
React.useEffect(() => {
return () => clearTimeout(timerRef.current)
}, [])
return (
{
setOpen(false)
window.clearTimeout(timerRef.current)
timerRef.current = window.setTimeout(() => {
setOpen(true)
}, 150)
}}
>
Single Toast
Subscribed!We'll be in touch.
)
}
```
#### Multiple Toasts
To use multiple toasts, you should pass `multipleToasts` to your `ToastViewport`.
Otherwise there'll be issues when swipe-dismissing or animating toasts.
```tsx
export default () => {
const [savedCount, setSavedCount] = React.useState(0)
return (
{
setSavedCount((old) => old + 1)
}}
>
Show toast
{[...Array(savedCount)].map((_, index) => (
Subscribed!We'll be in touch.
))}
)
}
```
## components/toast/1.15.15
---
title: Toast
description: A toast component with native features
name: toast
component: Toast
package: toast
demoName: Toast
---
# Toast Use to show feedback to user interactions
```tsx hero template=Toast
```
## Installation
Run the following command:
```bash
yarn add @tamagui/toast burnt
```
Then, if targeting native, rebuild your React Native app. React Native requires sub-dependencies with native dependencies always be hoisted to your apps package.json and Toast relies on the amazing [Burnt](https://github.com/nandorojo/burnt) library by Fernando Rojo to provide its native functionality.
## Anatomy
```tsx
```
## API Reference
### ToastProvider
Your toasts should be wrapped within a `ToastProvider`. This is usually done at the root of your application.
`,
description: `Options for the burnt package if you're using native toasts on mobile`,
},
{
name: `notificationOptions`,
required: false,
type: `NotificationOptions`,
description: `Options for the notification API if you're using native toasts on web`,
},
]}
/>
### ToastViewport
The portal for toasts to be directed to. Should be used inside [ToastProvider](#toastprovider). Beyond `View` props, adds:
### Toast
Contains the Title, Description, Action and Close component. Should be used inside [ToastProvider](#toastprovider). Extends `View` and adds:
### Toast.Title
Should be used inside [Toast](#toast). Extends [SizableText](/docs/components/text/1.0.0#sizabletext), adding:
### Toast.Description
Should be used inside [Toast](#toast). Extends [SizableText](/docs/components/text/1.0.0#sizabletext), adding:
### Toast.Close
Should be used inside [Toast](#toast). Extends `View`. You can pass `asChild` to this component and use a custom `` inside.
### Toast.Action
Should be used inside [Toast](#toast). Extends `View`. You can pass `asChild` to this component and use a custom `` inside.
### useToastController
Used to control the display of toasts. Should be used inside [ToastProvider](#toastprovider).
### useToastState
Used to render out your toast contents. Should be used inside [ToastProvider](#toastprovider). Doesn't take in anything and returns `ToastData`.
```tsx
const CurrentToast = () => {
const toast = useToastState()
// don't show any toast if no toast is present or it's handled natively
if (!toast || toast.isHandledNatively) {
return null
}
return (
{toast.title}{toast.message}
)
}
```
## Examples
### Position the viewport
To position the viewport on native toasts:
- iOS (burnt): Supports top or bottom placements. Adjustable by passing `from` to `burntOptions`:
```tsx
```
- Android (burnt): Not supported.
- Web (Notification API): Not supported.
To position the viewport on custom toasts:
You should change the positioning of your [``](#toastviewport). For instance, if you want them to appear from top right:
```tsx
```
Or for bottom center:
```tsx
```
When using multiple toasts, you can change the order of toasts by setting
`flexDirection` to `column` or `column-reverse`. Or even have them stack horizontally
using `row` or `row-reverse`.
### Mobile safe area
To show toasts inside device's safe area, install `react-native-safe-area-context` if you haven't, wrap your app inside ``, and then use the safe area insets to position the viewport inside the safe area.
```tsx
import { useSafeAreaInsets } from 'react-native-safe-area-context'
const SafeToastViewport = () => {
const { left, top, right } = useSafeAreaInsets()
return (
)
}
```
### Different viewports
To send toasts to different viewports, you can set up different viewports:
```tsx
const App = () => {
return (
{/* default viewport */}
)
}
```
And then, use the viewport's name on the toasts.
```tsx
const MyComponent = () => {
return // default viewport
}
const MyComponent2 = () => {
return
}
```
### Custom data
Just pass your custom data to the second parameter of the `show()` method.
```ts
const toastController = useToastController()
toastController.show('Title', { myPreset: 'error' }) // or toastController.show("Title", { customData: { myPreset: 'error' } })
```
then, when showing the toast, you can retrieve them like so:
```ts
const toastState = useToastState()
toastState.myPreset // or toastState.customData.myPreset
```
To add TypeScript auto-completion for your custom fields, you can use TS module augmentation:
```ts
declare module '@tamagui/toast' {
interface CustomData {
myPreset: 'error' | 'success' | 'warning'
}
}
```
### Without hooks
You can also use toasts without the hooks.
You can't use native toasts this way.
#### Single Toast
```tsx
export default () => {
const [open, setOpen] = React.useState(false)
const timerRef = React.useRef(0)
React.useEffect(() => {
return () => clearTimeout(timerRef.current)
}, [])
return (
{
setOpen(false)
window.clearTimeout(timerRef.current)
timerRef.current = window.setTimeout(() => {
setOpen(true)
}, 150)
}}
>
Single Toast
Subscribed!We'll be in touch.
)
}
```
#### Multiple Toasts
To use multiple toasts, you should pass `multipleToasts` to your `ToastViewport`.
Otherwise there'll be issues when swipe-dismissing or animating toasts.
```tsx
export default () => {
const [savedCount, setSavedCount] = React.useState(0)
return (
{
setSavedCount((old) => old + 1)
}}
>
Show toast
{[...Array(savedCount)].map((_, index) => (
Subscribed!We'll be in touch.
))}
)
}
```
## components/toast/2.0.0
---
title: Toast
description: Use to show feedback to user interactions.
name: toast
component: Toast
package: toast
---
This page documents the v2 imperative Toast API (`ToastProvider`,
`ToastViewport`, `useToastController`, `useToastState`), which was removed in
v3. Use the composable [Toast](/ui/toast) and the `toast()` function instead.
See the [upgrade guide](/docs/guides/how-to-upgrade) for the migration.
## Installation
Toast is included by default in `tamagui`. If you're using it standalone:
```bash
yarn add @tamagui/toast
```
### Native Toast Setup
For native toasts on iOS/Android (using [Burnt](https://github.com/nandorojo/burnt) by Fernando Rojo), install the native dependency and import the setup module:
```bash
yarn add burnt
```
```tsx
// App.tsx - before any Tamagui imports
import '@tamagui/native/setup-burnt'
```
Then rebuild your React Native app. Without this setup, toasts will still work but the `native` prop will have no effect on mobile.
## Anatomy
```tsx
```
## API Reference
### ToastProvider
Your toasts should be wrapped within a `ToastProvider`. This is usually done at the root of your application.
`,
description: `Options for the burnt package if you're using native toasts on mobile`,
},
{
name: `notificationOptions`,
required: false,
type: `NotificationOptions`,
description: `Options for the notification API if you're using native toasts on web`,
},
]}
/>
### ToastViewport
The portal for toasts to be directed to. Should be used inside [ToastProvider](#toastprovider). Beyond `View` props, adds:
### Toast
Contains the Title, Description, Action and Close component. Should be used inside [ToastProvider](#toastprovider). Extends `View` and adds:
### Toast.Title
Should be used inside [Toast](#toast). Extends [SizableText](/docs/components/text#sizabletext), adding:
### Toast.Description
Should be used inside [Toast](#toast). Extends [SizableText](/docs/components/text#sizabletext), adding:
### Toast.Close
Should be used inside [Toast](#toast). Extends `View`. You can pass `asChild` to this component and use a custom `` inside.
### Toast.Action
Should be used inside [Toast](#toast). Extends `View`. You can pass `asChild` to this component and use a custom `` inside.
### useToastController
Used to control the display of toasts. Should be used inside [ToastProvider](#toastprovider).
### useToastState
Used to render out your toast contents. Should be used inside [ToastProvider](#toastprovider). Doesn't take in anything and returns `ToastData`.
```tsx
const CurrentToast = () => {
const toast = useToastState()
// don't show any toast if no toast is present or it's handled natively
if (!toast || toast.isHandledNatively) {
return null
}
return (
{toast.title}{toast.message}
)
}
```
## Examples
### Position the viewport
To position the viewport on native toasts:
- iOS (burnt): Supports top or bottom placements. Adjustable by passing `from` to `burntOptions`:
```tsx
```
- Android (burnt): Not supported.
- Web (Notification API): Not supported.
To position the viewport on custom toasts:
You should change the positioning of your [``](#toastviewport). For instance, if you want them to appear from top right:
```tsx
```
Or for bottom center:
```tsx
```
When using multiple toasts, you can change the order of toasts by setting
`flexDirection` to `column` or `column-reverse`. Or even have them stack horizontally
using `row` or `row-reverse`.
### Mobile safe area
To show toasts inside device's safe area, install `react-native-safe-area-context` if you haven't, wrap your app inside ``, and then use the safe area insets to position the viewport inside the safe area.
```tsx
import { useSafeAreaInsets } from 'react-native-safe-area-context'
const SafeToastViewport = () => {
const { left, top, right } = useSafeAreaInsets()
return (
)
}
```
### Different viewports
To send toasts to different viewports, you can set up different viewports:
```tsx
const App = () => {
return (
{/* default viewport */}
)
}
```
And then, use the viewport's name on the toasts.
```tsx
const MyComponent = () => {
return // default viewport
}
const MyComponent2 = () => {
return
}
```
### Custom data
Just pass your custom data to the second parameter of the `show()` method.
```ts
const toastController = useToastController()
toastController.show('Title', { myPreset: 'error' }) // or toastController.show("Title", { customData: { myPreset: 'error' } })
```
then, when showing the toast, you can retrieve them like so:
```ts
const toastState = useToastState()
toastState.myPreset // or toastState.customData.myPreset
```
To add TypeScript auto-completion for your custom fields, you can use TS module augmentation:
```ts
declare module '@tamagui/toast' {
interface CustomData {
myPreset: 'error' | 'success' | 'warning'
}
}
```
### Without hooks
You can also use toasts without the hooks.
You can't use native toasts this way.
#### Single Toast
```tsx
export default () => {
const [open, setOpen] = React.useState(false)
const timerRef = React.useRef(0)
React.useEffect(() => {
return () => clearTimeout(timerRef.current)
}, [])
return (
{
setOpen(false)
window.clearTimeout(timerRef.current)
timerRef.current = window.setTimeout(() => {
setOpen(true)
}, 150)
}}
>
Single Toast
Subscribed!We'll be in touch.
)
}
```
#### Multiple Toasts
To use multiple toasts, you should pass `multipleToasts` to your `ToastViewport`.
Otherwise there'll be issues when swipe-dismissing or animating toasts.
```tsx
export default () => {
const [savedCount, setSavedCount] = React.useState(0)
return (
{
setSavedCount((old) => old + 1)
}}
>
Show toast
{[...Array(savedCount)].map((_, index) => (
Subscribed!We'll be in touch.
))}
)
}
```
## components/toast/3.0.0
---
title: Toast
description: Accessible toasts with stacking, gestures, and animations
name: toast
component: Toast
package: toast
demoName: Toast
---
Toast presents time-sensitive messages with accessible announcements,
stacking, gestures, and animated entry or exit.
```tsx hero template=Toast
```
## Installation
Toast ships with `tamagui`: there's nothing extra to install:
```tsx
import { Toast, toast } from 'tamagui/toast'
```
If you're using the unstyled primitives directly, it's also published on its own:
```bash
npm install @tamagui/toast
```
Native OS toasts are opt-in. By default toasts render as in-app Tamagui views on
every platform, so adding Toast to your app never pulls in a native dependency.
See [Native toasts](#native-toasts) if you want the real iOS/Android system
toast.
## Quick start
Toast is a composable component API: you mount the parts, and `toast()` drives
them from anywhere in your app.
```tsx
import { Button } from 'tamagui/button'
import { Toast, toast } from 'tamagui/toast'
function App() {
return (
toast('Hello!')}>Show Toast
)
}
```
Mount this inside your `TamaguiProvider`, usually next to your app navigation or
root layout. `toast()` is global, so callers don't need to be inside ``.
`` renders the default styled toast. Pass `renderItem` to own the
markup completely:
```tsx
(
{t.title}{t.description}
)}
/>
```
Omit `renderItem` and `Toast.List` renders the default styled toast content.
## Anatomy
```tsx
{/* Root - context and position */}
{/* Container with portal */}
{/* Individual toast with stacking/drag */}
```
## API reference
### toast()
The imperative toast function:
```tsx
// Basic
toast('Hello world')
// With types
toast.success('Saved!')
toast.error('Failed')
toast.warning('Careful')
toast.info('FYI')
toast.loading('Processing...')
// With options
toast('Message', {
description: 'More details here',
duration: 5000,
})
// Dismiss
const id = toast('Hello')
toast.dismiss(id) // dismiss specific
toast.dismiss() // dismiss all
```
### toast.promise()
For async operations:
```tsx
toast.promise(saveData(), {
loading: 'Saving...',
success: 'Saved!',
error: 'Failed to save',
})
```
### Toast (Root)
### Toast.Viewport
The portal container for toasts. Extends View.
### Toast.Item
Individual toast wrapper with stacking and drag support. Extends View.
Supports render prop for custom content:
```tsx
{({ toast, handleClose }) => (
{toast.title}Close
)}
```
### Toast.Title
Styled text for the toast title. Extends SizableText.
### Toast.Description
Styled text for the toast description. Extends SizableText.
### Toast.Close
Close button. Extends View.
### Toast.Action
Action button. Extends View.
### useToasts
Hook to access toast state from a descendant of ``. Most apps should use `` for rendering and reach for this hook only when they need custom list control.
```tsx
const { toasts, expanded, position } = useToasts()
```
## Features
### Stacking
Multiple toasts stack visually. Hover over the stack to expand and see all toasts.
### Swipe to dismiss
Toasts can be swiped away. The direction is auto-detected based on position:
- **Web:** Left/right positioned toasts swipe toward their edge, center toasts swipe horizontally
- **Native:** All positions swipe vertically (up/down) to avoid conflicting with iOS/Android navigation back gesture
When `react-native-gesture-handler` is set up via `@tamagui/native/setup-gesture-handler`, the toast uses native gesture coordination (`activeOffsetY`, `failOffsetX`) to prevent ScrollView from scrolling while swiping.
### Native toasts
Toasts render as in-app Tamagui views on every platform by default. To use the real iOS/Android system toast instead, install [Burnt](https://github.com/nandorojo/burnt) and import its setup module at your app entry:
```bash
npm install burnt
```
```tsx
import '@tamagui/native/setup-burnt'
```
Then opt in on the root:
```tsx
{/* ... */}
```
Burnt is resolved lazily, so apps that don't set it up never load it and never need the native module.
### Native safe area
On native, toasts portal to root and automatically respect safe area insets (status bar, Dynamic Island, home indicator) when configured:
```tsx
import { SafeAreaProvider } from 'react-native-safe-area-context'
import { useSafeAreaInsets } from 'react-native-safe-area-context'
import { TamaguiProvider } from 'tamagui'
function App() {
return (
)
}
function AppInner() {
const insets = useSafeAreaInsets()
return (
{/* toasts will automatically avoid status bar and home indicator */}
)
}
```
The toast reads insets from `TamaguiProvider` via `useConfiguration().insets`, the same mechanism used by other Tamagui components like Slider.
### Custom styling
Each part can be styled independently:
```tsx
{t.title}
{t.description}
```
## Migrating from v2
The v2 imperative API (`ToastProvider`, `ToastViewport`, `useToastController`, `useToastState`) was removed. Replace the provider tree with the composable parts shown above, and call `toast()` directly instead of going through a controller hook:
```tsx
// v2
const toast = useToastController()
toast.show('Saved!', { message: 'Your changes are live' })
// v3
toast('Saved!', { description: 'Your changes are live' })
```
See the [upgrade guide](/docs/guides/how-to-upgrade) for the full migration.
## components/toast/1.8.0
---
title: Toast
description: A toast component with native features
name: toast
component: Toast
package: toast
demoName: Toast
---
# Toast Use to show feedback to user interactions
```tsx hero template=Toast
```
## Install
For native support, run `yarn add burnt` to add `burnt`, then rebuild your React Native app. React Native requires sub-dependencies with native dependencies always be hoisted to your apps package.json and Toast relies on the amazing [Burnt](https://github.com/nandorojo/burnt) library by Fernando Rojo to provide its native functionality.
## Anatomy
```tsx
import { Toast, ToastProvider, ToastViewport } from 'tamagui' // or '@tamagui/toast'
export default () => (
)
```
## Usage
### Single Toast
```tsx
export default () => {
const [open, setOpen] = React.useState(false)
const timerRef = React.useRef(0)
React.useEffect(() => {
return () => clearTimeout(timerRef.current)
}, [])
return (
{
setOpen(false)
window.clearTimeout(timerRef.current)
timerRef.current = window.setTimeout(() => {
setOpen(true)
}, 150)
}}
>
Single Toast
Subscribed!We'll be in touch.
)
}
```
### Duplicate Toasts
```tsx
export default () => {
const [savedCount, setSavedCount] = React.useState(0)
return (
{
setSavedCount((old) => old + 1)
}}
>
Show toast
{[...Array(savedCount)].map((_, index) => (
Subscribed!We'll be in touch.
))}
)
}
```
### Using `createToast`
```tsx
import { Button } from 'tamagui' // or '@tamagui/button'
import { Toast, ToastProvider, createToast } from 'tamagui' // or '@tamagui/toast'
export const { ImperativeToastProvider, useToast } = createToast()
export default () => (
)
const CurrentToast = () => {
const { currentToast } = useToast()
if (!currentToast) return
return (
{currentToast.title}{currentToast.message}
)
}
const MyPage = () => {
const { show } = useToast()
return (
show('Done!', { message: 'Form submitted successfully.' })}>
Show Toast
)
}
```
## API
### ToastProvider
Your toasts should be wrapped within a `ToastProvider`. This is usually done at the root of your application.
### ToastViewport
The portal for toasts to be directed to. Should be inside [ToastProvider](#toastprovider). Beyond `View` props, adds:
### Toast
Contains the Title, Description, Action and Close component. Should be inside [ToastProvider](#toastprovider). Extends `View` and adds:
#### Toast.Title
Should be inside [Toast](#toast). Extends [SizableText](/docs/components/text/1.0.0#sizabletext).
#### Toast.Description
Should be inside [Toast](#toast). Extends [SizableText](/docs/components/text/1.0.0#sizabletext).
#### Toast.Close
Should be inside [Toast](#toast). Extends `View`. You can pass `asChild` to this component and use a custom `` inside.
#### Toast.Action
Should be inside [Toast](#toast). Extends `View`. You can pass `asChild` to this component and use a custom `` inside.
### createToast
An alternative way to work with toasts.
This function, then returns the following:
#### ImperativeToastProvider
Wrap children within this provider so that they can use `useToast()`. Takes no params.
#### useToast
You may export this hook to use it throughout your app.
## components/toast/1.13.0
---
title: Toast
description: A toast component with native features
name: toast
component: Toast
package: toast
demoName: Toast
---
# Toast Use to show feedback to user interactions
```tsx hero template=Toast
```
## Installation
Run the following command:
```
yarn add @tamagui/toast burnt
```
then, if targetin native, rebuild your React Native app. React Native requires sub-dependencies with native dependencies always be hoisted to your apps package.json and Toast relies on the amazing [Burnt](https://github.com/nandorojo/burnt) library by Fernando Rojo to provide its native functionality.
## Usage
To display the toast natively, you should either pass an array of native platforms (`native: ["ios", "web"]`), a single platform or `true` for all platforms.
```tsx
import { Toast, ToastProvider, useToastController, useToastState } from '@tamagui/toast'
import { Button } from 'tamagui' // or '@tamagui/button'
export default () => (
)
const CurrentToast = () => {
const toast = useToastState()
// only show the component if it's present and not handled by native toast
if (!toast || toast.isHandledNatively) {
return null
}
return (
{toast.title}{toast.message}
)
}
const MyPage = () => {
const { show } = useToastController()
return (
show('Done!', { message: 'Form submitted successfully.' })}>
Show Toast
)
}
```
## API
### ToastProvider
Your toasts should be wrapped within a `ToastProvider`. This is usually done at the root of your application.
`,
description: `Options for the burnt package if you're using native toasts on mobile`,
},
{
name: `notificationOptions`,
required: false,
type: `NotificationOptions`,
description: `Options for the notification API if you're using native toasts on web`,
},
]}
/>
### ToastViewport
The portal for toasts to be directed to. Should be used inside [ToastProvider](#toastprovider). Beyond `View` props, adds:
### Toast
Contains the Title, Description, Action and Close component. Should be used inside [ToastProvider](#toastprovider). Extends `View` and adds:
#### Toast.Title
Should be used inside [Toast](#toast). Extends [SizableText](/docs/components/text/1.0.0#sizabletext).
#### Toast.Description
Should be used inside [Toast](#toast). Extends [SizableText](/docs/components/text/1.0.0#sizabletext).
#### Toast.Close
Should be used inside [Toast](#toast). Extends `View`. You can pass `asChild` to this component and use a custom `` inside.
#### Toast.Action
Should be used inside [Toast](#toast). Extends `View`. You can pass `asChild` to this component and use a custom `` inside.
### useToastController
Used to control the display of toasts. Should be used inside [ToastProvider](#toastprovider).
### useToastState
Used to render out your toast contents. Should be used inside [ToastProvider](#toastprovider).
## FAQ
#### How to change the placement of toasts?
Native toasts:
- iOS (burnt): Supports top or bottom placements. Adjustable by passing `from` to `burntOptions`:
```tsx
```
- Android (burnt): Not supported.
- Web (Notification API): Not supported.
Non-native toasts:
You should change the positioning of your [``](#toastviewport). For instance, if you want them to appear from top right:
```tsx
```
Or for bottom center:
```tsx
```
When using multiple toasts, you can change the order of toasts by setting
`flexDirection` to `column` or `column-reverse`. Or even have them stack horizontally
using `row` or `row-reverse`.
#### How to show non-native toasts within safe area on mobile?
Install `react-native-safe-area-context` if you haven't, wrap your app inside ``, and use the safe area insets to position the viewport inside the safe area.
```tsx
import { useSafeAreaInsets } from 'react-native-safe-area-context'
const SafeToastViewport = () => {
const { left, top, right } = useSafeAreaInsets()
return (
)
}
```
#### Can I send toasts to different toast viewports?
Yes, but you will have to name them and then reference the viewport name on the `` component. for instance:
```tsx
const App = () => {
return (
{/* name will be "default" */}
)
}
const MyComponent = () => {
return {/* goes to default viewport */}
}
const MyComponent2 = () => {
return
}
```
#### How can I have more control over toasts?
You will have to opt-out of the native toasts and only use custom ones. Here are some examples:
##### Single Toast
```tsx
export default () => {
const [open, setOpen] = React.useState(false)
const timerRef = React.useRef(0)
React.useEffect(() => {
return () => clearTimeout(timerRef.current)
}, [])
return (
{
setOpen(false)
window.clearTimeout(timerRef.current)
timerRef.current = window.setTimeout(() => {
setOpen(true)
}, 150)
}}
>
Single Toast
Subscribed!We'll be in touch.
)
}
```
##### Multiple Toasts
To use multiple toasts, you should pass `multipleToasts` to your `ToastViewport`.
Otherwise there'll be issues when swipe-dismissing or animating toasts.
```tsx
export default () => {
const [savedCount, setSavedCount] = React.useState(0)
return (
{
setSavedCount((old) => old + 1)
}}
>
Show toast
{[...Array(savedCount)].map((_, index) => (
Subscribed!We'll be in touch.
))}
)
}
```
## components/intro/1.0.0
---
title: Tamagui Components
---
Tamagui Components is a complete suite of components that render nicely on both React web and
React Native and come in both styled and unstyled forms.
## Install
You can install each component separately, or all of them at once with:
```bash
yarn add tamagui
```
## Setup
The package `tamagui` is a superset of `@tamagui/core`, so if you've already installed core you can change all the references to `tamagui`.
You'll need to add a provider to the root of your app (unlike core, where that is optional), as it will set up the root portal for components like dialogs and popovers.
```tsx
import { createTamagui, TamaguiProvider, View } from 'tamagui'
import { defaultConfig } from '@tamagui/config/v6' // for quick config install this
const config = createTamagui(defaultConfig)
export default () => (
)
```
For a full guide configuration with `createTamagui`, check [the core configuration docs](/docs/core/configuration). For native setup, platform integrations, and the fast-path runtime, see [the Native documentation](/ui/native).
## components/intro/2.0.0
---
title: Tamagui Components
description: React Native UI kit with copy-paste composable components
---
Tamagui Components is a complete suite of copy-paste composable components that render nicely on
both React Native and React web, in both styled and unstyled forms.
## Install
You can install each component separately, or all of them at once with:
```bash
npm install tamagui
```
## Setup
The package `tamagui` is a superset of `@tamagui/core`, so if you've already installed core you can change all the references to `tamagui`.
You'll need to add a provider to the root of your app (unlike core, where that is optional), as it will set up the root portal for components like dialogs and popovers.
```tsx
import { createTamagui, TamaguiProvider, View } from 'tamagui'
import { defaultConfig } from '@tamagui/config/v6' // for quick config install this
const config = createTamagui(defaultConfig)
export default () => (
)
```
For a full configuration guide with `createTamagui`, check [the core configuration docs](/docs/core/configuration). For native setup, platform integrations, and the fast-path runtime, see [the Native documentation](/ui/native).
## components/unspaced/1.0.0
---
title: Unspaced
description: Avoids spacing for children inside a spacing container.
name: html
component: Unspaced
---
While the `space` property is deprecated, Unspaced lives on in a better form, one which is easily back-ported to add Unspaced support (as well as adding `gap` support).
When using the `space` style prop, you may want some children to not be spaced.
Use Unspaced:
```tsx
import { Text, Unspaced, View } from '@tamagui/core'
export default () => (
{/* space */}
{/* no */}
Some absolute positioned text
)
```
If you want the item to be visually hidden as well as unspaced, see
[VisuallyHidden](/docs/components/visually-hidden), which uses Unspaced but also hides the contents in an accessible manner.
## components/visually-hidden/1.0.0
---
title: Visually Hidden
description: Hide content accessibly.
name: html
component: VisuallyHidden
---
VisuallyHidden hides an item but ensures it remains visible to accessibility readers.
## Installation
VisuallyHidden is already installed in `tamagui`, or you can install it independently:
```bash
npm install @tamagui/visually-hidden
```
## Usage
Simply wrap the content you want hidden in VisuallyHidden:
```tsx
import { Text, VisuallyHidden } from 'tamagui'
export default () => (
Add annotations here
)
```
When using with the `space` property, it will avoid double-spacing:
```tsx
import { H1, Text, VisuallyHidden, YStack } from 'tamagui'
export default () => (
Title
Add annotations here
)
```
## components/visually-hidden/2.0.0
---
title: Visually Hidden
description: Hide content accessibly
name: visually-hidden
component: VisuallyHidden
package: visually-hidden
---
VisuallyHidden removes content from the visual layout while keeping it
available to assistive technology.
## Installation
VisuallyHidden is already installed in `tamagui`, or you can install it independently:
```bash
npm install @tamagui/visually-hidden
```
## Usage
Simply wrap the content you want hidden in VisuallyHidden:
```tsx
import { VisuallyHidden, html } from 'tamagui'
export default () => (
Add annotations here
)
```
When using with the `space` property, it will avoid double-spacing:
```tsx
import { H1, VisuallyHidden, YStack, html } from 'tamagui'
export default () => (
Title
Add annotations here
)
```
## components/tooltip/1.0.0
---
title: Tooltip
description: A simple tooltip component
name: tooltip
component: Tooltip
package: tooltip
demoName: Tooltip
---
# Tooltip
A tooltip on web, with only accessibility output on native
```tsx hero template=Tooltip
```
Note that Tooltip doesn't render on native platforms.
## Installation
Tooltip is already installed in `tamagui`, or you can install it independently:
```bash
npm install @tamagui/tooltip
```
### PortalProvider
When rendering into root of app instead of inline, you'll first need to install the `@tamagui/portal` package:
```bash
npm install @tamagui/portal
```
Then add `PortalProvider` to the root of your app:
```tsx fileName="App.tsx"
import { PortalProvider } from '@tamagui/portal'
import YourApp from './components/YourApp'
function App() {
return (
)
}
export default App
```
## Anatomy
```tsx
import { Tooltip } from 'tamagui' // or '@tamagui/tooltip'
export default () => (
{/* ... */}
)
```
## API Reference
### Tooltip
Contains every component for the tooltip.
void',
required: false,
},
{
name: 'modal',
type: 'boolean',
default: 'true',
required: false,
description: `Renders into root of app instead of inline`,
},
{
name: 'stayInFrame',
type: 'ShiftProps',
required: false,
description: `See floating-ui shift()`,
},
{
name: 'allowFlip',
type: 'FlipProps',
required: false,
description: `See floating-ui flip`,
},
{
name: 'offset',
type: 'OffsetOptions',
required: false,
description: `Determines the distance the Popover appears from the target, see floating-ui offset().`,
},
]}
/>
If using `modal={true}` (which is `true` by default), refer to the [PortalProvider
installation](/ui/tooltip/1.0.0#portalprovider) for more information.
### Tooltip.Trigger
Used to trigger opening of the popover when uncontrolled, see YStack in [Stacks](/docs/components/stacks).
### Tooltip.Content
Renders as SizableStack (see [Stacks](/docs/components/stacks)) with an extra `size` prop that accepts any `SizeTokens`.
### Tooltip.Anchor
Renders as YStack, see [Stacks](/docs/components/stacks).
When you want the Trigger to be in another location from where the Tooltip attaches, use Anchor. When used, Anchor is where the Tooltip will attach, while Trigger will open it.
## components/tooltip/1.105.0
---
title: Tooltip
description: A simple tooltip component
name: tooltip
component: Tooltip
package: tooltip
demoName: Tooltip
---
# Tooltip
A tooltip on web, with only accessibility output on native
```tsx hero template=Tooltip
```
Note that Tooltip doesn't render on native platforms.
## Installation
Tooltip is already installed in `tamagui`, or you can install it independently:
```bash
npm install @tamagui/tooltip
```
### PortalProvider
Only if you aren't not using `tamagui` (but rather `@tamagui/core`) you'll need to install the `@tamagui/portal` package:
```bash
npm install @tamagui/portal
```
Then add `PortalProvider` to the root of your app:
```tsx fileName="App.tsx"
import { PortalProvider } from '@tamagui/portal'
import YourApp from './components/YourApp'
function App() {
return (
)
}
export default App
```
## Anatomy
```tsx
import { Tooltip } from 'tamagui' // or '@tamagui/tooltip'
export default () => (
{/* ... */}
)
```
## API Reference
### Tooltip
Contains every component for the tooltip.
void',
required: false,
},
{
name: 'modal',
type: 'boolean',
default: 'true',
required: false,
description: `Renders into root of app instead of inline`,
},
{
name: 'stayInFrame',
type: 'ShiftProps',
required: false,
description: `See floating-ui shift()`,
},
{
name: 'allowFlip',
type: 'FlipProps',
required: false,
description: `See floating-ui flip`,
},
{
name: 'offset',
type: 'OffsetOptions',
required: false,
description: `Determines the distance the Popover appears from the target, see floating-ui offset().`,
},
]}
/>
If using `modal={true}` (which is `true` by default), refer to the [PortalProvider
installation](/ui/tooltip/1.0.0#portalprovider) for more information.
### Tooltip.Trigger
Used to trigger opening of the popover when uncontrolled, see YStack in [Stacks](/docs/components/stacks).
### Tooltip.Content
Renders as SizableStack (see [Stacks](/docs/components/stacks)) with an extra `size` prop that accepts any `SizeTokens`.
### Tooltip.Anchor
Renders as YStack, see [Stacks](/docs/components/stacks).
When you want the Trigger to be in another location from where the Tooltip attaches, use Anchor. When used, Anchor is where the Tooltip will attach, while Trigger will open it.
### TooltipGroup
This allows you to logically group any tooltips rendered below this component. You can control their delay props, and components inside a TooltipGroup will be smart about opening quicker if you are moving between targets with tooltips, ensuring that subsequent tooltips show immediately rather than after a delay.
See the [Floating UI docs](https://floating-ui.com/docs/floatingdelaygroup) for full details on how this works.
## components/tooltip/1.122.0
---
title: Tooltip
description: A tooltip on web, with only accessibility output on native.
name: tooltip
component: Tooltip
package: tooltip
demoName: Tooltip
---
```tsx hero template=Tooltip
```
Note that Tooltip doesn't render on native platforms.
## Installation
Tooltip is already installed in `tamagui`, or you can install it independently:
```bash
npm install @tamagui/tooltip
```
### PortalProvider
If you are not using `tamagui` (but rather `@tamagui/core`) you'll need to install the `@tamagui/portal` package:
```bash
npm install @tamagui/portal
```
Then add `PortalProvider` to the root of your app:
```tsx fileName="App.tsx"
import { PortalProvider } from '@tamagui/portal'
import YourApp from './components/YourApp'
function App() {
return (
)
}
export default App
```
## Anatomy
```tsx
import { Tooltip } from 'tamagui' // or '@tamagui/tooltip'
export default () => (
{/* ... */}
)
```
## API Reference
### Tooltip
Contains every component for the tooltip.
void',
required: false,
},
{
name: 'modal',
type: 'boolean',
default: 'true',
required: false,
description: `Renders into root of app instead of inline`,
},
{
name: 'stayInFrame',
type: 'ShiftProps',
required: false,
description: `See floating-ui shift()`,
},
{
name: 'allowFlip',
type: 'FlipProps',
required: false,
description: `See floating-ui flip`,
},
{
name: 'offset',
type: 'OffsetOptions',
required: false,
description: `Determines the distance the Popover appears from the target, see floating-ui offset().`,
},
]}
/>
If using `modal={true}` (which is `true` by default), refer to the [PortalProvider
installation](#portalprovider) for more information.
### Tooltip.Trigger
Used to trigger opening of the popover when uncontrolled, see YStack in [Stacks](/docs/components/stacks).
### Tooltip.Content
Renders as SizableStack (see [Stacks](/docs/components/stacks)) with an extra `size` prop that accepts any `SizeTokens`.
### Tooltip.Anchor
Renders as YStack, see [Stacks](/docs/components/stacks).
When you want the Trigger to be in another location from where the Tooltip attaches, use Anchor. When used, Anchor is where the Tooltip will attach, while Trigger will open it.
### TooltipGroup
This allows you to logically group any tooltips rendered below this component. You can control their delay props, and components inside a TooltipGroup will be smart about opening quicker if you are moving between targets with tooltips, ensuring that subsequent tooltips show immediately rather than after a delay.
See the [Floating UI docs](https://floating-ui.com/docs/floatingdelaygroup) for full details on how this works.
### closeOpenTooltips
A small helper function of type `() => void` that will close any open tooltips.
## components/tooltip/2.0.0
---
title: Tooltip
description: A tooltip on web, with only accessibility output on native
name: tooltip
component: Tooltip
package: tooltip
demoName: Tooltip
---
```tsx hero template=Tooltip
```
Tooltip displays contextual information when hovering over an element. It waits for your mouse to stop moving before appearing, supports animations, and [automatically stacks](/ui/z-index) above other content.
Note that Tooltip doesn't render on native platforms.
## Installation
Tooltip is already installed in `tamagui`, or you can install it independently:
```bash
npm install @tamagui/tooltip
```
## Anatomy
```tsx
import { Tooltip } from 'tamagui' // or '@tamagui/tooltip'
export default () => (
{/* ... */}
)
```
## API Reference
### Tooltip
Contains every component for the tooltip.
void',
required: false,
description: `Event handler called when the open state changes.`,
},
{
name: 'modal',
type: 'boolean',
default: 'true',
required: false,
description: `Renders into root of app instead of inline.`,
},
{
name: 'stayInFrame',
type: 'ShiftProps | boolean',
required: false,
default: '{ padding: 10 }',
description: `Shifts the tooltip horizontally to stay within viewport bounds. Pass an object to customize shift behavior (mainAxis, crossAxis, padding).`,
},
{
name: 'allowFlip',
type: 'FlipProps',
required: false,
description: `See floating-ui flip().`,
},
{
name: 'offset',
type: 'OffsetOptions',
required: false,
description: `Determines the distance the Popover appears from the target, see floating-ui offset().`,
},
{
name: 'zIndex',
type: 'number',
required: false,
description: `Sets the z-index of the tooltip portal. Only needed for global/scoped tooltips mounted at the root - inline tooltips automatically participate in z-index stacking. See the Scoping section below.`,
},
]}
/>
If using `modal={true}` (which is `true` by default), refer to the [PortalProvider
installation](#portalprovider) for more information.
### Tooltip.Trigger
Used to trigger opening of the popover when uncontrolled, see YStack in [Stacks](/docs/components/stacks).
### Tooltip.Content
Renders as SizableStack (see [Stacks](/docs/components/stacks)) with an extra `size` prop that accepts any `SizeTokens`.
### Tooltip.Anchor
Renders as YStack, see [Stacks](/docs/components/stacks).
When you want the Trigger to be in another location from where the Tooltip attaches, use Anchor. When used, Anchor is where the Tooltip will attach, while Trigger will open it.
### TooltipGroup
This allows you to logically group any tooltips rendered below this component. You can control their delay props, and components inside a TooltipGroup will be smart about opening quicker if you are moving between targets with tooltips, ensuring that subsequent tooltips show immediately rather than after a delay.
See the [Floating UI docs](https://floating-ui.com/docs/floatingdelaygroup) for full details on how this works.
### closeOpenTooltips
A small helper function of type `() => void` that will close any open tooltips.
## Scoping
Tooltip supports scoping which lets you mount a single Tooltip instance at
the root of your app, while having deeply nested Trigger components anywhere
in your tree attach to that single parent Tooltip.
This is useful for performance - you only render `Tooltip.Trigger` in your
list items or buttons, while the heavier `Tooltip` and `Tooltip.Content` live
at the root. It also lets you style all your tooltips consistently in one place.
When using a global scoped tooltip, you should set `zIndex` to ensure tooltips appear above dialogs and other portaled content. This is because the global tooltip's portal is mounted outside the stacking context of other portals. Inline tooltips (rendered alongside their trigger) automatically participate in z-index stacking and don't need this.
```tsx fileName=_layout.tsx
import { Tooltip } from 'tamagui'
export default ({ children }) => (
// zIndex needed for global tooltips to appear above dialogs
{/* your tooltip content renders here */}
{/* the rest of your app renders inside */}
{children}
)
```
```tsx fileName=MyButton.tsx
export default () => (
)
```
The `scope` prop on `Trigger` connects it to the `Tooltip` with the matching scope.
## components/tooltip/3.0.0
---
title: Tooltip
description: A tooltip on web, with only accessibility output on native
name: tooltip
component: Tooltip
package: tooltip
demoName: Tooltip
---
Tooltip adds positioned, animated context to a trigger while preserving an
accessible description.
```tsx hero template=Tooltip
```
Tooltip displays contextual information when hovering over an element. It waits for your mouse to stop moving before appearing, supports animations, and [automatically stacks](/ui/z-index) above other content.
Note that Tooltip doesn't render on native platforms.
## Installation
Tooltip is already installed in `tamagui`, or you can install it independently:
```bash
npm install @tamagui/tooltip
```
## Anatomy
```tsx
import { Tooltip } from 'tamagui' // or '@tamagui/tooltip'
export default () => (
{/* ... */}
)
```
## API reference
### Tooltip
Contains every component for the tooltip.
void',
required: false,
description: `Event handler called when the open state changes.`,
},
{
name: 'modal',
type: 'boolean',
default: 'true',
required: false,
description: `Renders into root of app instead of inline.`,
},
{
name: 'stayInFrame',
type: 'ShiftProps | boolean',
required: false,
default: '{ padding: 10 }',
description: `Shifts the tooltip horizontally to stay within viewport bounds. Pass an object to customize shift behavior (mainAxis, crossAxis, padding).`,
},
{
name: 'allowFlip',
type: 'FlipProps',
required: false,
description: `See floating-ui flip().`,
},
{
name: 'offset',
type: 'OffsetOptions',
required: false,
description: `Determines the distance the Tooltip appears from the target, see floating-ui offset().`,
},
{
name: 'zIndex',
type: 'number',
required: false,
description: `Sets the z-index of the tooltip portal. Only needed for global/scoped tooltips mounted at the root - inline tooltips automatically participate in z-index stacking. See the Scoping section below.`,
},
]}
/>
On native, pair modal tooltips with [native portal
setup](/ui/portal#native-portal-setup-recommended) to preserve React context
inside Tooltip content.
### Tooltip.Trigger
Used to trigger opening of the tooltip when uncontrolled, see YStack in [Stacks](/ui/stacks).
### Tooltip.Content
Renders as a YStack (see [Stacks](/ui/stacks)); `size` sets width and height.
### Tooltip.Anchor
Renders as YStack, see [Stacks](/ui/stacks).
When you want the Trigger to be in another location from where the Tooltip attaches, use Anchor. When used, Anchor is where the Tooltip will attach, while Trigger will open it.
### TooltipGroup
This allows you to logically group any tooltips rendered below this component. You can control their delay props, and components inside a TooltipGroup will be smart about opening quicker if you are moving between targets with tooltips, ensuring that subsequent tooltips show immediately rather than after a delay.
See the [Floating UI docs](https://floating-ui.com/docs/floatingdelaygroup) for full details on how this works.
### closeOpenTooltips
A small helper function of type `() => void` that will close any open tooltips.
## Scoping
Tooltip supports scoping which lets you mount a single Tooltip instance at
the root of your app, while having deeply nested Trigger components anywhere
in your tree attach to that single parent Tooltip.
This is useful for performance - you only render `Tooltip.Trigger` in your
list items or buttons, while the heavier `Tooltip` and `Tooltip.Content` live
at the root. It also lets you style all your tooltips consistently in one place.
When using a global scoped tooltip, you should set `zIndex` to ensure tooltips appear above dialogs and other portaled content. This is because the global tooltip's portal is mounted outside the stacking context of other portals. Inline tooltips (rendered alongside their trigger) automatically participate in z-index stacking and don't need this.
```tsx fileName=_layout.tsx
import { Tooltip } from 'tamagui'
export default ({ children }) => (
// zIndex needed for global tooltips to appear above dialogs
{/* your tooltip content renders here */}
{/* the rest of your app renders inside */}
{children}
)
```
```tsx fileName=MyButton.tsx
export default () => (
)
```
The `scope` prop on `Trigger` connects it to the `Tooltip` with the matching scope.
## components/text/1.0.0
---
title: Text
description: Text primitives with themes custom to each font.
name: text
component: Paragraph
package: text
demoName: Text
---
```tsx hero template=Text
```
## Installation
Text is already installed in `tamagui`, or you can install it independently:
```bash
npm install @tamagui/text
```
## Usage
```tsx
export default () => (
<>
TextSizable TextParagraph
>
)
```
## Text
Text in Tamagui matches to Text in react-native-web, just with the added [Tamagui Props](/docs/intro/props).
It explicitly doesn't inherit your theme color or other font properties, as it's meant to be plain and used for extension. Below, we'll show `SizableText` which extends Text, and `Paragraph` which extends SizableText. Generally, Paragraph is the useful view as it will use theme values, while you can extend Text if you'd like to derive your own design system.
```tsx
import { Text, XStack, YStack } from 'tamagui'
export default () => (
<>
Lorem ipsum
>
)
```
## SizableText
Tamagui lets you define font sizing, spacing, line height, letter spacing and other properties with `createFont`, of which you can have many different configurations. We've found a nice pattern is to "align" all your keys across these sub-objects.
SizableText adds a `size` property thats defined using a [spread variant](/docs/core/styled#spread-variants) which looks for a matching key on each of these properties (using `@tamagui/get-font-sized`):
- color
- fontStyle
- textTransform
- fontFamily
- fontWeight
- letterSpacing
- fontSize
- lineHeight
So, if you've defined `small`, `medium` and `large` keys on each createFont category, you can use it like so:
```tsx
```
[Source code for SizableText](https://github.com/tamagui/tamagui/blob/main/code/ui/text/src/SizableText.tsx).
## Paragraph
Paragraph extends SizableText and is defined as:
```tsx
export const Paragraph = styled(SizableText, {
displayName: 'Paragraph',
tag: 'p',
userSelect: 'auto',
color: 'color',
size: true,
whiteSpace: 'normal',
})
```
Note: Paragraph renders to a `p` tag on web, which can cause issues when you nest them
during SSR. If you don't mind rendering to a span, use `SizableText`, otherwise, be
careful when nesting items inside a Paragraph.
## components/text/1.0.0-alpha
---
title: Text
description: Text, Sized Text and Paragraph show one way to build a design system.
name: text
component: Paragraph
demoName: Text
---
# Text
Text, Sized Text and Paragraph show one way to build a design system.
```tsx hero template=Text
```
### Usage
Text in Tamagui matches to Text in react-native-web, just with the added [Tamagui Props](/docs/intro/props).
It explicitly doesn't inherit your theme color or other font properties, as it's meant to be plain and used for extension. Below, we'll show `SizableText` which extends Text, and `Paragraph` which extends SizableText. Generally, Paragraph is the useful view as it will use theme values, while you can extend Text if you'd like to derive your own design system.
```tsx
import { Text, XStack, YStack } from 'tamagui'
export default () => (
<>
Lorem ipsum
>
)
```
SizableText and Paragraph default to the "body" fontFamily defined in your config.
Headings all default to "heading".
## SizableText
[Seeing how SizableText is defined](https://github.com/tamagui/tamagui/blob/v2/code/ui/text/src/SizableText.tsx) is helpful for understanding Tamagui. They serve as a good example of how you can extend and compose components.
SizableText simply adds a single `size` property to maniplulate all of:
- fontSize
- lineHeight
- fontWeight
- letterSpacing
Based on the values set in your tokens. It uses [spread variants](/docs/core/styled#spread-variants) feature. Then Paragraph extends that and ensures it defaults to values from your theme - fontSize, lineHeight, color and fontFamily.
## Paragraph
Finally Paragraph extends `SizableText` and simply sets some default values from your theme:
```tsx
export const Paragraph = styled(SizableText, {
fontFamily: 'body',
color: 'color',
size: true,
})
```
## components/text/2.0.0
---
title: Text
description: Text primitives with themes custom to each font
name: text
component: Paragraph
package: text
demoName: Text
---
Text primitives connect semantic text, configured fonts, shared sizing, and
Tamagui style props across web and native.
```tsx hero template=Text
```
The base `Text` component is already included in `@tamagui/core` (and `tamagui`). This
package is optional and adds `SizableText` and `Paragraph`, which extend `Text` with the
`size` prop and theme-aware defaults.
## Installation
If you're using `tamagui`, `SizableText` and `Paragraph` are already included. Otherwise, install independently:
```bash
npm install @tamagui/text
```
## Usage
```tsx
export default () => (
<>
TextSizable TextParagraph
>
)
```
## Text
Text in Tamagui matches Text in react-native-web, with the added [Tamagui props](/docs/intro/props).
It explicitly does not inherit your theme color or other font properties, as it is meant to be plain and used for extension. Below, we show `SizableText` which extends Text, and `Paragraph` which extends SizableText. Generally, Paragraph is the useful component as it uses theme values, while you can extend Text if you want to derive your own design system.
```tsx
import { Text, XStack, YStack } from 'tamagui'
export default () => (
<>
Lorem ipsum
>
)
```
## SizableText
Tamagui lets you define font sizing, spacing, line height, letter spacing and other properties with `createFont`, of which you can have many different configurations. We've found a nice pattern is to "align" all your keys across these sub-objects.
SizableText adds a `size` property whose variant is a [`styled.dynamic`](/docs/core/variants#dynamic-variants) callback, with a [component resolver](/docs/core/styled#component-resolvers-resolve) that looks for a matching key on each of these properties (using `@tamagui/get-font-sized`):
- color
- fontStyle
- textTransform
- fontFamily
- fontWeight
- letterSpacing
- fontSize
- lineHeight
So, if you've defined `small`, `medium` and `large` keys on each createFont category, you can use it like so:
```tsx
```
[Source code for SizableText](https://github.com/tamagui/tamagui/blob/main/code/ui/text/src/SizableText.tsx).
## Paragraph
Paragraph extends SizableText and is defined as:
```tsx
export const Paragraph = styled(SizableText, {
displayName: 'Paragraph',
render: 'p',
userSelect: 'auto',
color: 'color',
whiteSpace: 'normal',
size: true,
})
```
Note: Paragraph renders to a `p` tag on web, which can cause issues when you nest them
during SSR. If you don't mind rendering to a span, use `SizableText`, otherwise, be
careful when nesting items inside a Paragraph.
## Inline text elements
For semantic inline text styling, `@tamagui/text` exports `Strong`, `Em`, and `Span`:
```tsx
import { Strong, Em, Span, Paragraph } from '@tamagui/text'
export default () => (
This is bold text, this is italic text, and this is a{' '}
colored span.
)
```
These render to their corresponding HTML elements (``, ``, ``) on web while remaining compatible with React Native.
## Text truncation
Tamagui provides two props for truncating text with ellipsis: `ellipsis` for single-line truncation and `numberOfLines` for multi-line clamping.
### Single-line ellipsis
Use the `ellipsis` prop to truncate text to a single line with an ellipsis:
```tsx
import { Text, SizableText, Paragraph } from 'tamagui'
export default () => (
This is a very long text that will be truncated with an ellipsis when it overflows.
)
```
On web, this applies `text-overflow: ellipsis`, `white-space: nowrap`, and `overflow: hidden`. On native, it sets `numberOfLines={1}`.
### Multi-line clamping
Use the `numberOfLines` prop to clamp text to a specific number of lines:
```tsx
import { Paragraph } from 'tamagui'
export default () => (
<>
Single line truncation, same as ellipsis.
This text will be clamped to three lines. Any overflow beyond the third
line will be hidden. This is useful for card descriptions or preview text
where you want to show a limited amount of content.
>
)
```
On web, multi-line clamping uses `-webkit-line-clamp` with `-webkit-box` display. On native, it passes `numberOfLines` directly to the React Native Text component.
The `numberOfLines` prop is supported on the base `Text` component from `@tamagui/core`
as well as `SizableText` and `Paragraph`. The `ellipsis` prop is a shorthand for
`numberOfLines={1}`.
## API reference
### Text
The base text component from `@tamagui/core`. Accepts all [Tamagui style props](/docs/intro/props) for text elements.
### SizableText
Extends Text with the `size` prop for theme-aware font sizing.
### Paragraph
Extends SizableText with semantic defaults. Renders as `
` on web, uses theme color, and sets `userSelect: 'auto'` and `whiteSpace: 'normal'`.
## components/headings/1.0.0
---
title: Headings
description: Heading components that mimic HTML equivalents
name: html
component: Headings
package: text
demoName: Headings
---
```tsx hero template=Headings
```
## Installation
Headings is already installed in `tamagui`, or you can install it independently:
```bash
npm install @tamagui/text
```
```tsx
import { H1, H2, H3, H4, H5, H6, Heading } from 'tamagui'
export default () => (
<>
Heading 1
Heading 2
Heading 3
Heading 4
Heading 5
Heading 6
Heading
>
)
```
The headings all extend from the base `Heading` component. Note, this is just our own theme for Inter headings, but you can change the styles for any font completely.
Tamagui expects for your [font.size](/docs/core/configuration#font-tokens) to have the keys 1-10 so headings work with your font tokens automatically.
## How it works
The `Heading` component is defined as follows:
```tsx
export const Heading = styled(Paragraph, {
tag: 'span',
displayName: 'Heading',
accessibilityRole: 'header',
fontFamily: 'heading',
size: '8',
margin: 0,
})
```
Note that Heading, and H1-H6 all default to the `heading` font family that must be defined in your tamagui.config.ts.
Because [Paragraph](/docs/components/text#paragraph) extends [SizableText](/docs/components/text#sizabletext), you get automatic styles based on your font theme. Let's see how `SizableText` defines the size variant, roughly, which gives a good idea of how Tamagui works, and how you could create or change your own headings at a lower level.
```tsx
import { Text } from 'tamagui' // or '@tamagui/core'
const SizableText = styled(Text, {
displayName: 'SizableText',
fontFamily: 'body',
color: 'color',
variants: {
size: {
'...fontSize': (val, { font, props }) => {
const fontSize = font.size[val]
const lineHeight = font.lineHeight[val]
const fontWeight = font.weight[val]
const letterSpacing = font.letterSpacing[val]
const fontStyle = font.style?.[val]
const textTransform = font.transform?.[val]
return {
fontStyle,
textTransform,
fontWeight,
letterSpacing,
fontSize,
lineHeight,
}
},
},
},
defaultVariants: {
size: true,
},
})
```
## API Reference
### Heading
Headings extend [SizableText props](/docs/components/text#sizabletext) inheriting all the [Tamagui standard props](/docs/intro/props).
## components/headings/2.0.0
---
title: Headings
description: Heading components that mirror HTML equivalents
name: headings
component: Headings
package: text
demoName: Headings
---
Heading components map semantic HTML headings to your configured font sizes
on web and native.
```tsx hero template=Headings
```
## Installation
Headings is already installed in `tamagui`, or you can install it independently:
```bash
npm install @tamagui/text
```
```tsx
import { H1, H2, H3, H4, H5, H6, Heading } from 'tamagui'
export default () => (
<>
Heading 1
Heading 2
Heading 3
Heading 4
Heading 5
Heading 6
Heading
>
)
```
The headings all extend from the base `Heading` component. This is our own theme for Inter headings; you can restyle them for any font.
Tamagui expects your [font.size](/docs/core/configuration#font-tokens) tokens to include the keys 1-10 so headings resolve automatically.
## How it works
The `Heading` component is defined as follows:
```tsx
export const Heading = styled(Paragraph, {
render: 'span',
displayName: 'Heading',
role: 'heading',
fontFamily: 'heading',
margin: 0,
size: '8',
})
```
Note that Heading, and H1-H6 all default to the `heading` font family that must be defined in your tamagui.config.ts.
Because [Paragraph](/ui/text#paragraph) extends [SizableText](/ui/text#sizabletext), you get automatic styles based on your font theme. Let's see how `SizableText` defines the size variant, roughly, which gives a good idea of how Tamagui works, and how you could create or change your own headings at a lower level.
```tsx
import { Text } from 'tamagui' // or '@tamagui/core'
const SizableText = styled(Text, {
displayName: 'SizableText',
fontFamily: 'body',
color: 'color',
variants: {
size: {
'...fontSize': (val, { font, props }) => {
const fontSize = font.size[val]
const lineHeight = font.lineHeight[val]
const fontWeight = font.weight[val]
const letterSpacing = font.letterSpacing[val]
const fontStyle = font.style?.[val]
const textTransform = font.transform?.[val]
return {
fontStyle,
textTransform,
fontWeight,
letterSpacing,
fontSize,
lineHeight,
}
},
},
},
defaultVariants: {
size: true,
},
})
```
## API reference
### Heading
Headings extend [SizableText props](/ui/text#sizabletext) inheriting all the [Tamagui standard props](/docs/intro/props).
## components/anchor/1.0.0
---
title: Anchor
description: Link to external websites.
name: html
component: Anchor
---
## Usage
The Anchor component provides a way to link to external websites. It extends [SizableText](/docs/components/text#sizable-text), adding the `href`, `target`, and `rel` attributes.
On native, it will use React Native `Linking.openURL`, on web it will render to an `a` element with `href` set appropriately.
## API Reference
### Anchor
Inherits [Tamagui props](/docs/intro/props) as well as:
## components/anchor/2.0.0
---
title: Anchor
description: Link to external websites
name: anchor
component: Anchor
---
Anchor adds Tamagui style props and cross-platform behavior to links.
## Installation
Anchor is already installed in `tamagui`:
## Usage
Anchor extends [SizableText](/ui/text#sizabletext), adding the `href`, `target`, and `rel` attributes.
On native, it uses React Native `Linking.openURL`. On web, it renders an `a` element with `href` set appropriately.
## API reference
### Anchor
Inherits [Tamagui props](/docs/intro/props) as well as:
## components/alert-dialog/1.0.0
---
title: AlertDialog
description: Show an alert prompt in a dialog
name: alertDialog
component: AlertDialog
package: alert-dialog
demoName: AlertDialog
---
```tsx hero template=AlertDialog
```
## Installation
Alert Dialog is already installed in `tamagui`, or you can install it independently:
```bash
npm install @tamagui/alert-dialog
```
In order to use this component independently of `tamagui`, you'll first need to install the `@tamagui/portal` package:
```bash
npm install @tamagui/portal
```
Then add `PortalProvider` to the root of your app:
```tsx fileName="App.tsx"
import { PortalProvider } from '@tamagui/portal'
import YourApp from './components/YourApp'
function App() {
return (
)
}
export default App
```
## Anatomy
```tsx
import { AlertDialog } from 'tamagui' // or '@tamagui/alert-dialog'
export default () => (
{/* ... */}
)
```
## API Reference
### AlertDialog
Contains every component for the AlertDialog. Shares all [Dialog Props](/docs/components/dialog#api), except modal which is on by default. Adds:
### AlertDialog.Trigger
Just [Tamagui Props](/docs/intro/props).
### AlertDialog.Portal
Renders AlertDialog into appropriate container. Beyond [Tamagui Props](/docs/intro/props), adds:
### AlertDialog.Content
Main container for AlertDialog content, this is where you should apply animations.
Beyond [Tamagui Props](/docs/intro/props), adds:
### AlertDialog.Overlay
Displays behind Content. Beyond [Tamagui Props](/docs/intro/props), adds:
### AlertDialog.Title
Required. Can wrap in VisuallyHidden to hide.
Defaults to H2, see [Headings](/docs/components/headings).
### AlertDialog.Description
Required. Can wrap in VisuallyHidden to hide.
Defaults to Paragraph, see [Paragraph](/docs/components/text).
### AlertDialog.Cancel
Closes the AlertDialog, accepts all YStack props. Recommended to use with your own component and `asChild`.
### PortalProvider
## Examples
### Inside native modals
If you're using native modals (maybe from react-navigation), you'll notice the Dialogs won't show up inside the modal. To get around this, you should wrap your screen inside `PortalProvider`, like so:
```tsx
import { PortalProvider } from 'tamagui'
// this component used in react-navigation/expo-router with `presentation: "modal"`
export function Page() {
return (
{/* rest of your page, including the Dialog... */}
)
}
```
## components/alert-dialog/2.0.0
---
title: Alert Dialog
description: Show an alert prompt in a dialog
name: alert-dialog
component: AlertDialog
package: alert-dialog
demoName: AlertDialog
---
AlertDialog asks users to confirm an action in a focused, accessible modal.
```tsx hero template=AlertDialog
```
AlertDialog is built on Dialog with stricter accessibility requirements, and [automatically stacks](/ui/z-index) above other content.
## Installation
Alert Dialog is already installed in `tamagui`, or you can install it independently:
```bash
npm install @tamagui/alert-dialog
```
## Anatomy
```tsx
import { AlertDialog } from 'tamagui' // or '@tamagui/alert-dialog'
export default () => (
{/* ... */}
)
```
## API reference
### AlertDialog
Contains every component for the AlertDialog. Shares all [Dialog Props](/ui/dialog#api-reference), except modal which is on by default. Adds:
### AlertDialog.Trigger
Just [Tamagui Props](/docs/intro/props).
### AlertDialog.Portal
Renders AlertDialog into appropriate container. Beyond [Tamagui Props](/docs/intro/props), adds:
### AlertDialog.Content
Main container for AlertDialog content, this is where you should apply animations.
Beyond [Tamagui Props](/docs/intro/props), adds:
### AlertDialog.Overlay
Displays behind Content. Beyond [Tamagui Props](/docs/intro/props), adds:
### AlertDialog.Title
Required. Can wrap in VisuallyHidden to hide.
Defaults to H2, see [Headings](/ui/headings).
### AlertDialog.Description
Required. Can wrap in VisuallyHidden to hide.
Defaults to Paragraph, see [Paragraph](/ui/text#paragraph).
### AlertDialog.Cancel
Closes the AlertDialog, accepts all View props. Recommended to use with your own component and `asChild`.
### AlertDialog.Action
Confirms the AlertDialog action, accepts all View props. Recommended to use with your own component and `asChild`.
### AlertDialog.Destructive
A destructive action button for the AlertDialog. When using `native` mode on iOS, this renders with the native red destructive button style. Accepts all View props.
```tsx
Delete AccountDelete Account?This action cannot be undone.CancelDelete
```
### PortalProvider
## components/native/2.0.0
---
title: Native
description: Native integrations for portals, sheets, menus, toasts, and gradients
name: native
---
`@tamagui/native` connects Tamagui UI components to platform integrations for
gestures, portals, sheets, menus, toasts, and gradients.
For native press handling and the style runtime, see
[core Native](/docs/core/native).
## Setup
The `@tamagui/native` package provides setup utilities for native integrations.
Import the setup modules at your app's entry point, before any Tamagui imports.
```bash
npm install @tamagui/native
```
`@tamagui/native` is a light setup package. When you use one of its optional
integrations, make sure the matching native dependency is installed in your
app as well, such as `react-native-gesture-handler`, `react-native-teleport`,
`@expo/ui`, `zeego`, or `burnt`. In a monorepo, install those native packages in the
parent app or workspace root rather than a leaf package.
## Portal
**Native portals preserve React context**. The default implementation on native
uses a JS-based approach that breaks React context, or requires you to hoist
data awkwardly. With native portals, your custom contexts (navigation, app
state) work inside Sheet, Dialog, Popover, and other portaled content.
### Setup
```bash
npm install react-native-teleport
```
```tsx
// App.tsx - before any Tamagui imports
import '@tamagui/native/setup-teleport'
```
See [Portal docs](/ui/portal) for full details.
## Gesture Handler
Enable native gesture coordination for Sheet dragging and scrolling:
```bash
npm install react-native-gesture-handler
```
```tsx
import { setupGestureHandler } from '@tamagui/native/setup-gesture-handler'
import { GestureHandlerRootView } from 'react-native-gesture-handler'
setupGestureHandler({ pressEvents: false, sheet: true })
export default function App() {
return (
{/* your app */}
)
}
```
See [Sheet](/ui/sheet) for component usage. For native press events and
`PressBoundary`, see [core Native](/docs/core/native#gesture-handler).
## Menu & ContextMenu
**Native menus** render platform-native context menus instead of custom
overlays. They use the OS menu API for haptics, blur effects, and system
integration.
### Setup
```bash
npm install @expo/ui
```
```tsx
// App.tsx - before any Tamagui imports
import '@tamagui/native/setup-expo-ui-menu'
```
Then use the `native` prop:
```tsx
```
The Zeego adapter remains available for apps on compatible React Native versions:
```bash
npm install zeego @react-native-menu/menu react-native-ios-context-menu react-native-ios-utilities sf-symbols-typescript
```
```tsx
import '@tamagui/native/setup-zeego'
```
See [Menu docs](/ui/menu) and
[ContextMenu docs](/ui/context-menu) for full details.
## Toast
**Native toasts** use platform-native toast implementations:
- iOS: SPIndicator (system-style indicators)
- Android: ToastAndroid
- Web: Notification API
### Setup
```bash
npm install burnt
```
```tsx
// App.tsx - before any Tamagui imports
import '@tamagui/native/setup-burnt'
```
Then use the `native` prop on ToastProvider:
```tsx
{/* your app */}
```
See [Toast docs](/ui/toast) for full details.
## LinearGradient
**Native gradients** use `expo-linear-gradient` for high-performance native gradient rendering.
### Setup
```bash
npm install expo-linear-gradient
```
```tsx
// App.tsx - before any Tamagui imports
import '@tamagui/native/setup-expo-linear-gradient'
```
See [LinearGradient docs](/ui/linear-gradient) for full details.
## Optimized native runtime (experimental)
The native style runtime is configured at the core level. See
[core Native](/docs/core/native#native-runtime) for setup.
## components/select/1.0.0
---
title: Select
description: A simple select component
name: select
component: Select
package: select
demoName: Select
---
# Select
Show a menu of items users can select from one of.
```tsx hero template=Select
```
### Anatomy
```tsx
import { Select } from 'tamagui' // or '@tamagui/select'
export default () => (
)
```
Note that Select only works on Native using the Adapt component to adapt it to a Sheet.
See below for docs.
### API
#### <Select />
Contains every component for the select:
void',
description: `Callback on value change`,
},
{
name: 'open',
type: 'boolean',
description: `Controlled open value`,
},
{
name: 'defaultOpen',
type: 'boolean',
description: `Default open value`,
},
{
name: 'onOpenChange',
type: '(open: boolean) => void',
description: `Callback on open change`,
},
{
name: 'dir',
type: 'Direction',
description: `Direction of text display`,
},
{
name: 'name',
type: 'string',
description: `For use in forms`,
},
]}
/>
#### <Trigger />
Extends [ListItem](/docs/components/list-item) to give sizing, icons, and more.
#### <Value />
Extends [Paragraph](/docs/components/text), adding:
#### <Content />
Main container for Select content, used to contain the up/down arrows, no API beyond children.
#### <ScrollUpButton />
Inside Content first, displays when you can scroll up, stuck to the top.
Extends [YStack](/docs/components/stacks).
#### <ScrollDownButton />
Inside Content last, displays when you can scroll down, stuck to the bottom.
Extends [YStack](/docs/components/stacks).
#### <Viewport />
Extends [ThemeableStack](/docs/components/stacks#themeablestack). Contains scrollable content items as children.
#### <Group />
Extends [YStack](/docs/components/stacks). Use only when grouping together items, alongside a Label as the first child.
#### <Label />
Extends [ListItem](/docs/components/list-item). Used to label Groups.
#### <Item />
Extends [ListItem](/docs/components/list-item). Used to add selectable values ot the list. Must provide an index as React Native doesn't give any escape hatch for us to configure that automatically.
#### <ItemText />
Extends [Paragraph](/docs/components/text). Used inside Item to provide unselectable text that will show above once selected in the parent Select.
#### <Sheet />
When used alongside ``, Select will render as a sheet when that breakpoint is active.
This is the only way to render a Select on Native for now, as mobile apps tend to show Select very differently from web and Tamagui wants to present the right abstractions for each platform.
See [Sheet](/docs/components/sheet) for more props.
Must use `Select.SheetContents` inside the `Select.Sheet.Frame` to insert the contents given to `Select.Content`
```tsx
import { Select } from 'tamagui' // or '@tamagui/select'
export default () => (
)
```
## components/select/1.128.0
---
title: Select
description: Show a menu of items that users can select from.
name: select
component: Select
package: select
demoName: Select
---
```tsx hero template=Select
```
## Installation
Select is already installed in `tamagui`, or you can install it independently:
```bash
npm install @tamagui/select
```
## Anatomy
```tsx
import { Select } from 'tamagui' // or '@tamagui/select'
export default () => (
)
```
Note that Select only works on Native using the Adapt component to adapt it to a Sheet.
See below for docs.
## API Reference
### Select
Contains every component for the select:
void',
description: `Callback on value change`,
},
{
name: 'open',
type: 'boolean',
description: `Controlled open value`,
},
{
name: 'defaultOpen',
type: 'boolean',
description: `Default open value`,
},
{
name: 'onOpenChange',
type: '(open: boolean) => void',
description: `Callback on open change`,
},
{
name: 'dir',
type: 'Direction',
description: `Direction of text display`,
},
{
name: 'name',
type: 'string',
description: `For use in forms`,
},
{
name: 'native',
type: 'NativeValue',
description: `If passed, will render a native component instead of the custom one. Currently only \`web\` is supported.`,
},
]}
/>
### Select.Trigger
Extends [ListItem](/docs/components/list-item) to give sizing, icons, and more.
#### Select.Value
Extends [Paragraph](/docs/components/text), adding:
#### SelectContent
Main container for Select content, used to contain the up/down arrows, no API beyond children.
#### Select.ScrollUpButton
Inside Content first, displays when you can scroll up, stuck to the top.
Extends [YStack](/docs/components/stacks).
### Select.ScrollDownButton
Inside Content last, displays when you can scroll down, stuck to the bottom.
Extends [YStack](/docs/components/stacks).
### Select.Viewport
Extends [ThemeableStack](/docs/components/stacks#themeablestack). Contains scrollable content items as children.
Make sure to not pass `height` prop as that is managed internally because of UX reasons
and having a fixed height will break that behavior
### Select.Group
Extends [YStack](/docs/components/stacks). Use only when grouping together items, alongside a Label as the first child.
### Select.Label
Extends [ListItem](/docs/components/list-item). Used to label Groups.
### Select.Item
Extends [ListItem](/docs/components/list-item). Used to add selectable values to the list. Must provide an index as React Native doesn't give any escape hatch for us to configure that automatically.
### Select.ItemText
Extends [Paragraph](/docs/components/text). Used inside Item to provide unselectable text that will show above once selected in the parent Select.
### Select.FocusScope
Provides access to the underlying FocusScope component used by Select for focus management. Can be used to control focus behavior from a parent component.
void',
description: `Event handler called when auto-focusing on mount. Can be prevented`,
},
{
name: 'onUnmountAutoFocus',
type: '(event: Event) => void',
description: `Event handler called when auto-focusing on unmount. Can be prevented`,
},
]}
/>
### Select.Sheet
When used alongside ``, Select will render as a sheet when that breakpoint is active.
This is the only way to render a Select on Native for now, as mobile apps tend to show Select very differently from web and Tamagui wants to present the right abstractions for each platform.
See [Sheet](/docs/components/sheet) for more props.
Must use `Select.Adapt.Contents` inside the `Select.Sheet.Frame` to insert the contents given to `Select.Content`
```tsx
import { Select } from 'tamagui' // or '@tamagui/select'
export default () => (
)
```
## components/select/1.40.0
---
title: Select
description: Show a menu of items that users can select from.
name: select
component: Select
package: select
demoName: Select
---
```tsx hero template=Select
```
## Installation
Select is already installed in `tamagui`, or you can install it independently:
```bash
npm install @tamagui/select
```
## Anatomy
```tsx
import { Select } from 'tamagui' // or '@tamagui/select'
export default () => (
)
```
Note that Select only works on Native using the Adapt component to adapt it to a Sheet.
See below for docs.
## API Reference
### Select
Contains every component for the select:
void',
description: `Callback on value change`,
},
{
name: 'open',
type: 'boolean',
description: `Controlled open value`,
},
{
name: 'defaultOpen',
type: 'boolean',
description: `Default open value`,
},
{
name: 'onOpenChange',
type: '(open: boolean) => void',
description: `Callback on open change`,
},
{
name: 'dir',
type: 'Direction',
description: `Direction of text display`,
},
{
name: 'name',
type: 'string',
description: `For use in forms`,
},
{
name: 'native',
type: 'NativeValue',
description: `If passed, will render a native component instead of the custom one. Currently only \`web\` is supported.`,
},
]}
/>
### Select.Trigger
Extends [ListItem](/docs/components/list-item) to give sizing, icons, and more.
#### Select.Value
Extends [Paragraph](/docs/components/text), adding:
#### SelectContent
Main container for Select content, used to contain the up/down arrows, no API beyond children.
#### Select.ScrollUpButton
Inside Content first, displays when you can scroll up, stuck to the top.
Extends [YStack](/docs/components/stacks).
### Select.ScrollDownButton
Inside Content last, displays when you can scroll down, stuck to the bottom.
Extends [YStack](/docs/components/stacks).
### Select.Viewport
Extends [ThemeableStack](/docs/components/stacks#themeablestack). Contains scrollable content items as children.
Make sure to not pass `height` prop as that is managed internally because of UX reasons
and having a fixed height will break that behavior
### Select.Group
Extends [YStack](/docs/components/stacks). Use only when grouping together items, alongside a Label as the first child.
### Select.Label
Extends [ListItem](/docs/components/list-item). Used to label Groups.
### Select.Item
Extends [ListItem](/docs/components/list-item). Used to add selectable values ot the list. Must provide an index as React Native doesn't give any escape hatch for us to configure that automatically.
### Select.ItemText
Extends [Paragraph](/docs/components/text). Used inside Item to provide unselectable text that will show above once selected in the parent Select.
### Select.Sheet
When used alongside ``, Select will render as a sheet when that breakpoint is active.
This is the only way to render a Select on Native for now, as mobile apps tend to show Select very differently from web and Tamagui wants to present the right abstractions for each platform.
See [Sheet](/docs/components/sheet) for more props.
Must use `Select.Adapt.Contents` inside the `Select.Sheet.Frame` to insert the contents given to `Select.Content`
```tsx
import { Select } from 'tamagui' // or '@tamagui/select'
export default () => (
)
```
## components/select/2.0.0
---
title: Select
description: Show a menu of items that users can select from
name: select
component: Select
package: select
demoName: Select
---
```tsx hero template=Select
```
Select provides a dropdown menu for choosing from a list of options. It's fully accessible with keyboard navigation, supports typeahead search, and [automatically stacks](/ui/z-index) above other content.
## Installation
Select is already installed in `tamagui`, or you can install it independently:
```bash
npm install @tamagui/select
```
For native apps, we recommend [setting up native
portals](/docs/components/portal#native-portal-setup-recommended) to preserve React
context inside Select content.
## Anatomy
```tsx
import { Select } from 'tamagui' // or '@tamagui/select'
export default () => (
)
```
Note that Select only works on Native using the Adapt component to adapt it to a Sheet.
See below for docs.
## API Reference
### Select
Contains every component for the select:
void',
description: `Callback on value change.`,
},
{
name: 'open',
type: 'boolean',
description: `Controlled open value.`,
},
{
name: 'defaultOpen',
type: 'boolean',
description: `Default open value.`,
},
{
name: 'onOpenChange',
type: '(open: boolean) => void',
description: `Callback on open change.`,
},
{
name: 'dir',
type: 'Direction',
description: `Direction of text display.`,
},
{
name: 'name',
type: 'string',
description: `For use in forms.`,
},
{
name: 'native',
type: 'NativeValue',
description: `If passed, will render a native component instead of the custom one. Currently only \`web\` is supported.`,
},
{
name: 'renderValue',
type: '(value: string) => ReactNode',
description: `Render function for the selected value. Useful for SSR when you need custom display text. By default, Select falls back to showing the raw value for SSR compatibility.`,
},
{
name: 'lazyMount',
type: 'boolean',
default: 'false',
description: `When true, defers mounting Select items until opened using React's startTransition. Significantly improves initial render performance for pages with many Selects. Should be combined with \`renderValue\` for best results.`,
},
{
name: 'zIndex',
type: 'number',
description: `z-index for the select portal. Use when select dropdowns need to appear above other portaled content like dialogs or fixed headers. Defaults to automatic stacking (~100000).`,
},
]}
/>
### Select.Trigger
Extends [ListItem](/docs/components/list-item) to give sizing, icons, and more.
### Select.Value
Extends [Paragraph](/docs/components/text), adding:
### Select.Content
Main container for Select content, used to contain the up/down arrows, no API beyond children.
### Select.ScrollUpButton
Inside Content first, displays when you can scroll up, stuck to the top.
Extends [YStack](/docs/components/stacks).
### Select.ScrollDownButton
Inside Content last, displays when you can scroll down, stuck to the bottom.
Extends [YStack](/docs/components/stacks).
### Select.Viewport
Extends [ThemeableStack](/docs/components/stacks#themeablestack). Contains scrollable content items as children.
Make sure to not pass `height` prop as that is managed internally because of UX reasons
and having a fixed height will break that behavior.
### Select.Group
Extends [YStack](/docs/components/stacks). Use only when grouping together items, alongside a Label as the first child.
### Select.Label
Extends [SizableText](/docs/components/text). Used to label Groups. Includes size-based padding and minHeight for consistent appearance with other Select items.
### Select.Item
Extends [ListItem](/docs/components/list-item). Used to add selectable values to the list. Must provide an index as React Native doesn't give any escape hatch for us to configure that automatically.
### Select.ItemText
Extends [Paragraph](/docs/components/text). Used inside Item to provide unselectable text that will show above once selected in the parent Select.
### Select.Indicator
An animated indicator that highlights the currently focused item. Place it inside `Select.Viewport` to enable a smooth sliding highlight animation as users navigate through options.
```tsx
{/* items */}
```
Use the `transition` prop to control the animation speed. You can use any animation name from your config like `quick`, `quicker`, or `quickest`.
By default, Select uses the item's `hover:` and `press:` clauses for hover feedback. Add
`Select.Indicator` for a smoother animated effect. If using the indicator, you may want
to set items to have transparent hover styles to avoid visual conflict.
### Select.FocusScope
Provides access to the underlying FocusScope component used by Select for focus management. Can be used to control focus behavior from a parent component.
void',
description: `Event handler called when auto-focusing on mount. Can be prevented.`,
},
{
name: 'onUnmountAutoFocus',
type: '(event: Event) => void',
description: `Event handler called when auto-focusing on unmount. Can be prevented.`,
},
]}
/>
## Performance
For pages with many Select components, you can significantly improve initial render performance by using the `lazyMount` prop combined with `renderValue`:
```tsx
const labels = {
apple: 'Apple',
orange: 'Orange',
banana: 'Banana',
}
```
**How it works:**
- `lazyMount` defers mounting all Select items until the dropdown is first opened
- The mount happens inside React's `startTransition`, keeping the trigger responsive
- `renderValue` provides the display text synchronously, avoiding the need to mount items just to show the selected value
- Once mounted, items stay mounted for fast subsequent opens
This is especially useful when rendering many Selects on a single page, as each Select with 20+ items would otherwise mount all those items on initial page load.
### Select.Sheet
When used alongside ``, Select will render as a sheet when that breakpoint is active.
This is the only way to render a Select on Native for now, as mobile apps tend to show Select very differently from web and Tamagui wants to present the right abstractions for each platform.
See [Sheet](/docs/components/sheet) for more props.
Must use `Select.Adapt.Contents` inside the `Select.Sheet.Frame` to insert the contents given to `Select.Content`
```tsx
import { Select } from 'tamagui' // or '@tamagui/select'
export default () => (
)
```
## components/select/3.0.0
---
title: Select
description: Show a menu of items that users can select from
name: select
component: Select
package: select
demoName: Select
---
Select gives users an accessible, keyboard-navigable list of choices and can
adapt its content into a Sheet on smaller screens.
```tsx hero template=Select
```
Select provides a dropdown menu for choosing from a list of options. It's fully accessible with keyboard navigation, supports typeahead search, and [automatically stacks](/ui/z-index) above other content.
In v3, Select keeps adapted sheet content mounted through the sheet exit
animation, and web trigger and viewport parts expose `data-state="open" | "closed"`
for styling.
Select also supports ordered multiple selection across custom web lists,
browser-native controls, adapted Sheets, and plain React Native content. The
same item registry and value controller power every path.
## Installation
Select is already installed in `tamagui`, or you can install it independently:
```bash
npm install @tamagui/select
```
For native apps, we recommend [setting up native
portals](/ui/portal#native-portal-setup-recommended) to preserve React
context inside Select content.
## Anatomy
```tsx
import { Select } from 'tamagui' // or '@tamagui/select'
export default () => (
)
```
## Multiple selection
Pass `multiple` to use a controlled or uncontrolled `string[]`. Selecting a new
item appends it. Selecting it again removes it without changing the order of the
remaining values. Multiple selection keeps custom lists and adapted Sheets open.
```tsx
const [fruit, setFruit] = useState>([])
```
`Select.Value` renders registered `Select.ItemText` labels in selection order,
separated by `, `. Use `renderValue` for chips, localized separators, summaries,
or labels that must render before lazy items mount.
```tsx
),
},
{
name: 'value',
required: false,
type: 'string',
description: (
The controlled value of the item to expand when type is{' '}
"single". Must be used in conjunction with{' '}
onValueChange.
),
},
{
name: 'defaultValue',
required: false,
type: 'string',
description: (
The value of the item to expand when initially rendered and type is{' '}
"single". Use when you do not need to control the state of the
items.
),
},
{
name: 'onValueChange',
required: false,
type: '(value: string) => void',
typeSimple: 'function',
description: (
Event handler called when the expanded state of an item changes and{' '}
type is "single".
),
},
{
name: 'value',
required: false,
default: '[]',
type: 'string[]',
description: (
The controlled value of the item to expand when type is{' '}
"multiple". Must be used in conjunction with{' '}
onValueChange.
),
},
{
name: 'defaultValue',
required: false,
default: '[]',
type: 'string[]',
description: (
The value of the item to expand when initially rendered when type{' '}
is "multiple". Use when you do not need to control the state of the
items.
),
},
{
name: 'onValueChange',
required: false,
type: '(value: string[]) => void',
typeSimple: 'function',
description: (
Event handler called when the expanded state of an item changes and{' '}
type is "multiple".
),
},
{
name: 'collapsible',
required: false,
default: 'false',
type: 'boolean',
description: (
When type is "single", allows closing content when
clicking trigger for an open item.
),
},
{
name: 'disabled',
required: false,
type: 'boolean',
default: 'false',
description: (
When true, prevents the user from interacting with the accordion
and all its items.
),
},
{
name: 'dir',
required: false,
type: '"ltr" | "rtl"',
typeSimple: 'enum',
default: '"ltr"',
description:
'The reading direction of the accordion when applicable. If omitted, assumes LTR (left-to-right) reading mode.',
},
]}
/>
### Item
Contains all the parts of a collapsible section.
When true, prevents the user from interacting with the item.
),
},
{
name: 'value',
required: true,
type: 'string',
description: 'A unique value for the item.',
},
]}
/>
### Header
Wraps an `Accordion.Trigger`. Use the `asChild` prop to update it to the appropriate heading level for your page.
### Trigger
Toggles the collapsed state of its associated item. It should be nested inside of an `Accordion.Header`.
### Content
Contains the collapsible content for an item.
Used to force mounting when more control is needed. Useful when controlling
animation with React animation libraries.
),
},
]}
/>
## Examples
### Expanded by default
Use the `defaultValue` prop to define the open item by default.
```jsx line=1
……
```
### Allow collapsing all items
Use the `collapsible` prop to allow all items to close.
```jsx line=1
……
```
### Multiple items open at the same time
Set the `type` prop to `multiple` to enable opening multiple items at once.
```jsx line=1
……
```
## Accessibility
Adheres to the [Accordion WAI-ARIA design pattern](https://www.w3.org/TR/wai-aria-practices-1.1/#accordion).
## components/accordion/2.0.0
---
title: Accordion
description: A vertically stacked set of interactive headings with content
name: accordion
component: Accordion
package: accordion
demoName: Accordion
---
Accordion organizes related sections into keyboard-navigable items that can
expand one at a time or in groups.
```tsx hero template=Accordion
```
## Installation
Accordion is already installed in `tamagui`, or you can install it independently:
```bash
npm install @tamagui/accordion
```
## Anatomy
Import all parts and piece them together.
```tsx
import { Accordion } from 'tamagui' // or '@tamagui/accordion'
export default () => (
)
```
## API reference
### Accordion
Contains all the parts of an accordion.
Determines whether one or multiple items can be opened at the same time.
),
},
{
name: 'value',
required: false,
type: 'string',
description: (
The controlled value of the item to expand when type is{' '}
"single". Must be used in conjunction with{' '}
onValueChange.
),
},
{
name: 'defaultValue',
required: false,
type: 'string',
description: (
The value of the item to expand when initially rendered and type is{' '}
"single". Use when you do not need to control the state of the
items.
),
},
{
name: 'onValueChange',
required: false,
type: '(value: string) => void',
typeSimple: 'function',
description: (
Event handler called when the expanded state of an item changes and{' '}
type is "single".
),
},
{
name: 'value',
required: false,
default: '[]',
type: 'string[]',
description: (
The controlled value of the item to expand when type is{' '}
"multiple". Must be used in conjunction with{' '}
onValueChange.
),
},
{
name: 'defaultValue',
required: false,
default: '[]',
type: 'string[]',
description: (
The value of the item to expand when initially rendered when type{' '}
is "multiple". Use when you do not need to control the state of the
items.
),
},
{
name: 'onValueChange',
required: false,
type: '(value: string[]) => void',
typeSimple: 'function',
description: (
Event handler called when the expanded state of an item changes and{' '}
type is "multiple".
),
},
{
name: 'collapsible',
required: false,
default: 'false',
type: 'boolean',
description: (
When type is "single", allows closing content when
clicking trigger for an open item.
),
},
{
name: 'disabled',
required: false,
type: 'boolean',
default: 'false',
description: (
When true, prevents the user from interacting with the accordion
and all its items.
),
},
{
name: 'orientation',
required: false,
type: '"horizontal" | "vertical"',
typeSimple: 'enum',
default: '"vertical"',
description:
'The layout of the accordion. Determines which arrow keys move between triggers.',
},
{
name: 'dir',
required: false,
type: '"ltr" | "rtl"',
typeSimple: 'enum',
default: '"ltr"',
description:
'The reading direction of the accordion when applicable. If omitted, assumes LTR (left-to-right) reading mode.',
},
]}
/>
### Accordion.Item
Contains all the parts of a collapsible section.
When true, prevents the user from interacting with the item.
),
},
{
name: 'value',
required: true,
type: 'string',
description: 'A unique value for the item.',
},
]}
/>
### Accordion.Header
Wraps an `Accordion.Trigger`. Use the `asChild` prop to update it to the appropriate heading level for your page.
### Accordion.Trigger
Toggles the collapsed state of its associated item. It should be nested inside of an `Accordion.Header`.
### Accordion.Content
Contains the collapsible content for an item.
Used to force mounting when more control is needed. Useful when controlling
animation with React animation libraries.
),
},
]}
/>
## Examples
### Expanded by default
Use the `defaultValue` prop to define the open item by default.
```tsx line=1
……
```
### Allow collapsing all items
Use the `collapsible` prop to allow all items to close.
```tsx line=1
……
```
### Multiple items open at the same time
Set the `type` prop to `multiple` to enable opening multiple items at once.
```tsx line=1
……
```
## Accessibility
Adheres to the [Accordion WAI-ARIA design pattern](https://www.w3.org/TR/wai-aria-practices-1.1/#accordion).
## components/menu/2.0.0
---
title: Menu
description: A selectable list in a popover with nested submenus
name: menu
component: Menu
package: menu
demoName: Menu
---
Menu presents keyboard-navigable actions, selections, groups, and submenus in
a popover or platform-native menu.
```tsx hero template=Menu
```
Menu displays a list of actions or options in a floating panel triggered by a button. It supports nested submenus, keyboard navigation, native platform menus, and [automatically stacks](/ui/z-index) above other content.
## Installation
Menu is already installed in `tamagui`, or you can install it independently:
```bash
npm install @tamagui/menu
```
For Expo 57 and React Native 0.86, use the Expo UI adapter:
```bash
npm install @expo/ui
```
Then add the setup import at your app entry point:
```tsx
import '@tamagui/native/setup-expo-ui-menu'
```
The Zeego adapter remains available for apps on compatible React Native versions:
```bash
npm install @react-native-menu/menu react-native-ios-context-menu react-native-ios-utilities zeego sf-symbols-typescript
```
```tsx
import '@tamagui/native/setup-zeego'
```
On native, `Menu.Trigger` coordinates with parent Tamagui pressables
automatically. You should not need extra wrapper views or empty `onPress`
handlers just to prevent a parent `press:` clause from activating.
**Expo Router users:** This import must run _before_ `expo-router/entry`. Create an `index.js` at your project root that imports the setup first, then expo-router, and update your `package.json` main field to `"index.js"`. See the [Expo guide](/docs/guides/expo) for details.
## Anatomy
Import all parts and piece them together.
```tsx
import { Menu } from 'tamagui' // or '@tamagui/menu'
export default () => (
)
```
## API reference
### Menu
Contains every component for the Menu.
void',
required: false,
description: 'Called when the menu opens or closes.',
},
{
name: 'onOpenWillChange',
type: '(open: boolean) => void',
required: false,
platform: 'ios',
description: 'Called before the open/close animation begins.',
},
{
name: 'modal',
type: 'boolean',
default: 'true',
required: false,
description:
'When true, traps focus inside the menu and blocks outside scroll/interactions.',
},
{
name: 'stayInFrame',
type: 'ShiftProps | boolean',
required: false,
default: '{ padding: 10 }',
description: 'Shifts the menu horizontally to stay within viewport bounds. Pass an object to customize shift behavior (mainAxis, crossAxis, padding).',
},
{
name: 'allowFlip',
type: 'FlipProps | boolean',
required: false,
default: '{ padding: 10 }',
description: 'Flips the menu to the opposite side if there is insufficient space.',
},
{
name: 'offset',
type: 'OffsetOptions',
default: '10',
required: false,
description: 'Distance between the menu and its trigger.',
},
{
name: 'resize',
type: 'boolean',
default: 'true',
required: false,
description: 'Constrains the menu to fit within available viewport space. When enabled, use Menu.ScrollView inside Content for scrollable overflow.',
},
{
name: 'unstyled',
required: false,
type: 'boolean',
description: 'Removes all default Tamagui styles.',
},
]}
/>
### Menu.Portal
Required for rendering the menu content.
### Menu.TriggerGroup
Wrap related menu triggers in `Menu.TriggerGroup` to make a web menubar. Hovering
an enabled sibling switches menus once a menu in that group is open. Hovering
an idle group does not open a menu. Each panel opens at its own trigger.
```tsx
```
The container accepts [Tamagui Props](/docs/intro/props) and `dir="ltr" | "rtl"`.
It lays out triggers in a row. Left and right arrow keys move between enabled
triggers and wrap at either end. While a menu is open, they switch to the next
menu and focus its first item. Submenu opening and closing keys keep their
usual behavior. Escape closes the menu and returns focus to its active trigger.
You can also place a trigger group inside one `Menu` whose triggers share a
single content panel. Use trigger `onMouseEnter` and `onFocus` handlers to
prepare the corresponding contents. Groups are independent, and grouped web
menus are non-modal so sibling triggers remain reachable. Native platform
menus keep their platform interactions. `Menu.Group` continues to group items
inside a menu panel.
### Menu.Trigger
Trigger element for the menu. Accepts [Tamagui Props](/docs/intro/props).
### Menu.Content
Contains the content of the menu.
void',
required: false,
description: 'Called when focus returns to the trigger after closing.',
},
{
name: 'onEscapeKeyDown',
type: '(event: KeyboardEvent) => void',
required: false,
description: 'Called when the escape key is pressed. Can be prevented.',
},
{
name: 'onPointerDownOutside',
type: '(event: PointerEvent) => void',
required: false,
description: 'Called when a pointer event occurs outside the content.',
},
{
name: 'onInteractOutside',
type: '(event: Event) => void',
required: false,
description: 'Called when any interaction occurs outside the content.',
},
]}
/>
### Menu.Item
A selectable menu item that triggers an action when selected.
void',
required: false,
description: 'Called when the item is selected via click or keyboard.',
},
{
name: 'onFocus',
type: '() => void',
required: false,
description: 'Called when the item receives focus.',
},
{
name: 'onBlur',
type: '() => void',
required: false,
description: 'Called when the item loses focus.',
},
{
name: 'textValue',
type: 'string',
required: false,
platform: 'ios/android',
description:
'Text used for typeahead and native menus. Required when ItemTitle contains a React node instead of a string.',
},
]}
/>
### Menu.ItemTitle
Renders the title of the menu item.
You can directly pass a text node to the ItemTitle. However, if you use a nested
React node like ``, you need to pass `textValue` to the `` so that it
works with native menus.
### Menu.ItemIcon
A component to render an icon. For non-native menus, you can pass an icon
component. For native menus, you can pass platform-specific icons to the
`android` and `ios` props.
On iOS, it renders the native
[SF Symbols](https://github.com/andrewtavis/sf-symbols-online) icons.
```tsx line=1
```
### Menu.ItemImage
A component to render an item image. For native menus, it only works on iOS. It
takes the same props as `@tamagui/image`.
### Menu.ItemSubtitle
A component to render a subtitle for the menu item. For native menus, it only
works on iOS.
### Menu.Group
A component that groups multiple menu items together.
### Menu.CheckboxItem
A menu item with a checkbox that can be toggled on and off.
void',
required: false,
description: 'Called when the item receives focus.',
},
{
name: 'onBlur',
type: '() => void',
required: false,
description: 'Called when the item loses focus.',
},
{
name: 'textValue',
type: 'string',
required: false,
platform: 'ios/android',
description:
'Text for native menus. Required when ItemTitle contains a React node.',
},
{
name: 'value',
type: "'on' | 'off' | 'mixed'",
required: false,
platform: 'ios/android',
description: 'Controlled checked state for native menus.',
},
{
name: 'onValueChange',
type: '(state, prevState) => void',
required: false,
platform: 'ios/android',
description: 'Called when checked state changes on native menus.',
},
{
name: 'checked',
type: 'boolean',
required: false,
description: 'Controlled checked state for web menus.',
},
{
name: 'onCheckedChange',
type: '(checked: boolean) => void',
required: false,
description: 'Called when checked state changes on web.',
},
]}
/>
### Menu.RadioGroup
Groups a set of `Menu.RadioItem` and manages the selected value. Works on web
and native. On native, the group is rendered as a native menu group and each
`RadioItem` is mapped onto the OS checkmark state so exactly one item shows as
selected at a time.
```tsx
const [color, setColor] = React.useState('blue')
RedBlue
```
void',
required: false,
description: 'Called when the selected value changes.',
},
]}
/>
### Menu.RadioItem
An item that can be selected inside a `Menu.RadioGroup`. Render a
`Menu.ItemIndicator` inside to show a checkmark for the active value on web;
on native the OS draws its own checkmark and the `ItemIndicator` child is
dropped automatically.
void',
required: false,
description: 'Called when the item receives focus.',
},
{
name: 'onBlur',
type: '() => void',
required: false,
description: 'Called when the item loses focus.',
},
]}
/>
### Menu.ItemIndicator
Use inside `Menu.CheckboxItem` or `Menu.RadioItem` to indicate when an item is checked.
This allows you to conditionally render a checkmark.
```tsx line=1
{/* This does not work with the native prop. */}
```
### Menu.Label
Renders a non-focusable label for a group of items. On native menus, only one
label is supported per menu and submenu.
### Menu.Arrow
Renders an arrow pointing to the trigger.
### Menu.Separator
Renders a visual divider between menu items. Web only.
### Menu.Sub
A container for nested submenu components.
void',
required: false,
description: 'Called when the submenu opens or closes.',
},
]}
/>
### Menu.SubContent
Renders the content of a submenu. Same props as `Menu.Content`.
### Menu.SubTrigger
A menu item that opens a submenu on hover or focus. Accepts the same props as
`Menu.Item`.
### Menu.ScrollView
A scrollable container for menu items. Use this inside `Menu.Content` when you have many items that may overflow. The menu automatically constrains to available viewport space (via `resize` prop), and ScrollView handles the overflow. Scrollbars are hidden by default.
```tsx
{/* Many menu items */}
```
## Styling
### Item highlight behavior
Menu items use the `focus` condition for highlighting. Write it as a typed
object key or a `focus:` string clause. Focusing the highlighted item keeps
mouse and keyboard navigation in sync, with only one item highlighted at a time.
When you hover over an item, it receives focus, which activates the `focus:` clause. When you use arrow keys to navigate, focus moves to the new item, removing the highlight from the previous one.
StringTyped
```tsx
// default highlight follows focus
Settings
// customize the focus highlight
Custom Highlight
```
```tsx
// default highlight follows focus
Settings
// customize the focus highlight
Custom Highlight
```
Avoid using a `hover:` background clause on Menu.Item. This can cause "double
highlighting" when switching between mouse and keyboard, where both the hovered item
and the focused item appear highlighted simultaneously.
## components/inputs/1.0.0
---
title: Input & Textarea
name: inputs
description: Flexible form fields in styled and unstyled forms.
component: Input
demoName: Inputs
---
```tsx hero template=Inputs
```
Using the same base component TextInput, from [React Native](https://reactnative.dev/docs/textinput) or [React Native Web](https://necolas.github.io/react-native-web/docs/text-input/), Tamagui simply wraps these components to allow the full set of style props, as well as scaling all the styles up or down using the `size` property, much like Button.
## Installation
Input is already installed in `tamagui`, or you can install it independently:
```bash
npm install @tamagui/input
```
## Input
A one-line input field:
```tsx
import { Input } from 'tamagui'
export const App = () => (
// Accepts size and style properties directly
)
```
## TextArea
For multi-line inputs:
```tsx
import { TextArea } from 'tamagui'
export const App = () => (
// Accepts size and style properties directly
)
```
## components/inputs/2.0.0
---
title: Input & TextArea
name: inputs
description: Flexible form fields in styled and unstyled forms
component: Input
package: input
demoName: Inputs
---
Input and TextArea add Tamagui styling and shared sizing to the React Native
text input API.
```tsx hero template=Inputs
```
Input and TextArea wrap React Native's `TextInput`, from [React Native](https://reactnative.dev/docs/textinput) or [React Native Web](https://necolas.github.io/react-native-web/docs/text-input/). That gives them the full set of style props and scales their styles up or down with the `size` prop, much like Button.
## Installation
Input is already installed in `tamagui`, or you can install it independently:
```bash
npm install @tamagui/input
```
## Input
A one-line input field:
```tsx
import { Input } from 'tamagui'
export const App = () => (
// Accepts size and style properties directly
)
```
## TextArea
For multiline inputs:
```tsx
import { TextArea } from 'tamagui'
export const App = () => (
// Accepts size and style properties directly
)
```
## components/scroll-view/1.0.0
---
title: ScrollView
description: React Native ScrollView with Tamagui props.
name: scrollView
component: ScrollView
package: scroll-view
demoName: ScrollView
---
```tsx hero template=ScrollView
```
## Installation
ScrollView is already installed in `tamagui`, or you can install it independently:
```bash
npm install @tamagui/scroll-view
```
## Usage
```tsx
import { ScrollView, YStack, ListItem } from 'tamagui'
export default () => (
1234
)
```
## API Reference
### ScrollView
See [React Native ScrollView](https://reactnative.dev/docs/scrollview) and [Tamagui style props](https://tamagui.dev/docs/intro/props).
## components/scroll-view/2.0.0
---
title: ScrollView
description: React Native ScrollView with Tamagui props
name: scrollView
component: ScrollView
package: scroll-view
demoName: ScrollView
---
```tsx hero template=ScrollView
```
## Installation
ScrollView is already installed in `tamagui`, or you can install it independently:
```bash
npm install @tamagui/scroll-view
```
## Usage
```tsx
import { ScrollView, YStack, ListItem } from 'tamagui'
export default () => (
1234
)
```
## API Reference
### ScrollView
See [React Native ScrollView](https://reactnative.dev/docs/scrollview) and [Tamagui style props](https://tamagui.dev/docs/intro/props).
## components/scroll-view/3.0.0
---
title: ScrollView
description: Cross-platform ScrollView with Tamagui props
name: scrollView
component: ScrollView
package: scroll-view
demoName: ScrollView
---
ScrollView brings the React Native scrolling API and Tamagui style props to
native and web without a react-native-web dependency.
```tsx hero template=ScrollView
```
In v3, `@tamagui/scroll-view` ships its own web implementation instead of
wrapping `react-native-web`. It supports the ScrollView surface Tamagui uses most:
`scrollTo`, `scrollToEnd`, `getScrollableNode`, React Native-shaped `onScroll`,
`contentContainerStyle`, `horizontal`, and scroll indicator props.
Props outside that mapped surface, such as momentum events, `snapTo*`, and
`keyboardDismissMode`, are not supported on web through the lite implementation.
## Installation
ScrollView is already installed in `tamagui`, or you can install it independently:
```bash
npm install @tamagui/scroll-view
```
## Usage
```tsx
import { ListItem, ScrollView, YStack, style } from 'tamagui'
const contentContainer = style({
padding: 16,
gap: 8,
})
export default () => (
1234
)
```
## API reference
### ScrollView
`ScrollView` accepts Tamagui standard props and the mapped React Native ScrollView surface.
See [React Native ScrollView](https://reactnative.dev/docs/scrollview) and [Tamagui style props](/docs/intro/props). On web, prefer the mapped props documented above.
## components/portal/1.0.0
---
title: Portal
description: Send items to other areas of the tree.
name: portal
component: Portal
package: portal
---
Portal is included in `tamagui` as it's used by a few components. For now, it's simply using [@gorhom/portal](https://github.com/gorhom/react-native-portal).
## components/portal/2.0.0
---
title: Portal
description: Send items to other areas of the tree
name: portal
component: Portal
package: portal
---
Portal sends content to another part of the rendered tree while preserving
Tamagui theme and configuration context.
Portal is included in `tamagui` and is used by [Sheet](/ui/sheet),
[Dialog](/ui/dialog), [Popover](/ui/popover),
[Select](/ui/select), and [Toast](/ui/toast). See
[Stacking (zIndex)](/ui/z-index) for how overlay layering works.
## PortalProvider
`TamaguiProvider` from `tamagui` already wraps your app in a `PortalProvider`
with a root `PortalHost`, so most apps need no setup. Add one yourself only when
you render the bare `TamaguiProvider` from `@tamagui/core`:
```tsx
import { TamaguiProvider } from '@tamagui/core'
import { PortalProvider } from '@tamagui/portal'
export function App() {
return (
{/* your app */}
)
}
```
Render only one `PortalProvider` with a root host. Nesting a second one under
`TamaguiProvider` from `tamagui` mounts two root hosts and causes hydration
mismatches; pass `shouldAddRootHost={false}` if you need the nested provider.
## Native portal setup (recommended)
On web, React's built-in `createPortal` preserves context automatically. On
native, the default portal implementation doesn't preserve React context.
Tamagui automatically re-propagates its own contexts (theme, configuration), but
your custom contexts like navigation or app state won't be available inside
portaled content. The re-propagation also adds some overhead.
We recommend using
[react-native-teleport](https://github.com/kirillzyusko/react-native-teleport)
to solve this. It uses React Native's native portal API to preserve context
automatically.
### Step 1: install react-native-teleport
```bash
npm install react-native-teleport
```
### Step 2: import the setup module
In your app's entry file (index.js or App.tsx), before any Tamagui imports:
```tsx
import '@tamagui/native/setup-teleport'
```
That's it! All portal-using components will now preserve context automatically
on native. Without native portals, your custom context from parent components
won't be available inside portaled content:
```tsx
const MyContext = createContext('default')
function App() {
return (
{/* Without native portal: you'd not have context here on native */}
)
}
```
## Alternative approaches
If you can't use react-native-teleport, there are other ways to handle context
in portals:
### Component scoping
For Dialog, Popover, and Tooltip, use the `scope` prop to mount a single
instance at your app root. This avoids portals entirely on native:
```tsx
// _layout.tsx - mount once at root with all your providers
{/* label set by trigger */}
{/* rest of your app */}
// anywhere in your app - just the trigger
```
This pattern is also a performance win for lists or tables with many interactive
elements.
### Manual context re-propagation
Wrap portal children with the necessary providers:
```tsx
const theme = useTheme()
const myValue = useContext(MyContext)
{/* content that needs context */}
```
This is more verbose and error-prone as you need to remember to re-propagate
every context you use.
## API reference
### Portal
Enable automatic z-index stacking. Tamagui intelligently stacks z-index both
horizontally (based on mount order) and vertically (nested content is always
above parent but below sibling content that comes after). true{' '}
enables stacking, a number adds to the stacked value, 'global' only
stacks horizontally without nesting logic.
),
},
{
name: 'passThrough',
type: 'boolean',
description: `When true, renders children directly without portal behavior.`,
},
]}
/>
This automatic stacking is already enabled by default in Dialog, Popover, Sheet, and other overlay
components. If you open a Popover from within a Dialog, the Popover will automatically have a
higher z-index than the Dialog without any configuration needed.
### PortalProvider
## Technical details
[react-native-teleport](https://github.com/kirillzyusko/react-native-teleport)
uses `ReactNativeFabricUIManager.createPortal` (Fabric) or
`UIManager.createPortal` (Paper) to create true native portals that preserve the
React fiber tree.
The default portal implementation, by contrast, uses a JS-based approach with
context providers and a reducer to manage portal state. While compatible with
older RN versions, it breaks React context because it re-renders content in a
separate provider tree.
Tamagui includes a `needsPortalRepropagation()` helper that returns `true` when
using the default portal implementation and `false` when using native portals,
so library authors can conditionally re-propagate context only when needed.
## components/slider/1.0.0
---
title: Slider
description: A simple slider component
name: slider
component: Slider
package: slider
demoName: Slider
---
# Slider
Drag to set values, vertically or horizontally
```tsx hero template=Slider
```
## Installation
Slider is already installed in `tamagui`, or you can install it independently:
```bash
npm install @tamagui/slider
```
## Usage
Slider comes as multiple components that ship with default styles and are sizable. The `size` prop on `` will automatically pass size down to all the sub-components.
```tsx
import { Slider } from 'tamagui'
export default () => (
)
```
You can also optionally style any component, either using inline style props or by wrapping with `styled`:
```tsx
import { Slider, styled } from 'tamagui'
const CustomSliderTrack = styled(Slider.Track, {
backgroundColor: 'red',
})
export default () => (
)
```
## API Reference
### Slider
Contains every component for the slider.
### Slider.Track
`Slider.Track` Inherits `SizableStack`, extending all the default [props](/docs/intro/props).
### Slider.TrackActive
`Slider.Track` Inherits `View`, extending all the default [props](/docs/intro/props).
### Slider.Thumb
`Slider.Track` Inherits `SizableStack`, extending all the default [props](/docs/intro/props), adding:
## components/slider/1.45.0
---
title: Slider
description: Drag to set values, vertically or horizontally.
name: slider
component: Slider
package: slider
demoName: Slider
---
```tsx hero template=Slider
```
## Installation
Slider is already installed in `tamagui`, or you can install it independently:
```bash
npm install @tamagui/slider
```
## Usage
Slider comes as multiple components that ship with default styles and are sizable. The `size` prop on `` will automatically pass size down to all the sub-components.
```tsx
import { Slider } from 'tamagui'
export default () => (
)
```
You can also optionally style any component, either using inline style props or by wrapping with `styled`:
```tsx
import { Slider, styled } from 'tamagui'
const CustomSliderTrack = styled(Slider.Track, {
backgroundColor: 'red',
})
export default () => (
)
```
## API Reference
### Slider
Contains every component for the slider.
void`,
description: `Called on slide start.`,
},
{
name: 'onSlideMove',
required: false,
type: `(event: GestureReponderEvent, value: number) => void`,
description: `Called on slide move.`,
},
{
name: 'onSlideEnd',
required: false,
type: `(event: GestureReponderEvent, value: number) => void`,
description: `Called on slide end.`,
},
]}
/>
### Slider.Track
`Slider.Track` Inherits `SizableStack`, extending all the default [props](/docs/intro/props).
### Slider.TrackActive
`Slider.TrackActive` Inherits `View`, extending all the default [props](/docs/intro/props).
### Slider.Thumb
`Slider.Thumb` Inherits `SizableStack`, extending all the default [props](/docs/intro/props), adding:
## components/slider/2.0.0
---
title: Slider
description: Drag to set values, vertically or horizontally
name: slider
component: Slider
package: slider
demoName: Slider
---
```tsx hero template=Slider
```
## Installation
Slider is already installed in `tamagui`, or you can install it independently:
```bash
npm install @tamagui/slider
```
## Usage
Slider comes as multiple components that ship with default styles and are sizable. The `size` prop on `` will automatically pass size down to all the sub-components.
```tsx
import { Slider } from 'tamagui'
export default () => (
)
```
You can also optionally style any component, either using inline style props or by wrapping with `styled`:
```tsx
import { Slider, styled } from 'tamagui'
const CustomSliderTrack = styled(Slider.Track, {
backgroundColor: 'red',
})
export default () => (
)
```
## Vertical Slider on iOS
When using a vertical slider on iOS, you need to pass safe area insets to `TamaguiProvider` for proper pointer position calculation. Without this, the slider thumb may not track your finger correctly due to iOS safe area offsets.
```tsx
import { TamaguiProvider } from 'tamagui'
import { useSafeAreaInsets } from 'react-native-safe-area-context'
import { config } from './tamagui.config'
function App() {
const insets = useSafeAreaInsets()
return (
{/* Your app content */}
)
}
```
Then you can use vertical sliders normally:
```tsx
import { Slider, XStack } from 'tamagui'
export default () => (
)
```
This is only required on iOS. On Android and web, the slider works correctly without
passing insets.
## API Reference
### Slider
Contains every component for the slider.
void`,
description: `Called on slide start.`,
},
{
name: 'onSlideMove',
required: false,
type: `(event: GestureReponderEvent, value: number) => void`,
description: `Called on slide move.`,
},
{
name: 'onSlideEnd',
required: false,
type: `(event: GestureReponderEvent, value: number) => void`,
description: `Called on slide end.`,
},
]}
/>
### Slider.Track
Slider.Track inherits `SizableStack`, extending all the default [props](/docs/intro/props).
### Slider.TrackActive
Slider.TrackActive inherits `View`, extending all the default [props](/docs/intro/props).
### Slider.Thumb
Slider.Thumb inherits `SizableStack`, extending all the default [props](/docs/intro/props), adding:
## components/slider/3.0.0
---
title: Slider
description: Drag to set values, vertically or horizontally
name: slider
component: Slider
package: slider
demoName: Slider
---
Slider lets users set one or more values with pointer, touch, or keyboard
input in horizontal and vertical layouts.
```tsx hero template=Slider
```
## Installation
Slider is already installed in `tamagui`, or you can install it independently:
```bash
npm install @tamagui/slider
```
## Usage
Slider comes as multiple components that ship with default styles and are sizable. The `size` prop on `` will automatically pass size down to all the sub-components.
```tsx
import { Slider } from 'tamagui'
export default () => (
)
```
You can also optionally style any component, either using inline style props or by wrapping with `styled`:
```tsx
import { Slider, styled } from 'tamagui'
const CustomSliderTrack = styled(Slider.Track, {
backgroundColor: 'red',
})
export default () => (
)
```
## Vertical slider on iOS
When using a vertical slider on iOS, you need to pass safe area insets to `TamaguiProvider` for proper pointer position calculation. Without this, the slider thumb may not track your finger correctly due to iOS safe area offsets.
```tsx
import { TamaguiProvider } from 'tamagui'
import { useSafeAreaInsets } from 'react-native-safe-area-context'
import { config } from './tamagui.config'
function App() {
const insets = useSafeAreaInsets()
return (
{/* Your app content */}
)
}
```
Then you can use vertical sliders normally:
```tsx
import { Slider, XStack } from 'tamagui'
export default () => (
)
```
This is only required on iOS. On Android and web, the slider works correctly without
passing insets.
## API reference
### Slider
Contains every component for the slider.
void`,
description: `Called on slide start.`,
},
{
name: 'onSlideMove',
required: false,
type: `(event: GestureReponderEvent, value: number) => void`,
description: `Called on slide move.`,
},
{
name: 'onSlideEnd',
required: false,
type: `(event: GestureReponderEvent, value: number) => void`,
description: `Called on slide end.`,
},
]}
/>
### Slider.Track
Slider.Track inherits `YStack`, extending all the default [props](/docs/intro/props).
### Slider.TrackActive
Slider.TrackActive inherits `View`, extending all the default [props](/docs/intro/props).
### Slider.Thumb
Slider.Thumb inherits `YStack`, extending all the default [props](/docs/intro/props), adding:
## components/group/1.7.0
---
title: Group
description: Render horizontal or vertical groups easily.
name: group
component: Group
demoName: Group
---
# Group
Layout buttons and more with groups.
```tsx hero template=Group
```
## Usage
You can use `Group` by itself with the `axis` property determining the direction it assumes.
By default, Groups will control the border radius of their children automatically - the first and last children will get their start/end radius set to match group radius. If it's a `YGroup`, it will adjust top/bottom radius. `XGroup` adjusts the left/right radius.
You can use Groups with or without `Group.Item`, depending on if you want to spacing and separators to be handled based on direct children, or on each Item rendered.
```tsx
import { Button, XGroup } from 'tamagui'
// usage with Item:
export default () => (
FirstSecondThird
)
```
For a simpler use case and backwards compat, you can also use it without `Group.Item`, which will just apply borders and spacing based on direct children. Note that Group will detect if any `Group.Item` is inside it, and automatically switch modes. If no Item, it spaces direct children:
```tsx
import { Button, XGroup } from 'tamagui'
// usage without Item:
export default () => (
FirstSecondThird
)
```
## Sizing
The `size` property will use your tokens to grab the appropriate radius for borderRadius values which it will pass to the first and last child as style props for borderRadius.
```tsx
import { Button, XGroup } from 'tamagui'
export default () => (
FirstSecondThird
)
```
## Disabled
The `disabled` property will pass to children
## Group API
### Group
`Group`, `XGroup`and`YGroup` extend [YStack](/docs/components/stacks), getting [Tamagui standard props](/docs/intro/props), plus:
### Group.Item
Wrap each of `XGroup` or `YGroup`'s children in one of these. It lets Tamagui apply the needed styles to them.
Accepts only a `children` prop.
## components/group/1.0.0
---
title: Group
description: Render horizontal or vertical groups easily.
name: group
component: Group
demoName: Group
---
# Group
Layout buttons and more with groups.
```tsx hero template=Group
```
## Usage
Instead of a generic `Group`, Tamagui prefers `XGroup` and `YGroup` for consistency with stacks.
By default, Groups will control the border radius of their children automatically - the first and last children will get their start/end radius set to match group radius. If it's a `YGroup`, it will adjust top/bottom radius. `XGroup` adjusts the left/right radius.
You can use Groups with or without `Group.Item`, depending on if you want to spacing and separators to be handled based on direct children, or on each Item rendered.
```tsx
import { Button, XGroup } from 'tamagui'
// usage with Item:
export default () => (
FirstSecondThird
)
```
For a simpler use case and backwards compat, you can also use it without `Group.Item`, which will just apply borders and spacing based on direct children. Note that Group will detect if any `Group.Item` is inside it, and automatically switch modes. If no Item, it spaces direct children:
```tsx
import { Button, XGroup } from 'tamagui'
// usage without Item:
export default () => (
FirstSecondThird
)
```
## Sizing
The `size` property will use your tokens to grab the appropriate radius for borderRadius values which it will pass to the first and last child as style props for borderRadius.
```tsx
import { Button, XGroup } from 'tamagui'
export default () => (
FirstSecondThird
)
```
## Disabled
The `disabled` property will pass to children
## Group API
### Group
`XGroup` and `YGroup` extend [YStack](/docs/components/stacks), getting [Tamagui standard props](/docs/intro/props), plus:
### Group.Item
Wrap each of `XGroup` or `YGroup`'s children in one of these. It lets Tamagui apply the needed styles to them.
Accepts only a `children` prop.
## components/group/1.56.1
---
title: Group
description: Render horizontal or vertical groups easily.
name: group
component: Group
demoName: Group
---
```tsx hero template=Group
```
## Installation
Group is already installed in `tamagui`, or you can install it independently:
```bash
npm install @tamagui/group
```
## Usage
You can use `Group` by itself with the `orientation` property determining the direction it assumes.
By default, Groups will control the border radius of their children automatically - the first and last children will get their start/end radius set to match group radius. If it's a `YGroup`, it will adjust top/bottom radius. `XGroup` adjusts the left/right radius.
You can use Groups with or without `Group.Item`, depending on if you want to spacing and separators to be handled based on direct children, or on each Item rendered.
```tsx
import { Button, XGroup } from 'tamagui'
// usage with Item:
export default () => (
FirstSecondThird
)
```
For a simpler use case and backwards compat, you can also use it without `Group.Item`, which will just apply borders and spacing based on direct children. Note that Group will detect if any `Group.Item` is inside it, and automatically switch modes. If no Item, it spaces direct children:
```tsx
import { Button, XGroup } from 'tamagui'
// usage without Item:
export default () => (
FirstSecondThird
)
```
## Sizing
The `size` property will use your tokens to grab the appropriate radius for borderRadius values which it will pass to the first and last child as style props for borderRadius.
```tsx
import { Button, XGroup } from 'tamagui'
export default () => (
FirstSecondThird
)
```
## Disabled
The `disabled` property will pass to children
## API Reference
### Group
`Group`, `XGroup` and `YGroup` extend [YStack](/docs/components/stacks), getting [Tamagui standard props](/docs/intro/props), plus:
### Group.Item
Wrap each of `XGroup` or `YGroup`'s children in one of these. It lets Tamagui apply the needed styles to them. It accepts the following props:
## components/group/1.6.0
---
title: Group
description: Render horizontal or vertical groups easily.
name: group
component: Group
demoName: Group
---
# Group
Layout buttons and more with groups.
```tsx hero template=Group
```
## Usage
You can use `Group` by itself with the `axis` property determining the direction it assumes.
By default, Groups will control the border radius of their children automatically - the first and last children will get their start/end radius set to match group radius. If it's a `YGroup`, it will adjust top/bottom radius. `XGroup` adjusts the left/right radius.
You can use Groups with or without `Group.Item`, depending on if you want to spacing and separators to be handled based on direct children, or on each Item rendered.
```tsx
import { Button, XGroup } from 'tamagui'
// usage with Item:
export default () => (
FirstSecondThird
)
```
For a simpler use case and backwards compat, you can also use it without `Group.Item`, which will just apply borders and spacing based on direct children. Note that Group will detect if any `Group.Item` is inside it, and automatically switch modes. If no Item, it spaces direct children:
```tsx
import { Button, XGroup } from 'tamagui'
// usage without Item:
export default () => (
FirstSecondThird
)
```
## Sizing
The `size` property will use your tokens to grab the appropriate radius for borderRadius values which it will pass to the first and last child as style props for borderRadius.
```tsx
import { Button, XGroup } from 'tamagui'
export default () => (
FirstSecondThird
)
```
## Disabled
The `disabled` property will pass to children
## Group API
### Group
`Group`, `XGroup`and`YGroup` extend [YStack](/docs/components/stacks), getting [Tamagui standard props](/docs/intro/props), plus:
### Group.Item
Wrap each of `XGroup` or `YGroup`'s children in one of these. It lets Tamagui apply the needed styles to them.
Accepts only a `children` prop.
## components/group/1.11.2
---
title: Group
description: Render horizontal or vertical groups easily.
name: group
component: Group
demoName: Group
---
# Group
Layout buttons and more with groups
```tsx hero template=Group
```
## Usage
You can use `Group` by itself with the `orientation` property determining the direction it assumes.
By default, Groups will control the border radius of their children automatically - the first and last children will get their start/end radius set to match group radius. If it's a `YGroup`, it will adjust top/bottom radius. `XGroup` adjusts the left/right radius.
You can use Groups with or without `Group.Item`, depending on if you want to spacing and separators to be handled based on direct children, or on each Item rendered.
```tsx
import { Button, XGroup } from 'tamagui'
// usage with Item:
export default () => (
FirstSecondThird
)
```
For a simpler use case and backwards compat, you can also use it without `Group.Item`, which will just apply borders and spacing based on direct children. Note that Group will detect if any `Group.Item` is inside it, and automatically switch modes. If no Item, it spaces direct children:
```tsx
import { Button, XGroup } from 'tamagui'
// usage without Item:
export default () => (
FirstSecondThird
)
```
## Sizing
The `size` property will use your tokens to grab the appropriate radius for borderRadius values which it will pass to the first and last child as style props for borderRadius.
```tsx
import { Button, XGroup } from 'tamagui'
export default () => (
FirstSecondThird
)
```
## Disabled
The `disabled` property will pass to children
## API Reference
### Group
`Group`, `XGroup` and `YGroup` extend [YStack](/docs/components/stacks), getting [Tamagui standard props](/docs/intro/props), plus:
### Group.Item
Wrap each of `XGroup` or `YGroup`'s children in one of these. It lets Tamagui apply the needed styles to them.
Accepts only a `children` prop.
## components/group/2.0.0
---
title: Group
description: Arrange controls in horizontal or vertical groups
name: group
component: Group
package: group
demoName: Group
---
Group arranges related controls horizontally or vertically, squares off the
border radius where items meet, and passes disabled state to its children.
```tsx hero template=Group
```
## Installation
Group is already installed in `tamagui`, or you can install it independently:
```bash
npm install @tamagui/group
```
## Usage
Use `Group` with `Group.Item` wrapping each child. The `orientation` property determines the layout direction.
Group zeros out the border radius on connecting sides of children: the first child keeps its start radius, the last keeps its end radius, and middle items have no radius on connecting sides. For `YGroup`, this affects top/bottom radius. For `XGroup`, left/right radius.
```tsx
import { Button, XGroup } from 'tamagui'
export default () => (
FirstSecondThird
)
```
## Sizing
Children control their own sizing. Apply size props directly to the child components. For responsive sizing, use media queries on the children:
```tsx
import { Activity, Airplay } from './components/icons' // your generated icons, see Lucide Icons
import { Button, XGroup } from 'tamagui'
export default () => (
First
Second
)
```
## Separators
Add separators manually between items:
```tsx
import { ListItem, Separator, YGroup } from 'tamagui'
export default () => (
)
```
## Disabled
The `disabled` property will pass to children.
## Custom components & nested items
Automatic index detection only works when `Group.Item` is a **direct child** of `Group`. If you wrap `Group.Item` inside a custom component, the automatic first/last detection won't work.
You have a few options:
### Option 1: use `forcePlacement`
If you know the position ahead of time, use the `forcePlacement` prop:
```tsx
function MyFirstItem({ children }) {
return {children}
}
export default () => (
FirstSecond
)
```
### Option 2: use `useGroupItem` hook
For full control, use the `useGroupItem` hook directly in your custom component:
```tsx
import { useGroupItem } from '@tamagui/group'
function MyItem({ children, forcePlacement }) {
const groupItemProps = useGroupItem({ disabled: false }, forcePlacement)
return React.cloneElement(children, groupItemProps)
}
```
### Option 3: pass placement from parent
Calculate positions in the parent and pass them down:
```tsx
const items = ['First', 'Second', 'Third']
export default () => (
{items.map((item, i) => (
{item}
))}
)
```
## API reference
### Group
`Group`, `XGroup` and `YGroup` extend [YStack](/ui/stacks), getting [Tamagui standard props](/docs/intro/props), plus:
### Group.Item
Wrap each of `XGroup` or `YGroup`'s children in one of these. It lets Tamagui apply the needed styles to them. It accepts the following props:
## components/field/1.0.0
---
title: Field
description: Accessible labels, messages, and validation state for form controls
name: field
component: Field
package: field
demoName: Field
---
Field connects labels, controls, descriptions, validation, and error messages
across web and native forms.
```tsx hero template=Field
```
Field groups a control with its accessible name, supporting messages, and
validation state. It works on its own for common forms and can also sit under
React Hook Form or another form library.
The package has no visual defaults. Style its parts directly or copy a small
skin into your app.
## Installation
Field is already installed in `tamagui`, or you can install it independently:
```bash
npm install @tamagui/field
```
## Usage
```tsx
import { Field, Form } from 'tamagui'
export function ProfileForm() {
return (
)
}
```
Controls integrate through `useFieldControl()`. The hook is also the extension
point for custom inputs:
```tsx
import { Field, Input } from 'tamagui'
import { useEffect, useRef } from 'react'
function EmailInput(props) {
const field = Field.useFieldControl()
const ref = useRef(null)
const value = useRef('')
useEffect(() => {
return field.registerControl({
id: field.ariaProps.id,
controlRef: ref,
inputRef: ref,
getValue: () => value.current,
required: props.required,
})
}, [field.ariaProps.id, field.registerControl, props.required])
return (
field.onBlur(value.current)}
onChange={(event) => {
value.current = event.target.value
field.onChange(value.current)
}}
/>
)
}
```
Outside a Field, `useFieldControl()` returns a stable no-op contract. A shared
control can consume it without adding a wrapper check or a second render path.
## Validation
Pass a function that returns one error, several errors, or `null`:
```tsx
{
if (String(value).length < 3) {
return 'Use at least 3 characters'
}
if (value === formValues.email) {
return 'Username and email must differ'
}
return null
}}
>
{/* ... */}
```
Async validators are supported. They never delay a submit. Field uses
monotonic validation commits, so a slow response for an old value cannot
replace a newer result.
Field also accepts a [Standard Schema](https://standardschema.dev/) directly.
Zod 4, Valibot, and ArkType implement this interface:
```tsx
import * as v from 'valibot'
const email = v.pipe(v.string(), v.email('Enter a valid email'))
{/* ... */}
```
`valid` is intentionally tri-state:
- `null` means the field has not been evaluated.
- `true` means the latest validation passed.
- `false` means the field is invalid.
Pristine fields do not emit valid or invalid state. A missing required value is
also suppressed until the user edits the field or tries to submit the form.
## Validation modes
`validationMode` can be set on Form and overridden by a Field:
- `onSubmit` is the default. After the first submit attempt, that field
revalidates as its value changes.
- `onBlur` validates when focus leaves the control.
- `onChange` validates on every change. Use `validationDebounceTime` to delay
expensive checks.
```tsx
```
## Styling field state
Every Field part emits `data-valid`, `data-invalid`, `data-touched`,
`data-dirty`, `data-filled`, `data-focused`, and `data-disabled` on web.
The same keys are published through `FieldStyledContext` on native and web.
```tsx
import { Field, FieldStyledContext, Input, styled } from 'tamagui'
const FieldInput = styled(Input, {
context: FieldStyledContext,
variants: {
invalid: {
true: {
borderColor: 'red-9',
},
},
},
})
```
## Groups with Field.Item
Use `Field.Item` when one logical field owns several controls. Each item gets
its own label and description while sharing the root field state.
```tsx
NotificationsChoose every channel you want to use.EmailPush
```
## Anatomy
```tsx
{/* integrated control */}
```
## API reference
### Field
Contains the field state and validation behavior. It accepts
[Tamagui Props](/docs/intro/props), plus:
### Field.Label
An accessible label. It is associated with the current item control
automatically. On native, pressing it focuses the registered control.
Accepts [Tamagui Props](/docs/intro/props).
### Field.Description
Supporting text added to the control description. Associations continue to
work when the message is rendered through a portal.
Accepts [Tamagui Props](/docs/intro/props).
### Field.Error
Renders the current validation or Form error when the field is invalid.
### Field.Item
Creates label and description scope for one member of a grouped field.
### useFieldState
Returns the current value, errors, validity, and the `touched`, `dirty`,
`filled`, `focused`, and `disabled` flags.
### useFieldControl
Returns the control integration contract: `name`, `disabled`, `ariaProps`,
`dataProps`, focus/change reporters, and `registerControl`.
## components/surface/3.0.0
---
title: Surface
description: A panel, well, and toolbar primitive built from facets and theme levels
name: surface
component: Surface
---
Surface is the starting point for panels, wells, and toolbars: a `YStack`
with a `level` prop and composable visual and interaction facets.
Nothing is on by default. A bare `` renders no chrome and no
interaction styling, and every facet is opt-in at the use site.
```tsx
import { Surface } from 'tamagui'
export default () =>
```
## Installation
Surface is already installed in `tamagui`:
```tsx
import { Surface } from 'tamagui'
```
Surface is also a registry item you can copy into your own component layer and
own; see [Build your own](#build-your-own).
## Levels
`level` shifts the subtree through the relative `level2`, `level3`, or `level4`
sub-theme. Because facets read theme generics (`background`, `border-color`, …)
and the level theme re-binds those generics, a level restyles every facet
beneath it with no cooperation. Read [Surfaces and
levels](/docs/core/surfaces) for the full theming model.
```tsx
Base panelA step upA step furtherThe highest default level
```
`level` is a prop, not a variant, because a theme boundary can only be created by
the `theme` prop or `` component. `` renders
`` around the frame.
## Facets
Facets are canonical boolean variants, each a pure function of theme generics plus
standard tokens. Chrome facets own one property family each and set static
styles only; the interaction facet owns pseudos only. Because family ownership
keeps them from colliding, any combination composes with zero coordination.
There are no preset combinations. The Material-style border-minus-fill look is just
`outlined` without `filled`: a documented composition, not a separate facet.
## Build your own
Most apps just import Surface. Copy it into your own component layer when you
want variants of your own: a card panel with different defaults than a toolbar
well, your own radius, or facets tuned to your design system. The copy is a plain
component: change it freely and nothing in the framework cares.
Copy it from the registry:
```bash
npx shadcn add surface
```
That installs `components/tamagui/Surface.tsx` plus its sibling
`components/tamagui/facets.tsx`, which holds the shared facet definitions.
`Surface.tsx` imports them through a relative `./facets` import, so keep the two
files together and adjust the path if your tree puts them elsewhere.
Surface is generated from a single definition. The `level` wrapper and the facet
set are all it is:
```tsx
import { type GetProps, styled, Theme } from '@tamagui/core'
import { YStack } from '@tamagui/stacks'
import { forwardRef } from 'react'
import { elevated, filled, interactive, outlined, rounded } from './facets'
export const SurfaceFrame = styled(YStack, {
displayName: 'Surface',
variants: {
filled,
outlined,
elevated,
roundedFacet: rounded,
interactive,
} as const,
})
export type SurfaceProps = Omit<
GetProps,
'roundedFacet' | 'rounded'
> & {
/** shift the subtree to a relative theme level. */
level?: 1 | 2 | 3 | 4
/** add the default component radius without depending on config shorthands. */
rounded?: boolean
}
export const Surface = forwardRef(function Surface(
{ level, rounded, ...props },
ref
) {
const frame =
if (!level || level === 1) return frame
return {frame}
})
```
The facets live in a sibling `facets.tsx` so any skin can compose the same chrome:
```tsx
export const filled = {
true: { backgroundColor: 'background' },
} as const
export const outlined = {
true: { borderWidth: 1, borderColor: 'border-color' },
} as const
export const elevated = {
true: {
shadowColor: 'shadow-color',
shadowRadius: 8,
shadowOffset: { width: 0, height: 2 },
},
} as const
export const rounded = {
true: { borderRadius: '4' },
} as const
export const interactive = {
true: {
backgroundColor: 'hover:background-hover press:background-press',
borderColor: 'hover:border-color-hover press:border-color-press',
scale: 'press:0.97',
outlineColor: 'focus-visible:outline-color',
outlineWidth: 'focus-visible:2px',
outlineStyle: 'focus-visible:solid',
},
} as const
```
Component skins like Card and ListItem do not extend Surface. They get their
family resemblance by styling against the same generics, so restyling a level
recolors them too. Fork the copy when you want a differently-shaped panel.
## API reference
### Surface
A `YStack` with a `level` prop and composable facets, plus all the
[Tamagui standard props](/docs/intro/props):
## components/adapt/3.0.0
---
title: Adapt
description: Re-render a component's contents into a different container per breakpoint or platform
name: adapt
component: Adapt
package: adapt
---
Adapt lets one component render its contents somewhere else. A Select that is a
dropdown on desktop becomes a Sheet on touch devices, without duplicating the
contents or the open state.
## Installation
Adapt ships with `tamagui`, or you can install it independently:
```bash
npm install @tamagui/adapt
```
## How it works
Adapt is two markers used together:
- `` says **when** to adapt and **what to adapt into**.
- `` says **where** inside that new container the original
contents should appear.
The contents are not re-rendered from scratch into the new tree. They are
published to a slot and read by `Adapt.Contents`, so state inside them survives
the switch, and the adapting component keeps owning `open` / `onOpenChange`.
```tsx
import { Adapt, Select, Sheet } from 'tamagui'
export default () => (
{/* the desktop dropdown */}
{/* Select.Content lands here when adapted */}
)
```
When the `max-md` media query is inactive, the `` subtree renders nothing
and `Select.Content` renders in place as usual. When it becomes active, the
Sheet mounts and `Select.Content` moves into it.
`` must be a child of a component that supports adapting. Rendering it
anywhere else throws. Dialog, Popover and Select also expose it as a static
property (``, ``, ``), which is the
same component.
## Choosing when to adapt
`when` and `platform` are both optional, but at least one is required: with
neither set, Adapt is never active.
```tsx
{/* active below the md breakpoint */}
{/* active on any touch device, at any width */}
{/* active only on a touch device below md */}
{/* always active */}
```
`when` takes any media query key from your config, or a boolean. `platform`
takes `'web'`, `'native'`, `'ios'`, `'android'` or `'touch'`.
When you set both, both must pass. `` is on
for every width, but only on touch devices.
Because `when` reads media state, you can also drive it from your own logic:
```tsx
{/* ... */}
```
## Adapting Dialog and Popover
Dialog and Popover use the same handoff. For both, put `Adapt.Contents` inside
the Sheet's scrollable area:
```tsx
import { Adapt, Popover, Sheet } from 'tamagui'
export default () => (
Open
{/* ... */}
)
```
Adapted content stays mounted through the Sheet's exit animation and only
unmounts once the Sheet reports that it is fully hidden, so closing looks the
same whether or not the component is adapted.
Parts that only make sense in the unadapted layout drop out while adapted:
`Popover.Arrow`, `Dialog.Overlay` and `Dialog.Close` all render `null`, and
Dialog's content renders without its own frame, focus scope or dismissable
layer, so the Sheet's take over. `Dialog.Close` takes `displayWhenAdapted` if
you do want it inside the Sheet.
## Scoping
Leave `scope` off and `` binds to the nearest adapting parent, which is
what you want nearly always. Nesting is already safe: a parent only reads an
`` from its own direct children (descending into fragments), so a Dialog
rendered inside a Popover's content does not steal the Popover's adaptation, or
hand it its own.
The `scope` on `` is not the `scope` you pass to Dialog, Popover or
Select. It is that component's internal adapt scope, which is its own scope
prefixed by the component name:
| Component | Adapt scope |
| --------- | ---------------------- |
| Dialog | `DialogAdapt` |
| Popover | `PopoverAdapt` |
| Select | `AdaptSelect` |
An unscoped Dialog is therefore `DialogAdapt`, and `
```
They accept your tokens/theme keys for color and size.
## Credit
The great [Lucide Icons](https://lucide.dev/), a superset of the wonderful [Feather Icons](https://feathericons.com/).
## components/lucide-icons/2.0.0
---
title: Lucide Icons
description: Cross-platform compatible SVG-based icons
name: lucide-icons
component: LucideIcons
package: lucide-icons
demoName: LucideIcons
---
## Installation
```sh
yarn add react-native-svg @tamagui/lucide-icons-2
```
If you're using icons in a monorepo, install `react-native-svg` in the parent
app or workspace root rather than a leaf package.
## Usage
Use them as regular React components:
```tsx
import { Button } from 'tamagui'
import { Plus } from '@tamagui/lucide-icons-2'
// Button will automatically pass size/theme to icon
export default () => Hello world
// or you can control it
export default () => }>Hello world
```
They accept your tokens/theme keys for color and size.
## Credit
The great [Lucide Icons](https://lucide.dev/), a superset of the wonderful [Feather Icons](https://feathericons.com/).
## components/lucide-icons/3.0.0
---
title: Lucide Icons
description: Generate only the icons you use, typed and themed for Tamagui
name: lucide-icons
component: LucideIcons
package: lucide-icons
demoName: LucideIcons
---
Tamagui v3 ships no bundled icon package. Instead you generate typed SVG
icon components for only the icons you use, rendering consistently on web
and native.
The browser above is a generated Lucide set, built with the command on this
page. v2's `@tamagui/lucide-icons-2` shipped all ~1700 Lucide icons as
one dependency; most apps use a few dozen, so v3 replaces it with
generation: one tree-shakeable file per icon, typed `IconProps`, and the same
`themed` size/color behavior through `@tamagui/helpers-icon`.
## Setup
Install the runtime dependencies:
```bash
npm install react-native-svg @tamagui/helpers-icon
```
If you're using icons in a monorepo, install `react-native-svg` in the parent
app or workspace root rather than a leaf package.
Then generate the icons you use. Each one becomes a file in
`components/icons`, and `components/icons/index.ts` exports all of them:
```bash
npx @tamagui/cli icons add Plus Search X
```
Run it again whenever you need another icon; it adds to the folder and rewrites
the index. `--out` picks a different folder. The same command generates
[Phosphor](https://phosphoricons.com/) and [Heroicons](https://heroicons.com/)
icons, or components from your own svg files, see
[the CLI reference](/docs/core/cli#icons).
The Tamagui repo generates its Lucide set from
`code/packages/local-icons/icons.json`, and this site's own Phosphor icons from
`code/tamagui.dev/components/icons/icons.json`.
## Usage
Use them as regular React components:
```tsx
import { Button } from 'tamagui'
import { Plus } from './components/icons'
// Button will automatically pass size/theme to icon
export default () => Hello world
// or you can control it
export default () => }>Hello world
```
They accept your theme keys for color and your font size tokens for size. In v3,
`size="4"` resolves through the current font's `font.size.4` scale so icons
line up with `4` text. Raw numeric sizes are unchanged.
```tsx
```
Themed icons no longer run full Tamagui style resolution, so media and pseudo
props are not accepted directly on the icon. Wrap the icon when you need those
styles:
```tsx
import { View, styled } from 'tamagui'
const IconFrame = styled(View, {
opacity: 'sm:0.6',
scale: 'hover:1.05',
})
```
## Migrating from v2
Replace `@tamagui/lucide-icons-2` imports with your generated directory and
generate every icon you use. The command stops on unknown names and lists
them; Lucide keeps most renamed icons under their old names, but a few were
removed (brand icons such as `Figma`), so check current names on
[lucide.dev](https://lucide.dev/) when it does.
## Credit
The great [Lucide Icons](https://lucide.dev/), a superset of the wonderful [Feather Icons](https://feathericons.com/).
## components/checkbox/1.85.0
---
title: Checkbox
description: A simple checkbox component
name: checkbox
component: Checkbox
package: checkbox
demoName: Checkbox
---
# Checkbox
Toggle state in forms.StyledUnstyledHeadless
```tsx hero template=Checkbox
```
```tsx hero template=Checkbox
```
```tsx hero template=CheckboxHeadless
```
## Installation
Checkbox is already installed in `tamagui`, or you can install it independently:
```bash
npm install @tamagui/checkbox
```
Checkbox is already installed in `tamagui`, or you can install it independently:
```bash
npm install @tamagui/checkbox
```
To use the headless switch, you want to import it from the
`@tamagui/switch-headless` package. This package has no dependency on
`@tamagui/core`, but still works off the react-native APIs.
This means can bring your own style library.
```bash
npm install @tamagui/switch-headless
```
## Usage
```tsx
import { Check } from '@tamagui/lucide-icons-2'
import { Checkbox } from 'tamagui'
export default () => (
)
```
Use the `createCheckbox` export to create a fully custom checkbox that still
uses the Tamagui styling system. This is similar to setting `unstyled`, but goes
a bit further. It doesn't add any types for `size` or `unstyled`, and it won't
automatically apply the `active` theme. If does pass the `checked` prop down as
indicated in the types of `createCheckbox`.
```tsx template=Checkbox
```
Using the `useCheckbox` API, you can make your own Checkbox from scratch.
```tsx template=CheckboxHeadless
```
## API Reference
### Checkbox
`Checkbox` extend ThemeableStack inheriting all the
[props](/docs/components/stacks#themeablestack), plus:
void',
description: 'Callback that fires when the checkbox state is changed.',
},
{
name: 'sizeAdjust',
type: 'number',
description: `Adjust the checkbox size scaling by this number.`,
},
{
name: 'scaleIcon',
type: 'number',
description: `Scale the indicator icon more than usual by this number.`,
},
{
name: 'scaleSize',
type: 'number',
default: '0.5',
description: `The Tamagui size tokens should map to the height of a button at any given step. This means you want somewhat smaller checkboxes typically.`,
},
{
name: 'unstyled',
required: false,
type: `boolean`,
description: `Removes all default Tamagui styles.`,
},
]}
/>
### Checkbox.Indicator
`Checkbox.Indicator` extend ThemeableStack inheriting all the
[props](/docs/components/stacks#themeablestack), plus:
## components/checkbox/1.3.0
---
title: Checkbox
description: A simple checkbox component
name: checkbox
component: Checkbox
package: checkbox
demoName: Checkbox
---
# Checkbox
Use in forms to toggle between two states.
```tsx hero template=Checkbox
```
## Installation
Checkbox is already installed in `tamagui`, or you can install it independently:
```bash
npm install @tamagui/checkbox
```
## Usage
```tsx
import { Check } from '@tamagui/lucide-icons-2'
import { Checkbox } from 'tamagui'
export default () => (
)
```
## API Reference
### Checkbox
`Checkbox` extend ThemeableStack inheriting all the [props](/docs/components/stacks#themeablestack), plus:
void',
description: 'Callback that fires when the checkbox state is changed.',
},
{
name: 'sizeAdjust',
type: 'number',
description: `Adjust the checkbox size scaling by this number.`,
},
{
name: 'scaleIcon',
type: 'number',
description: `Scale the indicator icon more than usual by this number.`,
},
{
name: 'scaleSize',
type: 'number',
default: '0.5',
description: `The Tamagui size tokens should map to the height of a button at any given step. This means you want somewhat smaller checkboxes typically.`,
},
{
name: 'unstyled',
required: false,
type: `boolean`,
description: `Removes all default Tamagui styles.`,
},
]}
/>
### Checkbox.Indicator
`Checkbox.Indicator` extend ThemeableStack inheriting all the [props](/docs/components/stacks#themeablestack), plus:
## components/checkbox/1.89.0
---
title: Checkbox
description: Toggle state in forms.
name: checkbox
component: Checkbox
package: checkbox
demoName: Checkbox
---
StyledUnstyledHeadless
```tsx hero template=Checkbox
```
```tsx hero template=Checkbox
```
```tsx hero template=CheckboxHeadless
```
## Installation
Checkbox is already installed in `tamagui`, or you can install it independently:
```bash
npm install @tamagui/checkbox
```
Checkbox is already installed in `tamagui`, or you can install it independently:
```bash
npm install @tamagui/checkbox
```
To use the headless checkbox, you want to import it from the
`@tamagui/checkbox-headless` package. This package has no dependency on
`@tamagui/core`, but still works off the react-native APIs.
This means you can bring your own style library.
```bash
npm install @tamagui/checkbox-headless
```
## Usage
```tsx
import { Check } from '@tamagui/lucide-icons-2'
import { Checkbox } from 'tamagui'
export default () => (
)
```
Use the `createCheckbox` export to create a fully custom checkbox that still
uses the Tamagui styling system. This is similar to setting `unstyled`, but goes
a bit further. It doesn't add any types for `size` or `unstyled`, and it won't
automatically apply the `active` theme. It does pass the `checked` prop down as
indicated in the types of `createCheckbox`.
```tsx template=Checkbox
```
Using the `useCheckbox` API, you can make your own Checkbox from scratch.
```tsx template=CheckboxHeadless
```
## API Reference
### Checkbox
`Checkbox` extend ThemeableStack inheriting all the
[props](/docs/components/stacks#themeablestack), plus:
void',
description: 'Callback that fires when the checkbox state is changed.',
},
{
name: 'sizeAdjust',
type: 'number',
description: `Adjust the checkbox size scaling by this number.`,
},
{
name: 'scaleIcon',
type: 'number',
description: `Scale the indicator icon more than usual by this number.`,
},
{
name: 'scaleSize',
type: 'number',
default: '0.5',
description: `The Tamagui size tokens should map to the height of a button at any given step. This means you want somewhat smaller checkboxes typically.`,
},
{
name: 'unstyled',
required: false,
type: `boolean`,
description: `Removes all default Tamagui styles.`,
},
]}
/>
### Checkbox.Indicator
`Checkbox.Indicator` extend ThemeableStack inheriting all the
[props](/docs/components/stacks#themeablestack), plus:
## components/checkbox/2.0.0
---
title: Checkbox
description: Toggle state in forms
name: checkbox
component: Checkbox
package: checkbox
demoName: Checkbox
---
```tsx hero template=Checkbox
```
## Installation
Checkbox is already installed in `tamagui`, or you can install it independently:
```bash
npm install @tamagui/checkbox
```
## Usage
```tsx
import { Check } from '@tamagui/lucide-icons-2'
import { Checkbox } from 'tamagui'
export default () => (
)
```
### Headless Usage
The `useCheckbox` hook provides all the state and accessibility props needed to build a custom checkbox with any styling solution:
```bash
npm install @tamagui/checkbox-headless
```
```tsx
import { useCheckbox } from '@tamagui/checkbox-headless'
import { useState } from 'react'
import { Pressable, View } from 'react-native'
function MyCheckbox({ defaultChecked, onCheckedChange, ...props }) {
const [checked, setChecked] = useState(defaultChecked || false)
const { checkboxProps, checkboxRef, bubbleInput } = useCheckbox(
props,
[checked, setChecked],
null
)
return (
<>
{checked && }
{bubbleInput}
>
)
}
```
## API Reference
### Checkbox
`Checkbox` extends YStack inheriting all the
[props](/ui/stacks), plus:
void',
description: 'Callback that fires when the checkbox state is changed.',
},
{
name: 'sizeAdjust',
type: 'number',
description: `Adjust the checkbox size scaling by this number.`,
},
{
name: 'scaleIcon',
type: 'number',
description: `Scale the indicator icon more than usual by this number.`,
},
{
name: 'unstyled',
required: false,
type: `boolean`,
description: `Removes all default Tamagui styles.`,
},
{
name: 'activeStyle',
required: false,
type: `StylePiece`,
description: `Styles to apply when the checkbox is checked. Accepts a style() piece.`,
},
{
name: 'activeTheme',
required: false,
type: `string | null`,
description: `Theme to apply when the checkbox is checked. Set to null for no theme change.`,
},
]}
/>
### Checkbox.Indicator
`Checkbox.Indicator` extends YStack inheriting all the
[props](/ui/stacks), plus:
### useCheckbox
The `useCheckbox` hook accepts three arguments:
```tsx
const { checkboxProps, checkboxRef, bubbleInput } = useCheckbox(
props, // CheckboxProps
state, // [checked: CheckedState, setChecked: (checked: CheckedState) => void]
ref // React.Ref
)
```
#### CheckedState
The checkbox supports three states:
- `true` - checked
- `false` - unchecked
- `'indeterminate'` - indeterminate/mixed state (useful for "select all" patterns)
#### Props (first argument)
void',
description: `Called when checked state changes.`,
},
{
name: 'onPress',
type: '(event) => void',
description: `Called when checkbox is pressed (composed with internal handler).`,
},
]}
/>
#### State (second argument)
A tuple of `[checked, setChecked]` where:
- `checked`: Current state (`boolean | 'indeterminate'`)
- `setChecked`: React state setter function
#### Return Value
| Property | Type | Description |
| --------------- | ------------------- | ---------------------------------------------------------------------------- |
| `checkboxProps` | `object` | Props to spread on your checkbox element (role, aria-checked, onPress, etc.) |
| `checkboxRef` | `Ref` | Composed ref to attach to your checkbox element |
| `bubbleInput` | `ReactNode \| null` | Hidden input for form compatibility (render as sibling, web only) |
## components/checkbox/3.0.0
---
title: Checkbox
description: Toggle state in forms
name: checkbox
component: Checkbox
package: checkbox
demoName: Checkbox
---
Checkbox provides accessible checked and indeterminate states with controlled
or uncontrolled state on web and native.
```tsx hero template=Checkbox
```
## Installation
Checkbox is already installed in `tamagui`, or you can install it independently:
```bash
npm install @tamagui/checkbox
```
## Usage
```tsx
import { Check } from './components/icons' // your generated icons, see Lucide Icons
import { Checkbox } from 'tamagui'
export default () => (
)
```
### Headless usage
The `useCheckbox` hook provides all the state and accessibility props needed to build a custom checkbox with any styling solution:
```bash
npm install @tamagui/checkbox-headless
```
```tsx
import { useCheckbox } from '@tamagui/checkbox-headless'
import { useState } from 'react'
import { Pressable, View } from 'react-native'
function MyCheckbox({ defaultChecked, onCheckedChange, ...props }) {
const [checked, setChecked] = useState(defaultChecked || false)
const { checkboxProps, checkboxRef, bubbleInput } = useCheckbox(
props,
[checked, setChecked],
null
)
return (
<>
{checked && }
{bubbleInput}
>
)
}
```
## API reference
### Checkbox
`Checkbox` extends View, inheriting all the
[View props](/docs/core/view-and-text). Sizes come from the shared
[Sizing](/docs/core/sizing) ladder.
void',
description: 'Callback that fires when the checkbox state is changed.',
},
{
name: 'unstyled',
required: false,
type: `boolean`,
description: `Removes all default Tamagui styles.`,
},
{
name: 'activeStyle',
required: false,
type: `StylePiece`,
description: `Styles to apply when the checkbox is checked. Accepts a style() piece.`,
},
{
name: 'activeTheme',
required: false,
type: `string | null`,
description: `Theme to apply when the checkbox is checked. Set to null for no theme change.`,
},
]}
/>
### Checkbox.Indicator
`Checkbox.Indicator` extends View, inheriting all the
[View props](/docs/core/view-and-text), plus:
### useCheckbox
The `useCheckbox` hook accepts three arguments:
```tsx
const { checkboxProps, checkboxRef, bubbleInput } = useCheckbox(
props, // CheckboxProps
state, // [checked: CheckedState, setChecked: (checked: CheckedState) => void]
ref // React.Ref
)
```
#### CheckedState
The checkbox supports three states:
- `true` - checked
- `false` - unchecked
- `'indeterminate'` - indeterminate/mixed state (useful for "select all" patterns)
#### Props (first argument)
void',
description: `Called when checked state changes.`,
},
{
name: 'onPress',
type: '(event) => void',
description: `Called when checkbox is pressed (composed with internal handler).`,
},
]}
/>
#### State (second argument)
A tuple of `[checked, setChecked]` where:
- `checked`: Current state (`boolean | 'indeterminate'`)
- `setChecked`: React state setter function
#### Return value
| Property | Type | Description |
| --------------- | ------------------- | ---------------------------------------------------------------------------- |
| `checkboxProps` | `object` | Props to spread on your checkbox element (role, aria-checked, onPress, etc.) |
| `checkboxRef` | `Ref` | Composed ref to attach to your checkbox element |
| `bubbleInput` | `ReactNode \| null` | Hidden input for form compatibility (render as sibling, web only) |
## components/progress/1.0.0
---
title: Progress
description: Display a bar to indicate percent completion.
name: progress
component: Progress
package: progress
demoName: Progress
---
# Progress
Show percent completion with a progress bar
```tsx hero template=Progress
```
ARIA progressbar role compliance
,
]}
/>
## Installation
Progress is already installed in `tamagui`, or you can install it independently:
```bash
npm install @tamagui/progress
```
## Usage
The `value` property controls the percent, but you can override it by adjusting the `x` property.
```tsx
import { Button, Progress } from 'tamagui'
export default () => (
)
```
## API Reference
### Progress
Progress extends [ThemeableStack](/docs/components/stacks#themeablestack), getting [Tamagui standard props](/docs/intro/props), plus:
### Progress.Indicator
`Progress.Indicator` extends [ThemeableStack](/docs/components/stacks#themeablestack), getting [Tamagui standard props](/docs/intro/props).
## components/progress/2.0.0
---
title: Progress
description: Show percent completion with a progress bar
name: progress
component: Progress
package: progress
demoName: Progress
---
```tsx hero template=Progress
```
ARIA progressbar role compliance
,
]}
/>
## Installation
Progress is already installed in `tamagui`, or you can install it independently:
```bash
npm install @tamagui/progress
```
## Usage
The `value` property controls the percent, but you can override it by adjusting the `x` property.
```tsx
import { Button, Progress } from 'tamagui'
export default () => (
)
```
## API Reference
### Progress
Progress extends [ThemeableStack](/docs/components/stacks#themeablestack), getting [Tamagui standard props](/docs/intro/props), plus:
### Progress.Indicator
`Progress.Indicator` extends [ThemeableStack](/docs/components/stacks#themeablestack), getting [Tamagui standard props](/docs/intro/props).
## components/progress/3.0.0
---
title: Progress
description: Show percent completion with a progress bar
name: progress
component: Progress
package: progress
demoName: Progress
---
Progress displays determinate completion with accessible values and
independently styled track and indicator parts.
```tsx hero template=Progress
```
ARIA progressbar role compliance
,
]}
/>
## Installation
Progress is already installed in `tamagui`, or you can install it independently:
```bash
npm install @tamagui/progress
```
## Usage
The `value` property controls the percent, but you can override it by adjusting the `x` property.
```tsx
import { Button, Progress } from 'tamagui'
export default () => (
)
```
## API reference
### Progress
Progress extends [YStack](/ui/stacks), getting [Tamagui standard props](/docs/intro/props), plus:
### Progress.Indicator
`Progress.Indicator` extends [YStack](/ui/stacks), getting [Tamagui standard props](/docs/intro/props).
## components/progress/1.48.0
---
title: Progress
description: Show percent completion with a progress bar.
name: progress
component: Progress
package: progress
demoName: Progress
---
```tsx hero template=Progress
```
ARIA progressbar role compliance
,
]}
/>
## Installation
Progress is already installed in `tamagui`, or you can install it independently:
```bash
npm install @tamagui/progress
```
## Usage
The `value` property controls the percent, but you can override it by adjusting the `x` property.
```tsx
import { Button, Progress } from 'tamagui'
export default () => (
)
```
## API Reference
### Progress
Progress extends [ThemeableStack](/docs/components/stacks#themeablestack), getting [Tamagui standard props](/docs/intro/props), plus:
### Progress.Indicator
`Progress.Indicator` extends [ThemeableStack](/docs/components/stacks#themeablestack), getting [Tamagui standard props](/docs/intro/props).
## components/z-index/2.0.0
---
title: Stacking (zIndex)
description: How Tamagui automatically stacks overlays and floating content
name: z-index
---
Tamagui automatically stacks dialogs, popovers, sheets, tooltips, and nested
overlays in the order they open.
## How it works
When you open overlay components, Tamagui automatically assigns z-index values so that:
- **Later-opened content appears above earlier content** - Open two dialogs? The second one stacks above the first.
- **Nested content appears above its parent** - A tooltip inside a dialog automatically renders above the dialog.
- **Closing restores order** - When content closes, z-index values are reclaimed.
This means you typically don't need to think about z-index at all.
## When to set zIndex
Only set `zIndex` when you need to override the automatic stacking. Common cases:
- **Fixed headers/footers** - If you have a sticky header at `z-index: 100`, overlays need to appear above it (they do by default).
- **Third-party libraries** - If integrating with libraries that use high z-index values.
- **Custom stacking requirements** - When you need specific layering that differs from open-order.
```tsx
// Only if you need to override automatic stacking
...
```
## Component defaults
All overlay components use the automatic stacking system. Here's where to set `zIndex` if needed:
| Component | Where to set zIndex |
|-----------|---------------------|
| [Dialog](/ui/dialog) | `` |
| [AlertDialog](/ui/alert-dialog) | `` |
| [Sheet](/ui/sheet) | `` |
| [Select](/ui/select) | `` |
| [Popover](/ui/popover) | `` |
| [Tooltip](/ui/tooltip) | `` |
| [Menu](/ui/menu) | `` |
| [ContextMenu](/ui/context-menu) | `` |
| [Toast](/ui/toast) | Via `position` on `` |
## Stacking hierarchy
By default, components stack in this order (lowest to highest):
1. **Popover, Tooltip, Menu** - pure auto-stacking from a low base
2. **Dialog, AlertDialog, Select, Sheet** - add a 100000 base to ensure they appear above most UI
Within each category, **mount order determines stacking** - open a popover, then another, the second is on top.
## Nested stacking
Nested floating elements automatically stack above their parent:
```tsx
{/* This tooltip will appear above the dialog */}
Hover me
I appear above the dialog!
```
The system tracks parent-child relationships through React context, so nested portals always stack correctly.
## Technical details
The stacking system uses two mechanisms:
### Horizontal stacking (siblings)
When multiple overlays open at the same level, each gets an incrementing z-index:
- First dialog: 100001
- Second dialog: 100002
- Third dialog: 100003
### Vertical stacking (nested)
When content opens inside another portal, it reads the parent's z-index from context and adds 1:
- Dialog opens at 100001
- Popover inside dialog: 100002
- Tooltip inside that popover: 100003
This ensures nested content is always visible above its parent, but sibling content opened later can still stack above.
## Portal component
The [Portal](/ui/portal) component powers this stacking system. If building custom overlay components, you can use Portal's `stackZIndex` prop:
```tsx
import { Portal } from 'tamagui'
// Enable automatic stacking
{/* content */}
// Or use a high base value (like Dialog does)
{/* content */}
```
See the [Portal documentation](/ui/portal) for more details.
## components/shapes/1.0.0
---
title: Shapes
description: Easy to use Square and Circle.
name: shapes
component: Square
package: shapes
demoName: Shapes
---
```tsx hero template=Shapes
```
## Installation
Shapes is already installed in `tamagui`, or you can install it independently:
```bash
npm install @tamagui/shapes
```
## Usage
Tamagui supports sizing shapes using your `size` tokens, or plain numbers.
```tsx
import { Circle, Square } from 'tamagui'
export default () => (
<>
>
)
```
## API Reference
### Square
`Square` extends View inheriting all the [Tamagui standard props](/docs/intro/props), plus:
### Circle
`Circle` extends [Square](#square), setting `circular` to `true`.
## components/shapes/2.0.0
---
title: Shapes
description: Easy to use Square and Circle
name: shapes
component: Square
package: shapes
demoName: Shapes
---
Square and Circle turn one size value into matching width and height with full
Tamagui styling.
```tsx hero template=Shapes
```
## Installation
Shapes is already installed in `tamagui`, or you can install it independently:
```bash
npm install @tamagui/shapes
```
## Usage
Tamagui supports sizing shapes using your `size` tokens, or plain numbers.
```tsx
import { Circle, Square } from 'tamagui'
export default () => (
<>
>
)
```
## API reference
### Square
Square extends View inheriting all the [Tamagui standard props](/docs/intro/props), plus:
### Circle
Circle extends [Square](#square), setting `circular` to `true`.
## components/button/1.0.0
---
title: Button
description: A simple button component
name: button
component: Button
package: button
demoName: Button
---
# Button
A simple, sizable button.
```tsx hero template=Button
```
### Usage
```tsx
import { Button } from 'tamagui'
export default () => Lorem ipsum
```
### Sizing
Sizing buttons provides a unique challenge especially for a compiler, because you need to adjust many different properties - not just on the outer frame, but on the text wrapped inside. Tamagui supports adjusting the padding, border radius, font size and icons sizes all in one with the `size` prop.
```tsx
import { Button } from 'tamagui'
export default () => Lorem ipsum
```
Given your theme defines a size `6`, the button will adjust all of the properties appropriately. You can also pass a plain number to get an arbitrary size.
### Icon Theming
You can pass icons as either elements or components. If passing components, Tamagui will automatically pass the `size` and `color` prop to them based on your theme.
You can [use the source of Button itself](https://github.com/tamagui/tamagui/blob/v2/code/ui/button/src/Button.tsx) to see in more detail what variants you can override, and how we use this pattern internally to create our Button component.
### Customization (Advanced)
Button only supports a limited subset of text props directly. If you need more control over state-specific text values, customize its text component with flat clauses.
Please note that this pattern is a bit antithetical to the multiple-components APIs that Tamagui generally prefers. In a future release we hope to fix this, but that change should be easy to migrate to.
```tsx
import { forwardRef } from 'react'
import {
ButtonFrame,
ButtonText,
GetProps,
ButtonProps as TamaguiButtonProps,
styled,
themeable,
useButton,
} from 'tamagui'
const CustomButtonFrame = styled(ButtonFrame, {
// ...
})
const CustomButtonText = styled(ButtonText, {
// ...
})
// to capture the custom variant types you define
type CustomButtonFrameProps = GetProps
type CustomButtonTextProps = GetProps
export type CustomButtonProps = TamaguiButtonProps &
CustomButtonFrameProps &
CustomButtonTextProps
export const Button = CustomButtonFrame.styleable((propsIn, ref) => {
const { props } = useButton(propsIn, { Text: CustomButtonText })
return
})
```
### Button props
Buttons extend View inheriting all the [Tamagui standard props](/docs/intro/props), plus:
## components/button/1.0.0-alpha
---
title: Button
description: A simple button component
name: button
component: Button
demoName: Button
---
# Button
A simple, sizable button.
```tsx hero template=Button
```
### Usage
```tsx
import { Button } from 'tamagui'
export default () => Lorem ipsum
```
### Sizing
Sizing buttons provides a unique challenge especially for a compiler, because
you need to adjust many different properties - not just on the outer frame, but
on the text wrapped inside. Tamagui supports adjusting the padding, border
radius, font size and icons sizes all in one with the `size` prop.
```tsx
import { Button } from 'tamagui'
export default () => Lorem ipsum
```
Given your theme defines a size `6`, the button will adjust all of the
properties appropriately. You can also pass a plain number to get an arbitrary
size.
### Icon Theming
You can pass icons as either elements or components. If passing components,
Tamagui will automatically pass the `size` and `color` prop to them based on
your theme.
### Button props
Button extends View, inheriting all the
[Tamagui standard props](/docs/intro/props), adding:
## components/button/1.28.0
---
title: Button
description: An incredibly flexible button.
name: button
component: Button
package: button
demoName: Button
---
```tsx hero template=Button
```
## Usage
When using the simple Button API, it's as simple as this:
```tsx
import { Button } from 'tamagui'
export default () => Lorem ipsum
```
### Sizing
Sizing buttons provides a unique challenge especially for a compiler, because you need to adjust many different properties - not just on the outer frame, but on the text wrapped inside. Tamagui supports adjusting the padding, border radius, font size and icons sizes all in one with the `size` prop.
```tsx
import { Button } from 'tamagui'
export default () => Lorem ipsum
```
Given your theme defines a size `6`, the button will adjust all of the properties appropriately. You can also pass a plain number to get an arbitrary size.
### Icon Theming
You can pass icons as either elements or components. If passing components, Tamagui will automatically pass the `size` and `color` prop to them based on your theme.
You can [use the source of Button itself](https://github.com/tamagui/tamagui/blob/main/code/ui/button/src/Button.tsx) to see in more detail what variants you can override, and how we use this pattern internally to create our Button component.
### Creating your own Button
Tamagui now has all the features necessary to make creating a custom Button easy enough that you may want to roll your own button. Learn how to do it with our dedicated guide, [How to Build a Button](/docs/guides/how-to-build-a-button).
The previous `useButton` API is deprecated and will be removed in a future version. It's
brittle and is easily replaced with the new compound component APIs as described in the
guide.
## API Reference
### Button
Buttons extend View inheriting all the [Tamagui standard props](/docs/intro/props), plus:
## components/button/2.0.0
---
title: Button
description: A simple button component
name: button
component: Button
package: button
demoName: Button
---
```tsx hero template=Button
```
## Installation
Button is already installed in `tamagui`, or you can install it independently:
```bash
npm install @tamagui/button
```
## Usage
```tsx
import { Button } from 'tamagui'
export default () => Lorem ipsum
```
## Sizing
Sizing buttons provides a unique challenge especially for a compiler, because
you need to adjust many different properties - not just on the outer frame, but
on the text wrapped inside. Tamagui supports adjusting the padding, border
radius, font size and icons sizes all in one with the `size` prop.
```tsx
import { Button } from 'tamagui'
export default () => Lorem ipsum
```
Given your theme defines a size `6`, the button will adjust all of the
properties appropriately. You can also pass a plain number to get an arbitrary
size.
## Variants
The Button component supports different visual styles through the `variant`
prop. Currently, the primary available variant is `"outlined"`.
```tsx
import { Button, XStack } from 'tamagui'
export default () => (
DefaultOutlined
)
```
When `variant="outlined"` is applied, the button typically has a transparent
background with a visible border. The exact appearance (border color,
hover/press states) is determined by your theme's definitions for an outlined
button.
## Icon Theming
You can pass icons as either elements or components. If passing components,
Tamagui will automatically theme them (passing `size`). The icon size is
determined by the Button's `size` prop by default.
You can also explicitly set the icon size using the `iconSize` prop, which
accepts a `SizeTokens` value (e.g., `"2"`). The `scaleIcon` prop can be used to
further adjust the size relative to the determined or explicitly set icon size.
When an icon is present, Tamagui automatically adds spacing between the icon and
the button's text using the CSS `gap` property on the Button frame. The gap is
derived from the Button's `size` token value, keeping spacing consistent with
the overall button dimensions.
```tsx
import { Button, Star } from 'tamagui'
export default () => (
<>
Icon sized by Button
Icon sized explicitly
>
)
```
You can
[use the source of Button itself](https://github.com/tamagui/tamagui/blob/v2/code/ui/button/src/Button.tsx)
to see in more detail what variants you can override, and how we use this
pattern internally to create our Button component.
## Group Theming
You can use `Button.Apply` to theme a group of Buttons using a shared context.
This is useful for applying consistent sizing or variants to multiple buttons
without passing props to each one individually.
```tsx
import { Button, ButtonDemo, YStack } from 'tamagui'
export default () => (
Small Outlined 1Small Outlined 2With IconLarge 1Large Themed
)
```
## Web Form Props
Button supports all standard HTML `` attributes for form integration.
These props are web-only and ignored on native:
```tsx
import { Button, Form } from 'tamagui'
export default () => (
)
```
Button defaults to `type="button"` to prevent unintended form submissions. Use
`type="submit"` explicitly when you want form submission behavior.
## Text Styling
**Changed from v1:** In v1, you could pass text style props like `color`,
`fontSize`, `fontWeight`, `fontFamily`, and `letterSpacing` directly to
`` and they would be forwarded to the button's text via context. In v2,
this is no longer supported — Button's frame is a `View` and text style props
passed to it are ignored.
To customize text appearance, use `Button.Text` explicitly:
```tsx
import { Button } from 'tamagui'
export default () => (
Styled text
)
```
### Interactive Text Styles
Combine `Button.Text` with the `group` prop on Button to coordinate text
styles across hover, press, and focus states:
TypedString
```tsx
import { Button } from 'tamagui'
export default () => (
Hello world
)
```
```tsx
import { Button } from 'tamagui'
export default () => (
Hello world
)
```
The `group` prop lets any child reference all of Tamagui's interactive states like
`group-hover/btn`, `group-press/btn`, and `group-focus/btn` for
precise styling per interaction state.
## Creating Your Own Button
Tamagui now has all the features necessary to make creating a custom Button easy
enough that you may want to roll your own button. Learn how to do it with our
dedicated guide, [How to Build a Button](/docs/guides/how-to-build-a-button).
## API Reference
Buttons extend View inheriting all the
[Tamagui standard props](/docs/intro/props), plus:
',
description: `Pass any React element or a component. Appears before the text. If a component is passed, 'color' and 'size' props are passed to it.`,
},
{
name: 'iconAfter',
required: false,
type: 'React.ReactNode | React.ComponentType<{ color?: string, size?: number }>',
description: `Pass any React element or a component. Appears after the text. If a component is passed, 'color' and 'size' props are passed to it.`,
},
{
name: 'iconSize',
required: false,
type: 'SizeTokens',
description: `Explicitly set the size of the icon, overriding the default size derived from the Button's 'size' prop. Uses theme size tokens (e.g., "2").`,
},
{
name: 'scaleIcon',
required: false,
type: 'number',
description: `Scale the icon by this factor. Default is 1. Applied after 'iconSize' or the default size calculation.`,
},
{
name: 'circular',
required: false,
type: `boolean`,
description: `Forces a circular button. Applies border radius to make the button a circle.`,
},
{
name: 'chromeless',
required: false,
type: 'boolean | "all"',
description:
'Removes default Tamagui visual styles (background, border) for a more minimal appearance, while keeping layout and interactions. If "all", removes hover/press/focus styles too.',
},
{
name: 'unstyled',
required: false,
type: `boolean`,
description: `Removes all Tamagui default styles, including layout, interactions, and accessibility attributes. Prefer 'chromeless' for removing only visual styles.`,
},
// Web-only form props
{
name: '// Web Form Props',
description:
'The following props are web-only and provide full HTML button form semantics. They are ignored on native.',
type: '---',
},
{
name: 'type',
required: false,
type: '"button" | "submit" | "reset"',
description:
'Web-only. The button type. Defaults to "button" to prevent unintended form submissions.',
},
{
name: 'form',
required: false,
type: 'string',
description: 'Web-only. The ID of the form element the button is associated with.',
},
{
name: 'formAction',
required: false,
type: 'string',
description: "Web-only. URL for form submission, overrides the form's action.",
},
{
name: 'formMethod',
required: false,
type: '"get" | "post"',
description: 'Web-only. HTTP method for form submission.',
},
{
name: 'formEncType',
required: false,
type: 'string',
description: 'Web-only. Encoding type for form data.',
},
{
name: 'formNoValidate',
required: false,
type: 'boolean',
description: 'Web-only. Bypass form validation when submitting.',
},
{
name: 'formTarget',
required: false,
type: 'string',
description: 'Web-only. Where to display form response (_self, _blank, etc).',
},
{
name: 'name',
required: false,
type: 'string',
description: 'Web-only. Name submitted with form data.',
},
{
name: 'value',
required: false,
type: 'string',
description: 'Web-only. Value submitted with form data.',
},
{
name: 'popoverTarget',
required: false,
type: 'string',
description: 'Web-only. ID of popover element to control.',
},
{
name: 'popoverTargetAction',
required: false,
type: '"hide" | "show" | "toggle"',
description: 'Web-only. Action to perform on the popover.',
},
// Note about text styling
{
name: '// Text Styling',
description:
'Button does not support `color` directly. To style text color, use `Button.Text` with the `group` prop for interactive states. See the Text Styling section above.',
type: '---',
},
]}
/>
## components/button/3.0.0
---
title: Button
description: A pressable button with sizing, icons, and themes
name: button
component: Button
package: button
demoName: Button
---
Button combines accessible press behavior with shared sizing, icons, themes,
and full HTML form semantics.
```tsx hero template=Button
```
## Installation
Button is already installed in `tamagui`:
```tsx
import { Button } from 'tamagui'
```
`@tamagui/button` holds the unstyled half: `useButton` and the parts, with no
styled `Button` of its own. Import from it when you are building your own button
rather than using ours; see [Build your own](#build-your-own).
## Usage
```tsx
import { Button } from 'tamagui'
export default () => Lorem ipsum
```
## Sizing
Sizing a button means adjusting many properties at once, not just on the outer
frame but on the text inside it. Tamagui adjusts padding, border radius, font
size, and icon size together with the `size` prop.
```tsx
import { Button } from 'tamagui'
export default () => Lorem ipsum
```
Each name is a rung in the shared [Sizing](/docs/core/sizing) ladder (`xs`
through `xl`, default `md`), so the frame, its text, and its icon agree by
construction.
## Variants
The `variant` prop selects a visual style. Tamagui ships `outlined` (transparent
background with a visible border) and `quiet` (no background, no border).
```tsx
import { Button, XStack } from 'tamagui'
export default () => (
DefaultOutlinedQuiet
)
```
## Icon theming
You can pass icons as either elements or components. If passing components,
Tamagui themes them, passing `size` and `color`. The icon size comes from the
Button's `size` prop by default.
Set the icon size explicitly with `iconSize` (pixels, e.g. `{18}`).
`scaleIcon` adjusts it further, relative to whichever size was resolved.
Spacing between an icon and the text comes from `gap` on the button frame,
derived from the `size` table so it stays consistent with the button's
dimensions.
```tsx
import { Button } from 'tamagui'
import { Star } from './components/icons' // your generated icons, see Lucide Icons
export default () => (
<>
Icon sized by Button
Icon sized explicitly
>
)
```
## Web form props
Button supports the standard HTML `` attributes for form integration.
These are web-only and ignored on native:
```tsx
import { Button, Form } from 'tamagui'
export default () => (
)
```
Button defaults to `type="button"` to prevent unintended form submissions. Use
`type="submit"` explicitly when you want form submission behavior.
## Text styling
Button's frame is a `View`, so text style props passed to it are ignored. The one
exception is `fontFamily`, which forwards to the wrapped text, since a font family
is a base value and needs no conditional resolution.
Everything else goes on `Button.Text`:
```tsx
import { Button } from 'tamagui'
export default () => (
Styled text
)
```
### Interactive text styles
Combine `Button.Text` with the `group` prop on Button to coordinate text styles
across hover, press, and focus states:
StringTyped
```tsx
import { Button } from 'tamagui'
export default () => (
Hello world
)
```
```tsx
import { Button } from 'tamagui'
export default () => (
Hello world
)
```
The `group` prop lets any child reference all of Tamagui's interactive states like
`group-hover/btn`, `group-press/btn`, and `group-focus/btn` for precise styling
per interaction state.
## Build your own
No two buttons are alike, so `@tamagui/button` ships the behavior and the parts
rather than a finished component. `useButton` themes the icon props, wraps bare
children in a text, and handles the html nesting rules; the skin is yours. This is
how `tamagui`'s own Button is built:
```tsx
import {
ButtonFrame as ButtonBehaviorFrame,
ButtonText as ButtonBehaviorText,
createStyledContext,
createStyledHOC,
styled,
useButton,
withStaticProperties,
} from '@tamagui/ui'
const ButtonContext = createStyledContext<{ size?: 'sm' | 'md' | 'lg' }>({
size: 'md',
})
const ButtonFrame = styled(ButtonBehaviorFrame, {
context: ButtonContext,
backgroundColor: 'background hover:background-hover press:background-press',
borderColor: 'border-color hover:border-color-hover',
borderWidth: 1,
cursor: 'web:pointer',
})
const ButtonText = styled(ButtonBehaviorText, {
context: ButtonContext,
color: 'color',
fontWeight: '600',
})
const ButtonComponent = createStyledHOC(ButtonFrame, function Button(props, ref) {
const { props: buttonProps } = useButton(props, { Text: ButtonText })
return
})
export const Button = withStaticProperties(ButtonComponent, {
Frame: ButtonFrame,
Text: ButtonText,
})
```
Because the frame declares `ButtonContext`, passing `size` to it is what
publishes it to the text and icons below. Each component owns its context;
there is no shared size context.
`useButton` also reads `ButtonNestingContext`, which is how a Button inside
another Button (a `Dialog.Trigger` inside a toolbar button, say) renders as a
`span` instead of nesting an invalid `` inside a ``.
For a longer walkthrough see
[How to Build a Button](/docs/guides/how-to-build-a-button).
## API reference
Button extends View, inheriting all the
[Tamagui standard props](/docs/intro/props). Sizes come from the shared
[Sizing](/docs/core/sizing) ladder.
` and sets `aria-disabled`.',
},
{
name: 'theme',
required: false,
type: 'string',
description: `Apply a theme just to the button and its children.`,
},
{
name: 'icon',
required: false,
type: 'React.ReactNode | React.ComponentType<{ color?: string, size?: number }>',
description: `Any React element or a component. Appears before the text. A component receives 'color' and 'size'.`,
},
{
name: 'iconAfter',
required: false,
type: 'React.ReactNode | React.ComponentType<{ color?: string, size?: number }>',
description: `Any React element or a component. Appears after the text. A component receives 'color' and 'size'.`,
},
{
name: 'iconSize',
required: false,
type: 'number',
description: `Explicitly set the icon size in pixels, overriding the one derived from 'size'.`,
},
{
name: 'scaleIcon',
required: false,
type: 'number',
description: `Scale the icon by this factor. Default is 1, applied after 'iconSize' or the default size calculation.`,
},
{
name: 'fontFamily',
required: false,
type: 'string',
description:
'The one text style prop the frame forwards, since a font family does not vary per breakpoint. Every other text style belongs on `Button.Text`.',
},
{
name: 'noTextWrap',
required: false,
type: 'boolean',
description:
'Render children as given instead of wrapping bare text in a `Button.Text`.',
},
// Web-only form props
{
name: '// Web Form Props',
description:
'The following props are web-only and provide full HTML button form semantics. They are ignored on native.',
type: '---',
},
{
name: 'type',
required: false,
type: '"button" | "submit" | "reset"',
description:
'Web-only. The button type. Defaults to "button" to prevent unintended form submissions.',
},
{
name: 'form',
required: false,
type: 'string',
description: 'Web-only. The ID of the form element the button is associated with.',
},
{
name: 'formAction',
required: false,
type: 'string',
description: "Web-only. URL for form submission, overrides the form's action.",
},
{
name: 'formMethod',
required: false,
type: '"get" | "post"',
description: 'Web-only. HTTP method for form submission.',
},
{
name: 'formEncType',
required: false,
type: 'string',
description: 'Web-only. Encoding type for form data.',
},
{
name: 'formNoValidate',
required: false,
type: 'boolean',
description: 'Web-only. Bypass form validation when submitting.',
},
{
name: 'formTarget',
required: false,
type: 'string',
description: 'Web-only. Where to display form response (_self, _blank, etc).',
},
{
name: 'name',
required: false,
type: 'string',
description: 'Web-only. Name submitted with form data.',
},
{
name: 'value',
required: false,
type: 'string',
description: 'Web-only. Value submitted with form data.',
},
// Note about text styling
{
name: '// Text Styling',
description:
'Apart from `fontFamily`, Button does not forward text style props. Use `Button.Text`, with the `group` prop for interactive states. See the Text Styling section above.',
type: '---',
},
]}
/>
### Button.Frame
The button's `View`. Renders as `` on web with
`role="button"`.
### Button.Text
The text the button wraps bare children in, and what you use to style the label.
Extends `SizableText`.
### Button.Icon
Sizes and colors an icon from the surrounding size context. Used for the `icon`
and `iconAfter` props, and available for your own compositions.
### useButton
Themes the `icon` and `iconAfter` props, wraps bare children in a text, and
applies the html nesting rules. Returns the props to spread onto a frame, with the
ones it consumed removed and everything else untouched.
```tsx
const { isNested, props } = useButton(props, { Text: MyButtonText })
```
## components/button/1.0.0-beta.0
---
title: Button
description: A simple button component
name: button
component: Button
demoName: Button
---
# Button
A simple, sizable button.
```tsx hero template=Button
```
### Usage
```tsx
import { Button } from 'tamagui'
export default () => Lorem ipsum
```
### Sizing
Sizing buttons provides a unique challenge especially for a compiler, because
you need to adjust many different properties - not just on the outer frame, but
on the text wrapped inside. Tamagui supports adjusting the padding, border
radius, font size and icons sizes all in one with the `size` prop.
```tsx
import { Button } from 'tamagui'
export default () => Lorem ipsum
```
Given your theme defines a size `6`, the button will adjust all of the
properties appropriately. You can also pass a plain number to get an arbitrary
size.
### Icon Theming
You can pass icons as either elements or components. If passing components,
Tamagui will automatically pass the `size` and `color` prop to them based on
your theme.
### Button props
Button extends View, inheriting all the
[Tamagui standard props](/docs/intro/props), plus:
## components/linear-gradient/1.0.0
---
title: Linear Gradient
description: Linear gradients that work with Tamagui style props
name: html
component: LinearGradient
demoName: LinearGradient
---
```tsx hero template=LinearGradient
```
## Installation
LinearGradient is already installed in `tamagui`, or you can install it independently:
```bash
npm install @tamagui/linear-gradient
```
To use this package you'll need to add `expo-linear-gradient` to your app. This works [with vanilla React Native](https://github.com/expo/expo/tree/sdk-47/packages/expo-linear-gradient) or Expo.
## Usage
Because LinearGradient requires a more complex to install native package, we've left it out of the `tamagui` export until Metro supports async import. Import it either separately or using the path `/linear-gradient`:
```tsx
import { LinearGradient } from '@tamagui/linear-gradient'
import { LinearGradient } from 'tamagui/linear-gradient'
```
LinearGradient is a YStack that accepts all Tamagui style props as well as theme colors, that places `expo-linear-gradient` inside it set to absoluteFill.
## API Reference
### LinearGradient
See the [expo docs](https://docs.expo.dev/versions/latest/sdk/linear-gradient/) for more complete information.
LinearGradient extends YStack, inheriting `View` props and therefore the [Tamagui standard props](/docs/intro/props), plus:
## components/linear-gradient/1.0.0-alpha
---
title: Linear Gradient
description: Linear Gradient that works with Tamagui style props.
name: html
component: LinearGradient
demoName: LinearGradient
---
# Linear Gradient
Linear Gradient that works with Tamagui style props.In beta, currently doesn't resolve theme values for the colors property.
```tsx hero template=LinearGradient
```
### Usage
We've included `` so it can accept Tamagui style props, and to patch a bug with the current expo version with some Webpack configurations.
## components/linear-gradient/2.0.0
---
title: Linear Gradient
description: Linear gradients that work with Tamagui style props
name: linear-gradient
component: LinearGradient
package: linear-gradient
demoName: LinearGradient
---
LinearGradient renders theme-aware gradients with the same style props on web
and native.
```tsx hero template=LinearGradient
```
## Installation
LinearGradient is already installed in `tamagui`, or you can install it independently:
```bash
npm install @tamagui/linear-gradient
```
### Native setup
On native, LinearGradient uses `expo-linear-gradient` for rendering. This works with vanilla React Native or Expo.
#### Step 1: install expo-linear-gradient
```bash
npm install expo-linear-gradient
```
#### Step 2: import the setup module
In your app's entry file (index.js or App.tsx), before any Tamagui imports:
```tsx
import '@tamagui/native/setup-expo-linear-gradient'
```
That's it! LinearGradient will automatically detect and use expo-linear-gradient when available.
#### Without expo-linear-gradient
If you don't set up expo-linear-gradient, LinearGradient will log a warning on native. On web, LinearGradient uses CSS gradients and doesn't require any additional setup.
## Usage
Because LinearGradient requires a native package, it is not included in the main `tamagui` export. Import it either separately or using the path `/linear-gradient`:
```tsx
import { LinearGradient } from '@tamagui/linear-gradient'
import { LinearGradient } from 'tamagui/linear-gradient'
```
LinearGradient is a YStack that accepts all Tamagui style props as well as theme colors. On native it renders `expo-linear-gradient` inside an `absoluteFill` wrapper.
## API reference
### LinearGradient
See the [expo docs](https://docs.expo.dev/versions/latest/sdk/linear-gradient/) for more complete information.
#### Alternative: backgroundImage style prop
For most gradients, use the [`backgroundImage` style prop](/docs/core/style-props#backgroundimage):
```tsx
```
Use `` for precise color stop control.
---
LinearGradient extends YStack, inheriting `View` props and therefore the [Tamagui standard props](/docs/intro/props), plus:
## components/linear-gradient/3.0.0
---
title: Linear Gradient
description: Linear gradients that work with Tamagui style props
name: linear-gradient
component: LinearGradient
package: linear-gradient
demoName: LinearGradient
---
LinearGradient renders theme-aware gradients with the same style props on web
and native.
```tsx hero template=LinearGradient
```
## Installation
LinearGradient is already installed in `tamagui`, or you can install it independently:
```bash
npm install @tamagui/linear-gradient
```
No native package or setup is needed. On native, LinearGradient draws a
`backgroundImage` gradient, which React Native 0.76 and later renders natively
with the new architecture. On web it is a CSS gradient.
## Usage
Import it separately or using the path `/linear-gradient`:
```tsx
import { LinearGradient } from '@tamagui/linear-gradient'
import { LinearGradient } from 'tamagui/linear-gradient'
```
LinearGradient is a YStack that accepts all Tamagui style props as well as theme colors. It draws its gradient as a `backgroundImage` on an `absoluteFill` layer behind its children.
## API reference
### LinearGradient
#### Alternative: backgroundImage style prop
For most gradients, use the [`backgroundImage` style prop](/docs/core/style-props#backgroundimage):
```tsx
```
Use `` for precise color stop control.
---
LinearGradient extends YStack, inheriting `View` props and therefore the [Tamagui standard props](/docs/intro/props), plus:
## components/spinner/1.0.0
---
title: Spinner
description: Render a loading indicator.
name: spinner
component: Spinner
demoName: Spinner
---
```tsx hero template=Spinner
```
## Installation
Spinner is already installed in `tamagui`, or you can install it independently:
```bash
npm install @tamagui/spinner
```
Note that due to the fact that Spinner is an extension of React Native [ActivityIndicator](https://reactnative.dev/docs/activityindicator), and that only accepts size `small` or `large`, we are currently limited to just these sizes.
```tsx
import { Button, Spinner } from 'tamagui'
export default () =>
```
## API Reference
### Spinner
Spinner extends [YStack](/docs/components/stacks), getting [Tamagui standard props](/docs/intro/props), plus:
## components/spinner/2.0.0
---
title: Spinner
description: Render a loading indicator
name: spinner
component: Spinner
package: spinner
demoName: Spinner
---
Spinner renders a theme-aware loading indicator in small and large sizes.
```tsx hero template=Spinner
```
## Installation
Spinner is already installed in `tamagui`, or you can install it independently:
```bash
npm install @tamagui/spinner
```
Spinner extends React Native's [ActivityIndicator](https://reactnative.dev/docs/activityindicator), which only accepts `small` or `large`, so those are the only sizes.
## Usage
```tsx
import { Button, Spinner } from 'tamagui'
export default () =>
```
## API reference
### Spinner
Spinner extends [YStack](/docs/components/stacks), getting [Tamagui standard props](/docs/intro/props), plus:
## components/image/1.0.0
---
title: Image
description: React Native Web Image + Tamagui style props.
name: html
component: Image
package: image
demoName: Image
---
# Image
React Native Web Image + Tamagui style props.
```tsx hero template=Image
```
### Props
[Tamagui props](/docs/intro/props) + [React Native Web Image props](https://necolas.github.io/react-native-web/docs/image/).
## components/image/2.0.0
---
title: Image
description: A lightweight cross-platform Image with Tamagui style props
name: image
component: Image
package: image
demoName: Image
---
Image provides a lightweight cross-platform image component with Tamagui
style props and pluggable renderers.
```tsx hero template=Image
```
## Installation
Image is already installed in `tamagui`, or you can install it independently:
```bash
npm install @tamagui/image
```
## Usage
Use the `src` prop for the image URL:
```tsx
import { Image } from 'tamagui'
export default () => (
)
```
Use `objectFit` to control how the image fits its container:
```tsx
```
### Sizing on native
`width` and `height` are Tamagui layout props. On native, they become React Native
Image styles. Remote images must resolve to a nonzero layout size, so use fixed
dimensions or place percentage-sized images inside a parent with defined dimensions:
```tsx
```
Tamagui converts a string `src` to `source={{ uri: src }}` on native. Layout
dimensions stay in `style`; they are not copied into the native source metadata.
React Native `source` objects remain supported for migration, but `src` is preferred.
## Using with expo-image
For better performance and features like blurhash placeholders and transitions,
you can use `createImage` to create a custom Image component with expo-image:
```tsx
import { Image as ExpoImage } from 'expo-image'
import { createImage } from '@tamagui/image'
export const Image = createImage({
Component: ExpoImage,
resizeModePropName: 'contentFit',
objectPositionPropName: 'contentPosition',
})
```
Now you can use all expo-image props alongside Tamagui's unified API:
```tsx
const blurhash = '|rF?hV%2WCj[ayj[a|j[az...'
export default () => (
)
```
### createImage options
## Accessibility
Always provide an `alt` attribute describing the image content. For decorative images, use an empty string (`alt=""`).
```tsx
// meaningful image
// decorative image
```
On React Native, use `accessible` and `accessibilityLabel` for screen reader support. See the [React Native Accessibility docs](https://reactnative.dev/docs/accessibility) for details.
```tsx
```
## API reference
### Image
Extends View with [Tamagui props](/docs/intro/props), plus:
### Web-only props
These props pass through to the native `` element on web:
### Accessibility props
Web props like `loading`, `srcSet`, and `fetchPriority` only apply on web. On native,
use expo-image via `createImage` for advanced features like blurhash placeholders and
priority loading.
## components/image/1.13.0
---
title: Image
description: React Native Web Image + Tamagui style props.
name: html
component: Image
package: image
demoName: Image
---
```tsx hero template=Image
```
## Installation
Image is already installed in `tamagui`, or you can install it independently:
```bash
npm install @tamagui/image
```
## Usage
Note that you need to set `source` like so - the `width` and `height` properties apply as styles around the image, but the actual image needs `source.width` and `source.height` for React Native:
```tsx
export default () => (
)
```
## API Reference
### Image
[Tamagui props](/docs/intro/props) + [React Native Web Image props](https://necolas.github.io/react-native-web/docs/image/).
## components/switch/1.0.0
---
title: Switch
description: A simple switch component
name: switch
component: Switch
package: switch
demoName: Switch
---
# Switch
Use in forms to toggle between two states.
```tsx hero template=Switch
```
### Usage
```tsx
import { Switch } from 'tamagui'
export default () => (
)
```
### Switch props
Switchs extend View inheriting all the [Tamagui standard props](/docs/intro/props), plus:
void',
},
{
name: 'unstyled',
type: 'boolean',
default: 'false',
description: `When true, remove all default tamagui styling.`,
},
]}
/>
## components/switch/1.89.0
---
title: Switch
description: A toggle between two states.
name: switch
component: Switch
package: switch
demoName: Switch
---
StyledUnstyledHeadless
```tsx hero template=Switch
```
```tsx hero template=Switch
```
```tsx hero template=SwitchHeadless
```
## Installation
Switch is already installed in `tamagui`, or you can install it independently:
```bash
npm install @tamagui/switch
```
Switch is already installed in `tamagui`, or you can install it independently:
```bash
npm install @tamagui/switch
```
To use the headless switch, you want to import it from the
`@tamagui/switch-headless` package. This package has no dependency on
`@tamagui/core`, but still works off the react-native APIs.
This means you can bring your own style library.
```bash
npm install @tamagui/switch-headless
```
## Usage
```tsx
import { Switch } from 'tamagui' // or '@tamagui/switch'
export default () => (
)
```
Using the `createSwitch` export, you can create an unstyled switch without using
any of the default styles. This is similar to the `unstyled` prop, but it
doesn't assume the props `size` or `unstyled` exist, and it won't automatically
apply the `active` theme.
You must pass `SwitchContext` as the `context` option to your Frame and Thumb
styled components.
If you define a `checked` variant, it will apply those styles.
```tsx template=Switch
```
Using the `useSwitch` API, you can make your own Switch from scratch.
```tsx template=SwitchHeadless
```
## API Reference
### Switch
`Switch` extends View inheriting all the
[Tamagui standard props](/docs/intro/props), plus:
void',
},
{
name: 'unstyled',
type: 'boolean',
default: 'false',
description: `When true, remove all default tamagui styling.`,
},
{
name: 'native',
type: 'NativeValue<"mobile" | "ios" | "android">',
description: `Render to a native switch. (Not supported on web)`,
},
{
name: 'nativeProps',
type: 'SwitchProps (from `react-native`)',
description: `Props to pass to the native Switch;`,
},
]}
/>
### Switch.Thumb
`Switch.Thumb` extends View inheriting all the
[Tamagui standard props](/docs/intro/props), plus:
{/* TODO: document createSwitch and useSwitch's API */}
## components/switch/1.28.0
---
title: Switch
description: A simple switch component
name: switch
component: Switch
package: switch
demoName: Switch
---
# Switch
Use in forms to toggle between two states.
```tsx hero template=Switch
```
## Installation
Switch is already installed in `tamagui`, or you can install it independently:
```bash
npm install @tamagui/switch
```
## Usage
```tsx
import { Switch } from 'tamagui' // or '@tamagui/switch'
export default () => (
)
```
## API Reference
### Switch
Switchs extend View inheriting all the [Tamagui standard props](/docs/intro/props), plus:
void',
},
{
name: 'unstyled',
type: 'boolean',
default: 'false',
description: `When true, remove all default tamagui styling.`,
},
{
name: 'native',
type: 'NativeValue<"mobile" | "ios" | "android">',
description: `Render to a native switch. (Not supported on web)`,
},
{
name: 'nativeProps',
type: 'SwitchProps (from `react-native`)',
description: `Props to pass to the native Switch;`,
},
]}
/>
### Switch.Thumb
`Switch.Thumb` extends View inheriting all the [Tamagui standard props](/docs/intro/props), plus:
## components/switch/2.0.0
---
title: Switch
description: A toggle between two states
name: switch
component: Switch
package: switch
demoName: Switch
---
```tsx hero template=Switch
```
## Installation
Switch is already installed in `tamagui`, or you can install it independently:
```bash
npm install @tamagui/switch
```
## Usage
```tsx
import { Switch } from 'tamagui' // or '@tamagui/switch'
export default () => (
)
```
### Headless Usage
To use the headless switch without `@tamagui/core`, install `@tamagui/switch-headless`:
```bash
npm install @tamagui/switch-headless
```
The `useSwitch` hook provides all the state and accessibility props needed to build a custom switch with any styling solution:
```tsx
import { useSwitch } from '@tamagui/switch-headless'
import { useState } from 'react'
import { Pressable, View } from 'react-native'
function MySwitch({ defaultChecked, onCheckedChange, ...props }) {
const [checked, setChecked] = useState(defaultChecked || false)
const { switchProps, switchRef, bubbleInput } = useSwitch(
props,
[checked, setChecked],
null
)
return (
<>
{bubbleInput}
>
)
}
```
## API Reference
### Switch
`Switch` extends View inheriting all the
[Tamagui standard props](/docs/intro/props), plus:
void',
description: `Callback called when checked state changes.`,
},
{
name: 'unstyled',
type: 'boolean',
default: 'false',
description: `When true, removes all default Tamagui styling.`,
},
{
name: 'native',
type: 'NativeValue<"mobile" | "ios" | "android">',
description: `Render to a native switch. (Not supported on web)`,
},
{
name: 'nativeProps',
type: 'SwitchProps (from `react-native`)',
description: `Props to pass to the native Switch.`,
},
{
name: 'activeStyle',
type: 'ViewStyle',
description: `Styles to apply when the switch is checked/active.`,
},
{
name: 'activeTheme',
type: 'string | null',
description: `Theme to apply when the switch is checked/active.`,
},
]}
/>
### Switch.Thumb
`Switch.Thumb` extends View inheriting all the
[Tamagui standard props](/docs/intro/props), plus:
### useSwitch
The `useSwitch` hook accepts three arguments:
```tsx
const { switchProps, switchRef, bubbleInput } = useSwitch(
props, // SwitchProps
state, // [checked: boolean, setChecked: (checked: boolean) => void]
ref // React.Ref
)
```
#### Props (first argument)
void',
description: `Called when switch is pressed (composed with internal handler).`,
},
]}
/>
#### State (second argument)
A tuple of `[checked, setChecked]` where:
- `checked`: Current boolean state
- `setChecked`: React state setter function
#### Return Value
| Property | Type | Description |
| ------------- | ------------------- | -------------------------------------------------------------------------- |
| `switchProps` | `object` | Props to spread on your switch element (role, aria-checked, onPress, etc.) |
| `switchRef` | `Ref` | Composed ref to attach to your switch element |
| `bubbleInput` | `ReactNode \| null` | Hidden input for form compatibility (render as sibling, web only) |
## components/switch/3.0.0
---
title: Switch
description: A toggle between two states
name: switch
component: Switch
package: switch
demoName: Switch
---
Switch exposes a controlled or uncontrolled boolean state through accessible,
independently styled frame and thumb parts.
```tsx hero template=Switch
```
## Installation
Switch is already installed in `tamagui`, or you can install it independently:
```bash
npm install @tamagui/switch
```
## Usage
```tsx
import { Switch } from 'tamagui' // or '@tamagui/switch'
export default () => (
)
```
### Headless usage
To use the headless switch without `@tamagui/core`, install `@tamagui/switch-headless`:
```bash
npm install @tamagui/switch-headless
```
The `useSwitch` hook provides all the state and accessibility props needed to build a custom switch with any styling solution:
```tsx
import { useSwitch } from '@tamagui/switch-headless'
import { useState } from 'react'
import { Pressable, View } from 'react-native'
function MySwitch({ defaultChecked, onCheckedChange, ...props }) {
const [checked, setChecked] = useState(defaultChecked || false)
const { switchProps, switchRef, bubbleInput } = useSwitch(
props,
[checked, setChecked],
null
)
return (
<>
{bubbleInput}
>
)
}
```
## API reference
### Switch
`Switch` extends View, inheriting all the
[Tamagui standard props](/docs/intro/props). Sizes come from the shared
[Sizing](/docs/core/sizing) ladder.
void',
description: `Callback called when checked state changes.`,
},
{
name: 'unstyled',
type: 'boolean',
default: 'false',
description: `When true, removes all default Tamagui styling.`,
},
{
name: 'native',
type: 'NativeValue<"mobile" | "ios" | "android">',
description: `Render to a native switch. (Not supported on web)`,
},
{
name: 'nativeProps',
type: 'SwitchProps (from `react-native`)',
description: `Props to pass to the native Switch.`,
},
{
name: 'activeStyle',
type: 'ViewStyle',
description: `Styles to apply when the switch is checked/active.`,
},
{
name: 'activeTheme',
type: 'string | null',
description: `Theme to apply when the switch is checked/active.`,
},
]}
/>
### Switch.Thumb
`Switch.Thumb` extends View inheriting all the
[Tamagui standard props](/docs/intro/props), plus:
### useSwitch
The `useSwitch` hook accepts three arguments:
```tsx
const { switchProps, switchRef, bubbleInput } = useSwitch(
props, // SwitchProps
state, // [checked: boolean, setChecked: (checked: boolean) => void]
ref // React.Ref
)
```
#### Props (first argument)
void',
description: `Called when switch is pressed (composed with internal handler).`,
},
]}
/>
#### State (second argument)
A tuple of `[checked, setChecked]` where:
- `checked`: Current boolean state
- `setChecked`: React state setter function
#### Return value
| Property | Type | Description |
| ------------- | ------------------- | -------------------------------------------------------------------------- |
| `switchProps` | `object` | Props to spread on your switch element (role, aria-checked, onPress, etc.) |
| `switchRef` | `Ref` | Composed ref to attach to your switch element |
| `bubbleInput` | `ReactNode \| null` | Hidden input for form compatibility (render as sibling, web only) |
## components/switch/1.58.0
---
title: Switch
description: A simple switch component
name: switch
component: Switch
package: switch
demoName: Switch
---
# Switch
Use in forms to toggle between two states.
```tsx hero template=Switch
```
## Installation
Switch is already installed in `tamagui`, or you can install it independently:
```bash
npm install @tamagui/switch
```
## Usage
```tsx
import { Switch } from 'tamagui' // or '@tamagui/switch'
export default () => (
)
```
## Headless with `createSwitch`
Using the `createSwitch` export, you can create a fully custom switch without using any of the default styles. This is similar to `unstyled`, but it doesn't assume the props `size` or `unstyled` exist, and it won't automatically apply the `active` theme.
You must pass `SwitchContext` as the `context` option to your Frame and Thumb styled components.
If you define a `checked` variant, it will apply those styles.
Here's an example:
```tsx
import { View, styled } from '@tamagui/core'
import { createSwitch } from '@tamagui/switch'
const Frame = styled(View, {
variants: {
checked: {
true: {
backgroundColor: 'yellow',
},
false: {
backgroundColor: 'green',
},
},
} as const,
})
const Thumb = styled(View, {
variants: {
checked: {
true: {
opacity: 1,
},
false: {
opacity: 0.5,
},
},
} as const,
})
export const Switch = createSwitch({
Frame,
Thumb,
})
```
## API Reference
### Switch
`Switch` extends View inheriting all the [Tamagui standard props](/docs/intro/props), plus:
void',
},
{
name: 'unstyled',
type: 'boolean',
default: 'false',
description: `When true, remove all default tamagui styling.`,
},
{
name: 'native',
type: 'NativeValue<"mobile" | "ios" | "android">',
description: `Render to a native switch. (Not supported on web)`,
},
{
name: 'nativeProps',
type: 'SwitchProps (from `react-native`)',
description: `Props to pass to the native Switch;`,
},
]}
/>
### Switch.Thumb
`Switch.Thumb` extends View inheriting all the [Tamagui standard props](/docs/intro/props), plus:
## components/dialog/1.0.0
---
title: Dialog
description: Show a modal with configurable layout and accessible actions.
name: dialog
component: Dialog
package: dialog
demoName: Dialog
---
```tsx hero template=Dialog
```
Dialog does not work on native, instead you can Adapt it to a Sheet as shown in the demo
code above.
## Installation
Dialog is already installed in `tamagui`, or you can install it independently:
```bash
npm install @tamagui/dialog
```
In order to use this component independently of `tamagui`, you'll first need to install the `@tamagui/portal` package:
```bash
npm install @tamagui/portal
```
Then add `PortalProvider` to the root of your app:
```tsx fileName="App.tsx"
import { PortalProvider } from '@tamagui/portal'
import YourApp from './components/YourApp'
function App() {
return (
)
}
export default App
```
## Anatomy
```tsx
import { Dialog } from 'tamagui' // or '@tamagui/dialog'
export default () => (
{/* ... */}
{/* Optional: Control focus behavior */}
{/* Focus scope will be applied to children */}
)
```
## API Reference
### Dialog
Contains every component for the dialog. Beyond [Tamagui Props](/docs/intro/props), adds:
void',
},
{
name: 'modal',
type: 'boolean',
default: 'true',
description: `Renders into root of app instead of inline`,
},
{
name: 'disableRemoveScroll',
type: 'boolean',
required: false,
description: `Used to disable the automatic removal of scrolling from the page when open.`,
},
]}
/>
### Dialog.Trigger
Just [Tamagui Props](/docs/intro/props).
### Dialog.Portal
Renders Dialog into appropriate container. Beyond [Tamagui Props](/docs/intro/props), adds:
### Dialog.Content
Main container for Dialog content, this is where you should apply animations.
Beyond [Tamagui Props](/docs/intro/props), adds:
### Dialog.Overlay
Displays behind Content. Beyond [Tamagui Props](/docs/intro/props), adds:
### Dialog.Title
Required. Can wrap in VisuallyHidden to hide.
Defaults to H2, see [Headings](/docs/components/headings).
### Dialog.Description
Required. Can wrap in VisuallyHidden to hide.
Defaults to Paragraph, see [Paragraph](/docs/components/text).
### Dialog.Close
Closes the Dialog, accepts the same props as YStack. Recommended to use with your own component and `asChild`.
Just [Tamagui Props](/docs/intro/props).
### Dialog.FocusScope
Provides access to the underlying FocusScope component used by Dialog for focus management. Can be used to control focus behavior from a parent component.
void',
description: `Event handler called when auto-focusing on mount. Can be prevented`,
},
{
name: 'onUnmountAutoFocus',
type: '(event: Event) => void',
description: `Event handler called when auto-focusing on unmount. Can be prevented`,
},
]}
/>
### Dialog.Sheet
When used with `Adapt`, Dialog will render as a sheet when that breakpoint is active.
See [Sheet](/docs/components/sheet) for more props.
Must use `Adapt.Contents` inside the `Dialog.Sheet.Frame` to insert the contents given to `Dialog.Content`
```tsx
import { Dialog } from 'tamagui' // or '@tamagui/dialog'
export default () => (
{/* ... */}
{/* optionally change to sheet when small screen */}
)
```
Note that Dialog.Sheet currently doesn't preserve state of the contents when it
transitions between Sheet and Portal. In the future, we can do this on the web using
react-reparenting.
### PortalProvider
## Examples
### Inside native modals
If you're using native modals (maybe from react-navigation), you'll notice the Dialogs won't show up inside the modal. To get around this, you should wrap your screen inside `PortalProvider`, like so:
```tsx
import { PortalProvider } from 'tamagui'
// this component used in react-navigation/expo-router with `presentation: "modal"`
export function Page() {
return (
{/* rest of your page, including the Dialog... */}
)
}
```
## components/dialog/1.131.0
---
title: Dialog
description: Show a modal with configurable layout and accessible actions.
name: dialog
component: Dialog
package: dialog
demoName: Dialog
---
```tsx hero template=Dialog
```
Dialog is a great way to show content inside a new floating window above
content. Be sure to open the code example above for a copy-paste implementation.
## Installation
Dialog is already installed in `tamagui`, or you can install it independently:
```bash
npm install @tamagui/dialog
```
If you aren't using `tamagui` and instead using the `@tamagui/dialog` package
separately, you'll first need to install the `@tamagui/portal` package:
```bash
npm install @tamagui/portal
```
Then add `PortalProvider` to the root of your app:
```tsx fileName="App.tsx"
import { PortalProvider } from '@tamagui/portal'
import YourApp from './components/YourApp'
function App() {
return (
)
}
export default App
```
## Anatomy
```tsx
import { Dialog } from 'tamagui' // or '@tamagui/dialog'
export default () => (
{/* ... */}
{/* Optional: Control focus behavior */}
{/* Focus scope will be applied to children */}
)
```
## Scoping
Dialog supports scoping which lets you mount one or more Dialog instances at the
root of your app, while having a deeply nested child Trigger or Content attach
to the proper parent Dialog instance.
In performance sensitive areas you may want to take advantage of this, it allows
you to only need to render the Dialog.Trigger inside the sensitive area as
Dialogs aren't the cheapest component - they have a lot of functionality.
Here's the basic anatomy of using `scope` and placing your Dialog higher up for
performance:
```tsx fileName=_layout.tsx
import { Dialog } from 'tamagui'
// in your root layout:
export default ({ children }) => (
{/* ... */}
{/* the rest of your app, note that it's inside of Dialog */}
{children}
)
```
```tsx fileName=UserProfile.tsx
export default () => (
Open Profile
)
```
Note that the `Trigger` scope ties to the `Dialog` scope.
## Dismissal Behavior
By default, dialogs can be dismissed by:
- Clicking outside the dialog content (on the overlay)
- Pressing the Escape key
- Clicking a Dialog.Close element
### Modal vs Non-Modal Dialogs
- **Modal dialogs** (`modal={true}`, which is the default):
- In v1, have `disableOutsidePointerEvents` set to `true` by default
- Still dismiss on outside click, but prevent interaction with elements behind the dialog
- Prevent right-click dismissal (right-clicks on the overlay are ignored)
- **Non-modal dialogs** (`modal={false}`):
- Allow interaction with elements behind the dialog
- Dismiss on any outside click
- Do not trap focus
### Preventing Outside Dismissal
To prevent a dialog from closing when clicking outside:
```tsx
{
event.preventDefault()
}}
>
{/* Dialog contents */}
```
## API Reference
### Dialog
Contains every component for the dialog. Beyond
[Tamagui Props](/docs/intro/props), adds:
void',
},
{
name: 'modal',
type: 'boolean',
default: 'true',
description: `Renders into root of app instead of inline`,
},
{
name: 'disableRemoveScroll',
type: 'boolean',
required: false,
description: `Used to disable the automatic removal of scrolling from the page when open.`,
},
]}
/>
### Dialog.Trigger
Just [Tamagui Props](/docs/intro/props).
### Dialog.Portal
Renders Dialog into appropriate container. Beyond
[Tamagui Props](/docs/intro/props), adds:
### Dialog.Content
Main container for Dialog content, this is where you should apply animations.
Beyond [Tamagui Props](/docs/intro/props), adds:
### Dialog.Overlay
Displays behind Content. Beyond [Tamagui Props](/docs/intro/props), adds:
### Dialog.Title
Required. Can wrap in VisuallyHidden to hide.
Defaults to H2, see [Headings](/docs/components/headings).
### Dialog.Description
Required. Can wrap in VisuallyHidden to hide.
Defaults to Paragraph, see [Paragraph](/docs/components/text).
### Dialog.Close
Closes the Dialog, accepts the same props as YStack. Recommended to use with
your own component and `asChild`.
Just [Tamagui Props](/docs/intro/props).
### Dialog.FocusScope
Provides access to the underlying FocusScope component used by Dialog for focus
management. Can be used to control focus behavior from a parent component.
void',
description: `Event handler called when auto-focusing on mount. Can be prevented`,
},
{
name: 'onUnmountAutoFocus',
type: '(event: Event) => void',
description: `Event handler called when auto-focusing on unmount. Can be prevented`,
},
]}
/>
### Dialog.Sheet
When used with `Adapt`, Dialog will render as a sheet when that breakpoint is
active.
See [Sheet](/docs/components/sheet) for more props.
Must use `Adapt.Contents` inside the `Dialog.Sheet.Frame` to insert the contents
given to `Dialog.Content`
```tsx
import { Dialog } from 'tamagui' // or '@tamagui/dialog'
export default () => (
{/* ... */}
{/* optionally change to sheet when small screen */}
)
```
Note that Dialog.Sheet currently doesn't preserve state of the contents when it
transitions between Sheet and Portal. In the future, we can do this on the web using
react-reparenting.
### PortalProvider
## Examples
### Inside native modals
If you're using native modals (maybe from react-navigation), you'll notice the
Dialogs won't show up inside the modal. To get around this, you should wrap your
screen inside `PortalProvider`, like so:
```tsx
import { PortalProvider } from 'tamagui'
// this component used in react-navigation/expo-router with `presentation: "modal"`
export function Page() {
return (
{/* rest of your page, including the Dialog... */}
)
}
```
## components/dialog/2.0.0
---
title: Dialog
description: Show a modal with configurable layout and accessible actions
name: dialog
component: Dialog
package: dialog
demoName: Dialog
---
```tsx hero template=Dialog
```
Dialog is a great way to show content inside a new floating window above
content. Dialogs [automatically stack](/ui/z-index) above other overlays. Be sure to open the code example above for a copy-paste implementation.
## Installation
Dialog is already installed in `tamagui`, or you can install it independently:
```bash
npm install @tamagui/dialog
```
For native apps, we recommend [setting up native
portals](/docs/components/portal#native-portal-setup-recommended) to preserve React
context inside Dialog content.
## Anatomy
```tsx
import { Dialog } from 'tamagui' // or '@tamagui/dialog'
export default () => (
{/* ... */}
{/* Optional: Control focus behavior */}
{/* Focus scope will be applied to children */}
)
```
## Scoping
Dialog supports scoping which lets you mount one or more Dialog instances at the
root of your app, while having a deeply nested child Trigger or Content attach
to the proper parent Dialog instance.
In performance sensitive areas you may want to take advantage of this, it allows
you to only need to render the Dialog.Trigger inside the sensitive area as
Dialogs aren't the cheapest component - they have a lot of functionality.
Here's the basic anatomy of using `scope` and placing your Dialog higher up for
performance:
```tsx fileName=_layout.tsx
import { Dialog } from 'tamagui'
// in your root layout:
export default ({ children }) => (
{/* ... */}
{/* the rest of your app, note that it's inside of Dialog */}
{children}
)
```
```tsx fileName=UserProfile.tsx
export default () => (
Open Profile
)
```
Note that the `Trigger` scope ties to the `Dialog` scope.
## Dismissal Behavior
By default, dialogs can be dismissed by:
- Clicking outside the dialog content (on the overlay)
- Pressing the Escape key
- Clicking a Dialog.Close element
### Modal vs Non-Modal Dialogs
- **Modal dialogs** (`modal={true}`, which is the default):
- In v1, have `disableOutsidePointerEvents` set to `true` by default
- Still dismiss on outside click, but prevent interaction with elements behind the dialog
- Prevent right-click dismissal (right-clicks on the overlay are ignored)
- **Non-modal dialogs** (`modal={false}`):
- Allow interaction with elements behind the dialog
- Dismiss on any outside click
- Do not trap focus
### Preventing Outside Dismissal
To prevent a dialog from closing when clicking outside:
```tsx
{
event.preventDefault()
}}
>
{/* Dialog contents */}
```
## API Reference
### Dialog
Contains every component for the dialog. Beyond
[Tamagui Props](/docs/intro/props), adds:
void',
description: `Called when the dialog opens or closes.`,
},
{
name: 'modal',
type: 'boolean',
default: 'true',
description: `Renders into root of app instead of inline.`,
},
{
name: 'keepChildrenMounted',
type: 'boolean',
default: 'false',
description: `When true, dialog content stays mounted in the DOM even when closed. Useful for preserving state across open/close cycles or when you need faster re-opening.`,
},
{
name: 'disableRemoveScroll',
type: 'boolean',
required: false,
description: `Used to disable the automatic removal of scrolling from the page when open.`,
},
]}
/>
### Dialog.Trigger
Just [Tamagui Props](/docs/intro/props).
### Dialog.Portal
Renders Dialog into appropriate container. Beyond
[Tamagui Props](/docs/intro/props), adds:
### Dialog.Content
Main container for Dialog content, this is where you should apply animations.
Beyond [Tamagui Props](/docs/intro/props), adds:
### Dialog.Overlay
Displays behind Content. Beyond [Tamagui Props](/docs/intro/props), adds:
### Dialog.Title
Required. Can wrap in VisuallyHidden to hide.
Defaults to H2, see [Headings](/docs/components/headings).
### Dialog.Description
Required. Can wrap in VisuallyHidden to hide.
Defaults to Paragraph, see [Paragraph](/docs/components/text).
### Dialog.Close
Closes the Dialog, accepts the same props as YStack. Recommended to use with
your own component and `asChild`.
Just [Tamagui Props](/docs/intro/props).
### Dialog.FocusScope
Provides access to the underlying FocusScope component used by Dialog for focus
management. Can be used to control focus behavior from a parent component.
void',
description: `Event handler called when auto-focusing on mount. Can be prevented`,
},
{
name: 'onUnmountAutoFocus',
type: '(event: Event) => void',
description: `Event handler called when auto-focusing on unmount. Can be prevented`,
},
]}
/>
### Dialog.Sheet
When used with `Adapt`, Dialog will render as a sheet when that breakpoint is
active.
See [Sheet](/docs/components/sheet) for more props.
Must use `Adapt.Contents` inside the `Dialog.Sheet.Frame` to insert the contents
given to `Dialog.Content`
```tsx
import { Dialog } from 'tamagui' // or '@tamagui/dialog'
export default () => (
{/* ... */}
{/* optionally change to sheet when small screen */}
)
```
Note that Dialog.Sheet currently doesn't preserve state of the contents when it
transitions between Sheet and Portal. In the future, we can do this on the web using
react-reparenting.
## components/dialog/3.0.0
---
title: Dialog
description: Show a modal with configurable layout and accessible actions
name: dialog
component: Dialog
package: dialog
demoName: Dialog
---
Dialog presents focused content in an accessible modal with composable parts
and optional Sheet adaptation.
```tsx hero template=Dialog
```
Dialog shows content in a floating window above the page. Dialogs
[automatically stack](/ui/z-index) above other overlays. Open the code example
above for a copy-paste implementation.
## Installation
Dialog is already installed in `tamagui`, or you can install it independently:
```bash
npm install @tamagui/dialog
```
For native apps, we recommend [setting up native
portals](/ui/portal#native-portal-setup-recommended) to preserve React
context inside Dialog content.
## Anatomy
```tsx
import { Dialog } from 'tamagui' // or '@tamagui/dialog'
export default () => (
{/* Optional: control focus from a parent */}
{/* ... */}
)
```
## Scoping
Dialog supports scoping which lets you mount one or more Dialog instances at the
root of your app, while having a deeply nested child Trigger or Content attach
to the proper parent Dialog instance.
In performance-sensitive areas this lets you render only `Dialog.Trigger` in
that subtree, since Dialog is not cheap to render and carries a lot of
functionality.
Here's the basic anatomy of using `scope` and placing your Dialog higher up for
performance:
```tsx fileName=_layout.tsx
import { Dialog } from 'tamagui'
// in your root layout:
export default ({ children }) => (
{/* ... */}
{/* the rest of your app, note that it's inside of Dialog */}
{children}
)
```
```tsx fileName=UserProfile.tsx
export default () => (
Open Profile
)
```
Note that the `Trigger` scope ties to the `Dialog` scope.
## Dismissal behavior
By default, dialogs can be dismissed by:
- Clicking outside the dialog content (on the overlay)
- Pressing the Escape key
- Clicking a Dialog.Close element
### Modal vs non-modal dialogs
- **Modal dialogs** (`modal={true}`, which is the default):
- In v1, have `disableOutsidePointerEvents` set to `true` by default
- Still dismiss on outside click, but prevent interaction with elements behind the dialog
- Prevent right-click dismissal (right-clicks on the overlay are ignored)
- **Non-modal dialogs** (`modal={false}`):
- Allow interaction with elements behind the dialog
- Dismiss on any outside click
- Do not trap focus
- Do not enable RemoveScroll while open in v3
In v3, Dialog parts own their presence lifecycle. Overlay and content can finish
exit animations before unmounting, and `onTransition` reports completion from
the animated part rather than from the root alone.
### Preventing outside dismissal
To prevent a dialog from closing when clicking outside:
```tsx
{
event.preventDefault()
}}
>
{/* Dialog contents */}
```
## API reference
### Dialog
Contains every component for the dialog. Beyond
[Tamagui Props](/docs/intro/props), adds:
void',
description: `Called when the dialog opens or closes.`,
},
{
name: 'modal',
type: 'boolean',
default: 'true',
description: `When true, traps focus, blocks outside scroll and interaction, and portals into the root of the app. When false, the dialog renders inline and is non-blocking.`,
},
{
name: 'keepChildrenMounted',
type: 'boolean',
default: 'false',
description: `When true, dialog content stays mounted in the DOM even when closed. Useful for preserving state across open/close cycles or when you need faster re-opening.`,
},
{
name: 'disableRemoveScroll',
type: 'boolean',
required: false,
description: `Used to disable the automatic removal of scrolling from the page when open.`,
},
{
name: 'onAnimationComplete',
type: '(info: { open: boolean }) => void',
required: false,
description: `Called when the dialog open or close animation completes.`,
},
]}
/>
### Dialog.Trigger
Just [Tamagui Props](/docs/intro/props).
### Dialog.Portal
Renders Dialog into appropriate container. Beyond
[Tamagui Props](/docs/intro/props), adds:
### Dialog.Content
Main container for Dialog content, this is where you should apply animations.
`Dialog.Content` no longer accepts the old no-op `size` variant in v3.
To sit the content a level above the page, wrap it in `theme="level2"` (see
[Surfaces and levels](/docs/core/surfaces)):
```tsx
{/* ... */}
```
Beyond [Tamagui Props](/docs/intro/props), adds:
void',
required: false,
description: `Called when Escape is pressed while the content is active.`,
},
{
name: 'onInteractOutside',
type: '(event: Event) => void',
required: false,
description: `Called when pointer or focus interaction happens outside the content. Prevent default to stop dismissal.`,
},
]}
/>
### Dialog.Overlay
Displays behind Content. Beyond [Tamagui Props](/docs/intro/props), adds:
### Dialog.Title
Required. Can wrap in VisuallyHidden to hide.
Defaults to H2, see [Headings](/ui/headings).
### Dialog.Description
Required. Can wrap in VisuallyHidden to hide.
Defaults to Paragraph, see [Paragraph](/ui/text).
### Dialog.Close
Closes the Dialog, accepts the same props as View. Recommended to use with
your own component and `asChild`.
### Dialog.FocusScope
Provides access to the underlying FocusScope component used by Dialog for focus
management. Can be used to control focus behavior from a parent component.
void',
description: `Event handler called when auto-focusing on mount. Can be prevented`,
},
{
name: 'onUnmountAutoFocus',
type: '(event: Event) => void',
description: `Event handler called when auto-focusing on unmount. Can be prevented`,
},
]}
/>
### Adapted Sheet
When used with [Adapt](/ui/adapt), Dialog can hand its content to a Sheet at the
matching breakpoint.
See [Sheet](/ui/sheet) for more props.
Use `Adapt.Contents` inside `Sheet.Container` to insert the contents given to
`Dialog.Content`.
```tsx
import { Adapt, Dialog, Sheet } from 'tamagui'
export default () => (
{/* ... */}
{/* optionally change to sheet when small screen */}
)
```
## intro/compiler-install
---
title: Tamagui Compiler
description: Adding the compiler to your apps
---
The Tamagui Compiler significantly improves performance of both web and native
applications through partial analysis and view flattening.
See the [Benchmarks](/docs/intro/benchmarks) or read the
[in-depth background](/docs/intro/introduction#why-a-compiler). Note that Tamagui features
work at compile-time and runtime, so installing the compiler is optional, and in
fact we recommend only setting it up once you're ready for production.
The compiler analyzes JSX and `styled` definitions, resolves static styles,
and lowers eligible components to platform-native primitives.
The end result is less abstraction, such as a `div` on web or a plain React Native
`View` on native: