# 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.
## intro/benchmarks
---
title: Benchmarks
description: Reproducible web and native performance comparisons
---
Uniwind was fastest in all 15 native cells. Tamagui Tailwind was second in all
15. On web, Tailwind CSS was fastest in 10 cells, Tamagui Tailwind in three,
and inline React in two.
Green cells mark the lowest mean in each row. All values are mean milliseconds.
Lower is faster.
## Web results
The web fixture renders 200 items, or 60 in the heavy case. Mount starts with an
empty root. Update changes fixture state without replacing the root. Remount
replaces the tree. Timing starts before the React state change and ends in
`useLayoutEffect`.
Each production build ran in Chromium `151.0.7922.34` on an Apple M5 Pro with 18
logical CPUs and 64 GiB of memory. The runner used Bun `1.3.14` on Darwin
`25.5.0`. Two warmup rounds preceded 10 retained rounds. Seed `73129` shuffled
every framework and scenario round. All 50 behavior checks passed before timing.
Simple, rich, group, and heavy use Tamagui `3.0.0-beta.901.1`. Animated uses
Tamagui `3.0.0-beta.917.1`. The animated compiler flattened all 14 candidates
with zero bailouts. The other versions are Tailwind CSS `4.3.0`, NativeWind
`5.0.0-preview.4`, React Native Web `0.21.2`, Uniwind `1.11.0`, React `19.2.3`,
and React DOM `19.2.3`.
Tamagui Tailwind mounted faster than NativeWind and Uniwind in all five web
scenarios. The paired 95% confidence intervals favored Tamagui in each of those
10 comparisons. Tailwind CSS had lower means than Tamagui in 10 of 15 cells.
Inline React had the lowest simple and group update means.
The Tamagui arm uses `@tamagui/tailwind`. This table does not measure Tamagui's
component APIs.
## Native results
All three Release apps render the same `native-class-bench.tsx` fixture. They use
the same class strings, component tree, item counts, state changes, and timing
boundary. Simple, themed, rich, and group render 200 items. Heavy renders 60
three-component groups.
The fixture uses explicit point dimensions. An earlier smoke run found that
`w-5` resolved to 52 points under the benchmark's Tamagui config and 20 points
in the other arms. That fixture was rejected. Fixture version 2 passed an
independent 20 by 20 layout check in all three apps before timing.
The campaign used Tamagui Tailwind `3.0.0-beta.917.1`, NativeWind
`5.0.0-preview.4`, and Uniwind `1.11.0`. NativeWind also used
`react-native-css` `3.0.7`, Reanimated `4.5.1`, and Worklets `0.10.1`. Every app
used Expo `57.0.15`, React `19.2.3`, and React Native `0.86.2`.
The apps ran on an iOS `26.4` simulator on an Apple M5 Pro with 18 logical CPUs
and 64 GiB of memory. The runner used Bun `1.4.0`, Node `26.3.0`, and
XcodeBuildMCP `2.3.0` on Darwin `25.5.0`. It waited until three host samples were
`71.8%`, `63.4%`, and `74.1%` idle. Two warmup rounds preceded 12 retained
rounds. Seed `73129` shuffled every round.
Uniwind had the lowest mean in every native cell. Its paired 95% confidence
intervals also favored it over Tamagui in all 15 cells. Tamagui had lower means
than NativeWind in all 15 cells. Several Tamagui and NativeWind paired intervals
overlapped zero because NativeWind's observations varied widely.
## Source record
The cross-framework harness began in `code/comparisons`. It was extracted from
this monorepo in [commit `300a4db8de`](https://github.com/tamagui/tamagui/commit/300a4db8def094229793e2c416b1a008fc5d20a5)
so NativeWind does not enter the main workspace dependency graph.
The web values come from committed receipts `8b45f79809` and `9896f9c488`. The
native values come from fixture source commit `84fc84bb5d` and receipt commit
`352925a2ac`. The native receipt contains all 180
retained observations, 30 warmups, confidence intervals, package versions,
fixture hashes, bundle hashes, build identities, and behavior signatures. The
comparison repository does not have a public remote yet. The web table is also
retained in the [Tamagui Tailwind post](/blog/tailwind-mode).
The monorepo still contains Tamagui's conformance harness, runtime
microbenchmarks, compiler graph benchmark, bundle checks, and browser fixture.
## What this does not measure
- The web run covers production rendering after load. It does not measure server
rendering, hydration, interaction latency, or network cost.
- The native run uses one simulator. It does not measure a physical device,
Android, startup, memory, GPU work, or energy use.
- The timers end in `useLayoutEffect`. They include React reconciliation and the
synchronous native commit. They do not include the next fully drawn frame.
- The behavior gate checks this fixture. It does not prove support or visual
equivalence for every utility.
- Compiler acceptance and rendered pixels are separate questions.
- Small differences can move with hardware, operating system, library version,
and application shape.
- Bundle size and build time are tracked separately. Each library ships a
different mix of runtime and build tooling.
## intro/styles
---
title: Styling
description: Style props, conditional values, and the modifiers that control them
---
Tamagui uses style props across all components. Every element accepts React
Native and web styles, with unified support for responsive, theme, and pseudo-state
conditions.
```tsx
import { Text, View } from 'tamagui'
export const Card = () => (
Hello
)
```
`padding`, `borderRadius`, and `backgroundColor` are style props. `"4"` and
`"lg"` are tokens, `background` is a theme key, and `md:` and `hover:` are
modifiers. That is the whole grammar. The same props go on `View`, `Text`,
every other [component](/docs/intro/components), and the second argument of
[`styled()`](/docs/core/styled).
Every style prop takes its value as a string or an object. Strings fit the
base value and its conditions into one short read:
```tsx
```
Objects spell the same thing with keys, and stay fully typed:
```tsx
```
Use strings for short, mostly static values — a prop with one or two
conditions, written inline. They exist so the common case stays terse and
readable. Use objects when the value is a number (numbers skip token lookup,
so `padding={16}` is always 16 pixels), when conditions stack up, or when you
build the value in code from variables and spreads — objects are real values,
so TypeScript checks every token and modifier as you type. Both spellings take
the same values and modifiers and render identically, so mix them per prop as
needed. The Conditional values section below details each spelling.
## Values
A style value is one of:
| Value | Example | Resolves to |
| --- | --- | --- |
| Number | `padding={16}` | 16 pixels on every platform |
| Pixel string | `padding="16px"` | An exact pixel value, even when a token has the same name |
| Token | `padding="4"`, `borderRadius="lg"` | The token from that prop's category |
| Theme key | `color="color-11"` | The active theme's value, updating when the theme changes |
| CSS value | `display="flex"`, `boxShadow="0 2px 8px shadow-3"` | Passed through on web and converted on native |
| `rem` | `padding="1rem"` | Scaled by the user's font size on native |
[Design system](/docs/intro/design-system) covers where tokens and theme keys
come from. [Style props](/docs/core/style-props) lists every prop, including
the ones that only render on web.
## Conditional values
Every style prop takes conditions in two spellings, a string of clauses or a
flat object. These render the same thing:
StringTyped
```tsx
```
```tsx
```
The object keys are the same modifiers the string uses, with `default` for the
base value. There is no mode to pick, and you can mix them per prop in one
component:
```tsx
```
Strings are shorter for one or two conditions. Objects work better when the
values are numbers, when the conditions stack up, or when you build the value
in code. Both compile to identical CSS.
An object only counts as conditional when it has a `default` key or its first
key is a valid modifier. A plain value object like
`shadowOffset={{ width: 0, height: 2 }}` is left alone.
## Font size and line height values
Numeric `lineHeight` authored in Tamagui styles is a font-size ratio on web and
native. A numeric string has the same ratio meaning when it is not a configured
font-token name. An explicit px string stays absolute. Values are never inferred
from magnitude, so `lineHeight={24}` means 24 times the effective font size and
`lineHeight="24px"` means 24 pixels.
Font configuration keeps a separate compatibility contract. Numeric
`lineHeight` entries remain absolute pixels. Use a numeric string for a relative
font token and a px string for an explicit absolute token:
```tsx
Platform line heightExact px size30px line box24px line box
const bodyFont = createFont({
family: 'Inter',
size: { body: 20 },
lineHeight: {
legacy: 24, // absolute pixels
relative: '1.5', // ratio
explicit: '24px', // absolute pixels
},
})
```
On native, Tamagui can resolve a ratio from the effective font size inherited
through Tamagui text. This inheritance continues through intervening Tamagui Views,
as it does on web. When a Tamagui text boundary sits below a raw React Native
or third-party text parent, pass the effective font size at that boundary because
Tamagui cannot inspect typography held only by the native host tree.
If a native ratio has no known font size, Tamagui explicitly sets `fontSize={14}`
and calculates the line height from 14. This also applies below a raw native text
parent, so pass its font size at the Tamagui boundary to preserve the glyph size.
## Modifiers
Every modifier applies to just the property carrying it. They fall into five
groups:
| Group | Modifiers | Example |
| --- | --- | --- |
| State | `hover:`, `press:`, `focus:`, `focus-visible:`, `focus-within:`, `disabled:` | `scale="press:0.98"` |
| Screen size | any media name from your config, such as `sm:` or `max-md:` | `padding="4 md:8"` |
| Theme | `dark:`, `light:`, or a top-level theme name | `color="dark:white"` |
| Platform | `web:`, `native:`, `ios:`, `android:` | `paddingTop="ios:12"` |
| Parent | `group-hover:`, `group-press:`, `group-focus:`, `@sm:` container queries | `color="group-hover:white"` |
Modifiers stack. `sm:hover:purple` applies on hover at the `sm` breakpoint.
When several clauses match, the most specific one wins.
[Flat conditional values](/docs/guides/flat-values) has the full precedence rules and
grammar.
### States
Add state clauses directly to the property they change:
- **`hover:`**: while hovering (web only, maps to CSS `:hover`)
- **`press:`**: while pressing
- **`focus:`**: while focused (maps to CSS `:focus`)
- **`focus-visible:`**: while keyboard-focused (maps to CSS `:focus-visible`)
- **`focus-within:`**: while a child has focus (maps to CSS `:focus-within`)
- **`disabled:`**: while `disabled={true}`
```tsx
```
`hover:` is web only. `focus:`, `focus-visible:`, and `focus-within:` map to the
matching CSS pseudo-classes on web.
### Enter and exit
For mount and unmount animations, `enter:` is the value the property animates
from on mount, and `exit:` is the value it animates to on unmount:
```tsx
```
See [Animations](/docs/core/animations).
### Screen size
Media queries come from `createTamagui({ media })`. Each name is a modifier on
native and web:
```tsx
```
### Theme and platform
Theme modifiers target top-level themes. With `light` and `light_subtle`
defined, only `light` can be targeted. Platform modifiers cover `ios`,
`android`, `web`, and `native`:
```tsx
```
### Groups
Mark a parent with `group` and children can react to its state:
```tsx
```
The states are `hover` (web only), `press`, and `focus`. Name a group to
target it from further down:
```tsx
InnerSibling
```
`Inner` turns blue when the `card` group is hovered. `Sibling` turns green
when its nearest group is hovered. For completion on named groups, declare
them next to your config types:
```tsx
declare module 'tamagui' {
interface TamaguiCustomConfig extends AppConfig {}
interface TypeOverride {
groupNames(): 'card' | 'header' | 'sidebar'
}
}
```
### Container queries
Add `container` to a group and children can respond to the parent's size,
using the same media names. Web outputs a CSS container query:
```tsx
```
Named containers work the same way with `@sm/card:`.
On native the container's size is only known after the first `onLayout`, so
children render once before the query applies. Set `untilMeasured="hide"` on
the container to keep it at opacity 0 until measured, or give the container an
explicit `width` and `height` so children can resolve the query on first
render.
## Next
- [Style props](/docs/core/style-props) for every accepted property and how it
renders per platform.
- [styled()](/docs/core/styled) and [Variants](/docs/core/variants) for
reusable components.
- [Flat conditional values](/docs/guides/flat-values) for the complete value
grammar and editor support.
## intro/introduction
---
title: Introduction
---
## Two ways to write styles
Tamagui ships two styling frontends. Both compile to the same atomic CSS on web
and the same native styles on iOS and Android, and both accept the same tokens,
themes, and media queries.
**Style props** on `View` and `Text` are the default. They are typed and
compile to atomic CSS on web or native styles on iOS and Android:
```tsx
import { Text, View } from 'tamagui'
export const Card = () => (
HelloWorld
)
```
**Tailwind** classes on `className` are the other option, for teams who already
think in utilities:
```tsx
import { Text, View } from '@tamagui/tailwind'
export const Card = () => (
HelloWorld
)
```
The choice is per import path, so one app can use both. Read
[Components](/docs/intro/components) for the style prop path and
[Tailwind](/docs/core/tailwind) for the utility path.
## Why a compiler
Cross-platform development often involves trade-offs between native feel, code reuse, and performance. React Native shares logic across platforms, but on web, styling in JavaScript can mean heavy runtime overhead and larger bundles.
Tamagui resolves this with `@tamagui/static`, an optimizing compiler that:
Here is what that looks like in code:
The `Text` became a `p` and the `YStack` became a `div`. On native the same
pass produces `Text` and `View`. Flattening is where most of the speed comes
from. Across real apps around a third to a half of components flatten, and the
result on web is close to hand-written CSS. On native the output is within a
few percent of hand-optimized React Native, with the whole feature set intact.
Everything still works at runtime. The compiler is an optimization you can add
later, and a component it cannot optimize simply renders the normal way. See
[Benchmarks](/docs/intro/benchmarks) for numbers and
[Compiler installation](/docs/intro/compiler-install) to turn it on.
## Where to go next
- [Installation](/docs/intro/installation) sets up a project or hands the job
to a coding agent.
- [Components](/docs/intro/components) covers `html.*`, `View`, `Text`, and how
they render on each platform.
- [Styling](/docs/intro/styles) explains style props, conditional values, and
states.
- [Design system](/docs/intro/design-system) introduces tokens, themes, and
fonts.
- [Tailwind](/docs/core/tailwind) covers the utility class frontend.
#### Highlights
---
## Community
Join us on:
- [Discord](https://discord.gg/4qh6tdcVDa)
- [X](https://x.com/tamagui_js)
- [GitHub](https://github.com/tamagui/tamagui)
---
## Credits
A big thanks to:
- [Stitches](https://stitches.dev) for the variants pattern.
- [JSXStyle](https://github.com/jsxstyle/jsxstyle) for providing the original
version of the compiler.
- [Modulz](https://github.com/modulz) for Radix, which inspired many of our component APIs, and for the initial structure of this website.
- [Framer Motion](https://github.com/framer/motion) for the AnimatePresence
implementation.
## intro/tokens
---
title: Tokens
description: The default Tamagui token scale and sizing structure
---
These are the default `@tamagui/config` tokens that power this site. Your app
can define its own scale and naming system.
### Understanding the size scale
Most design systems use pure exponential scaling (2, 4, 8, 16). Tamagui instead uses a hybrid scale: gradual increments from 1 to 10 for fine control across common UI controls, followed by exponential steps from 11 to 16 for large display elements. This gives components a practical range of sizes where small differences matter most.
#### Shared scale keys
Token groups (size, space, radius) and font groups share the same 1 through 16 keys. A size 2 Button naturally aligns with size 2 padding, a size 2 icon, and size 2 text.
#### Sub-unit scale (0.25, 0.5, 0.75)
While the primary scale starts at 1, Tamagui includes three fractional tokens below 1 for fine adjustments, borders, and sub-pixel alignment.
#### Primary scale (1-10)
Sizes 1 through 10 increase gradually for controls and containers. Size 2 suits compact UI elements, size 4 is the standard default size, sizes 5 and 6 provide emphasized controls, and sizes 7 and above serve larger call-to-action blocks.
#### Display scale (11-16)
Sizes 11 through 16 switch to exponential spacing for hero sections and display banners.
## intro/installation
---
title: Installation
description: Get Tamagui set up, step by step
---
Tamagui installs with no bundler configuration on web or native. Start small,
and add the compiler and bundler plugins once the basics work.
The fastest start is a starter template:
```bash
npm create tamagui@latest
```
## Requirements
- **React 19+**
- **TypeScript 5+**
- **React Native 0.81+** with the New Architecture enabled, for native apps.
Tamagui uses newer style features such as `boxShadow` and `filter`. On web
there are no version restrictions.
## Install
Install the full UI kit:
```bash
yarn add tamagui @tamagui/config
```
Or install only the style library. The `tamagui` package is a superset of
`@tamagui/core`, so anywhere the docs reference core you can use either:
```bash
yarn add @tamagui/core @tamagui/config
```
## Choose a styling frontend
Tamagui has two ways to write styles. Both produce the same output, and the
choice is per import path, so you can mix them later. Pick one to start.
Style propsTailwind
Typed style props on `View`, `Text`, and every other Tamagui component. This is
the default for new apps. It reads like React Native and needs no extra package.
```tsx fileName="App.tsx"
import { TamaguiProvider, View, createTamagui } from 'tamagui'
import { defaultConfig } from '@tamagui/config/v6'
const config = createTamagui(defaultConfig)
declare module 'tamagui' {
interface TamaguiCustomConfig extends typeof config {}
}
export default () => (
)
```
Continue with [Components](/docs/intro/components) and
[Styling](/docs/intro/styles).
Tailwind utility classes on `className`, extracted to atomic CSS on web and
resolved to native styles on iOS and Android.
```bash
yarn add @tamagui/tailwind
```
```tsx fileName="App.tsx"
import { TamaguiProvider, createTamagui } from 'tamagui'
import { View } from '@tamagui/tailwind'
import { defaultConfig } from '@tamagui/config/v6'
const config = createTamagui(defaultConfig)
declare module 'tamagui' {
interface TamaguiCustomConfig extends typeof config {}
}
export default () => (
)
```
Continue with [Tailwind](/docs/core/tailwind).
You usually export the config from a `tamagui.config.ts` file. See
[Configuration](/docs/core/configuration) for what goes in it.
Set your root theme with the `defaultTheme` prop on `TamaguiProvider` rather
than wrapping the app in ``. That keeps the
[`fastSchemeChange` setting](/docs/core/settings) working on native and lets
light and dark switch through media queries during SSR on web.
That is the whole setup:
```tsx class=preview line=5
import { Button } from 'tamagui'
export default function Demo() {
return
}
```
Every feature works at runtime, so nothing above depends on a bundler plugin.
When you are ready for production, [set up the compiler](/docs/intro/compiler-install)
for smaller bundles and faster renders.
## Bundler guides
Tamagui itself needs no bundler setup. React Native and React Native Web
tooling often does, so Tamagui ships plugins that handle it:
## Next steps
- **[CLI](/docs/core/cli)** for building, checking versions, and generating
agent prompts.
- **[Design system](/docs/intro/design-system)** for tokens, themes, and fonts.
- **[Custom UI package](/docs/guides/design-systems)** to build your own
optimized component library.
## intro/components
---
title: Components
description: The elements you build with, and what they render on each platform
---
Every Tamagui component takes style props. This page covers the base elements
those props go on, and which one to use when.
## View and Text
`View` and `Text` are the two primitives everything else is built on. They match
React Native's `View` and `Text` and add the Tamagui style props on top:
```tsx
import { View, Text } from 'tamagui'
export const Hello = () => (
HelloThe same layout on iOS, Android, and web.
)
```
Use them by default. A `View` is a flex column on every platform, so a layout you
build once looks the same on web and native. On web they render a `div` and a
`span`, and you can change the tag with [`render`](/docs/core/styled#render) when
you need semantic HTML, such as ``.
See [View & Text](/docs/core/view-and-text) for the full prop reference,
including event handlers and the non-style props Tamagui adds.
## Reusable components with styled()
`styled()` wraps any of the above into a named component with default styles
and typed variants. Inline props and `styled()` are both optimized by the
compiler, so pick whichever reads better:
```tsx
import { styled, View } from 'tamagui'
export const Card = styled(View, {
padding: '4',
borderRadius: 'lg',
backgroundColor: 'background',
variants: {
elevated: {
true: { boxShadow: '0 4px 12px shadow-3' },
},
} as const,
})
//
```
See [styled()](/docs/core/styled) and [Variants](/docs/core/variants).
## Themes in the tree
`Theme` switches the theme for a subtree, and any component takes a `theme`
prop for the same thing at one level:
```tsx
import { Button, Theme } from 'tamagui'
export const Panel = () => (
)
```
See [Theme](/docs/core/theme) for `ThemeUpdate`, custom variables, and how
sub-themes resolve.
## The UI kit
The `tamagui` package adds styled, accessible components on top of these
primitives: `Button`, `Dialog`, `Sheet`, `Select`, and the rest of the
[UI kit](/ui/intro). Every one of them accepts `unstyled` to drop its default
styles and keep only its behavior.
## HTML elements
The `html` export has an element for each semantic HTML tag, such as
`html.article`, `html.h1`, and `html.p`. On web each one renders that tag, and on
native it renders the matching React Native primitive. It follows web and
[React Strict DOM](https://facebook.github.io/react-strict-dom/) semantics, which
makes it a good fit for screens that are mostly web content, like docs,
marketing pages, and articles.
```tsx
import { html } from 'tamagui'
export const Article = () => (
A semantic heading
Renders an article, h1, and p on web.
)
```
`html.*` is in early development. Its elements keep the browser's default
`display`, so `html.div` is `display: block` on web, while React Native only
supports flex layout. A layout built from `html.*` can therefore look different
on native. Use `View` and `Text` when the same screen has to look the same
everywhere. See [HTML elements](/docs/core/html-primitives) for the element list
and native rules.
## Prefer utility classes?
The same elements exist in `@tamagui/tailwind`, styled with `className`
instead of props. See [Tailwind](/docs/core/tailwind).
## 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 uses Babel to analyze JSX and `styled` functions, then attempts to
statically analyze and optimize them down to platform-native primitives.
The end result is less abstraction, such as a `div` on web or a plain React Native
`View` on native:
The compiler generates built versions of your components and config into a `.tamagui`
directory. You'll want to add that directory to your `.gitignore`.
## Configuration with tamagui.build.ts
We recommend creating a `tamagui.build.ts` file in your project root as the
single source of truth for your compiler configuration. All bundler plugins and
the CLI automatically read from this file, so you only need to define your
options once.
This file lets the Tamagui CLI read your config and perform operations like
generating CSS, pre-compiling components, and verifying optimizations, while
also sharing that same configuration with whichever bundler plugin you use.
Without it, you'd need to duplicate options across your metro, babel, vite,
or webpack configs.
```ts fileName="tamagui.build.ts"
import type { TamaguiBuildOptions } from 'tamagui'
export default {
config: './tamagui.config.ts',
components: ['tamagui'],
outputCSS: './public/tamagui.generated.css',
// optional:
importsWhitelist: ['constants.js', 'colors.js'],
disableExtraction: process.env.NODE_ENV === 'development',
} satisfies TamaguiBuildOptions
```
With this file in place, your bundler plugins can be configured with no options
at all, because they pick up everything from `tamagui.build.ts`:
```tsx
// vite: tamaguiPlugin()
// webpack: new TamaguiPlugin()
// metro: withTamagui(config)
// all read from tamagui.build.ts automatically
```
You can still pass options directly to a plugin, and they'll be merged with
(and override) the options from `tamagui.build.ts`.
### Experimental native theme fast path
V3 beta includes an opt-in native theme engine for React Native 0.82 and newer.
It can update eligible Fabric views through a ShadowTree commit without
re-rendering each view in React. The API and compiler output are experimental.
Install the native engine and Nitro runtime:
```sh
bun add @tamagui/native-registry react-native-nitro-modules
```
Enable its 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
```
Install the engine before your themed application tree mounts:
```ts
import * as nativeRegistry from '@tamagui/native-registry'
import { setNativeStyleEngine } from '@tamagui/core'
if (nativeRegistry.isAvailable()) {
setNativeStyleEngine(nativeRegistry)
}
```
Rebuild the iOS and Android applications after adding the packages. The flag
defaults to `false`. When it is off, native compiler output is unchanged. When
the compiler flag is on but the engine is unavailable, components use the
existing theme hook and React render path. Web output is unchanged.
The registry keeps the existing `DynamicColorIOS` optimization in place. It
does not replace that path.
## Install
The recommended install is one package. `@tamagui/cli` carries the CLI and the
bundler integrations together, each behind a subpath:
```bash
yarn add -D @tamagui/cli
```
| Import | Gives you |
| -------------------- | ------------------------------------------- |
| `@tamagui/cli/vite` | the same exports as `@tamagui/vite-plugin` |
| `@tamagui/cli/metro` | the same exports as `@tamagui/metro-plugin` |
One dependency means one version to keep in sync, so the CLI and your bundler
plugin can't drift apart on an upgrade.
You can still install the standalone plugin instead, and if your project only
ever uses one bundler that is the smaller install. Both packages stay published
and the exports are identical, so the setup below is the same either way.
There are plugins for a variety of bundlers, or you can use the `@tamagui/cli`
to compile in-place:
### Webpack
```bash
yarn add tamagui-loader
```
We have a full example of a plain Webpack or Vite setup in the simple starter
accessible through `npm create tamagui@latest`, which shows a complete
configuration with more detail.
Add `tamagui-loader` and set up your `webpack.config.js`.
You can set it up more manually like so:
```js
const { shouldExclude } = require('tamagui-loader')
const tamaguiOptions = {
config: './tamagui.config.ts',
components: ['tamagui'],
importsWhitelist: ['constants.js', 'colors.js'],
logTimings: true,
disableExtraction: process.env.NODE_ENV === 'development',
}
module.exports = {
resolve: {
alias: {
// Resolve react-native to react-native-web
'react-native$': require.resolve('react-native-web'),
// optional, for lighter svg icons on web
'react-native-svg': require.resolve('@tamagui/react-native-svg'),
},
},
module: {
rules: [
{
test: /\.[jt]sx?$/,
// you'll likely want to adjust this helper function,
// but it serves as a decent start that you can copy/paste from
exclude: (path) => shouldExclude(path, __dirname, tamaguiOptions),
use: [
// optionally thread-loader for significantly faster compile!
'thread-loader',
// works nicely alongside esbuild
{
loader: 'esbuild-loader',
},
{
loader: 'tamagui-loader',
options: tamaguiOptions,
},
],
},
],
},
}
```
Or you can use the TamaguiPlugin which automates some of this setup for you.
If you have a `tamagui.build.ts`, you can pass no options:
```tsx
const { TamaguiPlugin } = require('tamagui-loader')
module.exports = {
plugins: [
// reads from tamagui.build.ts automatically
new TamaguiPlugin(),
],
}
```
Or pass options inline to override:
```tsx
const { TamaguiPlugin } = require('tamagui-loader')
module.exports = {
plugins: [
new TamaguiPlugin({
config: './tamagui.config.ts',
components: ['tamagui'],
importsWhitelist: ['constants.js', 'colors.js'],
logTimings: true,
disableExtraction: process.env.NODE_ENV === 'development',
}),
],
}
```
Some notes on the options:
- _importsWhitelist_: Tamagui takes a conservative approach to partial
evaluation, this field whitelists (matching against both .ts and .js) files to
allow files that import them to read and use their values during compilation.
Typically colors and constants files.
- _disableExtraction_: Useful for faster developer iteration as your design
system hot reloads more reliably.
### Vite
See the [Vite guide](/docs/guides/vite) for more complete setup.
The Vite plugin is ESM-only. Your project must have `"type": "module"` in its `package.json` (or use `.mjs`/`.mts` config files).
Update your `vite.config.ts`. If you have a `tamagui.build.ts`, no options are
needed:
```tsx
import { tamaguiPlugin } from '@tamagui/cli/vite'
export default defineConfig({
plugins: [
// reads from tamagui.build.ts automatically
tamaguiPlugin(),
],
})
```
If you installed `@tamagui/vite-plugin` on its own, import from there instead.
Or pass options inline:
```tsx
import { tamaguiPlugin } from '@tamagui/cli/vite'
export default defineConfig({
plugins: [
tamaguiPlugin({
config: 'src/tamagui.config.ts',
components: ['tamagui'],
disableExtraction: true,
}),
],
})
```
### Next.js
See the [guide](/docs/guides/next-js) for more complete setup.
Next.js with Turbopack (the default) works best with the CLI approach: create a
`tamagui.build.ts` and use `tamagui build` to optimize production builds. No
bundler plugin needed.
For older Webpack-based Next.js setups, add `@tamagui/next-plugin` and configure
your `next.config.js`:
```js
const { withTamagui } = require('@tamagui/next-plugin')
module.exports = function (name, { defaultConfig }) {
const tamaguiPlugin = withTamagui({
// reads from tamagui.build.ts automatically, or pass inline:
config: './tamagui.config.ts',
components: ['tamagui'],
disableExtraction: process.env.NODE_ENV === 'development',
excludeReactNativeWebExports: ['Switch', 'ProgressBar', 'Picker'],
})
return {
...defaultConfig,
...tamaguiPlugin(defaultConfig),
}
}
```
Note: If running into issues, the environment variable `IGNORE_TS_CONFIG_PATHS`
to "true" can fix issues with Tamagui being resolved incorrectly.
See the [Next.js Guide](/docs/guides/next-js) for more details on setting up
your app.
### Metro
The Metro integration runs the same shared compiler used by the other bundler
adapters after your configured Babel transformer.
Wrap your existing Metro config. With a `tamagui.build.ts`, you can omit the
inline options:
```js
const { getDefaultConfig } = require('expo/metro-config')
const { withTamagui } = require('@tamagui/cli/metro')
module.exports = withTamagui(getDefaultConfig(__dirname))
```
If you installed `@tamagui/metro-plugin` on its own, require from there instead.
Or pass options inline:
```js
const { getDefaultConfig } = require('expo/metro-config')
const { withTamagui } = require('@tamagui/cli/metro')
module.exports = withTamagui(getDefaultConfig(__dirname), {
components: ['tamagui'],
config: './tamagui.config.ts',
logTimings: true,
disableExtraction: process.env.NODE_ENV === 'development',
})
```
### Expo
[Check out the Expo guide](/docs/guides/expo) for more information on setting up
Expo. It uses the same Metro integration shown above.
### CLI-Based In-Place Compilation
For bundlers that don't have a Tamagui plugin yet (like Turbopack), or if you
prefer a simple setup, you can use `@tamagui/cli` to pre-compile your components
in-place before your build step.
This approach is meant for **production builds only** and should run in your
deployment pipeline, not during development. It rewrites files in place which
will mess up your working directory, but makes it highly compatible with any
bundler or tool. The downside is you don't get the helpful development
compatibility parts of the plugins, plus dev-mode debugging and `data-`
attributes.
For complete CLI documentation including all available commands, see the
[CLI Guide](/docs/core/cli).
#### Setup
1. Install:
```bash
yarn add -D @tamagui/cli
```
2. Create a `tamagui.build.ts` if you haven't already (see
[above](#configuration-with-tamaguibuildts)).
3. Add a build script to your `package.json`:
```json
{
"scripts": {
"build": "tamagui build ./src -- next build"
}
}
```
#### Usage
```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
# Verify minimum optimizations (useful in CI)
npx tamagui build --target web --expect-optimizations 10 ./src
```
#### CI Verification with --expect-optimizations
The `--expect-optimizations` flag ensures your build is actually optimizing components. This is useful in CI to catch configuration issues:
```json
{
"scripts": {
"build": "tamagui build --target web --expect-optimizations 10 ./src -- next build"
}
}
```
If the compiler produces fewer than the expected number of optimizations, the build will fail with an error message showing the actual count. This helps catch:
- Misconfigured `components` array
- Wrong source paths
- Configuration files not being found
#### Platform-Specific File Handling
The CLI automatically handles platform-specific files (`.web.tsx`,
`.native.tsx`, `.ios.tsx`, `.android.tsx`):
- Files with `.web.tsx` extensions are optimized for web only
- Files with `.native.tsx`, `.ios.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
#### Package.json Exports Support
The CLI supports `package.json` exports for path-specific imports. For example:
```json
{
"exports": {
".": "./src/index.tsx",
"./components/Button": "./src/Button.tsx"
}
}
```
Both import styles work:
```tsx
import { Button } from '@my/ui'
import { Button } from '@my/ui/components/Button'
```
#### Integration Examples
This works with **any build tool**: run `tamagui build` before your build
command. Here are some examples:
**Next.js with Turbopack** (Turbopack doesn't support plugins yet):
```json
{
"scripts": {
"dev": "next dev --turbopack",
"build": "tamagui build --target web ./src -- next build"
}
}
```
**Vite, Remix, or any other bundler:**
```json
{
"scripts": {
"build": "tamagui build --target web ./src -- vite build"
}
}
```
**React Native / Expo:**
```json
{
"scripts": {
"build:ios": "tamagui build --target native ./src -- eas build --platform ios",
"build:android": "tamagui build --target native ./src -- eas build --platform android"
}
}
```
**Using --output (no file restoration needed):**
If you prefer to output optimized files to a separate directory instead of modifying
source files in-place, use `--output`:
```json
{
"scripts": {
"build": "tamagui build --target web --output ./dist ./src && next build"
}
}
```
With `--output`, your source files are never modified. The optimized files are
written to the output directory with their directory structure preserved.
**Using --output-around (platform-specific files):**
The `--output-around` flag creates optimized platform-specific files (`.web.tsx` or
`.native.tsx`) next to your source files instead of modifying them. Bundlers
automatically pick up these files via platform-specific resolution:
```json
{
"scripts": {
"prebuild:native": "tamagui build --target native --output-around ./src",
"prebuild:web": "tamagui build --target web --output-around ./src"
}
}
```
This transforms `Button.tsx` → creates `Button.native.tsx` or `Button.web.tsx`
alongside it. Metro/Expo uses `.native.tsx` on native, and web bundlers use `.web.tsx`.
The Tamagui CLI optimizes your components in-place (or to an output directory),
then your bundler processes the already-optimized files.
**Learn more:** See the [CLI Guide](/docs/core/cli) for documentation on all
CLI commands including `check`, `generate`, `add`, and more.
## Props
All compiler plugins accept the same options:
### Component discovery
The compiler resolves every component it meets. Components defined with
`styled()` anywhere in your app are linked across files, and a component
imported from any package is evaluated the first time a file uses it as a JSX
element or as a `styled()` base. The result is cached for the life of the dev
server or build, and a package bump invalidates the cached plans that lowered
against it.
`components` is an optional warm-up list. Listing a package evaluates it once
when the project loads instead of on first use, which is worth doing for the
package most of your files import from. Nothing else is needed for the
compiler to optimize components from other packages.
A package that cannot run in Node during the build (one that touches native
modules at import time, for example) is skipped with a debug message, and the
elements that use it stay on the runtime path. Set `DEBUG=tamagui` to see which
modules discovery skipped and why.
### Disabling the compiler
You can disable the compiler optimizations for an entire file with a comment at
the top of your file:
```tsx
// tamagui-ignore
```
You can disable the compiler optimization for a single component with the
boolean property `disableOptimization`:
```tsx
import { html } from '@tamagui/core'
export default () =>
```
## intro/themes
---
title: Themes
description: Create themes and sub-themes
---
Themes map neatly to CSS variables: they are objects whose values you want to
contextually change at any point in your React tree. Tamagui lets you define,
nest, and read them on web and native.
Bare theme names are the first lookup for style values, and `useTheme` exposes
them directly. Themes nest both in the definition and at runtime, and values
resolve upward, ultimately all the way back to tokens.
For the recommended theme setup, see the [Config v6 docs](/docs/core/config-v6).
See the [ThemeBuilder guide](/docs/guides/theme-builder) for generating
custom theme suites.
To style `bento` or `tamagui` components, see
[Styling `tamagui` UI components](#styling-tamagui-components).
For a copy-paste custom palette and recipe tree, see the
[v6 Colors guide](/docs/core/config-v6-colors#add-a-custom-palette).
You define a theme like this:
```tsx
const dark = {
background: '#000',
color: '#fff',
// define any key to any string or number value
}
```
If you use tokens, you can share values from tokens down to themes. Tokens act
as fallback values for themes, like global CSS variables vs scoped ones:
```tsx
const tokens = createTokens({
color: {
black: '#000',
white: '#fff',
},
})
// theme:
const dark = {
background: tokens.color.black,
color: tokens.color.white,
}
```
### Sub-themes
Tamagui supports theme nesting. Define sub-themes using the `parentName_subName`
format, where each segment resolves as a valid theme. Sub-themes can nest across
multiple levels:
- `dark_green_subtle`
- `light_green_subtle`
```tsx
```
You can also access a specific sub-theme more specifically:
```tsx
```
### Inverse themes
In v3, `inverse` is a real sub-theme generated by the default theme setup. Use
it like any other sub-theme:
```tsx
```
The old `themeInverse` prop and `` path are removed. Because
`inverse` is a named sub-theme, SSR can emit the right CSS variables without
client-only light/dark inversion. See the
[theme creation guide](/docs/guides/theme-builder#function-children) for custom
theme generation details.
### Forcing a scheme with black and white
`inverse` is relative: it flips whichever scheme its parent happened to be. When
you need a specific scheme instead, use `black` or `white`. They resolve to the
dark and light themes from any parent, including from inside a palette
sub-theme:
```tsx
// dark, wherever this is mounted
// still dark, even though the parent is light and red
```
Reach for them when a subtree cannot know which scheme it is mounted under, such
as a menu that always reads as dark over a light page. Levels nest inside them
as usual, so `` steps within the dark
scheme rather than falling back to the parent's.
Keep themes consistent in shape, sharing the same named keys and typed values.
Sub-themes can define subsets of parent themes. The `useTheme` hook and style
system resolve missing keys upward through parent themes and back to tokens.
#### Component themes
V3 does not select themes from styled component display names. The `displayName`
option sets React's debugging identity and may add an `is_Circle` class, but it
does not make Tamagui search for `light_Circle` or affect style resolution.
Use a normal theme boundary when a component owns a theme operation:
```tsx
import { Theme, View, styled } from 'tamagui'
const CircleFrame = styled(View, {
displayName: 'Circle',
backgroundColor: 'background',
})
function Circle(props) {
return (
)
}
```
This composes under light, dark, color, inverse, and existing level themes
without a second component-specific lookup system.
---
### Styling Tamagui Components
The `tamagui` component suite uses standard semantic theme keys across all
components:
- `background`: component surface color
- `color`: text and icon color
- `border-color`: border color
- `shadow-color`: elevation and shadow color
- `placeholder-color`: placeholder text color
Each key supports interactive pseudo-states: `background-hover`,
`background-press`, and `background-focus`.
Defining these standard keys lets you re-theme both built-in UI components and
your own components consistently across light and dark mode.
A minimal theme might look like this:
```tsx
const dark = {
// Standard keys for all components
background: '#000',
'background-hover': '#111',
'background-press': '#222',
'background-focus': '#333',
color: '#fff',
'color-hover': '#eee',
'color-press': '#ddd',
'color-focus': '#ccc',
'border-color': '#555',
'border-color-hover': '#666',
'border-color-focus': '#777',
'border-color-press': '#888',
'placeholder-color': '#999',
'outline-color': '#aaa',
// Custom tokens like "brand"
'brand-background': '#000', // You can add your own tokens like "brand"
'brand-color': '#fff', // and use them in your components
}
const light = {
// Standard keys for all components
background: '#fff',
'background-hover': '#f5f5f5',
'background-press': '#e0e0e0',
'background-focus': '#d5d5d5',
color: '#000',
'color-hover': '#111',
'color-press': '#222',
'color-focus': '#333',
'border-color': '#444',
'border-color-hover': '#555',
'border-color-focus': '#666',
'border-color-press': '#777',
'placeholder-color': '#888',
'outline-color': '#999',
// Custom tokens like "brand"
'brand-background': '#000', // You can add your own tokens like "brand"
'brand-color': '#fff', // and use them in your components
}
```
You can of course do all of this yourself in your own design system with
`styled`:
If you are building a component with more than one sub-component, you can
follow this pattern:
```tsx
import { GetProps, View, Text, createStyledHOC, styled } from 'tamagui' // or '@tamagui/core'
const ButtonFrame = styled(View, {
displayName: 'Button',
backgroundColor: 'background',
})
const ButtonText = styled(Text, {
displayName: 'ButtonText',
color: 'color',
})
type ButtonProps = GetProps
// createStyledHOC wraps a functional component so it can be further styled with styled()
// see /docs/core/styled#createstyledhoc for full documentation
export const Button = createStyledHOC(
ButtonFrame,
({ children, ...props }: ButtonProps, ref) => {
return (
{children}
)
}
)
```
The frame and text share the active theme. Wrap the composed component in an
explicit named or relative level theme when it needs a different semantic
surface.
## Full Example
Let's start with an example of inline styling with a subset of the
configuration:
```tsx
import { TamaguiProvider, createTokens, createTamagui, html, Theme } from 'tamagui'
const tokens = createTokens({
color: {
darkRed: '#550000',
lightRed: '#ff0000',
},
// ... see configuration docs for required tokens
})
const config = createTamagui({
tokens,
themes: {
dark: {
red: tokens.color.darkRed,
},
light: {
red: tokens.color.lightRed,
},
},
})
export const App = () => (
)
```
In this example we've set up darkRed and lightRed variables and a dark and light
theme that use those variables. Tamagui will handle defining:
```css
:root {
--colors-dark-red: #550000;
--colors-light-red: #ff0000;
}
.tui_dark {
--red: var(--colors-dark-red);
}
.tui_light {
--red: var(--colors-light-red);
}
```
Which will automatically apply at runtime, or can be gathered for use in SSR
with `config.getCSS()`.
Finally, the compiler on web will extract your views roughly as so:
```tsx
export const App = () => (
)
// CSS output:
// .color-2nesi3 { background-color: var(--red); }
```
## Ensuring valid types
This structure keeps everything typed. Keep themes in a separate `themes.ts`
file, and structure it like this:
```tsx
import { tokens } from './tokens'
const light = {
background: '#fff',
'background-hover': tokens.color['gray-100'],
'background-press': tokens.color['gray-200'],
'background-focus': tokens.color['gray-300'],
'border-color': tokens.color['gray-200'],
'border-color-hover': tokens.color['gray-400'],
color: tokens.color['gray-950'],
'color-hover': tokens.color['gray-800'],
'color-press': tokens.color['gray-700'],
'color-focus': tokens.color['gray-400'],
'shadow-color': 'rgba(0, 0, 0, 0.12)',
}
// note: we set up a single consistent base type to validate the rest:
type BaseTheme = typeof light
// the rest of the themes use BaseTheme
const dark: BaseTheme = {
background: '#000',
'background-hover': tokens.color['gray-900'],
'background-press': tokens.color['gray-800'],
'background-focus': tokens.color['gray-700'],
'border-color': tokens.color['gray-800'],
'border-color-hover': tokens.color['gray-700'],
color: '#ddd',
'color-hover': tokens.color['gray-100'],
'color-press': tokens.color['gray-200'],
'color-focus': tokens.color['gray-400'],
'shadow-color': 'rgba(0, 0, 0, 0.25)',
}
const dark_translucent: BaseTheme = {
...dark,
background: 'rgba(0,0,0,0.7)',
'background-hover': 'rgba(0,0,0,0.5)',
'background-press': 'rgba(0,0,0,0.25)',
'background-focus': 'rgba(0,0,0,0.1)',
}
const light_translucent: BaseTheme = {
...light,
background: 'rgba(255,255,255,0.85)',
'background-hover': 'rgba(250,250,250,0.85)',
'background-press': 'rgba(240,240,240,0.85)',
'background-focus': 'rgba(240,240,240,0.7)',
}
export const allThemes = {
dark,
light,
dark_translucent,
light_translucent,
} satisfies { [key: string]: BaseTheme }
```
## Dynamic Themes
Sometimes you want to defer loading themes, or change existing theme values at
runtime. Tamagui exports three helpers for this in the package `@tamagui/theme`
which exports `addTheme`, `updateTheme`, and `replaceTheme`.
### addTheme
```tsx hero template=AddTheme
```
### updateTheme
```tsx hero template=UpdateTheme
```
### replaceTheme
```tsx hero template=ReplaceTheme
```
### Notes
- Dynamic themes only work on the client side and will be ignored on the server
side.
- The difference between `updateTheme` and `replaceTheme` is that `replaceTheme`
will replace the entire theme, while `updateTheme` will only update the values
that are passed in.
### Advanced Optimization
To omit theme objects from a server-rendered web app's client bundle, follow
[Tree shaking themes](/docs/core/tamagui-provider#tree-shaking-themes). That section
covers the CSS and bundler setup required before hydration.
## intro/static
---
title: Static Optimization
description: How @tamagui/static optimizes code at build time
---
The Tamagui compiler turns component styles into atomic CSS and flattened
platform primitives at build time.
For details on how the compiler optimizes your code through static analysis:
- [Why a compiler](/docs/intro/introduction#why-a-compiler): architectural background on compiler benefits
- [Compiler Installation](/docs/intro/compiler-install): how to set up the compiler in your project
- [Benchmarks](/docs/intro/benchmarks): performance measurements across platforms
## intro/errors
---
title: Errors
description: Common error messages and how to resolve them
---
Find an error number below to see its cause and the steps that resolve it.
### Error 001
Haven't called createTamagui yet.
This often happens due to having duplicate Tamagui sub-dependencies.
Tamagui needs every `@tamagui/*` dependency to be on the exact same version.
Starter kits include an upgrade script you can run with `yarn upgrade:tamagui` to
align package versions.
You can also remove `node_modules` and run a fresh install after upgrading.
### Error 002
Using global config fallback. This may indicate duplicate tamagui instances
(e.g., from Vite SSR bundling). This is handled automatically, but may cause
issues due to duplicate tamagui modules if ignored.
### Error 003
#### Static evaluation failed
The compiler evaluates your Tamagui config and configured component modules in
Node during the build. The error names the module that could not be evaluated,
the importing file, and the original failure. Fix that failure so the compiler
can read the same config and component metadata as the application.
If the module is only used at runtime and none of its exports create your
Tamagui config or components, ignore that module explicitly in
`tamagui.build.ts`:
```ts
export default {
components: ['tamagui'],
dangerouslyIgnoreStaticEvaluationModules: ['runtime-only-package'],
}
```
Ignored modules receive an empty object during static evaluation. Do not ignore
a module whose exports contribute to `createTamagui()` or a configured component.
### Error 004
#### Flat value clause condition depth exceeded
A flat value clause supports at most 5 non-platform conditions (e.g. `hover:sm:focus:dark:`). Reduce the number of chained modifiers on a single clause.
### Error 005
#### Invalid variable name
`createCSSVariable` expected a string variable name.
### Error 006
#### Missing component in styled()
`styled()` was called without a target component. Pass a valid React component or host element tag.
### Error 007
#### createTamagui in zero-runtime graph
`createTamagui` was invoked in a zero-runtime graph. In zero-runtime mode, config parsing and CSS generation happen at build time. Remove client config references or mark the module as full-runtime.
### Error 008
#### Reserved token name collision
A token name collides with a reserved CSS-wide keyword (`initial`, `inherit`, `unset`, etc.). Rename the token to avoid collision with standard CSS identifiers.
### Error 009
#### Missing default font
`settings.defaultFont` points to a font family that was not defined in the `fonts` configuration.
### Error 010
#### createComponent in zero-runtime graph
A Tamagui component renderer survived compilation in a zero-runtime bundle. Move the component to a declared full-runtime island or ensure static compilation extracts it.
### Error 011
#### Missing animation driver
No animation driver was configured. Pass an `animations` driver to `createTamagui()`.
### Error 012
#### Missing animated-number hooks in driver
The configured animation driver does not support animated numbers. For CSS animations, use `@tamagui/animations-css/extras`.
### Error 013
#### Invalid matchMedia implementation
`matchMedia` did not return a valid `MediaQueryList` object. Supply a compatible `matchMedia` polyfill on native platforms.
### Error 014
#### Missing parent theme for shallow render
`` requires a mounted parent `` component to derive its scoped theme hierarchy.
### Warning 002
You're rendering a Tamagui component without nesting it inside a
parent that is able to adapt.
Adapt must be a child of a Dialog, Popover or Select. If that parent uses a
`scope` prop, the Adapt needs the matching adapt scope. See [Adapt](/ui/adapt).
## intro/colors
---
title: Colors
description: Color palettes and customization options
---
Explore the default color palettes and learn how to configure custom palettes
with `@tamagui/colors` or Config v6.
Tamagui defaults to Tailwind-aligned colors in `@tamagui/config/v6`, while also supporting Radix palettes via `@tamagui/colors`. For configuring custom color palettes and recipe-generated adaptive themes, see the [v6 Colors guide](/docs/core/config-v6-colors).
## intro/design-system
---
title: Design system
description: Tokens, themes, fonts, media queries, and how they reach your styles
---
A Tamagui config is a small design system. Style props resolve their values
against it, so `padding="4"` and `color="color-11"` mean the same thing on
every screen and every platform.
Most apps start from the `@tamagui/config/v6` preset and customize from there.
It ships Tailwind's spacing scale, the full Tailwind palette, adaptive light
and dark themes, and Tailwind-aligned shorthands:
```tsx fileName="tamagui.config.ts"
import { defaultConfig } from '@tamagui/config/v6'
import { createTamagui } from 'tamagui'
export const config = createTamagui(defaultConfig)
declare module 'tamagui' {
interface TamaguiCustomConfig extends typeof config {}
}
```
See [Config v6](/docs/core/config-v6) for what the preset contains and
[Configuration](/docs/core/configuration) for building one from scratch.
## Tokens
Tokens are named static values. They become CSS variables on web and plain
values on native, and every style prop accepts a token from the matching
category:
| Category | Style props it feeds | v6 example |
| --- | --- | --- |
| `space` | padding, margin, gap, inset | `padding="4"` is 16px |
| `size` | width, height, min and max sizes | `width="64"` |
| `radius` | borderRadius and its corners | `borderRadius="lg"` |
| `color` | any color prop | `backgroundColor="blue-500"` |
| `zIndex` | zIndex | `zIndex="1"` |
A bare value such as `"4"` looks up the token for that prop's category. Numbers
are pixels, and `"4px"` or `px(4)` forces a pixel value when a token has the
same name. See [Tokens](/docs/core/tokens).
## Themes
Themes are values that change per subtree, most often colors. A theme is an
object of keys, and sub-themes named `parent_child` fall back to their parent:
```tsx
themes: {
light: { background: '#fff', color: '#000' },
dark: { background: '#000', color: '#fff' },
dark_blue: { background: '#0b1a33', color: '#e5efff' },
}
```
Style props read theme keys by name, so `backgroundColor="background"` follows
whichever theme is active. The v6 preset adds an adaptive ramp, `color-1`
through `color-11`, that steps from the page background toward the strongest
foreground in both schemes:
```tsx
Adapts to light and dark
```
Switch themes with `` or a `theme` prop. See
[Themes](/docs/intro/themes) for sub-themes, component themes, and inverse
themes, and [Creating themes](/docs/guides/theme-builder) to generate full
suites.
## Fonts
Fonts are tokens with structure. Each family carries its own size, line height,
weight, and letter spacing scales, so `fontSize="lg"` and `lineHeight="lg"`
pull from the same family. Text components pick a family with `fontFamily`, and
`SizableText`, `Paragraph`, and the headings take a `size` prop that sets all
four at once:
```tsx
Body copy
Body copy
```
See [Fonts](/docs/core/fonts) for `createFont`, native font files, and
per-language fonts.
## Media queries
Named media queries in the config become modifiers on every style prop, keys
on `useMedia`, and container query names:
StringTyped
```tsx
```
```tsx
```
The v6 preset defines `xs`, `sm`, `md`, `lg`, `xl`, and `xxl` as min-width
queries with matching `max-sm` style variants, plus `hoverable` and
`touchable`. See [useMedia](/docs/core/use-media).
## Where the pieces meet
```tsx
Save
```
Spacing and radius come from tokens, colors from the active theme, font size
from the body font, and `md:` from the media config. The compiler turns all of
it into atomic CSS on web, and the runtime resolves the same values on native.
## 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
```
## React Strict DOM for React Native
Tamagui's UI components follow the React Strict DOM model: DOM-shaped elements and props that render to real native views on native.
The root `html.*` export allows a single component tree to use semantic DOM elements on web and the corresponding React Native primitives on native:
- **Layout elements** (`html.div`, `html.article`, `html.main`, `html.section`, `html.ul`) render to `View` on native and literal semantic tags on web.
- **Text elements** (`html.p`, `html.span`, `html.strong`, `html.h1`–`html.h6`) render to `Text` on native and matching headings/paragraphs on web.
- **Media & inputs** (`html.img` maps to `Image`, `html.input` / `html.textarea` map to `TextInput`).
On native, DOM props are mapped to their React Native equivalents (such as `href` on `html.a` and `src` on `html.img`), and raw strings inside layout elements are automatically wrapped. Because each member is a standard Tamagui component, style props accept tokens, themes, shorthands, and flat conditional values with compiler lowering.
For more details, see the [HTML primitives documentation](/docs/core/html-primitives).
## 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
```
## React Strict DOM for React Native
Tamagui's UI components follow the React Strict DOM model: DOM-shaped elements and props that render to real native views on native.
The root `html.*` export allows a single component tree to use semantic DOM elements on web and the corresponding React Native primitives on native:
- **Layout elements** (`html.div`, `html.article`, `html.main`, `html.section`, `html.ul`) render to `View` on native and literal semantic tags on web.
- **Text elements** (`html.p`, `html.span`, `html.strong`, `html.h1`–`html.h6`) render to `Text` on native and matching headings/paragraphs on web.
- **Media & inputs** (`html.img` maps to `Image`, `html.input` / `html.textarea` map to `TextInput`).
On native, DOM props are mapped to their React Native equivalents (such as `href` on `html.a` and `src` on `html.img`), and raw strings inside layout elements are automatically wrapped. Because each member is a standard Tamagui component, style props accept tokens, themes, shorthands, and flat conditional values with compiler lowering.
For more details, see the [HTML primitives documentation](/docs/core/html-primitives).
## 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, html } 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/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/new-inputs/1.0.0
---
title: Input & Textarea
name: inputs
component: Input
demoName: Inputs
---
```tsx hero template=Inputs
```
Using Web APIs and relying on bare Tamagui with no `react-native-web` depedency on web compared to old Input component, support scaling all the styles up or down using the `size` property, and full `theme` support.
## Installation
LinearGradient 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/new-inputs/2.0.0
---
title: Input & TextArea
description: Single-line and multi-line text inputs with web-first API
name: inputs
component: Input
package: input
demoName: Inputs
---
Input and TextArea use web-standard form APIs and translate them to React
Native behavior on iOS and Android.
```tsx hero template=Inputs
```
## Installation
Input is already installed in `tamagui`, or you can install it independently:
```bash
npm install @tamagui/input
```
## Usage
A single-line text input:
```tsx
import { Input } from 'tamagui'
export default () =>
```
For multi-line text input, use `TextArea`:
```tsx
import { TextArea } from 'tamagui'
export default () =>
```
## Web-first API
The v2 Input uses standard HTML input attributes.
### Input types
Use the standard HTML `type` attribute. On native, these automatically map to the appropriate keyboard and behavior:
```tsx
// Password input
// Email input (shows email keyboard on native)
// Phone number (shows phone pad on native)
// Number input (shows numeric keyboard on native)
// URL input
// Search input
```
**Cross-platform mapping:**
| Web `type` | Native behavior |
| ---------- | ------------------------------ |
| `password` | `secureTextEntry={true}` |
| `email` | `keyboardType="email-address"` |
| `tel` | `keyboardType="phone-pad"` |
| `number` | `keyboardType="numeric"` |
| `url` | `keyboardType="url"` |
| `search` | `inputMode="search"` |
### Enter key behavior
Use `enterKeyHint` to control the enter/return key label on virtual keyboards:
```tsx
```
On native, this automatically maps to `returnKeyType`.
### Form attributes
Standard HTML form attributes work:
```tsx
```
### Events
Use standard web events:
```tsx
console.log(e.target.value)}
onFocus={(e) => console.log('focused')}
onBlur={(e) => console.log('blurred')}
onKeyDown={(e) => {
if (e.key === 'Enter') {
// handle enter
}
}}
/>
```
For convenience, `onChangeText` is still supported but deprecated:
```tsx
// Deprecated - use onChange instead
setText(text)} />
// Preferred
setText(e.target.value)} />
```
### Submit handling
To handle form submission on enter:
```tsx
{
console.log('Submitted:', e.nativeEvent.text)
}}
/>
```
## Styling
Input accepts all Tamagui style props and supports the `size` prop for consistent scaling:
```tsx
```
## Native-only props
Some props only apply on native platforms and have no web equivalent:
**keyboardAppearance (iOS)**: Controls the keyboard color scheme on iOS:
```tsx
```
**textContentType (iOS)**: Provides hints for iOS autofill:
```tsx
```
For web autofill, use the standard `autoComplete` attribute instead.
## Cross-platform text behavior
These props work on both web and native with automatic value conversion:
### autoCorrect
Controls automatic spelling correction:
```tsx
// Boolean values (work everywhere)
// String values (web-style, converted on native)
```
### autoCapitalize
Controls automatic text capitalization. Native values provide more granular control:
```tsx
// Native-style values (recommended - work everywhere)
// No capitalization
// Capitalize first letter of sentences
// Capitalize first letter of each word
// Capitalize all characters
// Web-style values (mapped on native)
// Maps to "none" on native
// Maps to "sentences" on native
```
## Migration from v1
If you're migrating from v1 Input, here are the key changes:
| v1 (React Native style) | v2 (Web style) |
| ------------------------------ | ---------------------------- |
| `secureTextEntry` | `type="password"` |
| `keyboardType="email-address"` | `type="email"` |
| `keyboardType="phone-pad"` | `type="tel"` |
| `keyboardType="numeric"` | `type="number"` |
| `keyboardType="url"` | `type="url"` |
| `returnKeyType="search"` | `enterKeyHint="search"` |
| `editable={false}` | `readOnly` or `disabled` |
| `onChangeText` | `onChange` |
| `multiline` | Use `TextArea` or `rows > 1` |
| `numberOfLines` | `rows` |
The v1 props still work but are deprecated. We recommend updating to the web-standard props for better cross-platform consistency.
## API reference
### Input props
Accepts all HTML `` attributes plus Tamagui style props.
void',
description: 'Called when enter/return is pressed.',
},
{
name: 'keyboardAppearance',
required: false,
type: '"default" | "light" | "dark"',
description: 'iOS only. Controls keyboard color scheme.',
},
{
name: 'textContentType',
required: false,
type: 'string',
description: 'iOS only. Hints for autofill. Use autoComplete for web.',
},
]}
/>
### TextArea props
Accepts all Input props plus:
## components/avatar/1.0.0
---
title: Avatar
description: Display aspect-fixed images with a fallback while loading
name: avatar
component: Avatar
package: avatar
demoName: Avatar
---
```tsx hero template=Avatar
```
## Installation
Avatar is already installed in `tamagui`, or you can install it independently:
```bash
npm install @tamagui/avatar
```
## Usage
```tsx
import { Avatar } from 'tamagui'
export default () => (
)
```
## API Reference
### Avatar
Avatar extends [Square](/docs/components/shapes#shape-props), giving it all the [Tamagui standard props](/docs/intro/props) as well as `size` and `circular`.
### Avatar.Fallback
Avatar.Fallback extends [YStack](/docs/components/stacks), plus:
### Avatar.Image
Avatar.Image extends [Image](/docs/components/image).
## components/avatar/2.0.0
---
title: Avatar
description: Display aspect-ratio-fixed images with a fallback while loading
name: avatar
component: Avatar
package: avatar
demoName: Avatar
---
```tsx hero template=Avatar
```
## Installation
Avatar is already installed in `tamagui`, or you can install it independently:
```bash
npm install @tamagui/avatar
```
## Usage
```tsx
import { Avatar } from 'tamagui'
export default () => (
)
```
## API Reference
### Avatar
Avatar extends [Square](/docs/components/shapes#shape-props), giving it all the [Tamagui standard props](/docs/intro/props) as well as `size` and `circular`.
### Avatar.Fallback
Avatar.Fallback extends [YStack](/docs/components/stacks), plus:
### Avatar.Image
Avatar.Image extends [Image](/docs/components/image).
## components/avatar/3.0.0
---
title: Avatar
description: Display images at a fixed size with a fallback while loading
name: avatar
component: Avatar
package: avatar
demoName: Avatar
---
Avatar displays an image at a consistent size and swaps in a fallback while
that image loads.
```tsx hero template=Avatar
```
## Installation
Avatar is already installed in `tamagui`, or you can install it independently:
```bash
npm install @tamagui/avatar
```
## Usage
```tsx
import { Avatar } from 'tamagui'
export default () => (
)
```
## API reference
### Avatar
Avatar extends [Square](/ui/shapes#square), giving it all the [Tamagui standard props](/docs/intro/props) as well as `size` and `circular`.
### Avatar.Fallback
Avatar.Fallback extends [YStack](/ui/stacks), plus:
### Avatar.Image
Avatar.Image extends [Image](/ui/image).
## 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/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.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.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.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/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.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/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/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.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.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/lucide-icons/1.0.0
---
title: Lucide Icons
description: Cross-platform compatible SVG based icons
demoName: LucideIcons
---
## Installation
```sh
yarn add react-native-svg @tamagui/lucide-icons-2
```
## 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 () =>
// 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/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 () =>
// 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@beta 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 () =>
// 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/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 () =>
```
### 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 () =>
```
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.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 () =>
```
### 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 () =>
```
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/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 () =>
```
### 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 () =>
```
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/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 () =>
```
### 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 () =>
```
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/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 () =>
```
## 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 () =>
```
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 () => (
)
```
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 () => (
<>
>
)
```
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 () => (
)
```
## Web Form Props
Button supports all standard HTML `