# Tamagui Complete Documentation



## components/accordion/1.0.0

---
title: Accordion
description: A vertically stacked set of interactive headings with content.
name: accordion
component: Accordion
package: accordion
---

<HeroContainer>
  <AccordionDemo />
</HeroContainer>

```tsx hero template=Accordion

```

<Highlights
  features={[
    'Full keyboard navigation.',
    'Can expand one or multiple items.',
    'Can be controlled or uncontrolled.',
  ]}
/>

## Installation

Accordion is already installed in `tamagui`, or you can install it independently:

```bash
npm install @tamagui/accordion
```

## Anatomy

Import all parts and piece them together.

```jsx
import { Accordion } from 'tamagui' // or '@tamagui/accordion'

export default () => (
  <Accordion>
    <Accordion.Item>
      <Accordion.Header>
        <Accordion.Trigger />
      </Accordion.Header>
      <Accordion.Content />
    </Accordion.Item>
  </Accordion>
)
```

## API Reference

### Accordion

Contains all the parts of an accordion.

<PropsTable
  data={[
    {
      name: 'asChild',
      required: false,
      type: 'boolean',
      default: 'false',
      description:
        'Change the component to the HTML tag or custom component of the only child. This will merge the original component props with the props of the supplied element/component and change the underlying DOM node.',
    },
    {
      name: 'type',
      required: true,
      type: '"single" | "multiple"',
      typeSimple: 'enum',
      description: (
        <span>
          Determines whether one or multiple items can be opened at the same time.
        </span>
      ),
    },
    {
      name: 'value',
      required: false,
      type: 'string',
      description: (
        <span>
          The controlled value of the item to expand when <Code>type</Code> is{' '}
          <Code>"single"</Code>. Must be used in conjunction with{' '}
          <Code>onValueChange</Code>.
        </span>
      ),
    },
    {
      name: 'defaultValue',
      required: false,
      type: 'string',
      description: (
        <span>
          The value of the item to expand when initially rendered and <Code>type</Code> is{' '}
          <Code>"single"</Code>. Use when you do not need to control the state of the
          items.
        </span>
      ),
    },
    {
      name: 'onValueChange',
      required: false,
      type: '(value: string) => void',
      typeSimple: 'function',
      description: (
        <span>
          Event handler called when the expanded state of an item changes and{' '}
          <Code>type</Code> is <Code>"single"</Code>.
        </span>
      ),
    },
    {
      name: 'value',
      required: false,
      default: '[]',
      type: 'string[]',
      description: (
        <span>
          The controlled value of the item to expand when <Code>type</Code> is{' '}
          <Code>"multiple"</Code>. Must be used in conjunction with{' '}
          <Code>onValueChange</Code>.
        </span>
      ),
    },
    {
      name: 'defaultValue',
      required: false,
      default: '[]',
      type: 'string[]',
      description: (
        <span>
          The value of the item to expand when initially rendered when <Code>type</Code>{' '}
          is <Code>"multiple"</Code>. Use when you do not need to control the state of the
          items.
        </span>
      ),
    },
    {
      name: 'onValueChange',
      required: false,
      type: '(value: string[]) => void',
      typeSimple: 'function',
      description: (
        <span>
          Event handler called when the expanded state of an item changes and{' '}
          <Code>type</Code> is <Code>"multiple"</Code>.
        </span>
      ),
    },
    {
      name: 'collapsible',
      required: false,
      default: 'false',
      type: 'boolean',
      description: (
        <span>
          When <Code>type</Code> is <Code>"single"</Code>, allows closing content when
          clicking trigger for an open item.
        </span>
      ),
    },
    {
      name: 'disabled',
      required: false,
      type: 'boolean',
      default: 'false',
      description: (
        <span>
          When <Code>true</Code>, prevents the user from interacting with the accordion
          and all its items.
        </span>
      ),
    },
    {
      name: 'dir',
      required: false,
      type: '"ltr" | "rtl"',
      typeSimple: 'enum',
      default: '"ltr"',
      description:
        'The reading direction of the accordion when applicable. If omitted, assumes LTR (left-to-right) reading mode.',
    },
  ]}
/>

### Item

Contains all the parts of a collapsible section.

<PropsTable
  data={[
    {
      name: 'asChild',
      required: false,
      type: 'boolean',
      default: 'false',
      description:
        'Change the component to the HTML tag or custom component of the only child. This will merge the original component props with the props of the supplied element/component and change the underlying DOM node.',
    },
    {
      name: 'disabled',
      required: false,
      type: 'boolean',
      default: 'false',
      description: (
        <span>
          When <Code>true</Code>, prevents the user from interacting with the item.
        </span>
      ),
    },
    {
      name: 'value',
      required: true,
      type: 'string',
      description: 'A unique value for the item.',
    },
  ]}
/>

### Header

Wraps an `Accordion.Trigger`. Use the `asChild` prop to update it to the appropriate heading level for your page.

<PropsTable
  data={[
    {
      name: 'asChild',
      required: false,
      type: 'boolean',
      default: 'false',
      description:
        'Change the component to the HTML tag or custom component of the only child. This will merge the original component props with the props of the supplied element/component and change the underlying DOM node.',
    },
  ]}
/>

### Trigger

Toggles the collapsed state of its associated item. It should be nested inside of an `Accordion.Header`.

<PropsTable
  data={[
    {
      name: 'asChild',
      required: false,
      type: 'boolean',
      default: 'false',
      description:
        'Change the component to the HTML tag or custom component of the only child. This will merge the original component props with the props of the supplied element/component and change the underlying DOM node.',
    },
  ]}
/>

### Content

Contains the collapsible content for an item.

<PropsTable
  data={[
    {
      name: 'asChild',
      required: false,
      type: 'boolean',
      default: 'false',
      description:
        'Change the component to the HTML tag or custom component of the only child. This will merge the original component props with the props of the supplied element/component and change the underlying DOM node.',
    },
    {
      name: 'forceMount',
      type: 'boolean',
      description: (
        <span>
          Used to force mounting when more control is needed. Useful when controlling
          animation with React animation libraries.
        </span>
      ),
    },
  ]}
/>

## Examples

### Expanded by default

Use the `defaultValue` prop to define the open item by default.

```jsx line=1
<Accordion type="single" __defaultValue__="item-2">
  <Accordion.Item value="item-1">…</Accordion.Item>
  <Accordion.Item value="item-2">…</Accordion.Item>
</Accordion>
```

### Allow collapsing all items

Use the `collapsible` prop to allow all items to close.

```jsx line=1
<Accordion type="single" __collapsible__>
  <Accordion.Item value="item-1">…</Accordion.Item>
  <Accordion.Item value="item-2">…</Accordion.Item>
</Accordion>
```

### Multiple items open at the same time

Set the `type` prop to `multiple` to enable opening multiple items at once.

```jsx line=1
<Accordion type="__multiple__">
  <Accordion.Item value="item-1">…</Accordion.Item>
  <Accordion.Item value="item-2">…</Accordion.Item>
</Accordion>
```

## Accessibility

Adheres to the [Accordion WAI-ARIA design pattern](https://www.w3.org/TR/wai-aria-practices-1.1/#accordion).


## components/accordion/2.0.0

---
title: Accordion
description: A vertically stacked set of interactive headings with content.
name: accordion
component: Accordion
package: accordion
---

<HeroContainer>
  <AccordionDemo />
</HeroContainer>

```tsx hero template=Accordion

```

<Highlights
  features={[
    'Full keyboard navigation.',
    'Can expand one or multiple items.',
    'Can be controlled or uncontrolled.',
  ]}
/>

## Installation

Accordion is already installed in `tamagui`, or you can install it independently:

```bash
npm install @tamagui/accordion
```

## Anatomy

Import all parts and piece them together.

```jsx
import { Accordion } from 'tamagui' // or '@tamagui/accordion'

export default () => (
  <Accordion>
    <Accordion.Item>
      <Accordion.Header>
        <Accordion.Trigger />
      </Accordion.Header>
      <Accordion.Content />
    </Accordion.Item>
  </Accordion>
)
```

## API Reference

### Accordion

Contains all the parts of an accordion.

<PropsTable
  data={[
    {
      name: 'asChild',
      required: false,
      type: 'boolean',
      default: 'false',
      description:
        'Change the component to the HTML tag or custom component of the only child. This will merge the original component props with the props of the supplied element/component and change the underlying DOM node.',
    },
    {
      name: 'type',
      required: true,
      type: '"single" | "multiple"',
      typeSimple: 'enum',
      description: (
        <span>
          Determines whether one or multiple items can be opened at the same time.
        </span>
      ),
    },
    {
      name: 'value',
      required: false,
      type: 'string',
      description: (
        <span>
          The controlled value of the item to expand when <Code>type</Code> is{' '}
          <Code>"single"</Code>. Must be used in conjunction with{' '}
          <Code>onValueChange</Code>.
        </span>
      ),
    },
    {
      name: 'defaultValue',
      required: false,
      type: 'string',
      description: (
        <span>
          The value of the item to expand when initially rendered and <Code>type</Code> is{' '}
          <Code>"single"</Code>. Use when you do not need to control the state of the
          items.
        </span>
      ),
    },
    {
      name: 'onValueChange',
      required: false,
      type: '(value: string) => void',
      typeSimple: 'function',
      description: (
        <span>
          Event handler called when the expanded state of an item changes and{' '}
          <Code>type</Code> is <Code>"single"</Code>.
        </span>
      ),
    },
    {
      name: 'value',
      required: false,
      default: '[]',
      type: 'string[]',
      description: (
        <span>
          The controlled value of the item to expand when <Code>type</Code> is{' '}
          <Code>"multiple"</Code>. Must be used in conjunction with{' '}
          <Code>onValueChange</Code>.
        </span>
      ),
    },
    {
      name: 'defaultValue',
      required: false,
      default: '[]',
      type: 'string[]',
      description: (
        <span>
          The value of the item to expand when initially rendered when <Code>type</Code>{' '}
          is <Code>"multiple"</Code>. Use when you do not need to control the state of the
          items.
        </span>
      ),
    },
    {
      name: 'onValueChange',
      required: false,
      type: '(value: string[]) => void',
      typeSimple: 'function',
      description: (
        <span>
          Event handler called when the expanded state of an item changes and{' '}
          <Code>type</Code> is <Code>"multiple"</Code>.
        </span>
      ),
    },
    {
      name: 'collapsible',
      required: false,
      default: 'false',
      type: 'boolean',
      description: (
        <span>
          When <Code>type</Code> is <Code>"single"</Code>, allows closing content when
          clicking trigger for an open item.
        </span>
      ),
    },
    {
      name: 'disabled',
      required: false,
      type: 'boolean',
      default: 'false',
      description: (
        <span>
          When <Code>true</Code>, prevents the user from interacting with the accordion
          and all its items.
        </span>
      ),
    },
    {
      name: 'dir',
      required: false,
      type: '"ltr" | "rtl"',
      typeSimple: 'enum',
      default: '"ltr"',
      description:
        'The reading direction of the accordion when applicable. If omitted, assumes LTR (left-to-right) reading mode.',
    },
  ]}
/>

### Item

Contains all the parts of a collapsible section.

<PropsTable
  data={[
    {
      name: 'asChild',
      required: false,
      type: 'boolean',
      default: 'false',
      description:
        'Change the component to the HTML tag or custom component of the only child. This will merge the original component props with the props of the supplied element/component and change the underlying DOM node.',
    },
    {
      name: 'disabled',
      required: false,
      type: 'boolean',
      default: 'false',
      description: (
        <span>
          When <Code>true</Code>, prevents the user from interacting with the item.
        </span>
      ),
    },
    {
      name: 'value',
      required: true,
      type: 'string',
      description: 'A unique value for the item.',
    },
  ]}
/>

### Header

Wraps an `Accordion.Trigger`. Use the `asChild` prop to update it to the appropriate heading level for your page.

<PropsTable
  data={[
    {
      name: 'asChild',
      required: false,
      type: 'boolean',
      default: 'false',
      description:
        'Change the component to the HTML tag or custom component of the only child. This will merge the original component props with the props of the supplied element/component and change the underlying DOM node.',
    },
  ]}
/>

### Trigger

Toggles the collapsed state of its associated item. It should be nested inside of an `Accordion.Header`.

<PropsTable
  data={[
    {
      name: 'asChild',
      required: false,
      type: 'boolean',
      default: 'false',
      description:
        'Change the component to the HTML tag or custom component of the only child. This will merge the original component props with the props of the supplied element/component and change the underlying DOM node.',
    },
  ]}
/>

### Content

Contains the collapsible content for an item.

<PropsTable
  data={[
    {
      name: 'asChild',
      required: false,
      type: 'boolean',
      default: 'false',
      description:
        'Change the component to the HTML tag or custom component of the only child. This will merge the original component props with the props of the supplied element/component and change the underlying DOM node.',
    },
    {
      name: 'forceMount',
      type: 'boolean',
      description: (
        <span>
          Used to force mounting when more control is needed. Useful when controlling
          animation with React animation libraries.
        </span>
      ),
    },
  ]}
/>

## Examples

### Expanded by default

Use the `defaultValue` prop to define the open item by default.

```jsx line=1
<Accordion type="single" __defaultValue__="item-2">
  <Accordion.Item value="item-1">…</Accordion.Item>
  <Accordion.Item value="item-2">…</Accordion.Item>
</Accordion>
```

### Allow collapsing all items

Use the `collapsible` prop to allow all items to close.

```jsx line=1
<Accordion type="single" __collapsible__>
  <Accordion.Item value="item-1">…</Accordion.Item>
  <Accordion.Item value="item-2">…</Accordion.Item>
</Accordion>
```

### Multiple items open at the same time

Set the `type` prop to `multiple` to enable opening multiple items at once.

```jsx line=1
<Accordion type="__multiple__">
  <Accordion.Item value="item-1">…</Accordion.Item>
  <Accordion.Item value="item-2">…</Accordion.Item>
</Accordion>
```

## Accessibility

Adheres to the [Accordion WAI-ARIA design pattern](https://www.w3.org/TR/wai-aria-practices-1.1/#accordion).


## components/alert-dialog/1.0.0

---
title: AlertDialog
description: Show an alert prompt in a dialog
name: alertDialog
component: AlertDialog
package: alert-dialog
demoName: AlertDialog
---

<HeroContainer showAnimationDriverControl>
  <AlertDialogDemo />
</HeroContainer>

```tsx hero template=AlertDialog

```

<Highlights
  features={[
    `Comes with styling, completely customizable and themeable.`,
    `Accepts animations, themes, size props and more.`,
    `Accessible with dev-time checks to ensure ARIA props.`,
  ]}
/>

## Installation

Alert Dialog is already installed in `tamagui`, or you can install it independently:

```bash
npm install @tamagui/alert-dialog
```

In order to use this component independently of `tamagui`, you'll first need to install the `@tamagui/portal` package: 

```bash
npm install @tamagui/portal
```

Then add `PortalProvider` to the root of your app:

```tsx fileName="App.tsx"
import { PortalProvider } from '@tamagui/portal'
import YourApp from './components/YourApp'

function App() {
  return (
    <PortalProvider shouldAddRootHost>
      <YourApp />
    </PortalProvider>
  )
}

export default App
```

## Anatomy

```tsx
import { AlertDialog } from 'tamagui' // or '@tamagui/alert-dialog'

export default () => (
  <AlertDialog>
    <AlertDialog.Trigger />
    <AlertDialog.Portal>
      <AlertDialog.Overlay />
      <AlertDialog.Content>
        <AlertDialog.Title />
        <AlertDialog.Description />
        <AlertDialog.Cancel />
        {/* ... */}
      </AlertDialog.Content>
    </AlertDialog.Portal>
  </AlertDialog>
)
```

## API Reference

### AlertDialog

Contains every component for the AlertDialog. Shares all [Dialog Props](/docs/components/dialog#api), except modal which is on by default. Adds:

<PropsTable
  data={[
    {
      name: 'native',
      type: 'boolean',
      required: false,
      description: `When true, on iOS it will render as a native AlertDialog.`,
    },
  ]}
/>

### AlertDialog.Trigger

Just [Tamagui Props](/docs/intro/props).

### AlertDialog.Portal

Renders AlertDialog into appropriate container. Beyond [Tamagui Props](/docs/intro/props), adds:

<PropsTable
  data={[
    {
      name: 'forceMount',
      type: 'boolean',
      required: false,
      description: `Used to force mounting when more control is needed. Useful when controlling animation with React animation libraries.`,
    },
  ]}
/>

### AlertDialog.Content

Main container for AlertDialog content, this is where you should apply animations.

Beyond [Tamagui Props](/docs/intro/props), adds:

<PropsTable
  data={[
    {
      name: 'forceMount',
      type: 'boolean',
      required: false,
      description: `Used to force mounting when more control is needed. Useful when controlling animation with React animation libraries.`,
    },
  ]}
/>

### AlertDialog.Overlay

Displays behind Content. Beyond [Tamagui Props](/docs/intro/props), adds:

<PropsTable
  data={[
    {
      name: 'forceMount',
      type: 'boolean',
      required: false,
      description: `Used to force mounting when more control is needed. Useful when controlling animation with React animation libraries.`,
    },
  ]}
/>

### AlertDialog.Title

Required. Can wrap in VisuallyHidden to hide.

Defaults to H2, see [Headings](/docs/components/headings).

### AlertDialog.Description

Required. Can wrap in VisuallyHidden to hide.

Defaults to Paragraph, see [Paragraph](/docs/components/text).

### AlertDialog.Cancel

Closes the AlertDialog, accepts all YStack props. Recommended to use with your own component and `asChild`.

<PropsTable
  data={[
    {
      name: 'displayWhenAdapted',
      type: 'boolean',
      description: `By default Cancel elements hide when Adapt is active. If set to true, they will show when adapted.`,
    },
  ]}
/>

### PortalProvider

<PropsTable
  data={[
    {
      name: 'shouldAddRootHost',
      type: 'boolean',
      required: false,
      description: `Defines whether to add a default root host or not.`,
    },
  ]}
/>

## Examples

### Inside native modals

If you're using native modals (maybe from react-navigation), you'll notice the Dialogs won't show up inside the modal. To get around this, you should wrap your screen inside `PortalProvider`, like so:

```tsx
import { PortalProvider } from 'tamagui'

// this component used in react-navigation/expo-router with `presentation: "modal"`
export function Page() {
  return (
    <PortalProvider>
      {/* rest of your page, including the Dialog... */}
    </PortalProvider>
  )
}
```


## components/alert-dialog/2.0.0

---
title: AlertDialog
description: Show an alert prompt in a dialog
name: alertDialog
component: AlertDialog
package: alert-dialog
demoName: AlertDialog
---

<HeroContainer showAnimationDriverControl>
  <AlertDialogDemo />
</HeroContainer>

```tsx hero template=AlertDialog

```

<Highlights
  features={[
    `Comes with styling, completely customizable and themeable.`,
    `Accepts animations, themes, size props and more.`,
    `Accessible with dev-time checks to ensure ARIA props.`,
  ]}
/>

## Installation

Alert Dialog is already installed in `tamagui`, or you can install it independently:

```bash
npm install @tamagui/alert-dialog
```

In order to use this component independently of `tamagui`, you'll first need to install the `@tamagui/portal` package: 

```bash
npm install @tamagui/portal
```

Then add `PortalProvider` to the root of your app:

```tsx fileName="App.tsx"
import { PortalProvider } from '@tamagui/portal'
import YourApp from './components/YourApp'

function App() {
  return (
    <PortalProvider shouldAddRootHost>
      <YourApp />
    </PortalProvider>
  )
}

export default App
```

## Anatomy

```tsx
import { AlertDialog } from 'tamagui' // or '@tamagui/alert-dialog'

export default () => (
  <AlertDialog>
    <AlertDialog.Trigger />
    <AlertDialog.Portal>
      <AlertDialog.Overlay />
      <AlertDialog.Content>
        <AlertDialog.Title />
        <AlertDialog.Description />
        <AlertDialog.Cancel />
        {/* ... */}
      </AlertDialog.Content>
    </AlertDialog.Portal>
  </AlertDialog>
)
```

## API Reference

### AlertDialog

Contains every component for the AlertDialog. Shares all [Dialog Props](/docs/components/dialog#api), except modal which is on by default. Adds:

<PropsTable
  data={[
    {
      name: 'native',
      type: 'boolean',
      required: false,
      description: `When true, on iOS it will render as a native AlertDialog.`,
    },
  ]}
/>

### AlertDialog.Trigger

Just [Tamagui Props](/docs/intro/props).

### AlertDialog.Portal

Renders AlertDialog into appropriate container. Beyond [Tamagui Props](/docs/intro/props), adds:

<PropsTable
  data={[
    {
      name: 'forceMount',
      type: 'boolean',
      required: false,
      description: `Used to force mounting when more control is needed. Useful when controlling animation with React animation libraries.`,
    },
  ]}
/>

### AlertDialog.Content

Main container for AlertDialog content, this is where you should apply animations.

Beyond [Tamagui Props](/docs/intro/props), adds:

<PropsTable
  data={[
    {
      name: 'forceMount',
      type: 'boolean',
      required: false,
      description: `Used to force mounting when more control is needed. Useful when controlling animation with React animation libraries.`,
    },
  ]}
/>

### AlertDialog.Overlay

Displays behind Content. Beyond [Tamagui Props](/docs/intro/props), adds:

<PropsTable
  data={[
    {
      name: 'forceMount',
      type: 'boolean',
      required: false,
      description: `Used to force mounting when more control is needed. Useful when controlling animation with React animation libraries.`,
    },
  ]}
/>

### AlertDialog.Title

Required. Can wrap in VisuallyHidden to hide.

Defaults to H2, see [Headings](/docs/components/headings).

### AlertDialog.Description

Required. Can wrap in VisuallyHidden to hide.

Defaults to Paragraph, see [Paragraph](/docs/components/text).

### AlertDialog.Cancel

Closes the AlertDialog, accepts all YStack props. Recommended to use with your own component and `asChild`.

<PropsTable
  data={[
    {
      name: 'displayWhenAdapted',
      type: 'boolean',
      description: `By default Cancel elements hide when Adapt is active. If set to true, they will show when adapted.`,
    },
  ]}
/>

### PortalProvider

<PropsTable
  data={[
    {
      name: 'shouldAddRootHost',
      type: 'boolean',
      required: false,
      description: `Defines whether to add a default root host or not.`,
    },
  ]}
/>

## Examples

### Inside native modals

If you're using native modals (maybe from react-navigation), you'll notice the Dialogs won't show up inside the modal. To get around this, you should wrap your screen inside `PortalProvider`, like so:

```tsx
import { PortalProvider } from 'tamagui'

// this component used in react-navigation/expo-router with `presentation: "modal"`
export function Page() {
  return (
    <PortalProvider>
      {/* rest of your page, including the Dialog... */}
    </PortalProvider>
  )
}
```


## components/anchor/1.0.0

---
title: Anchor
description: Link to external websites.
name: html
component: Anchor
---

<Highlights
  features={['Supports SSR.', 'Works on native and web.', 'Accepts Tamagui style props.']}
/>

## 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:

<PropsTable
  data={[
    {
      name: 'href',
      required: false,
      type: 'string',
      description: `Location to link to.`,
    },
    {
      name: 'target',
      required: false,
      type: 'string',
    },
    {
      name: 'rel',
      required: false,
      type: 'string',
    },
  ]}
/>


## components/anchor/2.0.0

---
title: Anchor
description: Link to external websites.
name: html
component: Anchor
---

<Highlights
  features={['Supports SSR.', 'Works on native and web.', 'Accepts Tamagui style props.']}
/>

## 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:

<PropsTable
  data={[
    {
      name: 'href',
      required: false,
      type: 'string',
      description: `Location to link to.`,
    },
    {
      name: 'target',
      required: false,
      type: 'string',
    },
    {
      name: 'rel',
      required: false,
      type: 'string',
    },
  ]}
/>


## 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
---

<HeroContainer>
  <AvatarDemo />
</HeroContainer>

```tsx hero template=Avatar

```

<Highlights
  features={[
    'Accepts size prop that stays in sync with other components.',
    'Completely control styles on every element.',
    'Automatically swaps fallback for image on load.',
  ]}
/>

## 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 () => (
  <Avatar circular size="$6">
    <Avatar.Image src="http://picsum.photos/200/300" />
    <Avatar.Fallback bc="red" />
  </Avatar>
)
```

## 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:

<PropsTable
  data={[
    {
      name: 'delayMs',
      required: false,
      type: 'number',
      description: `Milliseconds to wait before showing the fallback, to prevent flicker.`,
    },
  ]}
/>

### Avatar.Image

Avatar.Image extends [Image](/docs/components/image).


## components/avatar/2.0.0

---
title: Avatar
description: Display aspect-fixed images with a fallback while loading
name: avatar
component: Avatar
package: avatar
demoName: Avatar
---

<HeroContainer>
  <AvatarDemo />
</HeroContainer>

```tsx hero template=Avatar

```

<Highlights
  features={[
    'Accepts size prop that stays in sync with other components.',
    'Completely control styles on every element.',
    'Automatically swaps fallback for image on load.',
  ]}
/>

## 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 () => (
  <Avatar circular size="$6">
    <Avatar.Image src="http://picsum.photos/200/300" />
    <Avatar.Fallback bc="red" />
  </Avatar>
)
```

## 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:

<PropsTable
  data={[
    {
      name: 'delayMs',
      required: false,
      type: 'number',
      description: `Milliseconds to wait before showing the fallback, to prevent flicker.`,
    },
  ]}
/>

### Avatar.Image

Avatar.Image extends [Image](/docs/components/image).


## components/button/1.0.0-alpha

---
title: Button
description: A simple button component
name: button
component: Button
demoName: Button
---

# Button

<Description>A simple, sizable button.</Description>

<HeroContainer>
  <ButtonDemo />
</HeroContainer>

```tsx hero template=Button

```

<Highlights
  features={[
    'Accepts size prop that works on all styles.',
    'Can inverse theme with themeInverse.',
    'Place an icon before or after.',
  ]}
/>

### Usage

```tsx
import { Button } from 'tamagui'

export default () => <Button>Lorem ipsum</Button>
```

### 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 () => <Button size="$6">Lorem ipsum</Button>
```

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:

<PropsTable
  data={[
    {
      name: 'size',
      required: false,
      type: 'string | tokens.size',
      description: `Set a size, number or one of the size token values.`,
    },
    {
      name: 'theme',
      required: false,
      type: 'string',
      description: `Apply a theme just to the button and it's children`,
    },
    {
      name: 'themeInverse',
      required: false,
      type: 'boolean',
      description: `Helpful for "flipping" any theme between dark and light (including flipping a sub themes defined as [subtheme]-[dark/light]`,
    },
    {
      name: 'textProps',
      required: false,
      type: 'TextProps',
      description: `By default a button wraps contents in Text and will pass textProps if set.`,
    },
    {
      name: 'noTextWrap',
      required: false,
      type: 'boolean',
      description: `If true, Button won't wrap content with a Text element.`,
    },
    {
      name: 'icon',
      required: false,
      type: 'JSX.Element',
      description: `Pass any React element, appears before the text.`,
    },
    {
      name: 'iconAfter',
      required: false,
      type: 'JSX.Element',
      description: `Pass any React element, appears after the text.`,
    },
  ]}
/>


## components/button/1.0.0-beta.0

---
title: Button
description: A simple button component
name: button
component: Button
demoName: Button
---

# Button

<Description>A simple, sizable button.</Description>

<HeroContainer demoMultiple>
  <ButtonDemo />
</HeroContainer>

```tsx hero template=Button

```

<Highlights
  features={[
    'Accepts size prop that works on all styles.',
    'Can inverse theme with themeInverse.',
    'Place an icon before or after.',
  ]}
/>

### Usage

```tsx
import { Button } from 'tamagui'

export default () => <Button>Lorem ipsum</Button>
```

### 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 () => <Button size="$6">Lorem ipsum</Button>
```

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:

<PropsTable
  data={[
    {
      name: 'size',
      required: false,
      type: 'string | tokens.size',
      description: `Set a size, number or one of the size token values.`,
    },
    {
      name: 'theme',
      required: false,
      type: 'string',
      description: `Apply a theme just to the button and it's children`,
    },
    {
      name: 'themeInverse',
      required: false,
      type: 'boolean',
      description: `Helpful for "flipping" any theme between dark and light (including flipping a sub themes defined as [subtheme]-[dark/light]`,
    },
    {
      name: 'noTextWrap',
      required: false,
      type: 'boolean',
      description: `If true, Button won't wrap content with a Text element.`,
    },
    {
      name: 'icon',
      required: false,
      type: 'JSX.Element',
      description: `Pass any React element, appears before the text.`,
    },
    {
      name: 'iconAfter',
      required: false,
      type: 'JSX.Element',
      description: `Pass any React element, appears after the text.`,
    },
    {
      name: 'scaleIcon',
      required: false,
      type: 'number',
      description: `Scale the icon more than usual by this number.`,
    },
    {
      name: 'scaleSpace',
      required: false,
      type: 'number',
      description: `Scale the spacing more than usual by this number.`,
    },
    {
      name: 'spaceFlex',
      required: false,
      type: `boolean`,
      description: `Makes all space elements have a flex.`,
    },
    {
      name: 'color',
      required: false,
      type: `SizableTextProps['color']`,
      description: `Passes "color" down to the inner text component`,
    },
    {
      name: 'fontWeight',
      required: false,
      type: `SizableTextProps['fontWeight']`,
      description: `Passes "fontWeight" down to the inner text component`,
    },
    {
      name: 'letterSpacing',
      required: false,
      type: `SizableTextProps['letterSpacing']`,
      description: `Passes "letterSpacing" down to the inner text component`,
    },
    {
      name: 'textAlign',
      required: false,
      type: `SizableTextProps['textAlign']`,
      description: `Passes "textAlign" down to the inner text component`,
    },
  ]}
/>


## components/button/1.0.0

---
title: Button
description: A simple button component
name: button
component: Button
package: button
demoName: Button
---

# Button

<Description>A simple, sizable button.</Description>

<HeroContainer demoMultiple>
  <ButtonDemo />
</HeroContainer>

```tsx hero template=Button

```

<Highlights
  features={[
    'Accepts size prop that works on all styles.',
    'Can inverse theme with themeInverse.',
    'Place an icon before or after.',
  ]}
/>

### Usage

```tsx
import { Button } from 'tamagui'

export default () => <Button>Lorem ipsum</Button>
```

### 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 () => <Button size="$6">Lorem ipsum</Button>
```

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/master/packages/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, and doesn't accept `hoverStyle` text props. If you need more control, you can do a simple customization using some exported helpers.

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<typeof CustomButtonFrame>
type CustomButtonTextProps = GetProps<typeof CustomButtonText>

export type CustomButtonProps = TamaguiButtonProps &
  CustomButtonFrameProps &
  CustomButtonTextProps

export const Button = CustomButtonFrame.styleable<CustomButtonProps>((propsIn, ref) => {
  const { props } = useButton(propsIn, { Text: CustomButtonText })
  return <CustomButtonFrame {...props} ref={ref} />
})
```

### Button props

Buttons extend Stack views inheriting all the [Tamagui standard props](/docs/intro/props), plus:

<PropsTable
  data={[
    {
      name: 'size',
      required: false,
      type: 'string | tokens.size',
      description: `Set a size, number or one of the size token values.`,
    },
    {
      name: 'theme',
      required: false,
      type: 'string',
      description: `Apply a theme just to the button and it's children`,
    },
    {
      name: 'themeInverse',
      required: false,
      type: 'boolean',
      description: `Helpful for "flipping" any theme between dark and light (including flipping a sub themes defined as [subtheme]-[dark/light]`,
    },
    {
      name: 'noTextWrap',
      required: false,
      type: 'boolean',
      description: `If true, Button won't wrap content with a Text element.`,
    },
    {
      name: 'icon',
      required: false,
      type: 'JSX.Element',
      description: `Pass any React element, appears before the text.`,
    },
    {
      name: 'iconAfter',
      required: false,
      type: 'JSX.Element',
      description: `Pass any React element, appears after the text.`,
    },
    {
      name: 'scaleIcon',
      required: false,
      type: 'number',
      description: `Scale the icon more than usual by this number.`,
    },
    {
      name: 'scaleSpace',
      required: false,
      type: 'number',
      description: `Scale the spacing more than usual by this number.`,
    },
    {
      name: 'spaceFlex',
      required: false,
      type: `boolean`,
      description: `Makes all space elements have a flex.`,
    },
    {
      name: 'color',
      required: false,
      type: `SizableTextProps['color']`,
      description: `Passes "color" down to the inner text component`,
    },
    {
      name: 'fontWeight',
      required: false,
      type: `SizableTextProps['fontWeight']`,
      description: `Passes "fontWeight" down to the inner text component`,
    },
    {
      name: 'letterSpacing',
      required: false,
      type: `SizableTextProps['letterSpacing']`,
      description: `Passes "letterSpacing" down to the inner text component`,
    },
    {
      name: 'textAlign',
      required: false,
      type: `SizableTextProps['textAlign']`,
      description: `Passes "textAlign" down to the inner text component`,
    },
    {
      name: 'circular',
      required: false,
      type: `boolean`,
      description: `Forces a circular button.`,
    },
    {
      name: 'unstyled',
      required: false,
      type: `boolean`,
      description: `Removes all default Tamagui styles.`,
    },
  ]}
/>


## components/button/1.28.0

---
title: Button
description: An incredibly flexible button.
name: button
component: Button
package: button
demoName: Button
---

<HeroContainer demoMultiple>
  <ButtonDemo />
</HeroContainer>

```tsx hero template=Button

```

<Highlights
  features={[
    'Accepts size prop that works on all styles.',
    'Can inverse theme with themeInverse.',
    'Place an icon before or after.',
  ]}
/>

## Usage

When using the simple Button API, it's as simple as this:

```tsx
import { Button } from 'tamagui'

export default () => <Button>Lorem ipsum</Button>
```

### 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 () => <Button size="$6">Lorem ipsum</Button>
```

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).

<Notice>
  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.
</Notice>

## API Reference

### Button

Buttons extend Stack views inheriting all the [Tamagui standard props](/docs/intro/props), plus:

<PropsTable
  data={[
    {
      name: 'size',
      required: false,
      type: 'string | tokens.size',
      description: `Set a size, number or one of the size token values.`,
    },
    {
      name: 'theme',
      required: false,
      type: 'string',
      description: `Apply a theme just to the button and it's children`,
    },
    {
      name: 'themeInverse',
      required: false,
      type: 'boolean',
      description: `Helpful for "flipping" any theme between dark and light (including flipping a sub themes defined as [subtheme]-[dark/light]`,
    },
    {
      name: 'noTextWrap',
      required: false,
      type: 'boolean',
      description: `If true, Button won't wrap content with a Text element.`,
    },
    {
      name: 'icon',
      required: false,
      type: 'JSX.Element',
      description: `Pass any React element, appears before the text.`,
    },
    {
      name: 'iconAfter',
      required: false,
      type: 'JSX.Element',
      description: `Pass any React element, appears after the text.`,
    },
    {
      name: 'scaleIcon',
      required: false,
      type: 'number',
      description: `Scale the icon more than usual by this number.`,
    },
    {
      name: 'scaleSpace',
      required: false,
      type: 'number',
      description: `Scale the spacing more than usual by this number.`,
    },
    {
      name: 'spaceFlex',
      required: false,
      type: `boolean`,
      description: `Makes all space elements have a flex.`,
    },
    {
      name: 'color',
      required: false,
      type: `SizableTextProps['color']`,
      description: `Passes "color" down to the inner text component`,
    },
    {
      name: 'fontWeight',
      required: false,
      type: `SizableTextProps['fontWeight']`,
      description: `Passes "fontWeight" down to the inner text component`,
    },
    {
      name: 'letterSpacing',
      required: false,
      type: `SizableTextProps['letterSpacing']`,
      description: `Passes "letterSpacing" down to the inner text component`,
    },
    {
      name: 'textAlign',
      required: false,
      type: `SizableTextProps['textAlign']`,
      description: `Passes "textAlign" down to the inner text component`,
    },
    {
      name: 'circular',
      required: false,
      type: `boolean`,
      description: `Forces a circular button.`,
    },
    {
      name: 'unstyled',
      required: false,
      type: `boolean`,
      description: `Removes all default Tamagui styles.`,
    },
  ]}
/>


## components/button/2.0.0

---
title: Button
description: A simple button component
name: button
component: Button
package: button
demoName: Button
---

<HeroContainer demoMultiple>
  <ButtonDemo />
</HeroContainer>

```tsx hero template=Button

```

<Highlights
  features={[
    'Size prop that works on all styles.',
    'Place an icon before or after.',
    'Control icon size explicitly with `iconSize`.',
    'Theme groups of buttons with `Button.Apply`.',
  ]}
/>

### Usage

```tsx
import { Button } from 'tamagui'

export default () => <Button>Lorem ipsum</Button>
```

### 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 () => <Button size="$6">Lorem ipsum</Button>
```

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 () => (
  <XStack gap="$2">
    <Button>Default</Button>
    <Button variant="outlined">Outlined</Button>
  </XStack>
)
```

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. This space is calculated as 40% of the icon's final computed
size (after considering `size`, `iconSize`, and `scaleIcon`). This margin is
applied to the right of an `icon` and to the left of an `iconAfter`.

```tsx
import { Button, Star } from 'tamagui'

export default () => (
  <>
    <Button icon={Star} size="$5">
      Icon sized by Button
    </Button>
    <Button icon={Star} iconSize="$2" size="$5">
      Icon sized explicitly
    </Button>
  </>
)
```

You can
[use the source of Button itself](https://github.com/tamagui/tamagui/blob/master/packages/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 () => (
  <YStack gap="$4">
    <Button.Apply size="$2" variant="outlined">
      <Button>Small Outlined 1</Button>
      <Button>Small Outlined 2</Button>
      <Button icon={ButtonDemo}>With Icon</Button>
    </Button.Apply>

    <Button.Apply size="$5">
      <Button>Large 1</Button>
      <Button theme="blue">Large Themed</Button>
    </Button.Apply>
  </YStack>
)
```

### Web Form Props

Button supports all standard HTML `<button>` attributes for form integration.
These props are web-only and ignored on native:

```tsx
import { Button, Form } from 'tamagui'

export default () => (
  <Form action="/submit">
    <Button type="submit">Submit Form</Button>
    <Button type="reset">Reset</Button>
    <Button type="button">Regular Button (default)</Button>

    {/* Override form attributes */}
    <Button
      type="submit"
      formAction="/alternative-endpoint"
      formMethod="post"
    >
      Submit to Different Endpoint
    </Button>
  </Form>
)
```

<Notice>
  Button defaults to `type="button"` to prevent unintended form submissions.
  Use `type="submit"` explicitly when you want form submission behavior.
</Notice>

### 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).

### Button props

Buttons extend Stack views inheriting all the
[Tamagui standard props](/docs/intro/props), plus:

<PropsTable
  data={[
    {
      name: 'size',
      required: false,
      type: 'SizeTokens | number',
      description: `Set a size from your theme's size tokens (e.g., "$4") or a specific number. Adjusts padding, font size, and icon sizes.`,
    },
    {
      name: 'variant',
      required: false,
      type: '"outlined" | undefined',
      description: 'Applies the "outlined" button style.',
    },
    {
      name: 'disabled',
      required: false,
      type: 'boolean',
      description:
        'If true, the button will be non-interactive and visually disabled. Sets `pointerEvents: "none"`.',
    },
    {
      name: 'theme',
      required: false,
      type: 'string',
      description: `Apply a theme just to the button and its children.`,
    },
    {
      name: 'icon',
      required: false,
      type: 'React.ReactNode | React.ComponentType<{ color?: string, size?: number }>',
      description: `Pass any React element or a component. Appears before the text. If a component is passed, 'color' and 'size' props are passed to it.`,
    },
    {
      name: 'iconAfter',
      required: false,
      type: 'React.ReactNode | React.ComponentType<{ color?: string, size?: number }>',
      description: `Pass any React element or a component. Appears after the text. If a component is passed, 'color' and 'size' props are passed to it.`,
    },
    {
      name: 'iconSize',
      required: false,
      type: 'SizeTokens',
      description: `Explicitly set the size of the icon, overriding the default size derived from the Button's 'size' prop. Uses theme size tokens (e.g., "$2").`,
    },
    {
      name: 'scaleIcon',
      required: false,
      type: 'number',
      description: `Scale the icon by this factor. Default is 1. Applied after 'iconSize' or the default size calculation.`,
    },
    {
      name: 'circular',
      required: false,
      type: `boolean`,
      description: `Forces a circular button. Applies border radius to make the button a circle.`,
    },
    {
      name: 'chromeless',
      required: false,
      type: 'boolean | "all"',
      description:
        'Removes default Tamagui visual styles (background, border) for a more minimal appearance, while keeping layout and interactions. If "all", removes hover/press/focus styles too.',
    },
    {
      name: 'unstyled',
      required: false,
      type: `boolean`,
      description: `Removes all Tamagui default styles, including layout, interactions, and accessibility attributes. Prefer 'chromeless' for removing only visual styles.`,
    },
    // Web-only form props
    {
      name: '// Web Form Props',
      description:
        'The following props are web-only and provide full HTML button form semantics. They are ignored on native.',
      type: '---',
    },
    {
      name: 'type',
      required: false,
      type: '"button" | "submit" | "reset"',
      description: 'Web-only. The button type. Defaults to "button" to prevent unintended form submissions.',
    },
    {
      name: 'formAction',
      required: false,
      type: 'string',
      description: 'Web-only. URL for form submission, overrides the form\'s action.',
    },
    {
      name: 'formMethod',
      required: false,
      type: '"get" | "post"',
      description: 'Web-only. HTTP method for form submission.',
    },
    {
      name: 'formEncType',
      required: false,
      type: 'string',
      description: 'Web-only. Encoding type for form data.',
    },
    {
      name: 'formNoValidate',
      required: false,
      type: 'boolean',
      description: 'Web-only. Bypass form validation when submitting.',
    },
    {
      name: 'formTarget',
      required: false,
      type: 'string',
      description: 'Web-only. Where to display form response (_self, _blank, etc).',
    },
    {
      name: 'name',
      required: false,
      type: 'string',
      description: 'Web-only. Name submitted with form data.',
    },
    {
      name: 'value',
      required: false,
      type: 'string',
      description: 'Web-only. Value submitted with form data.',
    },
    {
      name: 'popoverTarget',
      required: false,
      type: 'string',
      description: 'Web-only. ID of popover element to control.',
    },
    {
      name: 'popoverTargetAction',
      required: false,
      type: '"hide" | "show" | "toggle"',
      description: 'Web-only. Action to perform on the popover.',
    },
    // Note about text styling
    {
      name: '// Text Styling',
      description:
        'To style the text inside the Button (e.g., color, fontWeight), use `styled(Button, { ... })` or create a custom button component using `Button.Text`. Direct text props are not passed to the inner text element for performance and simplicity.',
      type: '---',
    },
  ]}
/>{' '}


## components/card/1.0.0

---
title: Card
description: Sizable, themeable cards with a flexible API.
name: card
component: Card
package: card
demoName: Card
---

<HeroContainer>
  <CardDemo />
</HeroContainer>

```tsx hero template=Card

```

<Highlights
  features={[
    `Sizable with a size prop that passes to Card children.`,
    `Themeable helper props like elevate.`,
    `Background component that handles positioning.`,
  ]}
/>

## Installation

Card is already installed in `tamagui`, or you can install it independently:

```bash
npm install @tamagui/card
```

## Anatomy

```tsx
import { Card } from 'tamagui' // or '@tamagui/card'

export default () => (
  <Card>
    <Card.Header />
    <Card.Footer />
    {/* any other components */}
    <Card.Background />
  </Card>
)
```

## API Reference

### Card

Frame of the card, extends [ThemeableStack props](/docs/components/stacks#themeablestack), adding:

<PropsTable
  data={[
    {
      name: 'size',
      type: 'SizeTokens',
      required: false,
      description: `Passes size down to all sub-components when set for padding, arrow, borderRadius.`,
    },
    {
      name: 'unstyled',
      required: false,
      type: `boolean`,
      description: `Removes all default Tamagui styles.`,
    },
  ]}
/>

### Card.Header

Extends [ThemeableStack](/docs/components/stacks#themeablestack), adding:

<PropsTable
  data={[
    {
      name: 'unstyled',
      required: false,
      type: `boolean`,
      description: `Removes all default Tamagui styles.`,
    },
  ]}
/>

### Card.Footer

Extends [ThemeableStack](/docs/components/stacks#themeablestack), adding:

<PropsTable
  data={[
    {
      name: 'unstyled',
      required: false,
      type: `boolean`,
      description: `Removes all default Tamagui styles.`,
    },
  ]}
/>

### Card.Background

Extends [YStack](/docs/components/stacks), set to `fullscreen` and zIndex below Header/Footer.

<PropsTable
  data={[
    {
      name: 'unstyled',
      required: false,
      type: `boolean`,
      description: `Removes all default Tamagui styles.`,
    },
  ]}
/>


## components/card/2.0.0

---
title: Card
description: Sizable, themeable cards with a flexible API.
name: card
component: Card
package: card
demoName: Card
---

<HeroContainer>
  <CardDemo />
</HeroContainer>

```tsx hero template=Card

```

<Highlights
  features={[
    `Sizable with a size prop that passes to Card children.`,
    `Themeable helper props like elevate.`,
    `Background component that handles positioning.`,
  ]}
/>

## Installation

Card is already installed in `tamagui`, or you can install it independently:

```bash
npm install @tamagui/card
```

## Anatomy

```tsx
import { Card } from 'tamagui' // or '@tamagui/card'

export default () => (
  <Card>
    <Card.Header />
    <Card.Footer />
    {/* any other components */}
    <Card.Background />
  </Card>
)
```

## API Reference

### Card

Frame of the card, extends [ThemeableStack props](/docs/components/stacks#themeablestack), adding:

<PropsTable
  data={[
    {
      name: 'size',
      type: 'SizeTokens',
      required: false,
      description: `Passes size down to all sub-components when set for padding, arrow, borderRadius.`,
    },
    {
      name: 'unstyled',
      required: false,
      type: `boolean`,
      description: `Removes all default Tamagui styles.`,
    },
  ]}
/>

### Card.Header

Extends [ThemeableStack](/docs/components/stacks#themeablestack), adding:

<PropsTable
  data={[
    {
      name: 'unstyled',
      required: false,
      type: `boolean`,
      description: `Removes all default Tamagui styles.`,
    },
  ]}
/>

### Card.Footer

Extends [ThemeableStack](/docs/components/stacks#themeablestack), adding:

<PropsTable
  data={[
    {
      name: 'unstyled',
      required: false,
      type: `boolean`,
      description: `Removes all default Tamagui styles.`,
    },
  ]}
/>

### Card.Background

Extends [YStack](/docs/components/stacks), set to `fullscreen` and zIndex below Header/Footer.

<PropsTable
  data={[
    {
      name: 'unstyled',
      required: false,
      type: `boolean`,
      description: `Removes all default Tamagui styles.`,
    },
  ]}
/>


## components/checkbox/1.3.0

---
title: Checkbox
description: A simple checkbox component
name: checkbox
component: Checkbox
package: checkbox
demoName: Checkbox
---

# Checkbox

<Description>Use in forms to toggle between two states.</Description>

<HeroContainer>
  <CheckboxDemo />
</HeroContainer>

```tsx hero template=Checkbox

```

<Highlights
  features={[
    `Supports indeterminate state.`,
    `Accessible, easy to compose and customize.`,
    `Sizable & works controlled or uncontrolled.`,
    `Ability to opt-out to native checkbox on web.`,
  ]}
/>

## 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'
import { Checkbox } from 'tamagui'

export default () => (
  <Checkbox size="$4">
    <Checkbox.Indicator>
      <Check />
    </Checkbox.Indicator>
  </Checkbox>
)
```

## API Reference

### Checkbox

`Checkbox` extend ThemeableStack inheriting all the [props](/docs/components/stacks#themeablestack), plus:

<PropsTable
  data={[
    {
      name: 'labeledBy',
      type: 'string',
      description: `Set aria-labeled-by.`,
    },
    {
      name: 'name',
      type: 'string',
      description: `Equivalent to input name.`,
    },
    {
      name: 'value',
      type: 'string',
      description: `Give it a value (for use in HTML forms).`,
    },
    {
      name: 'checked',
      type: 'boolean',
      description: `Control the input.`,
    },
    {
      name: 'defaultChecked',
      type: 'boolean',
      description: `Uncontrolled default value.`,
    },
    {
      name: 'required',
      type: 'boolean',
      description: `Sets aria-required.`,
    },
    {
      name: 'native',
      type: 'boolean',
      description: `Renders native checkbox input on web.`,
      default: false,
    },
    {
      name: 'onCheckedChange',
      type: '(checked: boolean | "indeterminate") => 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:

<PropsTable
  data={[
    {
      name: 'forceMount',
      required: false,
      type: `boolean`,
      description: `Used to force mounting when more control is needed.`,
    },
    {
      name: 'disablePassStyles',
      required: false,
      type: 'boolean',
      description: `Used to disable passing styles down to children.`,
    },
  ]}
/>


## components/checkbox/1.85.0

---
title: Checkbox
description: A simple checkbox component
name: checkbox
component: Checkbox
package: checkbox
demoName: Checkbox
---

# Checkbox

<Description>Toggle state in forms.</Description>

<YStack className="is-sticky" />

<Tabs id="type" defaultValue="styled">
<Tabs.List>
  <TooltipSimple label="With Tamagui's default styles">
    <Tabs.Tab value="styled">Styled</Tabs.Tab>
  </TooltipSimple>
  <TooltipSimple label="No default styles, easy to customize">
    <Tabs.Tab value="unstyled">Unstyled</Tabs.Tab>
  </TooltipSimple>
  <TooltipSimple label="No dependency on Tamagui's core">
    <Tabs.Tab value="headless" label="No styles and no dependency on Tamagui's styling">Headless</Tabs.Tab>
  </TooltipSimple>
</Tabs.List>

<HeroContainer showAnimationDriverControl>
  <Tabs.Content
    value="styled"
    justifyContent="center"
    alignItems="center"
    width="100%"
  >
    <CheckboxDemo />
  </Tabs.Content>
  <Tabs.Content
    value="unstyled"
    justifyContent="center"
    alignItems="center"
    width="100%"
  >
    <CheckboxUnstyledDemo />
  </Tabs.Content>
  <Tabs.Content
    value="headless"
    justifyContent="center"
    alignItems="center"
    width="100%"
  >
    <CheckboxHeadlessDemo />
  </Tabs.Content>
</HeroContainer>

<Tabs.Content value="styled">
  ```tsx hero template=Checkbox

````
</Tabs.Content>
<Tabs.Content value="unstyled">
```tsx hero template=CheckboxUnstyled

````

</Tabs.Content>
  <Tabs.Content value="headless">
  ```tsx hero template=CheckboxHeadless

````
</Tabs.Content>

<Highlights
features={[
  `Supports indeterminate state.`,
  `Accessible, easy to compose and customize.`,
  `Sizable & works controlled or uncontrolled.`,
  `Ability to opt-out to native checkbox on web.`,
]}
/>

## Installation

<Tabs.Content value="styled">
Checkbox is already installed in `tamagui`, or you can install it independently:

```bash
npm install @tamagui/checkbox
````

</Tabs.Content>

<Tabs.Content value="unstyled">
Checkbox is already installed in `tamagui`, or you can install it independently:

```bash
npm install @tamagui/checkbox
```

</Tabs.Content>

<Tabs.Content value="headless">

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
```

</Tabs.Content>

## Usage

<Tabs.Content value="styled">

```tsx
import { Check } from '@tamagui/lucide-icons'
import { Checkbox } from 'tamagui'

export default () => (
  <Checkbox size="$4">
    <Checkbox.Indicator>
      <Check />
    </Checkbox.Indicator>
  </Checkbox>
)
```

</Tabs.Content>

<Tabs.Content value="unstyled">

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=CheckboxUnstyled

```

</Tabs.Content>

<Tabs.Content value="headless">

Using the `useCheckbox` API, you can make your own Checkbox from scratch.

```tsx template=CheckboxHeadless

```

</Tabs.Content>

## API Reference

### Checkbox

`Checkbox` extend ThemeableStack inheriting all the
[props](/docs/components/stacks#themeablestack), plus:

<PropsTable
  data={[
    {
      name: 'labeledBy',
      type: 'string',
      description: `Set aria-labeled-by.`,
    },
    {
      name: 'name',
      type: 'string',
      description: `Equivalent to input name.`,
    },
    {
      name: 'value',
      type: 'string',
      description: `Give it a value (for use in HTML forms).`,
    },
    {
      name: 'checked',
      type: 'boolean',
      description: `Control the input.`,
    },
    {
      name: 'defaultChecked',
      type: 'boolean',
      description: `Uncontrolled default value.`,
    },
    {
      name: 'required',
      type: 'boolean',
      description: `Sets aria-required.`,
    },
    {
      name: 'native',
      type: 'boolean',
      description: `Renders native checkbox input on web.`,
      default: false,
    },
    {
      name: 'onCheckedChange',
      type: '(checked: boolean | "indeterminate") => 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:

<PropsTable
  data={[
    {
      name: 'forceMount',
      required: false,
      type: `boolean`,
      description: `Used to force mounting when more control is needed.`,
    },
    {
      name: 'disablePassStyles',
      required: false,
      type: 'boolean',
      description: `Used to disable passing styles down to children.`,
    },
  ]}
/>

</Tabs>


## components/checkbox/1.89.0

---
title: Checkbox
description: Toggle state in forms.
name: checkbox
component: Checkbox
package: checkbox
demoName: Checkbox
---

<YStack className="is-sticky" />

<Tabs id="type" defaultValue="styled">
<Tabs.List>
  <TooltipSimple label="With Tamagui's default styles">
    <Tabs.Tab value="styled">Styled</Tabs.Tab>
  </TooltipSimple>
  <TooltipSimple label="No default styles, easy to customize">
    <Tabs.Tab value="unstyled">Unstyled</Tabs.Tab>
  </TooltipSimple>
  <TooltipSimple label="No dependency on Tamagui's core">
    <Tabs.Tab value="headless" label="No styles and no dependency on Tamagui's styling">Headless</Tabs.Tab>
  </TooltipSimple>
</Tabs.List>

<HeroContainer showAnimationDriverControl>
  <Tabs.Content
    value="styled"
    justifyContent="center"
    alignItems="center"
    width="100%"
  >
    <CheckboxDemo />
  </Tabs.Content>
  <Tabs.Content
    value="unstyled"
    justifyContent="center"
    alignItems="center"
    width="100%"
  >
    <CheckboxUnstyledDemo />
  </Tabs.Content>
  <Tabs.Content
    value="headless"
    justifyContent="center"
    alignItems="center"
    width="100%"
  >
    <CheckboxHeadlessDemo />
  </Tabs.Content>
</HeroContainer>

<Tabs.Content value="styled">
  ```tsx hero template=Checkbox

````
</Tabs.Content>
<Tabs.Content value="unstyled">
```tsx hero template=CheckboxUnstyled

````

</Tabs.Content>
  <Tabs.Content value="headless">
  ```tsx hero template=CheckboxHeadless

````
</Tabs.Content>

<Highlights
features={[
  `Supports indeterminate state.`,
  `Accessible, easy to compose and customize.`,
  `Sizable & works controlled or uncontrolled.`,
  `Ability to opt-out to native checkbox on web.`,
]}
/>

## Installation

<Tabs.Content value="styled">
Checkbox is already installed in `tamagui`, or you can install it independently:

```bash
npm install @tamagui/checkbox
````

</Tabs.Content>

<Tabs.Content value="unstyled">
Checkbox is already installed in `tamagui`, or you can install it independently:

```bash
npm install @tamagui/checkbox
```

</Tabs.Content>

<Tabs.Content value="headless">

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
```

</Tabs.Content>

## Usage

<Tabs.Content value="styled">

```tsx
import { Check } from '@tamagui/lucide-icons'
import { Checkbox } from 'tamagui'

export default () => (
  <Checkbox size="$4">
    <Checkbox.Indicator>
      <Check />
    </Checkbox.Indicator>
  </Checkbox>
)
```

</Tabs.Content>

<Tabs.Content value="unstyled">

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=CheckboxUnstyled

```

</Tabs.Content>

<Tabs.Content value="headless">

Using the `useCheckbox` API, you can make your own Checkbox from scratch.

```tsx template=CheckboxHeadless

```

</Tabs.Content>

## API Reference

### Checkbox

`Checkbox` extend ThemeableStack inheriting all the
[props](/docs/components/stacks#themeablestack), plus:

<PropsTable
  data={[
    {
      name: 'labeledBy',
      type: 'string',
      description: `Set aria-labeled-by.`,
    },
    {
      name: 'name',
      type: 'string',
      description: `Equivalent to input name.`,
    },
    {
      name: 'value',
      type: 'string',
      description: `Give it a value (for use in HTML forms).`,
    },
    {
      name: 'checked',
      type: 'boolean',
      description: `Control the input.`,
    },
    {
      name: 'defaultChecked',
      type: 'boolean',
      description: `Uncontrolled default value.`,
    },
    {
      name: 'required',
      type: 'boolean',
      description: `Sets aria-required.`,
    },
    {
      name: 'native',
      type: 'boolean',
      description: `Renders native checkbox input on web.`,
      default: false,
    },
    {
      name: 'onCheckedChange',
      type: '(checked: boolean | "indeterminate") => 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:

<PropsTable
  data={[
    {
      name: 'forceMount',
      required: false,
      type: `boolean`,
      description: `Used to force mounting when more control is needed.`,
    },
    {
      name: 'disablePassStyles',
      required: false,
      type: 'boolean',
      description: `Used to disable passing styles down to children.`,
    },
  ]}
/>

</Tabs>


## components/checkbox/2.0.0

---
title: Checkbox
description: Toggle state in forms.
name: checkbox
component: Checkbox
package: checkbox
demoName: Checkbox
---

<YStack className="is-sticky" />

<Tabs id="type" defaultValue="styled">
<Tabs.List>
  <TooltipSimple label="With Tamagui's default styles">
    <Tabs.Tab value="styled">Styled</Tabs.Tab>
  </TooltipSimple>
  <TooltipSimple label="No default styles, easy to customize">
    <Tabs.Tab value="unstyled">Unstyled</Tabs.Tab>
  </TooltipSimple>
  <TooltipSimple label="No dependency on Tamagui's core">
    <Tabs.Tab value="headless" label="No styles and no dependency on Tamagui's styling">Headless</Tabs.Tab>
  </TooltipSimple>
</Tabs.List>

<HeroContainer showAnimationDriverControl>
  <Tabs.Content
    value="styled"
    justifyContent="center"
    alignItems="center"
    width="100%"
  >
    <CheckboxDemo />
  </Tabs.Content>
  <Tabs.Content
    value="unstyled"
    justifyContent="center"
    alignItems="center"
    width="100%"
  >
    <CheckboxUnstyledDemo />
  </Tabs.Content>
  <Tabs.Content
    value="headless"
    justifyContent="center"
    alignItems="center"
    width="100%"
  >
    <CheckboxHeadlessDemo />
  </Tabs.Content>
</HeroContainer>

<Tabs.Content value="styled">
  ```tsx hero template=Checkbox

````
</Tabs.Content>
<Tabs.Content value="unstyled">
```tsx hero template=CheckboxUnstyled

````

</Tabs.Content>
  <Tabs.Content value="headless">
  ```tsx hero template=CheckboxHeadless

````
</Tabs.Content>

<Highlights
features={[
  `Supports indeterminate state.`,
  `Accessible, easy to compose and customize.`,
  `Sizable & works controlled or uncontrolled.`,
  `Ability to opt-out to native checkbox on web.`,
]}
/>

## Installation

<Tabs.Content value="styled">
Checkbox is already installed in `tamagui`, or you can install it independently:

```bash
npm install @tamagui/checkbox
````

</Tabs.Content>

<Tabs.Content value="unstyled">
Checkbox is already installed in `tamagui`, or you can install it independently:

```bash
npm install @tamagui/checkbox
```

</Tabs.Content>

<Tabs.Content value="headless">

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
```

</Tabs.Content>

## Usage

<Tabs.Content value="styled">

```tsx
import { Check } from '@tamagui/lucide-icons'
import { Checkbox } from 'tamagui'

export default () => (
  <Checkbox size="$4">
    <Checkbox.Indicator>
      <Check />
    </Checkbox.Indicator>
  </Checkbox>
)
```

</Tabs.Content>

<Tabs.Content value="unstyled">

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=CheckboxUnstyled

```

</Tabs.Content>

<Tabs.Content value="headless">

The `useCheckbox` hook provides all the state and accessibility props needed to build a custom checkbox with any styling solution.

```tsx template=CheckboxHeadless

```

### Basic Usage

```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 (
    <>
      <Pressable
        ref={checkboxRef}
        {...checkboxProps}
        style={{
          width: 24,
          height: 24,
          borderRadius: 4,
          borderWidth: 2,
          borderColor: checked ? '#3b82f6' : '#d1d5db',
          backgroundColor: checked ? '#3b82f6' : 'transparent',
          justifyContent: 'center',
          alignItems: 'center',
        }}
      >
        {checked && (
          <View style={{ width: 12, height: 12, backgroundColor: 'white' }} />
        )}
      </Pressable>
      {bubbleInput}
    </>
  )
}
```

</Tabs.Content>

## API Reference

### Checkbox

`Checkbox` extend ThemeableStack inheriting all the
[props](/docs/components/stacks#themeablestack), plus:

<PropsTable
  data={[
    {
      name: 'labeledBy',
      type: 'string',
      description: `Set aria-labeled-by.`,
    },
    {
      name: 'name',
      type: 'string',
      description: `Equivalent to input name.`,
    },
    {
      name: 'value',
      type: 'string',
      description: `Give it a value (for use in HTML forms).`,
    },
    {
      name: 'checked',
      type: 'boolean',
      description: `Control the input.`,
    },
    {
      name: 'defaultChecked',
      type: 'boolean',
      description: `Uncontrolled default value.`,
    },
    {
      name: 'required',
      type: 'boolean',
      description: `Sets aria-required.`,
    },
    {
      name: 'native',
      type: 'boolean',
      description: `Renders native checkbox input on web.`,
      default: false,
    },
    {
      name: 'onCheckedChange',
      type: '(checked: boolean | "indeterminate") => 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.`,
    },
    {
      name: 'activeStyle',
      required: false,
      type: `StyleProp`,
      description: `Styles to apply when the checkbox is checked.`,
    },
    {
      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` extend ThemeableStack inheriting all the
[props](/docs/components/stacks#themeablestack), plus:

<PropsTable
  data={[
    {
      name: 'forceMount',
      required: false,
      type: `boolean`,
      description: `Used to force mounting when more control is needed.`,
    },
    {
      name: 'disablePassStyles',
      required: false,
      type: 'boolean',
      description: `Used to disable passing styles down to children.`,
    },
  ]}
/>

<Tabs.Content value="headless">

### 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)

<PropsTable
  data={[
    {
      name: 'labelledBy',
      type: 'string',
      description: `Set aria-labelledby for accessibility.`,
    },
    {
      name: 'disabled',
      type: 'boolean',
      description: `Whether the checkbox is disabled.`,
    },
    {
      name: 'name',
      type: 'string',
      description: `Form input name for the hidden input.`,
    },
    {
      name: 'value',
      type: 'string',
      default: '"on"',
      description: `Form input value.`,
    },
    {
      name: 'required',
      type: 'boolean',
      description: `Whether the checkbox is required in a form.`,
    },
    {
      name: 'onCheckedChange',
      type: '(checked: CheckedState) => 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) |

</Tabs.Content>

</Tabs>


## components/context-menu/2.0.0

---
title: Context Menu
description:
  A menu component that is triggered by a right-click on the web and a long
  press on touch devices.
name: context menu
component: ContextMenu
package: context-menu
---

<HeroContainer>
  <ContextMenuDemo />
</HeroContainer>

```tsx hero template=ContextMenu

```

<Highlights
  features={[
    'Full keyboard navigation.',
    'Supports items, icons, images, checkboxes, groups, and more.',
    'Supports submenus.',
    'Supports modal and non-modal modes.',
    'Supports native menus on native platforms.',
    'Customizable alignment, offsets, and positioning.',
    'Support for native iOS and Android icons.',
    'Supports previews on iOS.',
  ]}
/>

## Installation

ContextMenu is already installed in `tamagui`, or you can install it
independently:

```bash
yarn add @tamagui/context-menu
```

If you want to use native menus, add these dependencies:

```sh
yarn add @react-native-menu/menu
yarn add react-native-ios-context-menu
yarn add react-native-ios-utilities
yarn add zeego
```

## Anatomy

Import all parts and piece them together.

```jsx
import { ContextMenu } from 'tamagui' // or '@tamagui/context-menu'

export default () => (
  <ContextMenu>
    <ContextMenu.Trigger asChild>
      <YStack>
        <Text>Right Click or longPress</Text>
      </YStack>
    </ContextMenu.Trigger>

    <ContextMenu.Portal zIndex={100}>
      <ContextMenu.Content>
        <ContextMenu.Item>
          <ContextMenu.ItemTitle>About Notes</ContextMenu.ItemTitle>
        </ContextMenu.Item>
        <ContextMenu.Item>
          <ContextMenu.ItemTitle>Settings</ContextMenu.ItemTitle>
        </ContextMenu.Item>
        {/* when title is nested inside a React element then you need to use `textValue` */}
        <ContextMenu.Item textValue="Calendar">
          <ContextMenu.ItemTitle>
            <Text>Calendar</Text>
          </ContextMenu.ItemTitle>
          <ContextMenu.ItemIcon>
            <Calendar color="gray" size="$1" />
          </ContextMenu.ItemIcon>
        </ContextMenu.Item>
        <ContextMenu.Separator />
        <ContextMenu.Sub>
          <ContextMenu.SubTrigger>
            <ContextMenu.ItemTitle>Actions</ContextMenu.ItemTitle>
          </ContextMenu.SubTrigger>
          <ContextMenu.Portal zIndex={200}>
            <ContextMenu.SubContent>
              <ContextMenu.Label fontSize={'$1'}>
                Note settings
              </ContextMenu.Label>
              <ContextMenu.Item onSelect={onSelect} key="create-note">
                <ContextMenu.ItemTitle>Create note</ContextMenu.ItemTitle>
              </ContextMenu.Item>
              <ContextMenu.Item onSelect={onSelect} key="delete-all">
                <ContextMenu.ItemTitle>Delete all notes</ContextMenu.ItemTitle>
              </ContextMenu.Item>
              <ContextMenu.Item onSelect={onSelect} key="sync-all">
                <ContextMenu.ItemTitle>Sync notes</ContextMenu.ItemTitle>
              </ContextMenu.Item>
            </ContextMenu.SubContent>
          </ContextMenu.Portal>
        </ContextMenu.Sub>
      </ContextMenu.Content>
    </ContextMenu.Portal>
  </ContextMenu>
)
```

## API Reference

### Context Menu

Contains every component for the Context Menu.

<PropsTable
  data={[
    {
      name: 'children',
      type: 'React.ReactNode',
      required: true,
      description: 'Menu parts: Trigger, Portal, Content, Items, etc.',
    },
    {
      name: 'placement',
      type: 'Placement',
      required: false,
      description: `Where the menu appears relative to the trigger. Options: 'top' | 'right' | 'bottom' | 'left' with optional '-start' | '-end' alignment.`,
    },
    {
      name: 'open',
      type: 'boolean',
      required: false,
      description: 'Controlled open state for the menu.',
    },
    {
      name: 'defaultOpen',
      type: 'boolean',
      required: false,
      description: 'Initial open state when uncontrolled.',
    },
    {
      name: 'onOpenChange',
      type: '(open: boolean) => void',
      required: false,
      description: 'Called when the menu opens or closes.',
    },
    {
      name: 'onOpenWillChange',
      type: '(open: boolean) => void',
      required: false,
      platform: 'ios',
      description: 'Called before the open/close animation begins.',
    },
    {
      name: 'modal',
      type: 'boolean',
      default: 'true',
      required: false,
      description:
        'When true, traps focus inside the menu and blocks outside scroll/interactions.',
    },
    {
      name: 'stayInFrame',
      type: 'ShiftProps | boolean',
      required: false,
      description: 'Shifts the menu to stay within viewport bounds.',
    },
    {
      name: 'allowFlip',
      type: 'FlipProps | boolean',
      required: false,
      description:
        'Flips the menu to the opposite side if there is insufficient space.',
    },
    {
      name: 'offset',
      type: 'OffsetOptions',
      required: false,
      description: 'Distance between the menu and its trigger.',
    },
    {
      name: 'unstyled',
      required: false,
      type: 'boolean',
      description: 'Removes all default Tamagui styles.',
    },
  ]}
/>

### Portal

This is necessary for the Context Menu.

<PropsTable
  data={[
    {
      name: 'zIndex',
      required: false,
      type: 'number',
      description: 'Stacking order of the portal layer.',
    },
    {
      name: 'children',
      type: 'React.ReactNode',
      required: true,
      description: 'Content to render inside the portal.',
    },
    {
      name: 'forceMount',
      type: 'true',
      required: false,
      description:
        'Forces the portal to stay mounted, useful for controlling animations.',
    },
  ]}
/>

### Trigger

The ContextMenu will only be triggered when the user right-clicks or
long-presses within the Trigger area.

<PropsTable
  data={[
    {
      name: 'action',
      required: false,
      type: 'press|longPress',
      default: 'longPress',
      platform: 'android/ios',
      description:
        "Works with the native prop and accepts 'press' or 'longPress'. The default is 'longPress' for ContextMenu.",
    },
  ]}
/>

### Content

Contains the content of the Context Menu.

<PropsTable
  data={[
    {
      name: 'children',
      type: 'React.ReactNode',
      required: true,
      description: 'Menu items, groups, labels, separators, and submenus.',
    },
    {
      name: 'loop',
      type: 'boolean',
      required: false,
      default: 'false',
      description: 'Whether keyboard navigation wraps from last to first item.',
    },
    {
      name: 'forceMount',
      type: 'true',
      required: false,
      description:
        'Forces the content to stay mounted, useful for controlling animations.',
    },
    {
      name: 'onCloseAutoFocus',
      type: '(event: Event) => void',
      required: false,
      description: 'Called when focus returns to the trigger after closing.',
    },
    {
      name: 'onEscapeKeyDown',
      type: '(event: KeyboardEvent) => void',
      required: false,
      description: 'Called when the escape key is pressed. Can be prevented.',
    },
    {
      name: 'onPointerDownOutside',
      type: '(event: PointerEvent) => void',
      required: false,
      description: 'Called when a pointer event occurs outside the content.',
    },
    {
      name: 'onInteractOutside',
      type: '(event: Event) => void',
      required: false,
      description: 'Called when any interaction occurs outside the content.',
    },
  ]}
/>

### Item

A selectable menu item that triggers an action when selected.

<PropsTable
  data={[
    {
      name: 'key',
      type: 'string',
      required: true,
      description: 'Unique identifier for the item.',
    },
    {
      name: 'disabled',
      type: 'boolean',
      required: false,
      default: 'false',
      description: 'Prevents interaction and dims the item.',
    },
    {
      name: 'destructive',
      type: 'boolean',
      required: false,
      platform: 'ios/android',
      description:
        'Renders the item in red on iOS to indicate a dangerous action (e.g. delete). No effect on web.',
    },
    {
      name: 'hidden',
      type: 'boolean',
      required: false,
      description: 'Hides the item from the menu.',
    },
    {
      name: 'onSelect',
      type: '(event?: Event) => void',
      required: false,
      description: 'Called when the item is selected via click or keyboard.',
    },
    {
      name: 'onFocus',
      type: '() => void',
      required: false,
      description: 'Called when the item receives focus.',
    },
    {
      name: 'onBlur',
      type: '() => void',
      required: false,
      description: 'Called when the item loses focus.',
    },
    {
      name: 'textValue',
      type: 'string',
      required: false,
      platform: 'ios/android',
      description:
        'Text used for typeahead and native menus. Required when ItemTitle contains a React node instead of a string.',
    },
  ]}
/>

### ItemTitle

Renders the title of the menu item.

<PropsTable
  data={[
    {
      name: 'children',
      type: 'string | React.ReactNode',
      required: true,
      description: 'The title text or element to display.',
    },
  ]}
/>
<Notice>
  You can directly pass a text node to the ItemTitle. However, if you use a nested
  React node like `<Text>`, you need to pass `textValue` to the `<Item>` so that it
  works with native menus.
</Notice>

### ItemIcon

A component to render an icon. For non-native menus, you can pass an icon
component. For native menus, you can pass platform-specific icons to the
`android`/`ios` props.

On iOS, it renders the native
[SF Symbols](https://github.com/andrewtavis/sf-symbols-online) icons.

<PropsTable
  data={[
    {
      name: 'children',
      type: 'React.ReactNode',
      required: false,
      description: 'Fallback icon for web when native icons are not available.',
    },
    {
      name: 'ios',
      type: 'object',
      required: false,
      platform: 'ios',
      description:
        'SF Symbol configuration: name, weight, scale, hierarchicalColor, paletteColors.',
    },
    {
      name: 'android',
      type: 'object',
      required: false,
      platform: 'android',
      description: 'Android resource drawable name.',
    },
  ]}
/>

```jsx line=1
<ContextMenu.ItemIcon
  ios={{
    name: '0.circle.fill', // required
    pointSize: 5,
    weight: 'semibold',
    scale: 'medium',
    // can also be a color string. Requires iOS 15+
    hierarchicalColor: {
      dark: 'blue',
      light: 'green',
    },
    // alternative to hierarchical color. Requires iOS 15+
    paletteColors: [
      {
        dark: 'blue',
        light: 'green',
      },
    ],
  }}
>
  <CircleIcon />
</ContextMenu.ItemIcon>
```

### ItemImage

A component to render an item image. For native menus, it only works on iOS. It
takes the same properties as `@tamagui/image`.

### ItemSubtitle

A component to render a subtitle for the menu item. For native menus, it only
works on iOS.

<PropsTable
  data={[
    {
      name: 'children',
      type: 'string',
      required: true,
      description: 'The subtitle text to display below the title.',
    },
  ]}
/>

### Group

A component that groups multiple menu items together.

<PropsTable
  data={[
    {
      name: 'children',
      type: 'React.ReactNode',
      required: true,
      description: 'Menu items to group together.',
    },
  ]}
/>

### CheckboxItem

A menu item with a checkbox that can be toggled on/off.

<PropsTable
  data={[
    {
      name: 'key',
      type: 'string',
      required: true,
      description: 'Unique identifier for the checkbox item.',
    },
    {
      name: 'disabled',
      type: 'boolean',
      required: false,
      default: 'false',
      description: 'Prevents interaction and dims the item.',
    },
    {
      name: 'destructive',
      type: 'boolean',
      required: false,
      platform: 'ios/android',
      description:
        'Renders the item in red on iOS to indicate a dangerous action. No effect on web.',
    },
    {
      name: 'hidden',
      type: 'boolean',
      required: false,
      description: 'Hides the item from the menu.',
    },
    {
      name: 'onFocus',
      type: '() => void',
      required: false,
      description: 'Called when the item receives focus.',
    },
    {
      name: 'onBlur',
      type: '() => void',
      required: false,
      description: 'Called when the item loses focus.',
    },
    {
      name: 'textValue',
      type: 'string',
      required: false,
      platform: 'ios/android',
      description:
        'Text for native menus. Required when ItemTitle contains a React node.',
    },
    {
      name: 'value',
      type: "'on' | 'off' | 'mixed'",
      required: false,
      platform: 'ios/android',
      description: 'Controlled checked state for native menus.',
    },
    {
      name: 'onValueChange',
      type: '(state, prevState) => void',
      required: false,
      platform: 'ios/android',
      description: 'Called when checked state changes on native menus.',
    },
    {
      name: 'checked',
      type: 'boolean',
      required: false,
      description: 'Controlled checked state for web menus.',
    },
    {
      name: 'onCheckedChange',
      type: '(checked: boolean) => void',
      required: false,
      description: 'Called when checked state changes on web.',
    },
  ]}
/>

### ItemIndicator

Use inside `CheckboxItem` or `RadioItem` to indicate when an item is checked.
This allows you to conditionally render a checkmark.

```jsx line=1
<ContextMenu.ItemIndicator>
  <CheckmarkIcon /> {/* This does not work with the native prop. */}
</ContextMenu.ItemIndicator>
```

<PropsTable
  data={[
    {
      name: 'children',
      type: 'React.ReactNode',
      required: false,
      description: 'Custom checkmark icon. Only works on web.',
    },
    {
      name: 'forceMount',
      type: 'true',
      required: false,
      description:
        'Forces the indicator to stay mounted for animation control.',
    },
  ]}
/>

### Label

Renders a non-focusable label for a group of items. On native menus, only one
label is supported per menu and submenu.

<PropsTable
  data={[
    {
      name: 'children',
      type: 'string',
      required: true,
      description: 'The label text.',
    },
    {
      name: 'textValue',
      type: 'string',
      required: false,
      platform: 'ios/android',
      description: 'Text for native menus when children is a React node.',
    },
  ]}
/>

### Arrow

Renders an arrow pointing to the trigger.

<PropsTable
  data={[
    {
      name: 'size',
      type: 'number | SizeToken',
      required: false,
      description: 'Width and height of the arrow.',
    },
    {
      name: 'unstyled',
      type: 'boolean',
      required: false,
      description: 'Removes default arrow styles.',
    },
  ]}
/>

### Separator

Renders a visual divider between menu items. Web only.

### Sub

A container for nested sub-menu components.

<PropsTable
  data={[
    {
      name: 'children',
      type: 'React.ReactNode',
      required: true,
      description: 'SubTrigger, Portal, and SubContent components.',
    },
    {
      name: 'open',
      type: 'boolean',
      required: false,
      description: 'Controlled open state for the sub-menu.',
    },
    {
      name: 'onOpenChange',
      type: '(open: boolean) => void',
      required: false,
      description: 'Called when the sub-menu opens or closes.',
    },
  ]}
/>

### SubContent

Renders the content of a sub-menu. Same props as `Content`, excluding `side` and
`align`.

### SubTrigger

A menu item that opens a sub-menu on hover/focus. Accepts the same props as
`Item`.

### Preview (iOS only)

When the Context Menu is visible, this renders a custom preview component. Only
works with native iOS menus.

<Notice>
  Should be rendered as a child of `<ContextMenu.Content />
  `.
</Notice>

<PropsTable
  data={[
    {
      name: 'children',
      type: 'React.ReactNode | (() => React.ReactNode)',
      required: true,
      platform: 'ios',
      description:
        'The preview content to render. Can be a function for lazy rendering.',
    },
    {
      name: 'size',
      type: '{ width?: number, height?: number }',
      required: false,
      platform: 'ios',
      description: 'Dimensions of the preview.',
    },
    {
      name: 'onPress',
      type: '() => void',
      required: false,
      platform: 'ios',
      description: 'Called when the preview is pressed.',
    },
    {
      name: 'backgroundColor',
      type: 'string | { dark: string, light: string }',
      required: false,
      platform: 'ios',
      description: 'Background color of the preview.',
    },
    {
      name: 'borderRadius',
      type: 'number',
      required: false,
      platform: 'ios',
      description: 'Border radius of the preview.',
    },
    {
      name: 'preferredCommitStyle',
      type: "'pop' | 'dismiss'",
      required: false,
      default: "'dismiss'",
      platform: 'ios',
      description:
        "Exit transition when preview is tapped. Use 'pop' when navigating to another screen.",
    },
  ]}
/>

```jsx line=1
<ContextMenu.Preview
  // optional props:
  preferredCommitStyle="pop" // or "dismiss"
  backgroundColor={{
    // or a color string directly
    dark: 'black',
    light: 'white',
  }}
>
  {() => <Preview />}
</ContextMenu.Preview>
```


## components/dialog/1.0.0

---
title: Dialog
description: Show a modal with configurable layout and accessible actions.
name: dialog
component: Dialog
package: dialog
demoName: Dialog
---

<HeroContainer showAnimationDriverControl>
  <DialogDemo />
</HeroContainer>

```tsx hero template=Dialog

```

<Highlights
  features={[
    `Comes with styling, yet completely customizable and themeable.`,
    `Accepts animations, themes, size props and more.`,
    `Accessible with dev-time checks to ensure ARIA props.`,
  ]}
/>

<Notice>
  Dialog does not work on native, instead you can Adapt it to a Sheet as shown in the demo
  code above.
</Notice>

## Installation

Dialog is already installed in `tamagui`, or you can install it independently:

```bash
npm install @tamagui/dialog
```

In order to use this component independently of `tamagui`, you'll first need to install the `@tamagui/portal` package: 

```bash
npm install @tamagui/portal
```

Then add `PortalProvider` to the root of your app:

```tsx fileName="App.tsx"
import { PortalProvider } from '@tamagui/portal'
import YourApp from './components/YourApp'

function App() {
  return (
    <PortalProvider shouldAddRootHost>
      <YourApp />
    </PortalProvider>
  )
}

export default App
```

## Anatomy

```tsx
import { Dialog } from 'tamagui' // or '@tamagui/dialog'

export default () => (
  <Dialog>
    <Dialog.Trigger />
    <Dialog.Portal>
      <Dialog.Overlay />
      <Dialog.Content>
        <Dialog.Title />
        <Dialog.Description />
        <Dialog.Close />
        {/* ... */}
      </Dialog.Content>
    </Dialog.Portal>
    
    {/* Optional: Control focus behavior */}
    <Dialog.FocusScope loop trapped focusOnIdle={true}>
      <Dialog.FocusScope.Scope>
        {/* Focus scope will be applied to children */}
      </Dialog.FocusScope.Scope>
    </Dialog.FocusScope>
  </Dialog>
)
```

## API Reference

### Dialog

Contains every component for the dialog. Beyond [Tamagui Props](/docs/intro/props), adds:

<PropsTable
  data={[
    {
      name: 'children',
      type: 'React.ReactNode',
      required: true,
      description: `Must contain Dialog.Content`,
    },
    {
      name: 'size',
      type: 'SizeTokens',
      description: `Passes size down too all sub-components when set for padding, arrow, borderRadius`,
    },
    {
      name: 'open',
      type: 'boolean',
      description: ``,
    },
    {
      name: 'defaultOpen',
      type: 'boolean',
    },
    {
      name: 'onOpenChange',
      type: '(open: boolean) => void',
    },
    {
      name: 'modal',
      type: 'boolean',
      default: 'true',
      description: `Renders into root of app instead of inline`,
    },
    {
      name: 'disableRemoveScroll',
      type: 'boolean',
      required: false,
      description: `Used to disable the automatic removal of scrolling from the page when open.`,
    },
  ]}
/>

### Dialog.Trigger

Just [Tamagui Props](/docs/intro/props).

### Dialog.Portal

Renders Dialog into appropriate container. Beyond [Tamagui Props](/docs/intro/props), adds:

<PropsTable
  data={[
    {
      name: 'forceMount',
      type: 'boolean',
      required: false,
      description: `Used to force mounting when more control is needed. Useful when controlling animation with React animation libraries.`,
    },
    {
      name: 'unstyled',
      required: false,
      type: `boolean`,
      description: `Removes all default Tamagui styles.`,
    },
  ]}
/>

### Dialog.Content

Main container for Dialog content, this is where you should apply animations.

Beyond [Tamagui Props](/docs/intro/props), adds:

<PropsTable
  data={[
    {
      name: 'forceMount',
      type: 'boolean',
      required: false,
      description: `Used to force mounting when more control is needed. Useful when controlling animation with React animation libraries.`,
    },
    {
      name: 'unstyled',
      required: false,
      type: `boolean`,
      description: `Removes all default Tamagui styles.`,
    },
  ]}
/>

### Dialog.Overlay

Displays behind Content. Beyond [Tamagui Props](/docs/intro/props), adds:

<PropsTable
  data={[
    {
      name: 'forceMount',
      type: 'boolean',
      required: false,
      description: `Used to force mounting when more control is needed. Useful when controlling animation with React animation libraries.`,
    },
  ]}
/>

### Dialog.Title

Required. Can wrap in VisuallyHidden to hide.

Defaults to H2, see [Headings](/docs/components/headings).

### Dialog.Description

Required. Can wrap in VisuallyHidden to hide.

Defaults to Paragraph, see [Paragraph](/docs/components/text).

### Dialog.Close

Closes the Dialog, accepts the same props as YStack. Recommended to use with your own component and `asChild`.

<PropsTable
  data={[
    {
      name: 'displayWhenAdapted',
      type: 'boolean',
      description: `By default Close elements hide when Adapt is active. If set to true, they will show when adapted.`,
    },
  ]}
/>

Just [Tamagui Props](/docs/intro/props).

### Dialog.FocusScope

Provides access to the underlying FocusScope component used by Dialog for focus management. Can be used to control focus behavior from a parent component.

<PropsTable
  data={[
    {
      name: 'enabled',
      type: 'boolean',
      default: 'true',
      description: `Whether focus management is enabled`,
    },
    {
      name: 'loop',
      type: 'boolean',
      default: 'false',
      description: `When true, tabbing from last item will focus first tabbable and shift+tab from first item will focus last tabbable`,
    },
    {
      name: 'trapped',
      type: 'boolean',
      default: 'false',
      description: `When true, focus cannot escape the focus scope via keyboard, pointer, or programmatic focus`,
    },
    {
      name: 'focusOnIdle',
      type: 'boolean | number',
      default: 'false',
      description: `When true, waits for idle before focusing. When a number, waits that many ms. This prevents reflows during animations`,
    },
    {
      name: 'onMountAutoFocus',
      type: '(event: Event) => void',
      description: `Event handler called when auto-focusing on mount. Can be prevented`,
    },
    {
      name: 'onUnmountAutoFocus',
      type: '(event: Event) => void',
      description: `Event handler called when auto-focusing on unmount. Can be prevented`,
    },
  ]}
/>

### Dialog.Sheet

When used with `Adapt`, Dialog will render as a sheet when that breakpoint is active.

See [Sheet](/docs/components/sheet) for more props.

Must use `Adapt.Contents` inside the `Dialog.Sheet.Frame` to insert the contents given to `Dialog.Content`

```tsx
import { Dialog } from 'tamagui' // or '@tamagui/dialog'

export default () => (
  <Dialog>
    <Dialog.Trigger />

    <Dialog.Portal>
      <Dialog.Overlay />
      <Dialog.Content>
        <Dialog.Title />
        <Dialog.Description />
        <Dialog.Close />
        {/* ... */}
      </Dialog.Content>
    </Dialog.Portal>

    {/* optionally change to sheet when small screen */}
    <Dialog.Adapt when="maxMd">
      <Dialog.Sheet>
        <Dialog.Sheet.Frame>
          <Dialog.Adapt.Contents />
        </Dialog.Sheet.Frame>
        <Dialog.Sheet.Overlay />
      </Dialog.Sheet>
    </Dialog.Adapt>
  </Dialog>
)
```

<Notice>
  Note that Dialog.Sheet currently doesn't preserve state of the contents when it
  transitions between Sheet and Portal. In the future, we can do this on the web using
  react-reparenting.
</Notice>

### PortalProvider

<PropsTable
  data={[
    {
      name: 'shouldAddRootHost',
      type: 'boolean',
      required: false,
      description: `Defines whether to add a default root host or not.`,
    },
  ]}
/>

## Examples

### Inside native modals

If you're using native modals (maybe from react-navigation), you'll notice the Dialogs won't show up inside the modal. To get around this, you should wrap your screen inside `PortalProvider`, like so:

```tsx
import { PortalProvider } from 'tamagui'

// this component used in react-navigation/expo-router with `presentation: "modal"`
export function Page() {
  return (
    <PortalProvider>
      {/* rest of your page, including the Dialog... */}
    </PortalProvider>
  )
}
```


## components/dialog/1.131.0

---
title: Dialog
description: Show a modal with configurable layout and accessible actions.
name: dialog
component: Dialog
package: dialog
demoName: Dialog
---

<HeroContainer showAnimationDriverControl>
  <DialogDemo />
</HeroContainer>

```tsx hero template=Dialog

```

<Highlights
  features={[
    `Comes with styling, yet completely customizable and themeable.`,
    `Accepts animations, themes, size props and more.`,
    `Accessible with dev-time checks to ensure ARIA props.`,
  ]}
/>

Dialog is a great way to show content inside a new floating window above
content. Be sure to open the code example above for a copy-paste implementation.

## Installation

Dialog is already installed in `tamagui`, or you can install it independently:

```bash
npm install @tamagui/dialog
```

If you aren't using `tamagui` and instead using the `@tamagui/dialog` package
separately, you'll first need to install the `@tamagui/portal` package:

```bash
npm install @tamagui/portal
```

Then add `PortalProvider` to the root of your app:

```tsx fileName="App.tsx"
import { PortalProvider } from '@tamagui/portal'
import YourApp from './components/YourApp'

function App() {
  return (
    <PortalProvider shouldAddRootHost>
      <YourApp />
    </PortalProvider>
  )
}

export default App
```

## Anatomy

```tsx
import { Dialog } from 'tamagui' // or '@tamagui/dialog'

export default () => (
  <Dialog>
    <Dialog.Trigger />
    <Dialog.Portal>
      <Dialog.Overlay />
      <Dialog.Content>
        <Dialog.Title />
        <Dialog.Description />
        <Dialog.Close />
        {/* ... */}
      </Dialog.Content>
    </Dialog.Portal>

    {/* Optional: Control focus behavior */}
    <Dialog.FocusScope loop trapped focusOnIdle={true}>
      <Dialog.FocusScope.Scope>
        {/* Focus scope will be applied to children */}
      </Dialog.FocusScope.Scope>
    </Dialog.FocusScope>
  </Dialog>
)
```

## Scoping

Dialog supports scoping which lets you mount one or more Dialog instances at the
root of your app, while having a deeply nested child Trigger or Content attach
to the proper parent Dialog instance.

In performance sensitive areas you may want to take advantage of this, it allows
you to only need to render the Dialog.Trigger inside the sensitive area as
Dialogs aren't the cheapest component - they have a lot of functionality.

Here's the basic anatomy of using `scope` and placing your Dialog higher up for
performance:

```tsx fileName=_layout.tsx
import { Dialog } from 'tamagui'

// in your root layout:
export default ({ children }) => (
  <Dialog scope="user-profile">
    <Dialog.Portal>
      <Dialog.Overlay />
      <Dialog.Content>
        <Dialog.Title />
        <Dialog.Description />
        <Dialog.Close />
        {/* ... */}
      </Dialog.Content>
    </Dialog.Portal>

    {/* the rest of your app, note that it's inside of Dialog */}
    {children}
  </Dialog>
)
```

```tsx fileName=UserProfile.tsx
export default () => (
  <Dialog.Trigger scope="user-profile">
    <Button>Open Profile</Button>
  </Dialog.Trigger>
)
```

Note that the `Trigger` scope ties to the `Dialog` scope.

## Dismissal Behavior

By default, dialogs can be dismissed by:
- Clicking outside the dialog content (on the overlay)
- Pressing the Escape key
- Clicking a Dialog.Close element

### Modal vs Non-Modal Dialogs

- **Modal dialogs** (`modal={true}`, which is the default):
  - In v1, have `disableOutsidePointerEvents` set to `true` by default
  - Still dismiss on outside click, but prevent interaction with elements behind the dialog
  - Prevent right-click dismissal (right-clicks on the overlay are ignored)
  
- **Non-modal dialogs** (`modal={false}`):
  - Allow interaction with elements behind the dialog
  - Dismiss on any outside click
  - Do not trap focus

### Preventing Outside Dismissal

To prevent a dialog from closing when clicking outside:

```tsx
<Dialog.Content
  onPointerDownOutside={(event) => {
    event.preventDefault()
  }}
>
  {/* Dialog contents */}
</Dialog.Content>
```

## API Reference

### Dialog

Contains every component for the dialog. Beyond
[Tamagui Props](/docs/intro/props), adds:

<PropsTable
  data={[
    {
      name: 'children',
      type: 'React.ReactNode',
      required: true,
      description: `Must contain Dialog.Content`,
    },
    {
      name: 'size',
      type: 'SizeTokens',
      description: `Passes size down to all sub-components when set for padding, arrow, borderRadius.`,
    },
    {
      name: 'open',
      type: 'boolean',
      description: ``,
    },
    {
      name: 'defaultOpen',
      type: 'boolean',
    },
    {
      name: 'onOpenChange',
      type: '(open: boolean) => void',
    },
    {
      name: 'modal',
      type: 'boolean',
      default: 'true',
      description: `Renders into root of app instead of inline`,
    },
    {
      name: 'disableRemoveScroll',
      type: 'boolean',
      required: false,
      description: `Used to disable the automatic removal of scrolling from the page when open.`,
    },
  ]}
/>

### Dialog.Trigger

Just [Tamagui Props](/docs/intro/props).

### Dialog.Portal

Renders Dialog into appropriate container. Beyond
[Tamagui Props](/docs/intro/props), adds:

<PropsTable
  data={[
    {
      name: 'forceMount',
      type: 'boolean',
      required: false,
      description: `Used to force mounting when more control is needed. Useful when controlling animation with React animation libraries.`,
    },
    {
      name: 'unstyled',
      required: false,
      type: `boolean`,
      description: `Removes all default Tamagui styles.`,
    },
  ]}
/>

### Dialog.Content

Main container for Dialog content, this is where you should apply animations.

Beyond [Tamagui Props](/docs/intro/props), adds:

<PropsTable
  data={[
    {
      name: 'forceMount',
      type: 'boolean',
      required: false,
      description: `Used to force mounting when more control is needed. Useful when controlling animation with React animation libraries.`,
    },
    {
      name: 'unstyled',
      required: false,
      type: `boolean`,
      description: `Removes all default Tamagui styles.`,
    },
    {
      name: 'disableOutsidePointerEvents',
      type: 'boolean',
      required: false,
      description: `When true, hover/focus/click interactions will be disabled on elements outside the Dialog. Users will need to click twice on outside elements to interact with them: once to close the Dialog, and again to trigger the element. Note: In v1, modal dialogs have this set to true by default.`,
    },
  ]}
/>

### Dialog.Overlay

Displays behind Content. Beyond [Tamagui Props](/docs/intro/props), adds:

<PropsTable
  data={[
    {
      name: 'forceMount',
      type: 'boolean',
      required: false,
      description: `Used to force mounting when more control is needed. Useful when controlling animation with React animation libraries.`,
    },
  ]}
/>

### Dialog.Title

Required. Can wrap in VisuallyHidden to hide.

Defaults to H2, see [Headings](/docs/components/headings).

### Dialog.Description

Required. Can wrap in VisuallyHidden to hide.

Defaults to Paragraph, see [Paragraph](/docs/components/text).

### Dialog.Close

Closes the Dialog, accepts the same props as YStack. Recommended to use with
your own component and `asChild`.

<PropsTable
  data={[
    {
      name: 'displayWhenAdapted',
      type: 'boolean',
      description: `By default Close elements hide when Adapt is active. If set to true, they will show when adapted.`,
    },
  ]}
/>

Just [Tamagui Props](/docs/intro/props).

### Dialog.FocusScope

Provides access to the underlying FocusScope component used by Dialog for focus
management. Can be used to control focus behavior from a parent component.

<PropsTable
  data={[
    {
      name: 'enabled',
      type: 'boolean',
      default: 'true',
      description: `Whether focus management is enabled`,
    },
    {
      name: 'loop',
      type: 'boolean',
      default: 'false',
      description: `When true, tabbing from last item will focus first tabbable and shift+tab from first item will focus last tabbable`,
    },
    {
      name: 'trapped',
      type: 'boolean',
      default: 'false',
      description: `When true, focus cannot escape the focus scope via keyboard, pointer, or programmatic focus`,
    },
    {
      name: 'focusOnIdle',
      type: 'boolean | number',
      default: 'false',
      description: `When true, waits for idle before focusing. When a number, waits that many ms. This prevents reflows during animations`,
    },
    {
      name: 'onMountAutoFocus',
      type: '(event: Event) => void',
      description: `Event handler called when auto-focusing on mount. Can be prevented`,
    },
    {
      name: 'onUnmountAutoFocus',
      type: '(event: Event) => void',
      description: `Event handler called when auto-focusing on unmount. Can be prevented`,
    },
  ]}
/>

### Dialog.Sheet

When used with `Adapt`, Dialog will render as a sheet when that breakpoint is
active.

See [Sheet](/docs/components/sheet) for more props.

Must use `Adapt.Contents` inside the `Dialog.Sheet.Frame` to insert the contents
given to `Dialog.Content`

```tsx
import { Dialog } from 'tamagui' // or '@tamagui/dialog'

export default () => (
  <Dialog>
    <Dialog.Trigger />

    <Dialog.Portal>
      <Dialog.Overlay />
      <Dialog.Content>
        <Dialog.Title />
        <Dialog.Description />
        <Dialog.Close />
        {/* ... */}
      </Dialog.Content>
    </Dialog.Portal>

    {/* optionally change to sheet when small screen */}
    <Dialog.Adapt when="maxMd">
      <Dialog.Sheet>
        <Dialog.Sheet.Frame>
          <Dialog.Adapt.Contents />
        </Dialog.Sheet.Frame>
        <Dialog.Sheet.Overlay />
      </Dialog.Sheet>
    </Dialog.Adapt>
  </Dialog>
)
```

<Notice>
  Note that Dialog.Sheet currently doesn't preserve state of the contents when
  it transitions between Sheet and Portal. In the future, we can do this on the
  web using react-reparenting.
</Notice>

### PortalProvider

<PropsTable
  data={[
    {
      name: 'shouldAddRootHost',
      type: 'boolean',
      required: false,
      description: `Defines whether to add a default root host or not.`,
    },
  ]}
/>

## Examples

### Inside native modals

If you're using native modals (maybe from react-navigation), you'll notice the
Dialogs won't show up inside the modal. To get around this, you should wrap your
screen inside `PortalProvider`, like so:

```tsx
import { PortalProvider } from 'tamagui'

// this component used in react-navigation/expo-router with `presentation: "modal"`
export function Page() {
  return (
    <PortalProvider>
      {/* rest of your page, including the Dialog... */}
    </PortalProvider>
  )
}
```


## components/dialog/2.0.0

---
title: Dialog
description: Show a modal with configurable layout and accessible actions.
name: dialog
component: Dialog
package: dialog
demoName: Dialog
---

<HeroContainer showAnimationDriverControl>
  <DialogDemo />
</HeroContainer>

```tsx hero template=Dialog

```

<Highlights
  features={[
    `Comes with styling, yet completely customizable and themeable.`,
    `Accepts animations, themes, size props and more.`,
    `Accessible with dev-time checks to ensure ARIA props.`,
  ]}
/>

Dialog is a great way to show content inside a new floating window above
content. Be sure to open the code example above for a copy-paste implementation.

## Installation

Dialog is already installed in `tamagui`, or you can install it independently:

```bash
npm install @tamagui/dialog
```

If you aren't using `tamagui` and instead using the `@tamagui/dialog` package
separately, you'll first need to install the `@tamagui/portal` package:

```bash
npm install @tamagui/portal
```

Then add `PortalProvider` to the root of your app:

```tsx fileName="App.tsx"
import { PortalProvider } from '@tamagui/portal'
import YourApp from './components/YourApp'

function App() {
  return (
    <PortalProvider shouldAddRootHost>
      <YourApp />
    </PortalProvider>
  )
}

export default App
```

<Notice theme="blue">
  For native apps, we recommend [setting up native portals](/docs/components/portal#native-portal-setup-recommended) to preserve React context inside Dialog content.
</Notice>

## Anatomy

```tsx
import { Dialog } from 'tamagui' // or '@tamagui/dialog'

export default () => (
  <Dialog>
    <Dialog.Trigger />
    <Dialog.Portal>
      <Dialog.Overlay />
      <Dialog.Content>
        <Dialog.Title />
        <Dialog.Description />
        <Dialog.Close />
        {/* ... */}
      </Dialog.Content>
    </Dialog.Portal>

    {/* Optional: Control focus behavior */}
    <Dialog.FocusScope loop trapped focusOnIdle={true}>
      <Dialog.FocusScope.Scope>
        {/* Focus scope will be applied to children */}
      </Dialog.FocusScope.Scope>
    </Dialog.FocusScope>
  </Dialog>
)
```

## Scoping

Dialog supports scoping which lets you mount one or more Dialog instances at the
root of your app, while having a deeply nested child Trigger or Content attach
to the proper parent Dialog instance.

In performance sensitive areas you may want to take advantage of this, it allows
you to only need to render the Dialog.Trigger inside the sensitive area as
Dialogs aren't the cheapest component - they have a lot of functionality.

Here's the basic anatomy of using `scope` and placing your Dialog higher up for
performance:

```tsx fileName=_layout.tsx
import { Dialog } from 'tamagui'

// in your root layout:
export default ({ children }) => (
  <Dialog scope="user-profile">
    <Dialog.Portal>
      <Dialog.Overlay />
      <Dialog.Content>
        <Dialog.Title />
        <Dialog.Description />
        <Dialog.Close />
        {/* ... */}
      </Dialog.Content>
    </Dialog.Portal>

    {/* the rest of your app, note that it's inside of Dialog */}
    {children}
  </Dialog>
)
```

```tsx fileName=UserProfile.tsx
export default () => (
  <Dialog.Trigger scope="user-profile">
    <Button>Open Profile</Button>
  </Dialog.Trigger>
)
```

Note that the `Trigger` scope ties to the `Dialog` scope.

## Dismissal Behavior

By default, dialogs can be dismissed by:
- Clicking outside the dialog content (on the overlay)
- Pressing the Escape key
- Clicking a Dialog.Close element

### Modal vs Non-Modal Dialogs

- **Modal dialogs** (`modal={true}`, which is the default):
  - In v1, have `disableOutsidePointerEvents` set to `true` by default
  - Still dismiss on outside click, but prevent interaction with elements behind the dialog
  - Prevent right-click dismissal (right-clicks on the overlay are ignored)
  
- **Non-modal dialogs** (`modal={false}`):
  - Allow interaction with elements behind the dialog
  - Dismiss on any outside click
  - Do not trap focus

### Preventing Outside Dismissal

To prevent a dialog from closing when clicking outside:

```tsx
<Dialog.Content
  onPointerDownOutside={(event) => {
    event.preventDefault()
  }}
>
  {/* Dialog contents */}
</Dialog.Content>
```

## API Reference

### Dialog

Contains every component for the dialog. Beyond
[Tamagui Props](/docs/intro/props), adds:

<PropsTable
  data={[
    {
      name: 'children',
      type: 'React.ReactNode',
      required: true,
      description: `Must contain Dialog.Content`,
    },
    {
      name: 'size',
      type: 'SizeTokens',
      description: `Passes size down to all sub-components when set for padding, arrow, borderRadius.`,
    },
    {
      name: 'open',
      type: 'boolean',
      description: ``,
    },
    {
      name: 'defaultOpen',
      type: 'boolean',
    },
    {
      name: 'onOpenChange',
      type: '(open: boolean) => void',
    },
    {
      name: 'modal',
      type: 'boolean',
      default: 'true',
      description: `Renders into root of app instead of inline`,
    },
    {
      name: 'disableRemoveScroll',
      type: 'boolean',
      required: false,
      description: `Used to disable the automatic removal of scrolling from the page when open.`,
    },
  ]}
/>

### Dialog.Trigger

Just [Tamagui Props](/docs/intro/props).

### Dialog.Portal

Renders Dialog into appropriate container. Beyond
[Tamagui Props](/docs/intro/props), adds:

<PropsTable
  data={[
    {
      name: 'forceMount',
      type: 'boolean',
      required: false,
      description: `Used to force mounting when more control is needed. Useful when controlling animation with React animation libraries.`,
    },
    {
      name: 'unstyled',
      required: false,
      type: `boolean`,
      description: `Removes all default Tamagui styles.`,
    },
  ]}
/>

### Dialog.Content

Main container for Dialog content, this is where you should apply animations.

Beyond [Tamagui Props](/docs/intro/props), adds:

<PropsTable
  data={[
    {
      name: 'forceMount',
      type: 'boolean',
      required: false,
      description: `Used to force mounting when more control is needed. Useful when controlling animation with React animation libraries.`,
    },
    {
      name: 'unstyled',
      required: false,
      type: `boolean`,
      description: `Removes all default Tamagui styles.`,
    },
    {
      name: 'disableOutsidePointerEvents',
      type: 'boolean',
      required: false,
      description: `When true, hover/focus/click interactions will be disabled on elements outside the Dialog. Users will need to click twice on outside elements to interact with them: once to close the Dialog, and again to trigger the element. Note: In v1, modal dialogs have this set to true by default.`,
    },
  ]}
/>

### Dialog.Overlay

Displays behind Content. Beyond [Tamagui Props](/docs/intro/props), adds:

<PropsTable
  data={[
    {
      name: 'forceMount',
      type: 'boolean',
      required: false,
      description: `Used to force mounting when more control is needed. Useful when controlling animation with React animation libraries.`,
    },
  ]}
/>

### Dialog.Title

Required. Can wrap in VisuallyHidden to hide.

Defaults to H2, see [Headings](/docs/components/headings).

### Dialog.Description

Required. Can wrap in VisuallyHidden to hide.

Defaults to Paragraph, see [Paragraph](/docs/components/text).

### Dialog.Close

Closes the Dialog, accepts the same props as YStack. Recommended to use with
your own component and `asChild`.

<PropsTable
  data={[
    {
      name: 'displayWhenAdapted',
      type: 'boolean',
      description: `By default Close elements hide when Adapt is active. If set to true, they will show when adapted.`,
    },
  ]}
/>

Just [Tamagui Props](/docs/intro/props).

### Dialog.FocusScope

Provides access to the underlying FocusScope component used by Dialog for focus
management. Can be used to control focus behavior from a parent component.

<PropsTable
  data={[
    {
      name: 'enabled',
      type: 'boolean',
      default: 'true',
      description: `Whether focus management is enabled`,
    },
    {
      name: 'loop',
      type: 'boolean',
      default: 'false',
      description: `When true, tabbing from last item will focus first tabbable and shift+tab from first item will focus last tabbable`,
    },
    {
      name: 'trapped',
      type: 'boolean',
      default: 'false',
      description: `When true, focus cannot escape the focus scope via keyboard, pointer, or programmatic focus`,
    },
    {
      name: 'focusOnIdle',
      type: 'boolean | number',
      default: 'false',
      description: `When true, waits for idle before focusing. When a number, waits that many ms. This prevents reflows during animations`,
    },
    {
      name: 'onMountAutoFocus',
      type: '(event: Event) => void',
      description: `Event handler called when auto-focusing on mount. Can be prevented`,
    },
    {
      name: 'onUnmountAutoFocus',
      type: '(event: Event) => void',
      description: `Event handler called when auto-focusing on unmount. Can be prevented`,
    },
  ]}
/>

### Dialog.Sheet

When used with `Adapt`, Dialog will render as a sheet when that breakpoint is
active.

See [Sheet](/docs/components/sheet) for more props.

Must use `Adapt.Contents` inside the `Dialog.Sheet.Frame` to insert the contents
given to `Dialog.Content`

```tsx
import { Dialog } from 'tamagui' // or '@tamagui/dialog'

export default () => (
  <Dialog>
    <Dialog.Trigger />

    <Dialog.Portal>
      <Dialog.Overlay />
      <Dialog.Content>
        <Dialog.Title />
        <Dialog.Description />
        <Dialog.Close />
        {/* ... */}
      </Dialog.Content>
    </Dialog.Portal>

    {/* optionally change to sheet when small screen */}
    <Dialog.Adapt when="maxMd">
      <Dialog.Sheet>
        <Dialog.Sheet.Frame>
          <Dialog.Adapt.Contents />
        </Dialog.Sheet.Frame>
        <Dialog.Sheet.Overlay />
      </Dialog.Sheet>
    </Dialog.Adapt>
  </Dialog>
)
```

<Notice>
  Note that Dialog.Sheet currently doesn't preserve state of the contents when
  it transitions between Sheet and Portal. In the future, we can do this on the
  web using react-reparenting.
</Notice>

### PortalProvider

<PropsTable
  data={[
    {
      name: 'shouldAddRootHost',
      type: 'boolean',
      required: false,
      description: `Defines whether to add a default root host or not.`,
    },
  ]}
/>

## Examples

### Inside native modals

If you're using native modals (maybe from react-navigation), you'll notice the
Dialogs won't show up inside the modal. To get around this, you should wrap your
screen inside `PortalProvider`, like so:

```tsx
import { PortalProvider } from 'tamagui'

// this component used in react-navigation/expo-router with `presentation: "modal"`
export function Page() {
  return (
    <PortalProvider>
      {/* rest of your page, including the Dialog... */}
    </PortalProvider>
  )
}
```


## components/focus-scope/1.128.0

---
title: FocusScope
description: Manage focus behavior within elements accessibly.
name: focus-scope
component: FocusScope
package: focus-scope
---

<IntroParagraph marginTop={-5} marginBottom={30}>
  A utility component for managing keyboard focus within a container. Controls
  focus trapping, auto-focus behavior, and focus cycling for accessible
  interactive components.
</IntroParagraph>

Note that this is a web-only component, on native it is a no-op.

<Highlights
  features={[
    'Trap focus within a container for modal-like behavior.',
    'Auto-focus on mount and return focus on unmount.',
    'Loop focus between first and last tabbable elements.',
    'Prevent reflows during animations with focusOnIdle.',
  ]}
/>

## Installation

FocusScope is already installed in `tamagui`, or you can install it
independently:

```bash
npm install @tamagui/focus-scope
```

## Usage

Wrap any content that needs focus management:

```tsx
import { Button, FocusScope, XStack } from 'tamagui'

export default () => (
  <FocusScope loop trapped>
    <XStack space="$4">
      <Button>First</Button>
      <Button>Second</Button>
      <Button>Third</Button>
    </XStack>
  </FocusScope>
)
```

## Focus Trapping

Use `trapped` to prevent focus from escaping the scope:

```tsx
import { Button, Dialog, FocusScope, XStack, YStack } from 'tamagui'

export default () => (
  <Dialog>
    <Dialog.Trigger asChild>
      <Button>Open Dialog</Button>
    </Dialog.Trigger>

    <Dialog.Portal>
      <Dialog.Overlay />
      {/* key used by AnimatePresence to animate */}
      <Dialog.Content key="content">
        <FocusScope trapped>
          <YStack space="$4">
            <Dialog.Title>Focused Content</Dialog.Title>
            <XStack space="$2">
              <Button>Cancel</Button>
              <Button>Confirm</Button>
            </XStack>
          </YStack>
        </FocusScope>
      </Dialog.Content>
    </Dialog.Portal>
  </Dialog>
)
```

## Focus Looping

Enable `loop` to cycle focus between first and last elements:

```tsx
import { Button, FocusScope, XStack } from 'tamagui'

export default () => (
  <FocusScope loop>
    <XStack space="$4">
      <Button>First</Button>
      <Button>Second</Button>
      <Button>Last</Button>
      {/* Tab from "Last" goes to "First" */}
    </XStack>
  </FocusScope>
)
```

## Animation-Friendly Focusing

Use `focusOnIdle` to prevent reflows during animations:

```tsx
import { Button, FocusScope, XStack } from 'tamagui'

export default () => (
  <FocusScope
    focusOnIdle={true} // Wait for idle callback
    // or focusOnIdle={200} // Wait 200ms
  >
    <XStack space="$4">
      <Button>Animated</Button>
      <Button>Content</Button>
    </XStack>
  </FocusScope>
)
```

## Advanced Control with FocusScopeController

Use the controller pattern for managing focus from parent components:

```tsx
import { Button, FocusScope, XStack, YStack } from 'tamagui'
import { useState } from 'react'

export default () => {
  const [trapped, setTrapped] = useState(false)

  return (
    <YStack space="$4">
      <Button onPress={() => setTrapped(!trapped)}>
        {trapped ? 'Disable' : 'Enable'} Focus Trap
      </Button>

      <FocusScope.Controller trapped={trapped} loop>
        <FocusScope>
          <XStack space="$4">
            <Button>Controlled</Button>
            <Button>Focus</Button>
            <Button>Behavior</Button>
          </XStack>
        </FocusScope>
      </FocusScope.Controller>
    </YStack>
  )
}
```

## Function as Children

For advanced use cases, pass a function to get access to focus props:

```tsx
import { FocusScope, View } from 'tamagui'

export default () => (
  <FocusScope loop>
    {({ onKeyDown, tabIndex, ref }) => (
      <View
        ref={ref}
        tabIndex={tabIndex}
        onKeyDown={onKeyDown}
        padding="$4"
        borderWidth={1}
        borderColor="$borderColor"
      >
        Custom focus container
      </View>
    )}
  </FocusScope>
)
```

## API Reference

### FocusScope

<PropsTable
  data={[
    {
      name: 'enabled',
      type: 'boolean',
      default: 'true',
      description: 'Whether focus management is enabled',
    },
    {
      name: 'loop',
      type: 'boolean',
      default: 'false',
      description:
        'When true, tabbing from last item will focus first tabbable and shift+tab from first item will focus last tabbable',
    },
    {
      name: 'trapped',
      type: 'boolean',
      default: 'false',
      description:
        'When true, focus cannot escape the focus scope via keyboard, pointer, or programmatic focus',
    },
    {
      name: 'focusOnIdle',
      type: 'boolean | number | { min?: number; max?: number }',
      default: 'false',
      description:
        'When true, waits for idle before focusing using requestIdleCallback. When a number, waits that many ms. Object sets a lower and upper bound. Helps to prevent reflows during animations, as focusing inputs easily blocks main thread.',
    },
    {
      name: 'onMountAutoFocus',
      type: '(event: Event) => void',
      description:
        'Event handler called when auto-focusing on mount. Can be prevented',
    },
    {
      name: 'onUnmountAutoFocus',
      type: '(event: Event) => void',
      description:
        'Event handler called when auto-focusing on unmount. Can be prevented',
    },
    {
      name: 'forceUnmount',
      type: 'boolean',
      default: 'false',
      description:
        'If unmount is animated, you want to force re-focus at start of animation not after',
    },
    {
      name: 'children',
      type: 'React.ReactNode | ((props: FocusProps) => React.ReactNode)',
      description:
        'Content to apply focus management to, or function that receives focus props',
    },
  ]}
/>

### FocusScope.Controller

Provides context-based control over FocusScope behavior:

<PropsTable
  data={[
    {
      name: 'enabled',
      type: 'boolean',
      description:
        'Override enabled state for all child FocusScope.Controller.Scope components',
    },
    {
      name: 'loop',
      type: 'boolean',
      description:
        'Override loop state for all child FocusScope.Controller.Scope components',
    },
    {
      name: 'trapped',
      type: 'boolean',
      description:
        'Override trapped state for all child FocusScope.Controller.Scope components',
    },
    {
      name: 'focusOnIdle',
      type: 'boolean | number',
      description:
        'Override focusOnIdle behavior for all child FocusScope.Controller.Scope components',
    },
    {
      name: 'onMountAutoFocus',
      type: '(event: Event) => void',
      description:
        'Override onMountAutoFocus handler for all child FocusScope.Controller.Scope components',
    },
    {
      name: 'onUnmountAutoFocus',
      type: '(event: Event) => void',
      description:
        'Override onUnmountAutoFocus handler for all child FocusScope.Controller.Scope components',
    },
    {
      name: 'forceUnmount',
      type: 'boolean',
      description:
        'Override forceUnmount behavior for all child FocusScope.Controller.Scope components',
    },
  ]}
/>

The FocusScope component automatically inherits props from the nearest
FocusScope.Controller, with controller props taking precedence over direct
props.

## Usage in Other Components

Many Tamagui components export FocusScope for advanced focus control:

```tsx
import { Dialog, Popover, Select } from 'tamagui'

// Available on:
<Dialog.FocusScope />
<Popover.FocusScope />
<Select.FocusScope />
// And more...
```

## Accessibility

FocusScope follows accessibility best practices:

- Manages `tabindex` appropriately for focus flow
- Respects user's reduced motion preferences
- Maintains focus visible indicators
- Provides proper ARIA support when used with other components
- Handles edge cases like disabled elements and hidden content

<Notice>
  FocusScope is primarily designed for web platforms. On React Native, it
  renders children without focus management since native platforms handle focus
  differently.
</Notice>

## Examples

### Modal Focus Management

```tsx
import { Button, Dialog, FocusScope, Input, XStack, YStack } from 'tamagui'

export default () => (
  <Dialog>
    <Dialog.Trigger asChild>
      <Button>Open Modal</Button>
    </Dialog.Trigger>

    <Dialog.Portal>
      <Dialog.Overlay />
      <Dialog.Content>
        <FocusScope trapped loop focusOnIdle={100}>
          <YStack space="$4" padding="$4">
            <Dialog.Title>User Details</Dialog.Title>
            <Input placeholder="Name" />
            <Input placeholder="Email" />
            <XStack space="$2">
              <Dialog.Close asChild>
                <Button variant="outlined">Cancel</Button>
              </Dialog.Close>
              <Button>Save</Button>
            </XStack>
          </YStack>
        </FocusScope>
      </Dialog.Content>
    </Dialog.Portal>
  </Dialog>
)
```

### Custom Focus Container

```tsx
import { Button, FocusScope, styled, XStack } from 'tamagui'

const FocusContainer = styled(XStack, {
  borderWidth: 2,
  borderColor: 'transparent',
  borderRadius: '$4',
  padding: '$4',

  variants: {
    focused: {
      true: {
        borderColor: '$blue10',
        shadowColor: '$blue10',
        shadowRadius: 10,
        shadowOpacity: 0.3,
      },
    },
  },
})

export default () => (
  <FocusScope loop>
    {({ onKeyDown, tabIndex, ref }) => (
      <FocusContainer
        ref={ref}
        tabIndex={tabIndex}
        onKeyDown={onKeyDown}
        space="$4"
        focused
      >
        <Button>Action 1</Button>
        <Button>Action 2</Button>
        <Button>Action 3</Button>
      </FocusContainer>
    )}
  </FocusScope>
)
```


## components/form/1.3.0

---
title: Form
description: A simple form component for native and web.
name: form
component: Form
package: form
demoName: Form
---

<HeroContainer>
  <FormsDemo />
</HeroContainer>

```tsx hero template=Forms

```

<Highlights
  features={[
    `Works on native and web.`,
    `Outputs accessible forms.`,
    `Works with every Tamagui prop.`,
  ]}
/>

## Installation

Form is already installed in `tamagui`, or you can install it independently:

```bash
npm install @tamagui/form
```

## Anatomy

```tsx
import { Form } from 'tamagui' // or '@tamagui/form'

export default () => (
  <Form>
    {/* ... */}
    <Form.Trigger asChild>
      <Button />
    </Form.Trigger>
  </Form>
)
```

## API Reference

### Form

<PropsTable
  data={[
    {
      name: 'onSubmit',
      type: '() => void',
      required: true,
      description: `Must use Form.Trigger to ensure onSubmit will callback.`,
    },
  ]}
/>

### Form.Trigger

Wrap this around your submitting element to make the form submit. We recommend using `asChild` to a child element of your choosing for more control.

Accepts [Tamagui Props](/docs/intro/props).


## components/form/2.0.0

---
title: Form
description: A simple form component for native and web.
name: form
component: Form
package: form
demoName: Form
---

<HeroContainer>
  <FormsDemo />
</HeroContainer>

```tsx hero template=Forms

```

<Highlights
  features={[
    `Works on native and web.`,
    `Outputs accessible forms.`,
    `Works with every Tamagui prop.`,
  ]}
/>

## Installation

Form is already installed in `tamagui`, or you can install it independently:

```bash
npm install @tamagui/form
```

## Anatomy

```tsx
import { Form } from 'tamagui' // or '@tamagui/form'

export default () => (
  <Form>
    {/* ... */}
    <Form.Trigger asChild>
      <Button />
    </Form.Trigger>
  </Form>
)
```

## API Reference

### Form

<PropsTable
  data={[
    {
      name: 'onSubmit',
      type: '() => void',
      required: true,
      description: `Must use Form.Trigger to ensure onSubmit will callback.`,
    },
  ]}
/>

### Form.Trigger

Wrap this around your submitting element to make the form submit. We recommend using `asChild` to a child element of your choosing for more control.

Accepts [Tamagui Props](/docs/intro/props).


## components/group/1.0.0

---
title: Group
description: Render horizontal or vertical groups easily.
name: group
component: Group
demoName: Group
---

# Group

<Description>Layout buttons and more with groups.</Description>

<HeroContainer>
  <GroupDemo />
</HeroContainer>

```tsx hero template=Group

```

<Highlights
  features={[
    'Accepts size prop that works on all styles.',
    'Align vertically or horizontally.',
    'Natural spacing and disabled props.',
    'Use with or without Item for more control.',
  ]}
/>

## Usage

Instead of a generic `Group`, Tamagui prefers `XGroup` and `YGroup` for consistency with stacks.

By default, Groups will control the border radius of their children automatically - the first and last children will get their start/end radius set to match group radius. If it's a `YGroup`, it will adjust top/bottom radius. `XGroup` adjusts the left/right radius.

You can use Groups with or without `Group.Item`, depending on if you want to spacing and separators to be handled based on direct children, or on each Item rendered.

```tsx
import { Button, XGroup } from 'tamagui'

// usage with Item:
export default () => (
  <XGroup>
    <XGroup.Item>
      <Button>First</Button>
    </XGroup.Item>
    <XGroup.Item>
      <Button>Second</Button>
    </XGroup.Item>
    <XGroup.Item>
      <Button>Third</Button>
    </XGroup.Item>
  </XGroup>
)
```

For a simpler use case and backwards compat, you can also use it without `Group.Item`, which will just apply borders and spacing based on direct children. Note that Group will detect if any `Group.Item` is inside it, and automatically switch modes. If no Item, it spaces direct children:

```tsx
import { Button, XGroup } from 'tamagui'

// usage without Item:
export default () => (
  <XGroup>
    <Button>First</Button>
    <Button>Second</Button>
    <Button>Third</Button>
  </XGroup>
)
```

## Sizing

The `size` property will use your tokens to grab the appropriate radius for borderRadius values which it will pass to the first and last child as style props for borderRadius.

```tsx
import { Button, XGroup } from 'tamagui'

export default () => (
  <XGroup size="$6">
    <XGroup.Item>
      <Button>First</Button>
    </XGroup.Item>
    <XGroup.Item>
      <Button>Second</Button>
    </XGroup.Item>
    <XGroup.Item>
      <Button>Third</Button>
    </XGroup.Item>
  </XGroup>
)
```

## Disabled

The `disabled` property will pass to children

<HeroContainer>
  <GroupDisabledDemo />
</HeroContainer>

## Group API

### Group

`XGroup` and `YGroup` extend [YStack](/docs/components/stacks), getting [Tamagui standard props](/docs/intro/props), plus:

<PropsTable
  data={[
    {
      name: 'size',
      required: false,
      type: 'string | SizeTokens',
      description: `Set a size, number or one of the size token values.`,
    },
    {
      name: 'disabled',
      required: false,
      type: 'boolean',
      description: `Pass disabled down to children.`,
    },
    {
      name: 'disablePassBorderRadius',
      required: false,
      type: 'boolean',
      description: `Disables passing border radius to first/last children.`,
    },
    {
      name: 'vertical',
      required: false,
      type: 'boolean',
      description: `Can be used to force an XGroup to render like a YGroup or vice-versa.`,
    },
    {
      name: 'forceUseItem',
      required: false,
      type: 'boolean',
      desctiption: `Can be used to force the group to assume it has Item components as children.`,
    },
  ]}
/>

### Group.Item

Wrap each of `XGroup` or `YGroup`'s children in one of these. It lets Tamagui apply the needed styles to them.

Accepts only a `children` prop.


## components/group/1.11.2

---
title: Group
description: Render horizontal or vertical groups easily.
name: group
component: Group
demoName: Group
---

# Group

<Description>Layout buttons and more with groups</Description>

<HeroContainer>
  <GroupDemo />
</HeroContainer>

```tsx hero template=Group

```

<Highlights
  features={[
    'Accepts size prop that works on all styles.',
    'Align vertically or horizontally.',
    'Natural spacing and disabled props.',
    'Use with or without Item for more control.',
  ]}
/>

## Usage

You can use `Group` by itself with the `orientation` property determining the direction it assumes.

By default, Groups will control the border radius of their children automatically - the first and last children will get their start/end radius set to match group radius. If it's a `YGroup`, it will adjust top/bottom radius. `XGroup` adjusts the left/right radius.

You can use Groups with or without `Group.Item`, depending on if you want to spacing and separators to be handled based on direct children, or on each Item rendered.

```tsx
import { Button, XGroup } from 'tamagui'

// usage with Item:
export default () => (
  <XGroup>
    <XGroup.Item>
      <Button>First</Button>
    </XGroup.Item>
    <XGroup.Item>
      <Button>Second</Button>
    </XGroup.Item>
    <XGroup.Item>
      <Button>Third</Button>
    </XGroup.Item>
  </XGroup>
)
```

For a simpler use case and backwards compat, you can also use it without `Group.Item`, which will just apply borders and spacing based on direct children. Note that Group will detect if any `Group.Item` is inside it, and automatically switch modes. If no Item, it spaces direct children:

```tsx
import { Button, XGroup } from 'tamagui'

// usage without Item:
export default () => (
  <XGroup>
    <Button>First</Button>
    <Button>Second</Button>
    <Button>Third</Button>
  </XGroup>
)
```

## Sizing

The `size` property will use your tokens to grab the appropriate radius for borderRadius values which it will pass to the first and last child as style props for borderRadius.

```tsx
import { Button, XGroup } from 'tamagui'

export default () => (
  <XGroup size="$6">
    <XGroup.Item>
      <Button>First</Button>
    </XGroup.Item>
    <XGroup.Item>
      <Button>Second</Button>
    </XGroup.Item>
    <XGroup.Item>
      <Button>Third</Button>
    </XGroup.Item>
  </XGroup>
)
```

## Disabled

The `disabled` property will pass to children

<HeroContainer>
  <GroupDisabledDemo />
</HeroContainer>

## API Reference

### Group

`Group`, `XGroup` and `YGroup` extend [YStack](/docs/components/stacks), getting [Tamagui standard props](/docs/intro/props), plus:

<PropsTable
  data={[
    {
      name: 'orientation',
      required: false,
      type: '"horizontal" | "vertical"',
      description: `Forces applying the border radius styles to left/right vs top/bottom. Defaults to horizontal for XGroup and vertical for YGroup.`,
    },
    {
      name: 'size',
      required: false,
      type: 'string | SizeTokens',
      description: `Set a size, number or one of the size token values.`,
    },
    {
      name: 'disabled',
      required: false,
      type: 'boolean',
      description: `Pass disabled down to children.`,
    },
    {
      name: 'disablePassBorderRadius',
      required: false,
      type: `boolean | 'bottom' | 'top' | 'start' | 'end'`,
      description: `Disables passing border radius to first/last children.`,
    },
    {
      name: 'forceUseItem',
      required: false,
      type: 'boolean',
      desctiption: `Can be used to force the group to assume it has Item components as children.`,
    },
    {
      name: 'unstyled',
      required: false,
      type: `boolean`,
      description: `Removes all default Tamagui styles.`,
    },
  ]}
/>

### Group.Item

Wrap each of `XGroup` or `YGroup`'s children in one of these. It lets Tamagui apply the needed styles to them.

Accepts only a `children` prop.


## components/group/1.56.1

---
title: Group
description: Render horizontal or vertical groups easily.
name: group
component: Group
demoName: Group
---

<HeroContainer>
  <GroupDemo />
</HeroContainer>

```tsx hero template=Group

```

<Highlights
  features={[
    'Accepts size prop that works on all styles.',
    'Align vertically or horizontally.',
    'Natural spacing and disabled props.',
    'Use with or without Item for more control.',
  ]}
/>

## Installation

Group is already installed in `tamagui`, or you can install it independently:

```bash
npm install @tamagui/group
```

## Usage

You can use `Group` by itself with the `orientation` property determining the direction it assumes.

By default, Groups will control the border radius of their children automatically - the first and last children will get their start/end radius set to match group radius. If it's a `YGroup`, it will adjust top/bottom radius. `XGroup` adjusts the left/right radius.

You can use Groups with or without `Group.Item`, depending on if you want to spacing and separators to be handled based on direct children, or on each Item rendered.

```tsx
import { Button, XGroup } from 'tamagui'

// usage with Item:
export default () => (
  <XGroup>
    <XGroup.Item>
      <Button>First</Button>
    </XGroup.Item>
    <XGroup.Item>
      <Button>Second</Button>
    </XGroup.Item>
    <XGroup.Item>
      <Button>Third</Button>
    </XGroup.Item>
  </XGroup>
)
```

For a simpler use case and backwards compat, you can also use it without `Group.Item`, which will just apply borders and spacing based on direct children. Note that Group will detect if any `Group.Item` is inside it, and automatically switch modes. If no Item, it spaces direct children:

```tsx
import { Button, XGroup } from 'tamagui'

// usage without Item:
export default () => (
  <XGroup>
    <Button>First</Button>
    <Button>Second</Button>
    <Button>Third</Button>
  </XGroup>
)
```

## Sizing

The `size` property will use your tokens to grab the appropriate radius for borderRadius values which it will pass to the first and last child as style props for borderRadius.

```tsx
import { Button, XGroup } from 'tamagui'

export default () => (
  <XGroup size="$6">
    <XGroup.Item>
      <Button>First</Button>
    </XGroup.Item>
    <XGroup.Item>
      <Button>Second</Button>
    </XGroup.Item>
    <XGroup.Item>
      <Button>Third</Button>
    </XGroup.Item>
  </XGroup>
)
```

## Disabled

The `disabled` property will pass to children

<HeroContainer>
  <GroupDisabledDemo />
</HeroContainer>

## API Reference

### Group

`Group`, `XGroup` and `YGroup` extend [YStack](/docs/components/stacks), getting [Tamagui standard props](/docs/intro/props), plus:

<PropsTable
  data={[
    {
      name: 'orientation',
      required: false,
      type: '"horizontal" | "vertical"',
      description: `Forces applying the border radius styles to left/right vs top/bottom. Defaults to horizontal for XGroup and vertical for YGroup.`,
    },
    {
      name: 'size',
      required: false,
      type: 'string | SizeTokens',
      description: `Set a size, number or one of the size token values.`,
    },
    {
      name: 'disabled',
      required: false,
      type: 'boolean',
      description: `Pass disabled down to children.`,
    },
    {
      name: 'disablePassBorderRadius',
      required: false,
      type: `boolean | 'bottom' | 'top' | 'start' | 'end'`,
      description: `Disables passing border radius to first/last children.`,
    },
    {
      name: 'forceUseItem',
      required: false,
      type: 'boolean',
      description: `Can be used to force the group to assume it has Item components as children.`,
    },
    {
      name: 'unstyled',
      required: false,
      type: `boolean`,
      description: `Removes all default Tamagui styles.`,
    },
  ]}
/>

### Group.Item

Wrap each of `XGroup` or `YGroup`'s children in one of these. It lets Tamagui apply the needed styles to them. It accepts the following props:

<PropsTable
  data={[
    {
      name: 'children',
      required: true,
      type: 'ReactNode',
    },
    {
      name: 'forcePlacement',
      required: false,
      type: '"first" | "center" | "last"',
      description: `Forces the item to be a starting, center or ending item and gets the respective styles`,
    },
  ]}
/>


## components/group/1.6.0

---
title: Group
description: Render horizontal or vertical groups easily.
name: group
component: Group
demoName: Group
---

# Group

<Description>Layout buttons and more with groups.</Description>

<HeroContainer>
  <GroupDemo />
</HeroContainer>

```tsx hero template=Group

```

<Highlights
  features={[
    'Accepts size prop that works on all styles.',
    'Align vertically or horizontally.',
    'Natural spacing and disabled props.',
    'Use with or without Item for more control.',
  ]}
/>

## Usage

You can use `Group` by itself with the `axis` property determining the direction it assumes.

By default, Groups will control the border radius of their children automatically - the first and last children will get their start/end radius set to match group radius. If it's a `YGroup`, it will adjust top/bottom radius. `XGroup` adjusts the left/right radius.

You can use Groups with or without `Group.Item`, depending on if you want to spacing and separators to be handled based on direct children, or on each Item rendered.

```tsx
import { Button, XGroup } from 'tamagui'

// usage with Item:
export default () => (
  <XGroup>
    <XGroup.Item>
      <Button>First</Button>
    </XGroup.Item>
    <XGroup.Item>
      <Button>Second</Button>
    </XGroup.Item>
    <XGroup.Item>
      <Button>Third</Button>
    </XGroup.Item>
  </XGroup>
)
```

For a simpler use case and backwards compat, you can also use it without `Group.Item`, which will just apply borders and spacing based on direct children. Note that Group will detect if any `Group.Item` is inside it, and automatically switch modes. If no Item, it spaces direct children:

```tsx
import { Button, XGroup } from 'tamagui'

// usage without Item:
export default () => (
  <XGroup>
    <Button>First</Button>
    <Button>Second</Button>
    <Button>Third</Button>
  </XGroup>
)
```

## Sizing

The `size` property will use your tokens to grab the appropriate radius for borderRadius values which it will pass to the first and last child as style props for borderRadius.

```tsx
import { Button, XGroup } from 'tamagui'

export default () => (
  <XGroup size="$6">
    <XGroup.Item>
      <Button>First</Button>
    </XGroup.Item>
    <XGroup.Item>
      <Button>Second</Button>
    </XGroup.Item>
    <XGroup.Item>
      <Button>Third</Button>
    </XGroup.Item>
  </XGroup>
)
```

## Disabled

The `disabled` property will pass to children

<HeroContainer>
  <GroupDisabledDemo />
</HeroContainer>

## Group API

### Group

`Group`, `XGroup`and`YGroup` extend [YStack](/docs/components/stacks), getting [Tamagui standard props](/docs/intro/props), plus:

<PropsTable
  data={[
    {
      name: 'axis',
      required: false,
      type: '"horizontal" | "vertical"',
      description: `Forces applying the border radius styles to left/right vs top/bottom. Defaults to horizontal for XGroup and vertical for YGroup.`,
    },
    {
      name: 'size',
      required: false,
      type: 'string | SizeTokens',
      description: `Set a size, number or one of the size token values.`,
    },
    {
      name: 'disabled',
      required: false,
      type: 'boolean',
      description: `Pass disabled down to children.`,
    },
    {
      name: 'disablePassBorderRadius',
      required: false,
      type: 'boolean',
      description: `Disables passing border radius to first/last children.`,
    },
    {
      name: 'forceUseItem',
      required: false,
      type: 'boolean',
      desctiption: `Can be used to force the group to assume it has Item components as children.`,
    },
  ]}
/>

### Group.Item

Wrap each of `XGroup` or `YGroup`'s children in one of these. It lets Tamagui apply the needed styles to them.

Accepts only a `children` prop.


## components/group/1.7.0

---
title: Group
description: Render horizontal or vertical groups easily.
name: group
component: Group
demoName: Group
---

# Group

<Description>Layout buttons and more with groups.</Description>

<HeroContainer>
  <GroupDemo />
</HeroContainer>

```tsx hero template=Group

```

<Highlights
  features={[
    'Accepts size prop that works on all styles.',
    'Align vertically or horizontally.',
    'Natural spacing and disabled props.',
    'Use with or without Item for more control.',
  ]}
/>

## Usage

You can use `Group` by itself with the `axis` property determining the direction it assumes.

By default, Groups will control the border radius of their children automatically - the first and last children will get their start/end radius set to match group radius. If it's a `YGroup`, it will adjust top/bottom radius. `XGroup` adjusts the left/right radius.

You can use Groups with or without `Group.Item`, depending on if you want to spacing and separators to be handled based on direct children, or on each Item rendered.

```tsx
import { Button, XGroup } from 'tamagui'

// usage with Item:
export default () => (
  <XGroup>
    <XGroup.Item>
      <Button>First</Button>
    </XGroup.Item>
    <XGroup.Item>
      <Button>Second</Button>
    </XGroup.Item>
    <XGroup.Item>
      <Button>Third</Button>
    </XGroup.Item>
  </XGroup>
)
```

For a simpler use case and backwards compat, you can also use it without `Group.Item`, which will just apply borders and spacing based on direct children. Note that Group will detect if any `Group.Item` is inside it, and automatically switch modes. If no Item, it spaces direct children:

```tsx
import { Button, XGroup } from 'tamagui'

// usage without Item:
export default () => (
  <XGroup>
    <Button>First</Button>
    <Button>Second</Button>
    <Button>Third</Button>
  </XGroup>
)
```

## Sizing

The `size` property will use your tokens to grab the appropriate radius for borderRadius values which it will pass to the first and last child as style props for borderRadius.

```tsx
import { Button, XGroup } from 'tamagui'

export default () => (
  <XGroup size="$6">
    <XGroup.Item>
      <Button>First</Button>
    </XGroup.Item>
    <XGroup.Item>
      <Button>Second</Button>
    </XGroup.Item>
    <XGroup.Item>
      <Button>Third</Button>
    </XGroup.Item>
  </XGroup>
)
```

## Disabled

The `disabled` property will pass to children

<HeroContainer>
  <GroupDisabledDemo />
</HeroContainer>

## Group API

### Group

`Group`, `XGroup`and`YGroup` extend [YStack](/docs/components/stacks), getting [Tamagui standard props](/docs/intro/props), plus:

<PropsTable
  data={[
    {
      name: 'axis',
      required: false,
      type: '"horizontal" | "vertical"',
      description: `Forces applying the border radius styles to left/right vs top/bottom. Defaults to horizontal for XGroup and vertical for YGroup.`,
    },
    {
      name: 'size',
      required: false,
      type: 'string | SizeTokens',
      description: `Set a size, number or one of the size token values.`,
    },
    {
      name: 'disabled',
      required: false,
      type: 'boolean',
      description: `Pass disabled down to children.`,
    },
    {
      name: 'disablePassBorderRadius',
      required: false,
      type: `boolean | 'bottom' | 'top' | 'start' | 'end'`,
      description: `Disables passing border radius to first/last children.`,
    },
    {
      name: 'forceUseItem',
      required: false,
      type: 'boolean',
      desctiption: `Can be used to force the group to assume it has Item components as children.`,
    },
  ]}
/>

### Group.Item

Wrap each of `XGroup` or `YGroup`'s children in one of these. It lets Tamagui apply the needed styles to them.

Accepts only a `children` prop.


## components/group/2.0.0

---
title: Group
description: Render horizontal or vertical groups easily.
name: group
component: Group
demoName: Group
---

<HeroContainer>
  <GroupDemo />
</HeroContainer>

```tsx hero template=Group

```

<Highlights
  features={[
    'Accepts size prop for border radius.',
    'Align vertically or horizontally.',
    'Children control their own sizing.',
    'Disabled prop passes to children.',
  ]}
/>

## Installation

Group is already installed in `tamagui`, or you can install it independently:

```bash
npm install @tamagui/group
```

## Usage

Use `Group` with `Group.Item` wrapping each child. The `orientation` property determines the layout direction.

Group zeros out the border radius on connecting sides of children - the first child keeps its start radius, the last keeps its end radius, and middle items have no radius on connecting sides. For `YGroup`, this affects top/bottom radius. For `XGroup`, left/right radius.

```tsx
import { Button, XGroup } from 'tamagui'

export default () => (
  <XGroup>
    <XGroup.Item>
      <Button>First</Button>
    </XGroup.Item>
    <XGroup.Item>
      <Button>Second</Button>
    </XGroup.Item>
    <XGroup.Item>
      <Button>Third</Button>
    </XGroup.Item>
  </XGroup>
)
```

## Sizing

In v2, children control their own sizing. Apply size props directly to the child components. For responsive sizing, use media queries on the children:

```tsx
import { Activity, Airplay } from '@tamagui/lucide-icons'
import { Button, XGroup } from 'tamagui'

export default () => (
  <XGroup>
    <XGroup.Item>
      <Button size="$3" $gtSm={{ size: '$5' }} icon={Activity}>
        First
      </Button>
    </XGroup.Item>
    <XGroup.Item>
      <Button size="$3" $gtSm={{ size: '$5' }} icon={Airplay}>
        Second
      </Button>
    </XGroup.Item>
  </XGroup>
)
```

The `size` prop on Group itself only affects the border radius of the group container:

```tsx
import { Button, XGroup } from 'tamagui'

export default () => (
  <XGroup size="$6">
    <XGroup.Item>
      <Button>First</Button>
    </XGroup.Item>
    <XGroup.Item>
      <Button>Second</Button>
    </XGroup.Item>
    <XGroup.Item>
      <Button>Third</Button>
    </XGroup.Item>
  </XGroup>
)
```

## Separators

Add separators manually between items:

```tsx
import { ListItem, Separator, YGroup } from 'tamagui'

export default () => (
  <YGroup>
    <YGroup.Item>
      <ListItem title="First" />
    </YGroup.Item>
    <Separator />
    <YGroup.Item>
      <ListItem title="Second" />
    </YGroup.Item>
    <Separator />
    <YGroup.Item>
      <ListItem title="Third" />
    </YGroup.Item>
  </YGroup>
)
```

## Disabled

The `disabled` property will pass to children.

<HeroContainer>
  <GroupDisabledDemo />
</HeroContainer>

## Custom Components & Nested Items

Automatic index detection only works when `Group.Item` is a **direct child** of `Group`. If you wrap `Group.Item` inside a custom component, the automatic first/last detection won't work.

You have a few options:

### Option 1: Use `forcePlacement`

If you know the position ahead of time, use the `forcePlacement` prop:

```tsx
function MyFirstItem({ children }) {
  return (
    <XGroup.Item forcePlacement="first">
      {children}
    </XGroup.Item>
  )
}

export default () => (
  <XGroup>
    <MyFirstItem>
      <Button>First</Button>
    </MyFirstItem>
    <XGroup.Item>
      <Button>Second</Button>
    </XGroup.Item>
  </XGroup>
)
```

### Option 2: Use `useGroupItem` hook

For full control, use the `useGroupItem` hook directly in your custom component:

```tsx
import { useGroupItem } from '@tamagui/group'

function MyItem({ children, forcePlacement }) {
  const groupItemProps = useGroupItem(
    { disabled: false },
    forcePlacement
  )

  return React.cloneElement(children, groupItemProps)
}
```

### Option 3: Pass placement from parent

Calculate positions in the parent and pass them down:

```tsx
const items = ['First', 'Second', 'Third']

export default () => (
  <XGroup>
    {items.map((item, i) => (
      <XGroup.Item
        key={item}
        forcePlacement={i === 0 ? 'first' : i === items.length - 1 ? 'last' : 'center'}
      >
        <Button>{item}</Button>
      </XGroup.Item>
    ))}
  </XGroup>
)
```

## API Reference

### Group

`Group`, `XGroup` and `YGroup` extend [YStack](/docs/components/stacks), getting [Tamagui standard props](/docs/intro/props), plus:

<PropsTable
  data={[
    {
      name: 'orientation',
      required: false,
      type: '"horizontal" | "vertical"',
      description: `Forces applying the border radius styles to left/right vs top/bottom. Defaults to horizontal for XGroup and vertical for YGroup.`,
    },
    {
      name: 'size',
      required: false,
      type: 'string | SizeTokens',
      description: `Sets the border radius of the Group container using your size tokens.`,
    },
    {
      name: 'disabled',
      required: false,
      type: 'boolean',
      description: `Pass disabled down to children.`,
    },
    {
      name: 'unstyled',
      required: false,
      type: `boolean`,
      description: `Removes all default Tamagui styles.`,
    },
  ]}
/>

### Group.Item

Wrap each of `XGroup` or `YGroup`'s children in one of these. It lets Tamagui apply the needed styles to them. It accepts the following props:

<PropsTable
  data={[
    {
      name: 'children',
      required: true,
      type: 'ReactNode',
    },
    {
      name: 'forcePlacement',
      required: false,
      type: '"first" | "center" | "last"',
      description: `Forces the item to be a starting, center or ending item and gets the respective styles`,
    },
  ]}
/>


## components/headings/1.0.0

---
title: Headings
description: Heading components that mimic HTML equivalents
name: html
component: Headings
package: text
demoName: Headings
---

<HeroContainer>
  <HeadingsDemo />
</HeroContainer>

```tsx hero template=Headings

```

<Highlights
  features={[
    'Accepts size prop that works on all styles.',
    'Define custom fonts with styles per-size.',
  ]}
/>

## Installation

Headings is already installed in `tamagui`, or you can install it independently:

```bash
npm install @tamagui/text
```

```tsx
import { H1, H2, H3, H4, H5, H6, Heading } from 'tamagui'

export default () => (
  <>
    <H1>Heading 1</H1>
    <H2>Heading 2</H2>
    <H3>Heading 3</H3>
    <H4>Heading 4</H4>
    <H5>Heading 5</H5>
    <H6>Heading 6</H6>
    <Heading>Heading</Heading>
  </>
)
```

The headings all extend from the base `Heading` component. Note, this is just our own theme for Inter headings, but you can change the styles for any font completely.

Tamagui expects for your [font.size](/docs/core/configuration#font-tokens) to have the keys 1-10 so headings work with your font tokens automatically.

## How it works

The `Heading` component is defined as follows:

```tsx
export const Heading = styled(Paragraph, {
  tag: 'span',
  name: 'Heading',
  accessibilityRole: 'header',
  fontFamily: '$heading',
  size: '$8',
  margin: 0,
})
```

Note that Heading, and H1-H6 all default to the `heading` font family that must be defined in your tamagui.config.ts.

Because [Paragraph](/docs/components/text#paragraph) extends [SizableText](/docs/components/text#sizabletext), you get automatic styles based on your font theme. Let's see how `SizableText` defines the size variant, roughly, which gives a good idea of how Tamagui works, and how you could create or change your own headings at a lower level.

```tsx
import { Text } from 'tamagui' // or '@tamagui/core'

const SizableText = styled(Text, {
  name: 'SizableText',
  fontFamily: '$body',
  color: '$color',

  variants: {
    size: {
      '...fontSize': (val, { font, props }) => {
        const fontSize = font.size[val]
        const lineHeight = font.lineHeight[val]
        const fontWeight = font.weight[val]
        const letterSpacing = font.letterSpacing[val]
        const fontStyle = font.style?.[val]
        const textTransform = font.transform?.[val]
        return {
          fontStyle,
          textTransform,
          fontWeight,
          letterSpacing,
          fontSize,
          lineHeight,
        }
      },
    },
  },

  defaultVariants: {
    // note tamagui uses a generic "true" token that your sizes should set to be the same as the default on your scale
    size: '$true',
  },
})
```

## API Reference

### Heading

Headings extend [SizableText props](/docs/components/text#sizabletext) inheriting all the [Tamagui standard props](/docs/intro/props).


## components/headings/2.0.0

---
title: Headings
description: Heading components that mimic HTML equivalents
name: html
component: Headings
package: text
demoName: Headings
---

<HeroContainer>
  <HeadingsDemo />
</HeroContainer>

```tsx hero template=Headings

```

<Highlights
  features={[
    'Accepts size prop that works on all styles.',
    'Define custom fonts with styles per-size.',
  ]}
/>

## Installation

Headings is already installed in `tamagui`, or you can install it independently:

```bash
npm install @tamagui/text
```

```tsx
import { H1, H2, H3, H4, H5, H6, Heading } from 'tamagui'

export default () => (
  <>
    <H1>Heading 1</H1>
    <H2>Heading 2</H2>
    <H3>Heading 3</H3>
    <H4>Heading 4</H4>
    <H5>Heading 5</H5>
    <H6>Heading 6</H6>
    <Heading>Heading</Heading>
  </>
)
```

The headings all extend from the base `Heading` component. Note, this is just our own theme for Inter headings, but you can change the styles for any font completely.

Tamagui expects for your [font.size](/docs/core/configuration#font-tokens) to have the keys 1-10 so headings work with your font tokens automatically.

## How it works

The `Heading` component is defined as follows:

```tsx
export const Heading = styled(Paragraph, {
  tag: 'span',
  name: 'Heading',
  accessibilityRole: 'header',
  fontFamily: '$heading',
  size: '$8',
  margin: 0,
})
```

Note that Heading, and H1-H6 all default to the `heading` font family that must be defined in your tamagui.config.ts.

Because [Paragraph](/docs/components/text#paragraph) extends [SizableText](/docs/components/text#sizabletext), you get automatic styles based on your font theme. Let's see how `SizableText` defines the size variant, roughly, which gives a good idea of how Tamagui works, and how you could create or change your own headings at a lower level.

```tsx
import { Text } from 'tamagui' // or '@tamagui/core'

const SizableText = styled(Text, {
  name: 'SizableText',
  fontFamily: '$body',
  color: '$color',

  variants: {
    size: {
      '...fontSize': (val, { font, props }) => {
        const fontSize = font.size[val]
        const lineHeight = font.lineHeight[val]
        const fontWeight = font.weight[val]
        const letterSpacing = font.letterSpacing[val]
        const fontStyle = font.style?.[val]
        const textTransform = font.transform?.[val]
        return {
          fontStyle,
          textTransform,
          fontWeight,
          letterSpacing,
          fontSize,
          lineHeight,
        }
      },
    },
  },

  defaultVariants: {
    // note tamagui uses a generic "true" token that your sizes should set to be the same as the default on your scale
    size: '$true',
  },
})
```

## API Reference

### Heading

Headings extend [SizableText props](/docs/components/text#sizabletext) inheriting all the [Tamagui standard props](/docs/intro/props).


## components/html-elements/1.0.0

---
title: HTML Elements
description: Render semantic HTML with these elements.
name: html-elements
component: Layouts
---

To assist in creating accessible web apps, the following components are exported, all mapping directly to DOM elements of the lowercase names:

- `Section` (`section`)
- `Article` (`article`)
- `Main` (`main`)
- `Header` (`header`)
- `Aside` (`aside`)
- `Footer` (`footer`)
- `Nav` (`nav`)

## Installation

It's exported by `tamagui`, or:

```bash
npm install @tamagui/elements
```

## HTML element props

All HTML components extend View, inheriting all the [Tamagui standard props](/docs/intro/props).


## components/html-elements/2.0.0

---
title: HTML Elements
description: Render semantic HTML with these elements.
name: html-elements
component: Layouts
---

To assist in creating accessible web apps, the following components are exported, all mapping directly to DOM elements of the lowercase names:

- `Section` (`section`)
- `Article` (`article`)
- `Main` (`main`)
- `Header` (`header`)
- `Aside` (`aside`)
- `Footer` (`footer`)
- `Nav` (`nav`)

## Installation

It's exported by `tamagui`, or:

```bash
npm install @tamagui/elements
```

## HTML element props

All HTML components extend View, inheriting all the [Tamagui standard props](/docs/intro/props).


## components/image/1.0.0

---
title: Image
description: React Native Web Image + Tamagui style props.
name: html
component: Image
package: image
demoName: Image
---

# Image

<Description>React Native Web Image + Tamagui style props.</Description>

<HeroContainer noPad>
  <ImageDemo />
</HeroContainer>

```tsx hero template=Image

```

<Highlights
  features={['Supports SSR.', 'Works on native and web.', 'Accepts Tamagui style props.']}
/>

### Props

[Tamagui props](/docs/intro/props) + [React Native Web Image props](https://necolas.github.io/react-native-web/docs/image/).


## components/image/1.13.0

---
title: Image
description: React Native Web Image + Tamagui style props.
name: html
component: Image
package: image
demoName: Image
---

<HeroContainer noPad>
  <ImageDemo />
</HeroContainer>

```tsx hero template=Image

```

<Highlights
  features={['Supports SSR.', 'Works on native and web.', 'Accepts Tamagui style props.']}
/>

## Installation

Image is already installed in `tamagui`, or you can install it independently:

```bash
npm install @tamagui/image
```

## Usage

Note that you need to set `source` like so - the `width` and `height` properties apply as styles around the image, but the actual image needs `source.width` and `source.height` for React Native:

```tsx
export default () => (
  <Image
    source={{ width: 200, height: 200, uri: 'https://...' }}
    width="100%"
    height="100%"
  />
)
```

## API Reference

### Image

[Tamagui props](/docs/intro/props) + [React Native Web Image props](https://necolas.github.io/react-native-web/docs/image/).


## components/image/2.0.0

---
title: Image
description: A pure, lightweight Image component with Tamagui style props.
name: html
component: Image
package: image
demoName: Image
---

<HeroContainer noPad>
  <ImageDemo />
</HeroContainer>

```tsx hero template=Image

```

<Highlights
  features={[
    'Supports SSR.',
    'Works on native and web.',
    'Accepts Tamagui style props.',
    'Pluggable architecture with createImage for expo-image and more.',
  ]}
/>

## Installation

Image is already installed in `tamagui`, or you can install it independently:

```bash
npm install @tamagui/image
```

## Usage

Use the `src` prop for the image URL:

```tsx
import { Image } from 'tamagui'

export default () => (
  <Image
    src="https://example.com/photo.jpg"
    width={200}
    height={200}
  />
)
```

You can also use `objectFit` to control how the image fits its container:

```tsx
<Image
  src="https://example.com/photo.jpg"
  width="100%"
  height={300}
  objectFit="cover"
/>
```

## Using with expo-image

For better performance and features like blurhash placeholders and transitions,
you can use `createImage` to create a custom Image component with expo-image:

```tsx
import { Image as ExpoImage } from 'expo-image'
import { createImage } from '@tamagui/image'

export const Image = createImage({
  Component: ExpoImage,
  resizeModePropName: 'contentFit',
  objectPositionPropName: 'contentPosition',
})
```

Now you can use all expo-image props alongside Tamagui's unified API:

```tsx
const blurhash = '|rF?hV%2WCj[ayj[a|j[az...'

export default () => (
  <Image
    src="https://example.com/photo.jpg"
    width={200}
    height={300}
    objectFit="cover"
    placeholder={{ blurhash }}
    transition={300}
  />
)
```

### createImage Options

<SimpleTable
  headers={['Option', 'Type', 'Default', 'Description']}
  rows={[
    ['Component', '`ComponentType`', '-', 'The underlying image component (expo-image, react-native-fast-image, etc.)'],
    ['resizeModePropName', '`string`', "`'resizeMode'`", "The prop name for resize mode. expo-image uses `'contentFit'`"],
    ['objectPositionPropName', '`string`', '`undefined`', "The prop name for object position. expo-image uses `'contentPosition'`"],
    ['mapObjectFitToResizeMode', '`function`', 'Default mapper', 'Custom function to map `objectFit` values to the component\'s resize mode'],
    ['transformSource', '`function`', 'Default transformer', 'Custom function to transform source props'],
  ]}
/>

## API Reference

### Image

[Tamagui props](/docs/intro/props).

<PropsTable
  data={[
    {
      name: 'src',
      type: 'string',
      description: 'The image URL. Preferred over source for better web alignment.',
    },
    {
      name: 'objectFit',
      type: "'cover' | 'contain' | 'fill' | 'none' | 'scale-down'",
      description: 'How the image should be resized to fit its container. Maps to CSS object-fit on web and resizeMode on native.',
    },
    {
      name: 'objectPosition',
      type: 'string',
      description: 'How the image should be positioned within its container. On native, requires expo-image or similar for full support.',
    },
    {
      name: 'source',
      type: 'ImageSourcePropType',
      description: 'Deprecated. Use src instead.',
    },
    {
      name: 'resizeMode',
      type: 'ImageResizeMode',
      description: 'Deprecated. Use objectFit instead.',
    },
  ]}
/>


## components/inputs/1.0.0

---
title: Input & Textarea
name: inputs
description: Flexible form fields in styled and unstyled forms.
component: Input
demoName: Inputs
---

<HeroContainer demoMultiple>
  <InputsDemo />
</HeroContainer>

```tsx hero template=Inputs

```

Using the same base component TextInput, from [React Native](https://reactnative.dev/docs/textinput) or [React Native Web](https://necolas.github.io/react-native-web/docs/text-input/), Tamagui simply wraps these components to allow the full set of style props, as well as scaling all the styles up or down using the `size` property, much like Button.

## Installation

Input is already installed in `tamagui`, or you can install it independently:

```bash
npm install @tamagui/input
```

## Input

A one-line input field:

```tsx
import { Input } from 'tamagui'

export const App = () => (
  // Accepts size and style properties directly
  <Input size="$4" borderWidth={2} />
)
```

## TextArea

For multi-line inputs:

```tsx
import { TextArea } from 'tamagui'

export const App = () => (
  // Accepts size and style properties directly
  <TextArea size="$4" borderWidth={2} />
)
```


## components/inputs/2.0.0

---
title: Input & Textarea
name: inputs
description: Flexible form fields in styled and unstyled forms.
component: Input
demoName: Inputs
---

<HeroContainer demoMultiple>
  <InputsDemo />
</HeroContainer>

```tsx hero template=Inputs

```

Using the same base component TextInput, from [React Native](https://reactnative.dev/docs/textinput) or [React Native Web](https://necolas.github.io/react-native-web/docs/text-input/), Tamagui simply wraps these components to allow the full set of style props, as well as scaling all the styles up or down using the `size` property, much like Button.

## Installation

Input is already installed in `tamagui`, or you can install it independently:

```bash
npm install @tamagui/input
```

## Input

A one-line input field:

```tsx
import { Input } from 'tamagui'

export const App = () => (
  // Accepts size and style properties directly
  <Input size="$4" borderWidth={2} />
)
```

## TextArea

For multi-line inputs:

```tsx
import { TextArea } from 'tamagui'

export const App = () => (
  // Accepts size and style properties directly
  <TextArea size="$4" borderWidth={2} />
)
```


## components/intro/1.0.0

---
title: Tamagui UI
---

<IntroParagraph>
Tamagui UI is a complete suite of components that render nicely on both React web and React native and come in both
styled and unstyled forms.
</IntroParagraph>

You can install each component separately, or all of them at once with:

```bash
yarn add tamagui
```

## Setup

The package `tamagui` is a superset of `@tamagui/core`, so if you've already installed core you can change all the references to `tamagui`.

You'll need to add a provider to the root of your app (unlike core, where that is optional), as it will set up the root portal for components like dialogs and popovers.

```tsx
import { createTamagui,TamaguiProvider, View } from 'tamagui'
import { defaultConfig } from '@tamagui/config/v5' // for quick config install this

const config = createTamagui(defaultConfig)

export default () => (
  <TamaguiProvider config={config}>
    <View />
  </TamaguiProvider>
)
```

For a full guide configuration with `createTamagui`, check [the core configuration docs](/docs/core/configuration).


## components/intro/2.0.0

---
title: Tamagui UI
---

<IntroParagraph>
Tamagui UI is a complete suite of components that render nicely on both React web and React native and come in both
styled and unstyled forms.
</IntroParagraph>

You can install each component separately, or all of them at once with:

```bash
yarn add tamagui
```

## Setup

The package `tamagui` is a superset of `@tamagui/core`, so if you've already installed core you can change all the references to `tamagui`.

You'll need to add a provider to the root of your app (unlike core, where that is optional), as it will set up the root portal for components like dialogs and popovers.

```tsx
import { createTamagui, TamaguiProvider, View } from 'tamagui'
import { defaultConfig } from '@tamagui/config/v5' // for quick config install this

const config = createTamagui(defaultConfig)

export default () => (
  <TamaguiProvider config={config}>
    <View />
  </TamaguiProvider>
)
```

For a full guide configuration with `createTamagui`, check [the core configuration docs](/docs/core/configuration).


## components/label/1.0.0

---
title: Label
description: Label form elements with accessibility.
name: label
component: Label
package: label
demoName: Label
---

<HeroContainer>
  <LabelDemo />
</HeroContainer>

```tsx hero template=Label

```

<Highlights
  features={[
    `Supports nested controls and custom controls.`,
    `Sizable and styleable inline.`,
    `Works on web with aria-labelledby.`,
  ]}
/>

## Installation

Label is already installed in `tamagui`, or you can install it independently:

```bash
npm install @tamagui/label
```

## Usage

```tsx
import { Label } from 'tamagui'

export default () => (
  <>
    <Label htmlFor="name">Name</Label>
    <Input id="name" defaultValue="Nate Wienert" />
  </>
)
```

## Accessibility

Use with Input or other form elements to automatically get correct labelling by id and aria-labelledby. You can also use the provided `useLabelContext` hook to build your own controls.

## API Reference

### Label

Labels extend Stack views inheriting all the [Tamagui standard props](/docs/intro/props), plus:

<PropsTable
  data={[
    {
      name: 'htmlFor',
      type: 'string',
      required: true,
      description: `Matching to a Tamagui form element id.`,
    },
    {
      name: 'unstyled',
      required: false,
      type: `boolean`,
      description: `Removes all default Tamagui styles.`,
    },
  ]}
/>


## components/label/2.0.0

---
title: Label
description: Label form elements with accessibility.
name: label
component: Label
package: label
demoName: Label
---

<HeroContainer>
  <LabelDemo />
</HeroContainer>

```tsx hero template=Label

```

<Highlights
  features={[
    `Supports nested controls and custom controls.`,
    `Sizable and styleable inline.`,
    `Works on web with aria-labelledby.`,
  ]}
/>

## Installation

Label is already installed in `tamagui`, or you can install it independently:

```bash
npm install @tamagui/label
```

## Usage

```tsx
import { Label } from 'tamagui'

export default () => (
  <>
    <Label htmlFor="name">Name</Label>
    <Input id="name" defaultValue="Nate Wienert" />
  </>
)
```

## Accessibility

Use with Input or other form elements to automatically get correct labelling by id and aria-labelledby. You can also use the provided `useLabelContext` hook to build your own controls.

## API Reference

### Label

Labels extend Stack views inheriting all the [Tamagui standard props](/docs/intro/props), plus:

<PropsTable
  data={[
    {
      name: 'htmlFor',
      type: 'string',
      required: true,
      description: `Matching to a Tamagui form element id.`,
    },
    {
      name: 'unstyled',
      required: false,
      type: `boolean`,
      description: `Removes all default Tamagui styles.`,
    },
  ]}
/>


## components/linear-gradient/1.0.0-alpha

---
title: Linear Gradient
description: Linear Gradient that works with Tamagui style props.
name: html
component: LinearGradient
demoName: LinearGradient
---

# Linear Gradient

<Description>Linear Gradient that works with Tamagui style props.</Description>

<Notice>In beta, currently doesn't resolve theme values for the colors property.</Notice>

<HeroContainer>
  <LinearGradientDemo />
</HeroContainer>

```tsx hero template=LinearGradient

```

<Highlights
  features={[
    'Works on native and web.',
    'Bundles nicely with webpack.',
    'Accepts Tamagui style props.',
  ]}
/>

### Usage

We've included `<LinearGradient />` so it can accept Tamagui style props, and to patch a bug with the current expo version with some Webpack configurations.


## components/linear-gradient/1.0.0

---
title: Linear Gradient
description: Linear gradients that work with Tamagui style props
name: html
component: LinearGradient
demoName: LinearGradient
---

<HeroContainer>
  <LinearGradientDemo />
</HeroContainer>

```tsx hero template=LinearGradient

```

<Highlights
  features={[
    'Works on native and web.',
    'Accepts Tamagui style props and theme colors.',
    'Bundles easily with webpack.',
  ]}
/>

## Installation

LinearGradient is already installed in `tamagui`, or you can install it independently:

```bash
npm install @tamagui/linear-gradient
```

To use this package you'll need to add `expo-linear-gradient` to your app. This works [with vanilla React Native](https://github.com/expo/expo/tree/sdk-47/packages/expo-linear-gradient) or Expo.

## Usage

Because LinearGradient requires a more complex to install native package, we've left it out of the `tamagui` export until Metro supports async import. Import it either separately or using the path `/linear-gradient`:

```tsx
import { LinearGradient } from '@tamagui/linear-gradient'
import { LinearGradient } from 'tamagui/linear-gradient'
```

LinearGradient is a YStack that accepts all Tamagui style props as well as theme colors, that places `expo-linear-gradient` inside it set to absoluteFill.

## API Reference

### LinearGradient

See the [expo docs](https://docs.expo.dev/versions/latest/sdk/linear-gradient/) for more complete information.

LinearGradient extends YStack, inheriting [Stack props](/docs/components/stacks) and therefore the [Tamagui standard props](/docs/intro/props), plus:

<PropsTable
  data={[
    {
      name: 'colors',
      required: true,
      type: 'string[]',
      description: `Two or more colors.`,
    },
    {
      name: 'locations',
      required: false,
      type: 'number[] | null',
      description: `An array that contains numbers ranging from 0 to 1, inclusive, and is the same length as the colors property. Each number indicates a color-stop location where each respective color should be located.`,
      default: '[0.0, 1.0]',
    },
    {
      name: 'start',
      required: false,
      type: 'LinearGradientPoint | null',
      default: '{ x: 0.5, y: 0.0 }',
      description: `For example, { x: 0.1, y: 0.2 } means that the gradient will start 10% from the left and 20% from the top.`,
    },
    {
      name: 'end',
      required: false,
      type: 'LinearGradientPoint | null',
      default: '{ x: 0.5, y: 1.0 }',
      description: `For example, { x: 0.1, y: 0.2 } means that the gradient will end 10% from the left and 20% from the bottom.`,
    },
  ]}
/>


## components/linear-gradient/2.0.0

---
title: Linear Gradient
description: Linear gradients that work with Tamagui style props
name: html
component: LinearGradient
demoName: LinearGradient
---

<HeroContainer>
  <LinearGradientDemo />
</HeroContainer>

```tsx hero template=LinearGradient

```

<Highlights
  features={[
    'Works on native and web.',
    'Accepts Tamagui style props and theme colors.',
    'Bundles easily with webpack.',
  ]}
/>

## Installation

LinearGradient is already installed in `tamagui`, or you can install it independently:

```bash
npm install @tamagui/linear-gradient
```

To use this package you'll need to add `expo-linear-gradient` to your app. This works [with vanilla React Native](https://github.com/expo/expo/tree/sdk-47/packages/expo-linear-gradient) or Expo.

## Usage

Because LinearGradient requires a more complex to install native package, we've left it out of the `tamagui` export until Metro supports async import. Import it either separately or using the path `/linear-gradient`:

```tsx
import { LinearGradient } from '@tamagui/linear-gradient'
import { LinearGradient } from 'tamagui/linear-gradient'
```

LinearGradient is a YStack that accepts all Tamagui style props as well as theme colors, that places `expo-linear-gradient` inside it set to absoluteFill.

## API Reference

### LinearGradient

See the [expo docs](https://docs.expo.dev/versions/latest/sdk/linear-gradient/) for more complete information.

LinearGradient extends YStack, inheriting [Stack props](/docs/components/stacks) and therefore the [Tamagui standard props](/docs/intro/props), plus:

<PropsTable
  data={[
    {
      name: 'colors',
      required: true,
      type: 'string[]',
      description: `Two or more colors.`,
    },
    {
      name: 'locations',
      required: false,
      type: 'number[] | null',
      description: `An array that contains numbers ranging from 0 to 1, inclusive, and is the same length as the colors property. Each number indicates a color-stop location where each respective color should be located.`,
      default: '[0.0, 1.0]',
    },
    {
      name: 'start',
      required: false,
      type: 'LinearGradientPoint | null',
      default: '{ x: 0.5, y: 0.0 }',
      description: `For example, { x: 0.1, y: 0.2 } means that the gradient will start 10% from the left and 20% from the top.`,
    },
    {
      name: 'end',
      required: false,
      type: 'LinearGradientPoint | null',
      default: '{ x: 0.5, y: 1.0 }',
      description: `For example, { x: 0.1, y: 0.2 } means that the gradient will end 10% from the left and 20% from the bottom.`,
    },
  ]}
/>


## components/list-item/1.0.0

---
title: ListItem
description: A component for showing columns of items.
name: list-item
component: ListItem
package: list-item
demoName: ListItem
---

<HeroContainer>
  <ListItemDemo />
</HeroContainer>

```tsx hero template=ListItem

```

<Highlights
  features={[
    'Accepts size prop that works on all styles.',
    'Place an icon before or after.',
    'Works with themes, animations, Group.',
  ]}
/>

## Installation

ListItem is already installed in `tamagui`, or you can install it independently:

```bash
npm install @tamagui/list-item
```

## Usage

```tsx
import { ListItem } from 'tamagui'

export default () => <ListItem>Lorem ipsum</ListItem>
```

## Sizing

Sizing listItems 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 { ListItem } from 'tamagui'

export default () => <ListItem size="$6">Lorem ipsum</ListItem>
```

Given your theme defines a size `6`, the listItem 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.

## Customization

ListItem only supports a limited subset of text props directly, and doesn't accept `hoverStyle` text props. If you need more control, you can do a simple customization:

```tsx
import { forwardRef } from 'react'
import {
  ListItemFrame,
  ListItemText,
  ListItemTitle,
  ListItemSubtitle,
  styled,
  themeable,
  useListItem,
} from 'tamagui'

const CustomListItemFrame = styled(ListItemFrame, {
  backgroundColor: 'orange', // or "$color", etc.
})

const CustomListItemTitle = styled(ListItemTitle, {
  color: 'blue',
})

const CustomListItemSubtitle = styled(ListItemSubtitle, {
  color: 'pink',
})

const CustomListItemText = styled(ListItemText, {
  color: 'red',
})

export const ListItem = CustomListItemFrame.styleable((propsIn, ref) => {
  const { props } = useListItem(propsIn, {
    Title: CustomListItemTitle,
    Text: CustomListItemText,
    Subtitle: CustomListItemSubtitle,
  })

  return <CustomListItemFrame {...props} ref={ref} />
})
```

There are 3 different components you can customize: `ListItemText`, `ListItemSubtitle` and `ListItemTitle`.

You can include whichever one you want to customize specifically.

If you only want to customize the the text pieces, you don't have to include `CustomListItemFrame`:

```tsx
// all the text changes from above, with a default ListItemFrame
export const ListItem = themeable(
  forwardRef<TamaguiElement, ListItemProps>((propsIn, ref) => {
    const { props } = useListItem(propsIn, {
      Title: CustomListItemTitle,
      Text: CustomListItemText,
      Subtitle: CustomListItemSubtitle,
    })

    return <ListItemFrame {...props} ref={ref} />
  })
)
```

## API Reference

### ListItem

ListItems extend Stack views inheriting all the [Tamagui standard props](/docs/intro/props), plus:

<PropsTable
  data={[
    {
      name: 'title',
      required: false,
      type: 'React.ReactNode',
      description: `Can use either children or title + subTitle to set the contents.`,
    },
    {
      name: 'subTitle',
      required: false,
      type: 'React.ReactNode',
      description: `Sets a subTitle, recommended to use with title.`,
    },
    {
      name: 'size',
      required: false,
      type: 'string | tokens.size',
      description: `Set a size, number or one of the size token values.`,
    },
    {
      name: 'theme',
      required: false,
      type: 'string',
      description: `Apply a theme just to the listItem and it's children`,
    },
    {
      name: 'themeInverse',
      required: false,
      type: 'boolean',
      description: `Helpful for "flipping" any theme between dark and light (including flipping a sub themes defined as [subtheme]-[dark/light]`,
    },
    {
      name: 'noTextWrap',
      required: false,
      type: 'boolean',
      description: `If true, ListItem won't wrap content with a Text element.`,
    },
    {
      name: 'icon',
      required: false,
      type: 'JSX.Element',
      description: `Pass any React element, appears before the text.`,
    },
    {
      name: 'iconAfter',
      required: false,
      type: 'JSX.Element',
      description: `Pass any React element, appears after the text.`,
    },
    {
      name: 'scaleIcon',
      required: false,
      type: 'number',
      description: `Scale the icon more than usual by this number.`,
    },
    {
      name: 'scaleSpace',
      required: false,
      type: 'number',
      description: `Scale the spacing more than usual by this number.`,
    },
    {
      name: 'spaceFlex',
      required: false,
      type: `boolean`,
      description: `Makes all space elements have a flex.`,
    },
    {
      name: 'color',
      required: false,
      type: `SizableTextProps['color']`,
      description: `Passes "color" down to the inner text component`,
    },
    {
      name: 'fontWeight',
      required: false,
      type: `SizableTextProps['fontWeight']`,
      description: `Passes "fontWeight" down to the inner text component`,
    },
    {
      name: 'letterSpacing',
      required: false,
      type: `SizableTextProps['letterSpacing']`,
      description: `Passes "letterSpacing" down to the inner text component`,
    },
    {
      name: 'textAlign',
      required: false,
      type: `SizableTextProps['textAlign']`,
      description: `Passes "textAlign" down to the inner text component`,
    },
    {
      name: 'unstyled',
      required: false,
      type: `boolean`,
      description: `Removes all default Tamagui styles.`,
    },
  ]}
/>

### ListItem.Title

`ListItem.Title` extend `SizableText` inheriting all the [props](/docs/components/text#sizabletext).

### ListItem.Subtitle

`ListItem.Subtitle` extend `SizableText` inheriting all the [props](/docs/components/text#sizabletext).

### ListItem.Text

`ListItem.Text` extend `SizableText` inheriting all the [props](/docs/components/text#sizabletext).


## components/list-item/2.0.0

---
title: ListItem
description: A component for showing columns of items.
name: list-item
component: ListItem
package: list-item
demoName: ListItem
---

<HeroContainer>
  <ListItemDemo />
</HeroContainer>

```tsx hero template=ListItem

```

<Highlights
  features={[
    'Accepts size prop that works on all styles.',
    'Place an icon before or after.',
    'Works with themes, animations, Group.',
    'Supports variant prop (e.g., outlined).',
    'Use Apply to pass color/size/variant to children.',
  ]}
/>

## Installation

ListItem is already installed in `tamagui`, or you can install it independently:

```bash
npm install @tamagui/list-item
```

## Usage

Basic usage involves providing children, or using the `title` and `subTitle`
props.

```tsx
import { ListItem, Separator, YGroup } from 'tamagui'
import { ChevronRight } from '@tamagui/lucide-icons' // Example icon

export default () => (
  <YGroup 
    self="center"
    borderWidth={1}
    borderColor="$borderColor"
    rounded="$4"
    width={240}
    size="$4"
  >
    <YGroup.Item>
      <ListItem title="Star" icon={ChevronRight} />
    </YGroup.Item>
    <YGroup.Item>
      <ListItem
        title="Moon"
        subTitle="Subtitle for Moon"
        iconAfter={ChevronRight}
      />
    </YGroup.Item>
    <YGroup.Item>
      <ListItem>Custom children content</ListItem>
    </YGroup.Item>
  </YGroup>
)
```

## Sizing

The `size` prop adjusts various properties of the ListItem, including padding,
minimum height, and the default size for text elements within it. It accepts
theme size tokens (e.g., `"$3"`, `"$4"`).

```tsx
import { ListItem } from 'tamagui'

export default () => <ListItem size="$5" title="Large List Item" />
```

## Title and SubTitle

Use the `title` and `subTitle` props for a structured layout. These props accept
`ReactNode`, so you can pass strings or more complex JSX.

- If `title` or `subTitle` is provided, children will be rendered inside the
  main text area (under title/subtitle).
- `ListItem.Title` and `ListItem.Subtitle` components are used internally and
  can be targeted for styling (see Customization).

```tsx
import { ListItem, Avatar } from 'tamagui'
import { User } from '@tamagui/lucide-icons'

export default () => (
  <ListItem
    icon={
      <Avatar circular size="$3">
        <Avatar.Image src="/placeholder.png" />
        <Avatar.Fallback bc="$backgroundFocus" />
      </Avatar>
    }
    title="User Profile"
    subTitle="View and edit your profile details."
    iconAfter={User}
    size="$4"
  />
)
```

## Icon Theming

Icons can be passed to `icon` (before content) or `iconAfter` (after content)
props.

- **Automatic Sizing & Spacing**: Icons are automatically sized based on the
  ListItem's `size` prop. You can override this with the `iconSize` prop
  (accepts `SizeTokens`). Spacing between the icon and text is also
  automatically applied (40% of the icon's final size).
- **Scaling**: Use `scaleIcon` (number, default: 1) to further adjust the icon
  size relative to its base size.
- **Component Props**: If you pass a component as an icon, it will receive
  `size` (the calculated pixel size) as a prop.

```tsx
import { ListItem } from 'tamagui'
import { Star, Settings } from '@tamagui/lucide-icons'

export default () => (
  <>
    <ListItem icon={Star} title="Default Icon Size" size="$4" />
    <ListItem
      icon={Settings}
      iconSize="$2"
      title="Explicit Icon Size"
      subTitle="iconSize='$2'"
      size="$5"
    />
    <ListItem
      icon={Star}
      scaleIcon={1.5}
      title="Scaled Icon"
      subTitle="scaleIcon={1.5}"
      size="$4"
    />
  </>
)
```

## Variant

ListItem supports a `variant` prop for different visual styles:

```tsx
import { ListItem } from 'tamagui'

export default () => <ListItem variant="outlined" title="Outlined Style" />
```

Currently supports: `outlined` (transparent background with border).

## Apply (Context)

Use `ListItem.Apply` to pass `color`, `size`, and `variant` to multiple children via context:

```tsx
import { ListItem, YGroup } from 'tamagui'
import { Trash } from '@tamagui/lucide-icons'

export default () => (
  <YGroup>
    <ListItem.Apply color="$red10">
      <YGroup.Item>
        <ListItem icon={Trash} title="Delete item" />
      </YGroup.Item>
      <YGroup.Item>
        <ListItem icon={Trash} title="Remove all" />
      </YGroup.Item>
    </ListItem.Apply>
  </YGroup>
)
```

The `color` prop passed to `Apply` will be inherited by icons within children.

## Customization

For customization, Tamagui exports the building blocks: `ListItem.Frame`,
`ListItem.Text`, `ListItem.Title`, `ListItem.Subtitle`, and `ListItem.Icon`.

```tsx
import { YStack, ListItem as TamaguiListItem } from 'tamagui'
import { GetProps, styled } from '@tamagui/web'

const CustomFrame = styled(TamaguiListItem.Frame, {
  padding: '$5',
  backgroundColor: '$backgroundHover',
})

const CustomTitle = styled(TamaguiListItem.Title, {
  color: '$red10',
  fontWeight: 'bold',
})

const CustomSubtitle = styled(TamaguiListItem.Subtitle, {
  color: '$gray10',
  fontStyle: 'italic',
})

// Recompose, for example, if you need a different internal structure
export const MyAdvancedListItem = ({
  icon,
  title,
  subTitle,
  children,
  ...frameProps
}) => {
  return (
    <CustomFrame {...frameProps}>
      {icon && <TamaguiListItem.Icon>{icon}</TamaguiListItem.Icon>}
      <YStack flex={1} justify="center">
        {title && <CustomTitle>{title}</CustomTitle>}
        {subTitle && <CustomSubtitle>{subTitle}</CustomSubtitle>}
        {children && <TamaguiListItem.Text>{children}</TamaguiListItem.Text>}
      </YStack>
      {/* iconAfter could go here */}
    </CustomFrame>
  )
}
```

## API Reference

### ListItem

ListItems extend Stack views inheriting all the
[Tamagui standard props](/docs/intro/props), plus:

<PropsTable
  data={[
    {
      name: 'title',
      required: false,
      type: 'React.ReactNode',
      description:
        'Main text content of the list item. Renders using `ListItem.Title`.',
    },
    {
      name: 'subTitle',
      required: false,
      type: 'React.ReactNode',
      description:
        'Secondary text content, rendered below the title. Renders using `ListItem.Subtitle`.',
    },
    {
      name: 'size',
      required: false,
      type: 'SizeTokens',
      description:
        'Adjusts padding, min-height, and default font/icon sizes. Uses theme size tokens (e.g., "$3", "$4").',
    },
    {
      name: 'variant',
      required: false,
      type: "'outlined'",
      description:
        'Visual variant style. Currently supports "outlined" (transparent background with border).',
    },
    {
      name: 'icon',
      required: false,
      type: 'JSX.Element | React.ComponentType<{ size?: number }>',
      description:
        'Icon element or component displayed before the main content. Receives `size` prop if a component.',
    },
    {
      name: 'iconAfter',
      required: false,
      type: 'JSX.Element | React.ComponentType<{ size?: number }>',
      description:
        'Icon element or component displayed after the main content. Receives `size` prop if a component.',
    },
    {
      name: 'iconSize',
      required: false,
      type: 'SizeTokens',
      description:
        'Explicitly set the size of the icon, overriding the default size derived from the ListItem\'s `size` prop. Uses theme size tokens (e.g., "$2").',
    },
    {
      name: 'scaleIcon',
      required: false,
      type: 'number',
      description:
        'Scale factor for the icon (default: 1). Applied after `iconSize` or default size calculation.',
    },
    {
      name: 'disabled',
      required: false,
      type: 'boolean',
      description: 'If true, reduces opacity and disables pointer events.',
    },
    {
      name: 'unstyled',
      required: false,
      type: 'boolean',
      description:
        'Removes all default Tamagui styles (padding, background, etc.). Default is `false` unless `TAMAGUI_HEADLESS` is set.',
    },
    {
      name: '// Styling Text',
      type: '---',
      description:
        'To style the `title`, `subTitle`, or general `children` text, use `styled(ListItem, { ... })` to target nested `ListItem.Title`, `ListItem.Subtitle`, or `ListItem.Text` components, or compose your own with these parts. Direct text styling props are not passed for simplicity and performance.',
    },
  ]}
/>

### ListItem.Apply

A context provider that passes `color`, `size`, and `variant` to all ListItem children.

<PropsTable
  data={[
    {
      name: 'color',
      required: false,
      type: 'ColorTokens | string',
      description: 'Color token to pass to icons in children ListItems.',
    },
    {
      name: 'size',
      required: false,
      type: 'SizeTokens',
      description: 'Size token to apply to children ListItems.',
    },
    {
      name: 'variant',
      required: false,
      type: "'outlined'",
      description: 'Variant to apply to children ListItems.',
    },
  ]}
/>

### ListItem.Frame

The base `View` component for the ListItem. Inherits `Stack` props.

### ListItem.Text

Used for wrapping `children` when `title` or `subTitle` are not used. Extends
`SizableText`.

### ListItem.Title

Used for rendering the `title` prop. Extends `SizableText`.

### ListItem.Subtitle

Used for rendering the `subTitle` prop. Extends `SizableText`. Its font size is
automatically set to one step smaller than the ListItem's main `size`.

### ListItem.Icon

A helper component for rendering icons within ListItem, handling size and color.
Typically used internally but can be leveraged in custom compositions.


## components/lucide-icons/1.0.0

---
title: Lucide Icons
description: Cross-platform compatible SVG based icons
demoName: LucideIcons
---

<HeroContainer noPad>
  <LucideIconsDemo />
</HeroContainer>

## Installation

```sh
yarn add react-native-svg @tamagui/lucide-icons
```

## Usage

Use them as regular React components

```tsx
import { Button } from 'tamagui'
import { Plus } from '@tamagui/lucide-icons'

// Button will automatically pass size/theme to icon
export default () => (
  <Button icon={Plus}>
    Hello world
  </Button>
)

// or you can control it
export default () => (
  <Button icon={<Plus size="$4" />}>
    Hello world
  </Button>
)
```

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
demoName: LucideIcons
---

<HeroContainer noPad>
  <LucideIconsDemo />
</HeroContainer>

## Installation

```sh
yarn add react-native-svg @tamagui/lucide-icons
```

## Usage

Use them as regular React components

```tsx
import { Button } from 'tamagui'
import { Plus } from '@tamagui/lucide-icons'

// Button will automatically pass size/theme to icon
export default () => (
  <Button icon={Plus}>
    Hello world
  </Button>
)

// or you can control it
export default () => (
  <Button icon={<Plus size="$4" />}>
    Hello world
  </Button>
)
```

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/menu/2.0.0

---
title: Menu
description: A selectable list in a popover with nesting sub-menus.
name: dropdown menu
component: Menu
package: menu
---

<HeroContainer>
  <MenuDemo />
</HeroContainer>

```tsx hero template=Menu

```

<Highlights
  features={[
    'Full keyboard navigation.',
    'Submenus, items, icons, images, checkboxes, groups, and more.',
    'Modal and non-modal modes.',
    'Native menus on native platforms.',
  ]}
/>

## Installation

Menu is already installed in `tamagui`, or you can install it independently:

```bash
yarn add @tamagui/menu
```

If you want to use native menus, add these dependencies:

```sh
yarn add @react-native-menu/menu
yarn add react-native-ios-context-menu
yarn add react-native-ios-utilities
yarn add zeego
```

## Anatomy

Import all parts and piece them together.

```jsx
import { Menu } from 'tamagui' // or '@tamagui/menu'

export default () => (
  <Menu>
    <Menu.Trigger asChild>
      <Button />
    </Menu.Trigger>

    <Menu.Portal zIndex={100}>
      <Menu.Content>
        <Menu.Item>
          <Menu.ItemTitle>About Notes</Menu.ItemTitle>
        </Menu.Item>
        <Menu.Item>
          <Menu.ItemTitle>Settings</Menu.ItemTitle>
        </Menu.Item>
        {/* when title is nested inside a React element then you need to use `textValue` */}
        <Menu.Item textValue="Calendar">
          <Menu.ItemTitle>
            <Text>Calendar</Text>
          </Menu.ItemTitle>
          <Menu.ItemIcon>
            <Calendar color="gray" size="$1" />
          </Menu.ItemIcon>
        </Menu.Item>
        <Menu.Separator />
        <Menu.Sub>
          <Menu.SubTrigger>
            <Menu.ItemTitle>Actions</Menu.ItemTitle>
          </Menu.SubTrigger>
          <Menu.Portal zIndex={200}>
            <Menu.SubContent>
              <Menu.Label fontSize={'$1'}>Note settings</Menu.Label>
              <Menu.Item onSelect={onSelect} key="create-note">
                <Menu.ItemTitle>Create note</Menu.ItemTitle>
              </Menu.Item>
              <Menu.Item onSelect={onSelect} key="delete-all">
                <Menu.ItemTitle>Delete all notes</Menu.ItemTitle>
              </Menu.Item>
              <Menu.Item onSelect={onSelect} key="sync-all">
                <Menu.ItemTitle>Sync notes</Menu.ItemTitle>
              </Menu.Item>
            </Menu.SubContent>
          </Menu.Portal>
        </Menu.Sub>
      </Menu.Content>
    </Menu.Portal>
  </Menu>
)
```

## API Reference

### Menu

Contains every component for the Menu.

<PropsTable
  data={[
    {
      name: 'children',
      type: 'React.ReactNode',
      required: true,
      description: 'Menu parts: Trigger, Portal, Content, Items, etc.',
    },
    {
      name: 'placement',
      type: 'Placement',
      required: false,
      description: `Where the menu appears relative to the trigger. Options: 'top' | 'right' | 'bottom' | 'left' with optional '-start' | '-end' alignment.`,
    },
    {
      name: 'open',
      type: 'boolean',
      required: false,
      description: 'Controlled open state for the menu.',
    },
    {
      name: 'defaultOpen',
      type: 'boolean',
      required: false,
      description: 'Initial open state when uncontrolled.',
    },
    {
      name: 'onOpenChange',
      type: '(open: boolean) => void',
      required: false,
      description: 'Called when the menu opens or closes.',
    },
    {
      name: 'onOpenWillChange',
      type: '(open: boolean) => void',
      required: false,
      platform: 'ios',
      description: 'Called before the open/close animation begins.',
    },
    {
      name: 'modal',
      type: 'boolean',
      default: 'true',
      required: false,
      description:
        'When true, traps focus inside the menu and blocks outside scroll/interactions.',
    },
    {
      name: 'stayInFrame',
      type: 'ShiftProps | boolean',
      required: false,
      description: 'Shifts the menu to stay within viewport bounds.',
    },
    {
      name: 'allowFlip',
      type: 'FlipProps | boolean',
      required: false,
      description:
        'Flips the menu to the opposite side if there is insufficient space.',
    },
    {
      name: 'offset',
      type: 'OffsetOptions',
      required: false,
      description: 'Distance between the menu and its trigger.',
    },
    {
      name: 'unstyled',
      required: false,
      type: 'boolean',
      description: 'Removes all default Tamagui styles.',
    },
  ]}
/>

### Portal

This is necessary for the Menu.

<PropsTable
  data={[
    {
      name: 'zIndex',
      required: false,
      type: 'number',
      description: 'Stacking order of the portal layer.',
    },
    {
      name: 'children',
      type: 'React.ReactNode',
      required: true,
      description: 'Content to render inside the portal.',
    },
    {
      name: 'forceMount',
      type: 'true',
      required: false,
      description:
        'Forces the portal to stay mounted, useful for controlling animations.',
    },
  ]}
/>

### Trigger

The Menu will only be triggered when the user right-clicks or long-presses
within the Trigger area.

<PropsTable
  data={[
    {
      name: 'action',
      required: false,
      type: 'press|longPress',
      default: 'longPress',
      platform: 'android/ios',
      description:
        "Works with the native prop and accepts 'press' or 'longPress'. The default is 'longPress' for Menu.",
    },
  ]}
/>

### Content

Contains the content of the Menu.

<PropsTable
  data={[
    {
      name: 'children',
      type: 'React.ReactNode',
      required: true,
      description: 'Menu items, groups, labels, separators, and submenus.',
    },
    {
      name: 'loop',
      type: 'boolean',
      required: false,
      default: 'false',
      description: 'Whether keyboard navigation wraps from last to first item.',
    },
    {
      name: 'forceMount',
      type: 'true',
      required: false,
      description:
        'Forces the content to stay mounted, useful for controlling animations.',
    },
    {
      name: 'onCloseAutoFocus',
      type: '(event: Event) => void',
      required: false,
      description: 'Called when focus returns to the trigger after closing.',
    },
    {
      name: 'onEscapeKeyDown',
      type: '(event: KeyboardEvent) => void',
      required: false,
      description: 'Called when the escape key is pressed. Can be prevented.',
    },
    {
      name: 'onPointerDownOutside',
      type: '(event: PointerEvent) => void',
      required: false,
      description: 'Called when a pointer event occurs outside the content.',
    },
    {
      name: 'onInteractOutside',
      type: '(event: Event) => void',
      required: false,
      description: 'Called when any interaction occurs outside the content.',
    },
  ]}
/>

### Item

A selectable menu item that triggers an action when selected.

<PropsTable
  data={[
    {
      name: 'key',
      type: 'string',
      required: true,
      description: 'Unique identifier for the item.',
    },
    {
      name: 'disabled',
      type: 'boolean',
      required: false,
      default: 'false',
      description: 'Prevents interaction and dims the item.',
    },
    {
      name: 'destructive',
      type: 'boolean',
      required: false,
      platform: 'ios/android',
      description:
        'Renders the item in red on iOS to indicate a dangerous action (e.g. delete). No effect on web.',
    },
    {
      name: 'hidden',
      type: 'boolean',
      required: false,
      description: 'Hides the item from the menu.',
    },
    {
      name: 'onSelect',
      type: '(event?: Event) => void',
      required: false,
      description: 'Called when the item is selected via click or keyboard.',
    },
    {
      name: 'onFocus',
      type: '() => void',
      required: false,
      description: 'Called when the item receives focus.',
    },
    {
      name: 'onBlur',
      type: '() => void',
      required: false,
      description: 'Called when the item loses focus.',
    },
    {
      name: 'textValue',
      type: 'string',
      required: false,
      platform: 'ios/android',
      description:
        'Text used for typeahead and native menus. Required when ItemTitle contains a React node instead of a string.',
    },
  ]}
/>

### ItemTitle

Renders the title of the menu item.

<PropsTable
  data={[
    {
      name: 'children',
      type: 'string | React.ReactNode',
      required: true,
      description: 'The title text or element to display.',
    },
  ]}
/>
<Notice>
  You can directly pass a text node to the ItemTitle. However, if you use a nested
  React node like `<Text>`, you need to pass `textValue` to the `<Item>` so that it
  works with native menus.
</Notice>

### ItemIcon

A component to render an icon. For non-native menus, you can pass an icon
component. For native menus, you can pass platform-specific icons to the
`android`/`ios` props.

On iOS, it renders the native
[SF Symbols](https://github.com/andrewtavis/sf-symbols-online) icons.

<PropsTable
  data={[
    {
      name: 'children',
      type: 'React.ReactNode',
      required: false,
      description: 'Fallback icon for web when native icons are not available.',
    },
    {
      name: 'ios',
      type: 'object',
      required: false,
      platform: 'ios',
      description:
        'SF Symbol configuration: name, weight, scale, hierarchicalColor, paletteColors.',
    },
    {
      name: 'android',
      type: 'object',
      required: false,
      platform: 'android',
      description: 'Android resource drawable name.',
    },
  ]}
/>

```jsx line=1
<Menu.ItemIcon
  ios={{
    name: '0.circle.fill', // required
    pointSize: 5,
    weight: 'semibold',
    scale: 'medium',
    // can also be a color string. Requires iOS 15+
    hierarchicalColor: {
      dark: 'blue',
      light: 'green',
    },
    // alternative to hierarchical color. Requires iOS 15+
    paletteColors: [
      {
        dark: 'blue',
        light: 'green',
      },
    ],
  }}
>
  <CircleIcon />
</Menu.ItemIcon>
```

### ItemImage

A component to render an item image. For native menus, it only works on iOS. It
takes the same properties as `@tamagui/image`.

### ItemSubtitle

A component to render a subtitle for the menu item. For native menus, it only
works on iOS.

<PropsTable
  data={[
    {
      name: 'children',
      type: 'string',
      required: true,
      description: 'The subtitle text to display below the title.',
    },
  ]}
/>

### Group

A component that groups multiple menu items together.

<PropsTable
  data={[
    {
      name: 'children',
      type: 'React.ReactNode',
      required: true,
      description: 'Menu items to group together.',
    },
  ]}
/>

### CheckboxItem

A menu item with a checkbox that can be toggled on/off.

<PropsTable
  data={[
    {
      name: 'key',
      type: 'string',
      required: true,
      description: 'Unique identifier for the checkbox item.',
    },
    {
      name: 'disabled',
      type: 'boolean',
      required: false,
      default: 'false',
      description: 'Prevents interaction and dims the item.',
    },
    {
      name: 'destructive',
      type: 'boolean',
      required: false,
      platform: 'ios/android',
      description:
        'Renders the item in red on iOS to indicate a dangerous action. No effect on web.',
    },
    {
      name: 'hidden',
      type: 'boolean',
      required: false,
      description: 'Hides the item from the menu.',
    },
    {
      name: 'onFocus',
      type: '() => void',
      required: false,
      description: 'Called when the item receives focus.',
    },
    {
      name: 'onBlur',
      type: '() => void',
      required: false,
      description: 'Called when the item loses focus.',
    },
    {
      name: 'textValue',
      type: 'string',
      required: false,
      platform: 'ios/android',
      description:
        'Text for native menus. Required when ItemTitle contains a React node.',
    },
    {
      name: 'value',
      type: "'on' | 'off' | 'mixed'",
      required: false,
      platform: 'ios/android',
      description: 'Controlled checked state for native menus.',
    },
    {
      name: 'onValueChange',
      type: '(state, prevState) => void',
      required: false,
      platform: 'ios/android',
      description: 'Called when checked state changes on native menus.',
    },
    {
      name: 'checked',
      type: 'boolean',
      required: false,
      description: 'Controlled checked state for web menus.',
    },
    {
      name: 'onCheckedChange',
      type: '(checked: boolean) => void',
      required: false,
      description: 'Called when checked state changes on web.',
    },
  ]}
/>

### ItemIndicator

Use inside `CheckboxItem` or `RadioItem` to indicate when an item is checked.
This allows you to conditionally render a checkmark.

```jsx line=1
<Menu.ItemIndicator>
  <CheckmarkIcon /> {/* This does not work with the native prop. */}
</Menu.ItemIndicator>
```

<PropsTable
  data={[
    {
      name: 'children',
      type: 'React.ReactNode',
      required: false,
      description: 'Custom checkmark icon. Only works on web.',
    },
    {
      name: 'forceMount',
      type: 'true',
      required: false,
      description:
        'Forces the indicator to stay mounted for animation control.',
    },
  ]}
/>

### Label

Renders a non-focusable label for a group of items. On native menus, only one
label is supported per menu and submenu.

<PropsTable
  data={[
    {
      name: 'children',
      type: 'string',
      required: true,
      description: 'The label text.',
    },
    {
      name: 'textValue',
      type: 'string',
      required: false,
      platform: 'ios/android',
      description: 'Text for native menus when children is a React node.',
    },
  ]}
/>

### Arrow

Renders an arrow pointing to the trigger.

<PropsTable
  data={[
    {
      name: 'size',
      type: 'number | SizeToken',
      required: false,
      description: 'Width and height of the arrow.',
    },
    {
      name: 'unstyled',
      type: 'boolean',
      required: false,
      description: 'Removes default arrow styles.',
    },
  ]}
/>

### Separator

Renders a visual divider between menu items. Web only.

### Sub

A container for nested sub-menu components.

<PropsTable
  data={[
    {
      name: 'children',
      type: 'React.ReactNode',
      required: true,
      description: 'SubTrigger, Portal, and SubContent components.',
    },
    {
      name: 'open',
      type: 'boolean',
      required: false,
      description: 'Controlled open state for the sub-menu.',
    },
    {
      name: 'onOpenChange',
      type: '(open: boolean) => void',
      required: false,
      description: 'Called when the sub-menu opens or closes.',
    },
  ]}
/>

### SubContent

Renders the content of a sub-menu. Same props as `Content`, excluding `side` and
`align`.

### SubTrigger

A menu item that opens a sub-menu on hover/focus. Accepts the same props as
`Item`.


## components/new-inputs/1.0.0

---
title: Input & Textarea
name: inputs
component: Input
demoName: Inputs
---

<HeroContainer demoMultiple>
  <NewInputsDemo />
</HeroContainer>

```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
  <Input gap="$4" borderWidth={2} />
)
```

## TextArea

For multi-line inputs:

```tsx
import { TextArea } from 'tamagui'

export const App = () => (
  // Accepts size and style properties directly
  <TextArea gap="$4" borderWidth={2} />
)
```


## components/new-inputs/2.0.0

---
title: Input & TextArea
name: inputs
component: Input
demoName: Inputs
---

<HeroContainer demoMultiple>
  <NewInputsDemo />
</HeroContainer>

```tsx hero template=Inputs

```

The v2 Input uses web-standard HTML APIs as the primary interface, with automatic conversion to React Native equivalents on native platforms. This means you write web-first code that works everywhere.

## Installation

Input is already installed in `tamagui`, or you can install it independently:

```bash
npm install @tamagui/input
```

## Usage

### Input

A single-line text input:

```tsx
import { Input } from 'tamagui'

export default () => (
  <Input
    placeholder="Enter text..."
    size="$4"
  />
)
```

### TextArea

For multi-line text input:

```tsx
import { TextArea } from 'tamagui'

export default () => (
  <TextArea
    placeholder="Enter long text..."
    rows={4}
  />
)
```

## Web-First API

The v2 Input uses standard HTML input attributes. Here are the key props:

### Input Types

Use the standard HTML `type` attribute. On native, these automatically map to the appropriate keyboard and behavior:

```tsx
// Password input
<Input type="password" />

// Email input (shows email keyboard on native)
<Input type="email" />

// Phone number (shows phone pad on native)
<Input type="tel" />

// Number input (shows numeric keyboard on native)
<Input type="number" />

// URL input
<Input type="url" />

// Search input
<Input type="search" />
```

**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
<Input enterKeyHint="search" />
<Input enterKeyHint="send" />
<Input enterKeyHint="done" />
<Input enterKeyHint="go" />
<Input enterKeyHint="next" />
```

On native, this automatically maps to `returnKeyType`.

### Form Attributes

Standard HTML form attributes work as expected:

```tsx
<Input
  name="email"
  placeholder="Enter email"
  required
  maxLength={100}
  minLength={3}
  pattern="[a-z@.]+"
  autoComplete="email"
  autoFocus
  readOnly={false}
  disabled={false}
/>
```

### Events

Use standard web events:

```tsx
<Input
  onChange={(e) => 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
<Input onChangeText={(text) => setText(text)} />

// Preferred
<Input onChange={(e) => setText(e.target.value)} />
```

### Submit Handling

For handling form submission on enter:

```tsx
<Input
  onSubmitEditing={(e) => {
    console.log('Submitted:', e.nativeEvent.text)
  }}
/>
```

## Styling

Input accepts all Tamagui style props and supports the `size` prop for consistent scaling:

```tsx
<Input
  size="$4"
  borderWidth={2}
  borderColor="$borderColor"
  borderRadius="$4"
  backgroundColor="$background"
  color="$color"
  placeholderTextColor="$placeholderColor"
  selectionColor="$accentColor"

  hoverStyle={{
    borderColor: '$borderColorHover',
  }}

  focusStyle={{
    borderColor: '$borderColorFocus',
  }}
/>
```

## 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
<Input keyboardAppearance="dark" />
<Input keyboardAppearance="light" />
<Input keyboardAppearance="default" />
```

### textContentType (iOS)

Provides hints for iOS autofill:

```tsx
<Input textContentType="emailAddress" />
<Input textContentType="password" />
<Input textContentType="newPassword" />
<Input textContentType="oneTimeCode" />
<Input textContentType="telephoneNumber" />
<Input textContentType="username" />
```

For web autofill, use the standard `autoComplete` attribute instead.

## 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 `<input>` attributes plus Tamagui style props.

<PropsTable
  data={[
    {
      name: 'size',
      required: false,
      type: 'SizeTokens',
      description: 'Scale the input size consistently.',
    },
    {
      name: 'type',
      required: false,
      type: '"text" | "password" | "email" | "tel" | "number" | "url" | "search"',
      description: 'HTML input type. Automatically maps to native keyboard types.',
    },
    {
      name: 'enterKeyHint',
      required: false,
      type: '"enter" | "done" | "go" | "next" | "previous" | "search" | "send"',
      description: 'Hint for the enter key label. Maps to returnKeyType on native.',
    },
    {
      name: 'placeholderTextColor',
      required: false,
      type: 'ColorTokens',
      description: 'Color of placeholder text. Accepts Tamagui color tokens.',
    },
    {
      name: 'selectionColor',
      required: false,
      type: 'ColorTokens',
      description: 'Color of text selection. Accepts Tamagui color tokens.',
    },
    {
      name: 'onSubmitEditing',
      required: false,
      type: '(e: { nativeEvent: { text: string } }) => 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:

<PropsTable
  data={[
    {
      name: 'rows',
      required: false,
      type: 'number',
      description: 'Number of visible text lines.',
    },
  ]}
/>


## components/popover/1.0.0

---
title: Popover
description: A simple popover component
name: popover
component: Popover
package: popover
demoName: Popover
---

# Popover

<Description>Show content with a trigger in a floating pane.</Description>

<HeroContainer showAnimationDriverControl>
  <PopoverDemo />
</HeroContainer>

```tsx hero template=Popover

```

<Highlights
  features={[
    `Optional arrow to point to content.`,
    `Keeps within bounds of page.`,
    `Can be placed into 12 anchor positions.`,
  ]}
/>

Popovers are a great way to show content that's only visible when trigger is pressed, floating above the current content.

<Notice>
  Support for native was contributed by a community member, but not guaranteed to work
  until we get time to fully test and improve native interactions. We don't recommend the
  Popover pattern for phone-sized devices either way, and instead you can adapt it to a
  Sheet.
</Notice>

## Installation

Popover is already installed in `tamagui`, or you can install it independently:

```bash
npm install @tamagui/popover
```

## Anatomy

```tsx
import { Popover } from 'tamagui' // or '@tamagui/popover'

export default () => (
  <Popover>
    <Popover.Trigger />

    <Popover.Content>
      <Popover.Arrow />
      <Popover.Close />
      {/* ScrollView is optional, can just put any contents inside if not scrollable */}
      <Popover.ScrollView>{/* ... */}</Popover.ScrollView>
      {/* ... */}
    </Popover.Content>

    {/* optionally change to sheet when small screen */}
    <Popover.Adapt when="maxMd">
      <Popover.Sheet>
        <Popover.Sheet.Overlay />
        <Popover.Sheet.Frame>
          <Popover.Sheet.ScrollView>
            <Popover.Adapt.Contents />
          </Popover.Sheet.ScrollView>
        </Popover.Sheet.Frame>
      </Popover.Sheet>
    </Popover.Adapt>
  </Popover>
)
```

## API Reference

### Popover

Contains every component for the popover.

<PropsTable
  data={[
    {
      name: 'children',
      type: 'React.ReactNode',
      required: true,
      description: `Must contain Popover.Content`,
    },
    {
      name: 'size',
      type: 'SizeTokens',
      required: false,
      description: `Passes size down too all sub-components when set for padding, arrow, borderRadius`,
    },
    {
      name: 'placement',
      type: 'Placement',
      required: false,
      description: `'top' | 'right' | 'bottom' | 'left' | 'top-start' | 'top-end' | 'right-start' | 'right-end' | 'bottom-start' | 'bottom-end' | 'left-start' | 'left-end'`,
    },
    {
      name: 'open',
      type: 'boolean',
      required: false,
      description: ``,
    },
    {
      name: 'defaultOpen',
      type: 'boolean',
      required: false,
    },
    {
      name: 'onOpenChange',
      type: '(open: boolean) => void',
      required: false,
    },
    {
      name: 'modal',
      type: 'boolean',
      default: 'true',
      required: false,
      description: `Renders into root of app instead of inline.`,
    },
    {
      name: 'stayInFrame',
      type: 'ShiftProps | boolean',
      required: false,
      description: `Keeps the Popover inside the frame, see floating-ui shift().`,
    },
    {
      name: 'allowFlip',
      type: 'FlipProps | boolean',
      required: false,
      description: `Moves the Popover to other sides when space allows it, see floating-ui flip().`,
    },
    {
      name: 'offset',
      type: 'OffsetOptions',
      required: false,
      description: `Determines the distance the Popover appears from the target, see floating-ui offset().`,
    },
    {
      name: 'unstyled',
      required: false,
      type: `boolean`,
      description: `Removes all default Tamagui styles.`,
    },
  ]}
/>

### Popover.Trigger

Used to trigger opening of the popover when uncontrolled, just renders a YStack, see [Stacks](/docs/components/stacks).

### Popover.Content

Renders as SizableStack which is just a YStack (see [Stacks](/docs/components/stacks)) with an extra `size` prop that accepts any `SizeTokens`.

Used to display the content of the popover.

### Popover.Anchor

Renders as YStack, see [Stacks](/docs/components/stacks).

When you want the Trigger to be in another location from where the Popover attaches, use Anchor. When used, Anchor is where the Popover will attach, while Trigger will open it.

### Popover.Sheet

When used with `Adapt`, Popover will render as a sheet when that breakpoint is active.

See [Sheet](/docs/components/sheet) for more props.

Must use `Adapt.Contents` inside the `Popover.Sheet.Frame` to insert the contents given to `Popover.Content`

### Popover.ScrollView

Must be nested inside Content. Renders as a plain React Native ScrollView. If used alongside `<Adapt />` and Sheet, Tamagui will automatically know to remove this ScrollView when swapping into the Sheet, as the Sheet must use it's own ScrollView that handles special logic for interactions with dragging.


## components/popover/1.110.0

---
title: Popover
description: Show content in a floating pane.
name: popover
component: Popover
package: popover
demoName: Popover
---

<HeroContainer showAnimationDriverControl>
  <PopoverDemo />
</HeroContainer>

```tsx hero template=Popover

```

<Highlights
  features={[
    `Optional arrow to point to content.`,
    `Keeps within bounds of page.`,
    `Can be placed into 12 anchor positions.`,
  ]}
/>

Popovers are a great way to show content that's only visible when trigger is pressed, floating above the current content.

## Installation

Popover is already installed in `tamagui`, or you can install it independently:

```bash
npm install @tamagui/popover
```

### 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 (
    <PortalProvider shouldAddRootHost>
      <YourApp />
    </PortalProvider>
  )
}

export default App
```

<PropsTable
  data={[
    {
      name: 'shouldAddRootHost',
      type: 'boolean',
      required: false,
      description: `Defines whether to add a default root host or not.`,
    },
  ]}
/>

## Anatomy

```tsx
import { Popover, Adapt } from 'tamagui' // or '@tamagui/popover'

export default () => (
  <Popover>
    <Popover.Trigger />

    <Popover.Content>
      <Popover.Arrow />
      <Popover.Close />
      {/* ScrollView is optional, can just put any contents inside if not scrollable */}
      <Popover.ScrollView>{/* ... */}</Popover.ScrollView>
      {/* ... */}
    </Popover.Content>

    {/* optionally change to sheet when small screen */}
    {/* you can also use <Popover.Adapt /> */}
    <Adapt when="maxMd">
      <Popover.Sheet>
        <Popover.Sheet.Overlay />
        <Popover.Sheet.Frame>
          <Popover.Sheet.ScrollView>
            <Adapt.Contents />
          </Popover.Sheet.ScrollView>
        </Popover.Sheet.Frame>
      </Popover.Sheet>
    </Adapt>
  </Popover>
)
```

## API Reference

### Popover

Contains every component for the popover.

<PropsTable
  data={[
    {
      name: 'children',
      type: 'React.ReactNode',
      required: true,
      description: `Must contain Popover.Content`,
    },
    {
      name: 'size',
      type: 'SizeTokens',
      required: false,
      description: `Passes size down too all sub-components when set for padding, arrow, borderRadius`,
    },
    {
      name: 'placement',
      type: 'Placement',
      required: false,
      description: `'top' | 'right' | 'bottom' | 'left' | 'top-start' | 'top-end' | 'right-start' | 'right-end' | 'bottom-start' | 'bottom-end' | 'left-start' | 'left-end'`,
    },
    {
      name: 'open',
      type: 'boolean',
      required: false,
      description: ``,
    },
    {
      name: 'defaultOpen',
      type: 'boolean',
      required: false,
    },
    {
      name: 'onOpenChange',
      type: '(open: boolean) => void',
      required: false,
    },
    {
      name: 'keepChildrenMounted',
      type: 'boolean',
      required: false,
      description: `By default, Popover removes children from DOM/rendering when fully hidden. Setting true will keep children mounted even when hidden. This can be beneficial for performance if your popover content is expensive to render.`,
    },
    {
      name: 'stayInFrame',
      type: 'ShiftProps | boolean',
      required: false,
      description: `Keeps the Popover inside the frame, see floating-ui shift().`,
    },
    {
      name: 'allowFlip',
      type: 'FlipProps | boolean',
      required: false,
      description: `Moves the Popover to other sides when space allows it, see floating-ui flip().`,
    },
    {
      name: 'offset',
      type: 'OffsetOptions',
      required: false,
      description: `Determines the distance the Popover appears from the target, see floating-ui offset().`,
    },
    {
      name: 'hoverable',
      type: 'boolean | UseFloatingProps',
      required: false,
      description: `Allows hovering on the trigger to open the popover. See UseFloatingProps from floating-ui: accepts boolean or object of { delay: number, restMs: number, handleClose: Function, mouseOnly: boolean, move: boolean }`,
    },
  ]}
/>

<Notice>
  If using `modal={true}` (which is `true` by default), refer to the [PortalProvider installation](/ui/popover/1.83.0#portalprovider) for more information.
</Notice>

### Popover.Arrow

Popover.Arrow can be used to show an arrow that points at the Trigger element. In order for the Arrow to show you must have a Trigger element within your Popover. Arrows extend YStack, see [Stacks](/docs/components/stacks).

### Popover.Trigger

Used to trigger opening of the popover when uncontrolled, just renders a YStack, see [Stacks](/docs/components/stacks).

### Popover.Content

Renders as SizableStack which is just a YStack (see [Stacks](/docs/components/stacks)) with an extra `size` prop that accepts any `SizeTokens`.

Used to display the content of the popover.

<PropsTable
  data={[
    {
      name: 'size',
      type: 'SizeTokens',
      required: false,
    },
    {
      name: 'unstyled',
      required: false,
      type: `boolean`,
      description: `Removes all default Tamagui styles.`,
    },
  ]}
/>

### Popover.Anchor

Renders as YStack, see [Stacks](/docs/components/stacks).

When you want the Trigger to be in another location from where the Popover attaches, use Anchor. When used, Anchor is where the Popover will attach, while Trigger will open it.

### Popover.Sheet

When used with `Adapt`, Popover will render as a sheet when that breakpoint is active.

See [Sheet](/docs/components/sheet) for more props.

Must use `Adapt.Contents` inside the `Popover.Sheet.Frame` to insert the contents given to `Popover.Content`

### Popover.ScrollView

Must be nested inside Content. Renders as a plain React Native ScrollView. If used alongside `<Adapt />` and Sheet, Tamagui will automatically know to remove this ScrollView when swapping into the Sheet, as the Sheet must use it's own ScrollView that handles special logic for interactions with dragging.


## components/popover/1.125.0

---
title: Popover
description: Show content with a trigger in a floating pane.
name: popover
component: Popover
package: popover
demoName: Popover
---

<HeroContainer showAnimationDriverControl>
  <PopoverDemo />
</HeroContainer>

```tsx hero template=Popover

```

<Highlights
  features={[
    `Optional arrow to point to content.`,
    `Keeps within bounds of page.`,
    `Can be placed into 12 anchor positions.`,
  ]}
/>

Popovers are a great way to show content that's only visible when trigger is pressed, floating above the current content.

<Notice>
  Note: Popovers are not a recommended pattern for mobile apps, and so aren't supported on native. Instead you can use Adapt and render them as a Sheet, or just conditionally render them. We landed support for them at one point, but we need the community to contribute tests in order to support them for mobile again.
</Notice>

## Installation

Popover is already installed in `tamagui`, or you can install it independently:

```bash
npm install @tamagui/popover
```

### 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 (
    <PortalProvider shouldAddRootHost>
      <YourApp />
    </PortalProvider>
  )
}

export default App
```

<PropsTable
  data={[
    {
      name: 'shouldAddRootHost',
      type: 'boolean',
      required: false,
      description: `Defines whether to add a default root host or not.`,
    },
  ]}
/>

## Anatomy

```tsx
import { Popover, Adapt } from 'tamagui' // or '@tamagui/popover'

export default () => (
  <Popover>
    <Popover.Trigger />

    <Popover.Content>
      <Popover.Arrow />
      <Popover.Close />
      {/* ScrollView is optional, can just put any contents inside if not scrollable */}
      <Popover.ScrollView>{/* ... */}</Popover.ScrollView>
      {/* ... */}
    </Popover.Content>

    {/* optionally change to sheet when small screen */}
    {/* you can also use <Popover.Adapt /> */}
    <Adapt when="maxMd">
      <Popover.Sheet>
        <Popover.Sheet.Overlay />
        <Popover.Sheet.Frame>
          <Popover.Sheet.ScrollView>
            <Adapt.Contents />
          </Popover.Sheet.ScrollView>
        </Popover.Sheet.Frame>
      </Popover.Sheet>
    </Adapt>
  </Popover>
)
```

## API Reference

### Popover

Contains every component for the popover.

<PropsTable
  data={[
    {
      name: 'children',
      type: 'React.ReactNode',
      required: true,
      description: `Must contain Popover.Content`,
    },
    {
      name: 'size',
      type: 'SizeTokens',
      required: false,
      description: `Passes size down too all sub-components when set for padding, arrow, borderRadius`,
    },
    {
      name: 'placement',
      type: 'Placement',
      required: false,
      description: `'top' | 'right' | 'bottom' | 'left' | 'top-start' | 'top-end' | 'right-start' | 'right-end' | 'bottom-start' | 'bottom-end' | 'left-start' | 'left-end'`,
    },
    {
      name: 'open',
      type: 'boolean',
      required: false,
      description: ``,
    },
    {
      name: 'defaultOpen',
      type: 'boolean',
      required: false,
    },
    {
      name: 'onOpenChange',
      type: '(open: boolean) => void',
      required: false,
    },
    {
      name: 'keepChildrenMounted',
      type: 'boolean',
      required: false,
      description: `By default, Popover removes children from DOM/rendering when fully hidden. Setting true will keep children mounted even when hidden. This can be beneficial for performance if your popover content is expensive to render.`,
    },
    {
      name: 'stayInFrame',
      type: 'ShiftProps | boolean',
      required: false,
      description: `Keeps the Popover inside the frame, see floating-ui shift().`,
    },
    {
      name: 'allowFlip',
      type: 'FlipProps | boolean',
      required: false,
      description: `Moves the Popover to other sides when space allows it, see floating-ui flip().`,
    },
    {
      name: 'offset',
      type: 'OffsetOptions',
      required: false,
      description: `Determines the distance the Popover appears from the target, see floating-ui offset().`,
    },
    {
      name: 'hoverable',
      type: 'boolean | UseFloatingProps',
      required: false,
      description: `Allows hovering on the trigger to open the popover. See UseFloatingProps from floating-ui: accepts boolean or object of { delay: number, restMs: number, handleClose: Function, mouseOnly: boolean, move: boolean }`,
    },
    {
      name: 'resize',
      type: 'SizeProps | boolean',
      required: false,
      description: `Will set maxWidth and maxHeight of Content to fit inside outer window when it won't fit, see floating-ui size().`,
    },
  ]}
/>

For most of these properties, you'll want to reference the [floating-ui docs](https://floating-ui.com/docs/getting-started).

<Notice>
  If using `modal={true}` (which is `true` by default), refer to the [PortalProvider installation](/ui/popover/1.83.0#portalprovider) for more information.
</Notice>

### Popover.Arrow

Popover.Arrow can be used to show an arrow that points at the Trigger element. In order for the Arrow to show you must have a Trigger element within your Popover. Arrows extend YStack, see [Stacks](/docs/components/stacks).

### Popover.Trigger

Used to trigger opening of the popover when uncontrolled, just renders a YStack, see [Stacks](/docs/components/stacks).

### Popover.Content

Renders as SizableStack which is just a YStack (see [Stacks](/docs/components/stacks)) with an extra `size` prop that accepts any `SizeTokens`.

Used to display the content of the popover.

<PropsTable
  data={[
    {
      name: 'size',
      type: 'SizeTokens',
      required: false,
    },
    {
      name: 'unstyled',
      required: false,
      type: `boolean`,
      description: `Removes all default Tamagui styles.`,
    },
  ]}
/>

### Popover.Anchor

Renders as YStack, see [Stacks](/docs/components/stacks).

When you want the Trigger to be in another location from where the Popover attaches, use Anchor. When used, Anchor is where the Popover will attach, while Trigger will open it.

### Popover.Sheet

When used with `Adapt`, Popover will render as a sheet when that breakpoint is active.

See [Sheet](/docs/components/sheet) for more props.

Must use `Adapt.Contents` inside the `Popover.Sheet.Frame` to insert the contents given to `Popover.Content`

### Popover.ScrollView

Must be nested inside Content. Renders as a plain React Native ScrollView. If used alongside `<Adapt />` and Sheet, Tamagui will automatically know to remove this ScrollView when swapping into the Sheet, as the Sheet must use it's own ScrollView that handles special logic for interactions with dragging.


## components/popover/1.128.0

---
title: Popover
description: Show content with a trigger in a floating pane.
name: popover
component: Popover
package: popover
demoName: Popover
---

<HeroContainer showAnimationDriverControl>
  <PopoverDemo />
</HeroContainer>

```tsx hero template=Popover

```

<Highlights
  features={[
    `Optional arrow to point to content.`,
    `Keeps within bounds of page.`,
    `Can be placed into 12 anchor positions.`,
  ]}
/>

Popovers are a great way to show content that's only visible when trigger is
pressed, floating above the current content.

<Notice>
  Note: Popovers are not a recommended pattern for mobile apps, and so aren't
  supported on native. Instead you can use Adapt and render them as a Sheet, or
  just conditionally render them. We landed support for them at one point, but
  we need the community to contribute tests in order to support them for mobile
  again.
</Notice>

## Installation

Popover is already installed in `tamagui`, or you can install it independently:

```bash
npm install @tamagui/popover
```

### 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 (
    <PortalProvider shouldAddRootHost>
      <YourApp />
    </PortalProvider>
  )
}

export default App
```

<PropsTable
  data={[
    {
      name: 'shouldAddRootHost',
      type: 'boolean',
      required: false,
      description: `Defines whether to add a default root host or not.`,
    },
  ]}
/>

## Anatomy

```tsx
import { Popover, Adapt } from 'tamagui' // or '@tamagui/popover'

export default () => (
  <Popover>
    <Popover.Trigger />

    {/* Optional: Control focus behavior */}
    <Popover.FocusScope loop trapped focusOnIdle={true}>
      <Popover.Content>
        <Popover.Arrow />
        <Popover.Close />
        {/* ScrollView is optional, can just put any contents inside if not scrollable */}
        <Popover.ScrollView>{/* ... */}</Popover.ScrollView>
        {/* ... */}
      </Popover.Content>
    </Popover.FocusScope>

    {/* optionally change to sheet when small screen */}
    {/* you can also use <Popover.Adapt /> */}
    <Adapt when="maxMd">
      <Popover.Sheet>
        <Popover.Sheet.Overlay />
        <Popover.Sheet.Frame>
          <Popover.Sheet.ScrollView>
            <Adapt.Contents />
          </Popover.Sheet.ScrollView>
        </Popover.Sheet.Frame>
      </Popover.Sheet>
    </Adapt>
  </Popover>
)
```

## API Reference

### Popover

Contains every component for the popover.

<PropsTable
  data={[
    {
      name: 'children',
      type: 'React.ReactNode',
      required: true,
      description: `Must contain Popover.Content`,
    },
    {
      name: 'size',
      type: 'SizeTokens',
      required: false,
      description: `Passes size down too all sub-components when set for padding, arrow, borderRadius`,
    },
    {
      name: 'placement',
      type: 'Placement',
      required: false,
      description: `'top' | 'right' | 'bottom' | 'left' | 'top-start' | 'top-end' | 'right-start' | 'right-end' | 'bottom-start' | 'bottom-end' | 'left-start' | 'left-end'`,
    },
    {
      name: 'open',
      type: 'boolean',
      required: false,
      description: ``,
    },
    {
      name: 'defaultOpen',
      type: 'boolean',
      required: false,
    },
    {
      name: 'onOpenChange',
      type: '(open: boolean) => void',
      required: false,
    },
    {
      name: 'keepChildrenMounted',
      type: 'boolean',
      required: false,
      description: `By default, Popover removes children from DOM/rendering when fully hidden. Setting true will keep children mounted even when hidden. This can be beneficial for performance if your popover content is expensive to render.`,
    },
    {
      name: 'stayInFrame',
      type: 'ShiftProps | boolean',
      required: false,
      description: `Keeps the Popover inside the frame, see floating-ui shift().`,
    },
    {
      name: 'allowFlip',
      type: 'FlipProps | boolean',
      required: false,
      description: `Moves the Popover to other sides when space allows it, see floating-ui flip().`,
    },
    {
      name: 'offset',
      type: 'OffsetOptions',
      required: false,
      description: `Determines the distance the Popover appears from the target, see floating-ui offset().`,
    },
    {
      name: 'hoverable',
      type: 'boolean | UseFloatingProps',
      required: false,
      description: `Allows hovering on the trigger to open the popover. See UseFloatingProps from floating-ui: accepts boolean or object of { delay: number, restMs: number, handleClose: Function, mouseOnly: boolean, move: boolean }`,
    },
    {
      name: 'resize',
      type: 'SizeProps | boolean',
      required: false,
      description: `Will set maxWidth and maxHeight of Content to fit inside outer window when it won't fit, see floating-ui size().`,
    },
  ]}
/>

For most of these properties, you'll want to reference the
[floating-ui docs](https://floating-ui.com/docs/getting-started).

<Notice>
  If using `modal={true}` (which is `true` by default), refer to the
  [PortalProvider installation](/ui/popover/1.83.0#portalprovider) for more
  information.
</Notice>

### Popover.Arrow

Popover.Arrow can be used to show an arrow that points at the Trigger element.
In order for the Arrow to show you must have a Trigger element within your
Popover. Arrows extend YStack, see [Stacks](/docs/components/stacks).

### Popover.Trigger

Used to trigger opening of the popover when uncontrolled, just renders a YStack,
see [Stacks](/docs/components/stacks).

### Popover.Content

Extends PopperContent which extends SizableStack which extends a YStack (see
[Stacks](/docs/components/stacks)).

Also extends

Used to display the content of the popover.

<PropsTable
  data={[
    {
      name: 'enableAnimationForPositionChange',
      type: 'boolean',
      description: 'Disabled animate presence animations in favor of regular animation, useful for doing sliding popovers.'
    },
    {
      name: 'size',
      type: 'SizeTokens',
      required: false,
      description: 'Controls default padding/borderRadius when unstyled is false.'
    },
    {
      name: 'unstyled',
      required: false,
      type: `boolean`,
      default: false,
      description: `Removes all default Tamagui styles.`,
    },
    {
      name: 'trapFocus',
      type: 'boolean',
      default: false,
      description: 'Whether focus should be trapped within the `Popover`',
    },
    {
      name: 'disableFocusScope',
      type: 'boolean',
      default: false,
      description: 'Whether popover should not focus contents on open'
    },
    {
      name: 'onOpenAutoFocus',
      type: `FocusScopeProps['onMountAutoFocus']`,
      default: false,
      description: 'Event handler called when auto-focusing on open. Can be prevented.'
    },
    {
      name: 'onCloseAutoFocus',
      type: `FocusScopeProps['onUnmountAutoFocus'] | false`,
      default: false,
      description: 'Event handler called when auto-focusing on close. Can be prevented.'
    },
    {
      name: 'lazyMount',
      required: false,
      type: `boolean`,
      description: `Removes all default Tamagui styles.`,
    },
  ]}
/>

### Popover.Anchor

Renders as YStack, see [Stacks](/docs/components/stacks).

When you want the Trigger to be in another location from where the Popover
attaches, use Anchor. When used, Anchor is where the Popover will attach, while
Trigger will open it.

### Popover.Sheet

When used with `Adapt`, Popover will render as a sheet when that breakpoint is
active.

See [Sheet](/docs/components/sheet) for more props.

Must use `Adapt.Contents` inside the `Popover.Sheet.Frame` to insert the
contents given to `Popover.Content`

### Popover.FocusScope

Provides access to the underlying FocusScope component used by Popover for focus
management. Can be used to control focus behavior from a parent component.

<PropsTable
  data={[
    {
      name: 'enabled',
      type: 'boolean',
      default: 'true',
      description: `Whether focus management is enabled`,
    },
    {
      name: 'loop',
      type: 'boolean',
      default: 'false',
      description: `When true, tabbing from last item will focus first tabbable and shift+tab from first item will focus last tabbable`,
    },
    {
      name: 'trapped',
      type: 'boolean',
      default: 'false',
      description: `When true, focus cannot escape the focus scope via keyboard, pointer, or programmatic focus`,
    },
    {
      name: 'focusOnIdle',
      type: 'boolean | number',
      default: 'false',
      description: `When true, waits for idle before focusing. When a number, waits that many ms. This prevents reflows during animations`,
    },
    {
      name: 'onMountAutoFocus',
      type: '(event: Event) => void',
      description: `Event handler called when auto-focusing on mount. Can be prevented`,
    },
    {
      name: 'onUnmountAutoFocus',
      type: '(event: Event) => void',
      description: `Event handler called when auto-focusing on unmount. Can be prevented`,
    },
  ]}
/>

### Popover.ScrollView

Must be nested inside Content. Renders as a plain React Native ScrollView. If
used alongside `<Adapt />` and Sheet, Tamagui will automatically know to remove
this ScrollView when swapping into the Sheet, as the Sheet must use it's own
ScrollView that handles special logic for interactions with dragging.


## components/popover/1.129.0

---
title: Popover
description: Show content with a trigger in a floating pane.
name: popover
component: Popover
package: popover
demoName: Popover
---

<HeroContainer showAnimationDriverControl>
  <PopoverDemo />
</HeroContainer>

```tsx hero template=Popover

```

<Highlights
  features={[
    `Optional arrow to point to content.`,
    `Keeps within bounds of page.`,
    `Can be placed into 12 anchor positions.`,
  ]}
/>

Popovers are a great way to show content that's only visible when trigger is
pressed, floating above the current content.

<Notice>
  Note: Popovers are not a recommended pattern for mobile apps, and so aren't
  supported on native. Instead you can use Adapt and render them as a Sheet, or
  just conditionally render them. We landed support for them at one point, but
  we need the community to contribute tests in order to support them for mobile
  again.
</Notice>

## Installation

Popover is already installed in `tamagui`, or you can install it independently:

```bash
npm install @tamagui/popover
```

### 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 (
    <PortalProvider shouldAddRootHost>
      <YourApp />
    </PortalProvider>
  )
}

export default App
```

<PropsTable
  data={[
    {
      name: 'shouldAddRootHost',
      type: 'boolean',
      required: false,
      description: `Defines whether to add a default root host or not.`,
    },
  ]}
/>

## Anatomy

```tsx
import { Popover, Adapt } from 'tamagui' // or '@tamagui/popover'

export default () => (
  <Popover>
    <Popover.Trigger />

    {/* Optional: Control focus behavior */}
    <Popover.FocusScope loop trapped focusOnIdle={true}>
      <Popover.Content>
        <Popover.Arrow />
        <Popover.Close />
        {/* ScrollView is optional, can just put any contents inside if not scrollable */}
        <Popover.ScrollView>{/* ... */}</Popover.ScrollView>
        {/* ... */}
      </Popover.Content>
    </Popover.FocusScope>

    {/* optionally change to sheet when small screen */}
    {/* you can also use <Popover.Adapt /> */}
    <Adapt when="maxMd">
      <Popover.Sheet>
        <Popover.Sheet.Overlay />
        <Popover.Sheet.Frame>
          <Popover.Sheet.ScrollView>
            <Adapt.Contents />
          </Popover.Sheet.ScrollView>
        </Popover.Sheet.Frame>
      </Popover.Sheet>
    </Adapt>
  </Popover>
)
```

## API Reference

### Popover

Contains every component for the popover.

<PropsTable
  data={[
    {
      name: 'children',
      type: 'React.ReactNode',
      required: true,
      description: `Must contain Popover.Content`,
    },
    {
      name: 'size',
      type: 'SizeTokens',
      required: false,
      description: `Passes size down too all sub-components when set for padding, arrow, borderRadius`,
    },
    {
      name: 'placement',
      type: 'Placement',
      required: false,
      description: `'top' | 'right' | 'bottom' | 'left' | 'top-start' | 'top-end' | 'right-start' | 'right-end' | 'bottom-start' | 'bottom-end' | 'left-start' | 'left-end'`,
    },
    {
      name: 'open',
      type: 'boolean',
      required: false,
      description: ``,
    },
    {
      name: 'defaultOpen',
      type: 'boolean',
      required: false,
    },
    {
      name: 'onOpenChange',
      type: '(open: boolean) => void',
      required: false,
    },
    {
      name: 'keepChildrenMounted',
      type: 'boolean | "lazy"',
      required: false,
      description: `By default, Popover removes children from DOM/rendering when fully hidden. Setting true will keep children mounted even when hidden. This can be beneficial for performance if your popover content is expensive to render. The "lazy" value will only initially mount the children after a React startTransition, and then keep them mounted thereafter.`,
    },
    {
      name: 'stayInFrame',
      type: 'ShiftProps | boolean',
      required: false,
      description: `Keeps the Popover inside the frame, see floating-ui shift().`,
    },
    {
      name: 'allowFlip',
      type: 'FlipProps | boolean',
      required: false,
      description: `Moves the Popover to other sides when space allows it, see floating-ui flip().`,
    },
    {
      name: 'offset',
      type: 'OffsetOptions',
      required: false,
      description: `Determines the distance the Popover appears from the target, see floating-ui offset().`,
    },
    {
      name: 'hoverable',
      type: 'boolean | UseFloatingProps',
      required: false,
      description: `Allows hovering on the trigger to open the popover. See UseFloatingProps from floating-ui: accepts boolean or object of { delay: number, restMs: number, handleClose: Function, mouseOnly: boolean, move: boolean }`,
    },
    {
      name: 'resize',
      type: 'SizeProps | boolean',
      required: false,
      description: `Will set maxWidth and maxHeight of Content to fit inside outer window when it won't fit, see floating-ui size().`,
    },
  ]}
/>

For most of these properties, you'll want to reference the
[floating-ui docs](https://floating-ui.com/docs/getting-started).

<Notice>
  If using `modal={true}` (which is `true` by default), refer to the
  [PortalProvider installation](/ui/popover/1.83.0#portalprovider) for more
  information.
</Notice>

### Popover.Arrow

Popover.Arrow can be used to show an arrow that points at the Trigger element.
In order for the Arrow to show you must have a Trigger element within your
Popover. Arrows extend YStack, see [Stacks](/docs/components/stacks).

### Popover.Trigger

Used to trigger opening of the popover when uncontrolled, just renders a YStack,
see [Stacks](/docs/components/stacks).

### Popover.Content

Extends PopperContent which extends SizableStack which extends a YStack (see
[Stacks](/docs/components/stacks)).

Also extends

Used to display the content of the popover.

<PropsTable
  data={[
    {
      name: 'enableAnimationForPositionChange',
      type: 'boolean',
      description:
        'Disabled animate presence animations in favor of regular animation, useful for doing sliding popovers.',
    },
    {
      name: 'size',
      type: 'SizeTokens',
      required: false,
      description:
        'Controls default padding/borderRadius when unstyled is false.',
    },
    {
      name: 'unstyled',
      required: false,
      type: `boolean`,
      default: false,
      description: `Removes all default Tamagui styles.`,
    },
    {
      name: 'trapFocus',
      type: 'boolean',
      default: false,
      description: 'Whether focus should be trapped within the `Popover`',
    },
    {
      name: 'disableFocusScope',
      type: 'boolean',
      default: false,
      description: 'Whether popover should not focus contents on open',
    },
    {
      name: 'onOpenAutoFocus',
      type: `FocusScopeProps['onMountAutoFocus']`,
      default: false,
      description:
        'Event handler called when auto-focusing on open. Can be prevented.',
    },
    {
      name: 'onCloseAutoFocus',
      type: `FocusScopeProps['onUnmountAutoFocus'] | false`,
      default: false,
      description:
        'Event handler called when auto-focusing on close. Can be prevented.',
    },
    {
      name: 'lazyMount',
      required: false,
      type: `boolean`,
      description: `Removes all default Tamagui styles.`,
    },
  ]}
/>

### Popover.Anchor

Renders as YStack, see [Stacks](/docs/components/stacks).

When you want the Trigger to be in another location from where the Popover
attaches, use Anchor. When used, Anchor is where the Popover will attach, while
Trigger will open it.

### Popover.Sheet

When used with `Adapt`, Popover will render as a sheet when that breakpoint is
active.

See [Sheet](/docs/components/sheet) for more props.

Must use `Adapt.Contents` inside the `Popover.Sheet.Frame` to insert the
contents given to `Popover.Content`

### Popover.FocusScope

Provides access to the underlying FocusScope component used by Popover for focus
management. Can be used to control focus behavior from a parent component.

<PropsTable
  data={[
    {
      name: 'enabled',
      type: 'boolean',
      default: 'true',
      description: `Whether focus management is enabled`,
    },
    {
      name: 'loop',
      type: 'boolean',
      default: 'false',
      description: `When true, tabbing from last item will focus first tabbable and shift+tab from first item will focus last tabbable`,
    },
    {
      name: 'trapped',
      type: 'boolean',
      default: 'false',
      description: `When true, focus cannot escape the focus scope via keyboard, pointer, or programmatic focus`,
    },
    {
      name: 'focusOnIdle',
      type: 'boolean | number',
      default: 'false',
      description: `When true, waits for idle before focusing. When a number, waits that many ms. This prevents reflows during animations`,
    },
    {
      name: 'onMountAutoFocus',
      type: '(event: Event) => void',
      description: `Event handler called when auto-focusing on mount. Can be prevented`,
    },
    {
      name: 'onUnmountAutoFocus',
      type: '(event: Event) => void',
      description: `Event handler called when auto-focusing on unmount. Can be prevented`,
    },
  ]}
/>

### Popover.ScrollView

Must be nested inside Content. Renders as a plain React Native ScrollView. If
used alongside `<Adapt />` and Sheet, Tamagui will automatically know to remove
this ScrollView when swapping into the Sheet, as the Sheet must use it's own
ScrollView that handles special logic for interactions with dragging.


## components/popover/1.131.0

---
title: Popover
description: Show content with a trigger in a floating pane.
name: popover
component: Popover
package: popover
demoName: Popover
---

<HeroContainer showAnimationDriverControl>
  <PopoverDemo />
</HeroContainer>

```tsx hero template=Popover

```

<Highlights
  features={[
    `Optional arrow to point to content.`,
    `Keeps within bounds of page.`,
    `Can be placed into 12 anchor positions.`,
  ]}
/>

Popovers are a great way to show content that's only visible when trigger is
pressed, floating above the current content. Be sure to open the code example
above for a copy-paste implementation.

<Notice>
  Note: Popovers are not a recommended pattern for mobile apps. Instead you can
  use Adapt and render them as a Sheet, or just conditionally render them to
  some native UI.
</Notice>

## Installation

Popover is already installed in `tamagui`, or you can install it independently:

```bash
npm install @tamagui/popover
```

### PortalProvider

_Only_ if you are not using `tamagui` and installing popover independently -
you'll need to set up the portal for it with 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 (
    <PortalProvider shouldAddRootHost>
      <YourApp />
    </PortalProvider>
  )
}

export default App
```

<PropsTable
  data={[
    {
      name: 'shouldAddRootHost',
      type: 'boolean',
      required: false,
      description: `Defines whether to add a default root host or not.`,
    },
  ]}
/>

## Anatomy

```tsx
import { Popover, Adapt } from 'tamagui' // or '@tamagui/popover'

export default () => (
  <Popover>
    <Popover.Trigger />

    {/* Optional: Control focus behavior */}
    <Popover.FocusScope loop trapped focusOnIdle={true}>
      <Popover.Content>
        <Popover.Arrow />
        <Popover.Close />
        {/* ScrollView is optional, can just put any contents inside if not scrollable */}
        <Popover.ScrollView>{/* ... */}</Popover.ScrollView>
        {/* ... */}
      </Popover.Content>
    </Popover.FocusScope>

    {/* optionally change to sheet when small screen */}
    {/* you can also use <Popover.Adapt /> */}
    <Adapt when="maxMd">
      <Popover.Sheet>
        <Popover.Sheet.Overlay />
        <Popover.Sheet.Frame>
          <Popover.Sheet.ScrollView>
            <Adapt.Contents />
          </Popover.Sheet.ScrollView>
        </Popover.Sheet.Frame>
      </Popover.Sheet>
    </Adapt>
  </Popover>
)
```

## Scoping

Popover supports scoping which lets you mount one or more Popover instances at
the root of your app, while having a deeply nested child Trigger or Content
attach to the proper parent Popover instance.

In performance sensitive areas you may want to take advantage of this as it allows
you to only render the Popover.Trigger inside the sensitive area.
Popover isn't the cheapest component - it has a lot of functionality
inside of it like scroll management, focus management, and tracking position.

Here's the basic anatomy of using `scope` and placing your Popover higher up
for performance:

```tsx fileName=_layout.tsx
import { Popover } from 'tamagui'

// in your root layout:
export default ({ children }) => (
  <Popover scope="user-avatar">
    <Popover.Content>
      <Popover.Arrow />
      <Popover.Close />
      <Popover.ScrollView>{/* ... */}</Popover.ScrollView>
    </Popover.Content>

    {/* the rest of your app, note that it's inside of Popover */}
    {children}
  </Popover>
)
```

```tsx fileName=UserAvatar.tsx
export default () => (
  <Popover.Trigger scope="user-avatar">
    <Avatar />
  </Popover.Trigger>
)
```

Note that the `Trigger` scope ties to the `Popover` scope.

## API Reference

### Popover

Contains every component for the popover.

<PropsTable
  data={[
    {
      name: 'children',
      type: 'React.ReactNode',
      required: true,
      description: `Must contain Popover.Content`,
    },
    {
      name: 'size',
      type: 'SizeTokens',
      required: false,
      description: `Passes size down to all sub-components when set for padding, arrow, borderRadius.`,
    },
    {
      name: 'placement',
      type: 'Placement',
      required: false,
      description: `'top' | 'right' | 'bottom' | 'left' | 'top-start' | 'top-end' | 'right-start' | 'right-end' | 'bottom-start' | 'bottom-end' | 'left-start' | 'left-end'`,
    },
    {
      name: 'open',
      type: 'boolean',
      required: false,
      description: ``,
    },
    {
      name: 'defaultOpen',
      type: 'boolean',
      required: false,
    },
    {
      name: 'onOpenChange',
      type: '(open: boolean) => void',
      required: false,
    },
    {
      name: 'keepChildrenMounted',
      type: 'boolean | "lazy"',
      required: false,
      description: `By default, Popover removes children from DOM/rendering when fully hidden. Setting true will keep children mounted even when hidden. This can be beneficial for performance if your popover content is expensive to render. The "lazy" value will only initially mount the children after a React startTransition, and then keep them mounted thereafter.`,
    },
    {
      name: 'stayInFrame',
      type: 'ShiftProps | boolean',
      required: false,
      description: `Keeps the Popover inside the frame, see floating-ui shift().`,
    },
    {
      name: 'allowFlip',
      type: 'FlipProps | boolean',
      required: false,
      description: `Moves the Popover to other sides when space allows it, see floating-ui flip().`,
    },
    {
      name: 'offset',
      type: 'OffsetOptions',
      required: false,
      description: `Determines the distance the Popover appears from the target, see floating-ui offset().`,
    },
    {
      name: 'hoverable',
      type: 'boolean | UseFloatingProps',
      required: false,
      description: `Allows hovering on the trigger to open the popover. See UseFloatingProps from floating-ui: accepts boolean or object of { delay: number, restMs: number, handleClose: Function, mouseOnly: boolean, move: boolean }`,
    },
    {
      name: 'resize',
      type: 'SizeProps | boolean',
      required: false,
      description: `Will set maxWidth and maxHeight of Content to fit inside outer window when it won't fit, see floating-ui size().`,
    },
  ]}
/>

For most of these properties, you'll want to reference the
[floating-ui docs](https://floating-ui.com/docs/getting-started).

<Notice>
  If using `modal={true}` (which is `true` by default), refer to the
  [PortalProvider installation](#portalprovider) for more
  information.
</Notice>

### Popover.Arrow

Popover.Arrow can be used to show an arrow that points at the Trigger element.
In order for the Arrow to show you must have a Trigger element within your
Popover. Arrows extend YStack, see [Stacks](/docs/components/stacks).

### Popover.Trigger

Used to trigger opening of the popover when uncontrolled, just renders a YStack,
see [Stacks](/docs/components/stacks).

### Popover.Content

Extends PopperContent which extends SizableStack which extends a YStack (see
[Stacks](/docs/components/stacks)).

Also extends

Used to display the content of the popover.

<PropsTable
  data={[
    {
      name: 'enableAnimationForPositionChange',
      type: 'boolean',
      description:
        'Disabled animate presence animations in favor of regular animation, useful for doing sliding popovers.',
    },
    {
      name: 'size',
      type: 'SizeTokens',
      required: false,
      description:
        'Controls default padding/borderRadius when unstyled is false.',
    },
    {
      name: 'unstyled',
      required: false,
      type: `boolean`,
      default: false,
      description: `Removes all default Tamagui styles.`,
    },
    {
      name: 'trapFocus',
      type: 'boolean',
      default: false,
      description: 'Whether focus should be trapped within the `Popover`',
    },
    {
      name: 'disableFocusScope',
      type: 'boolean',
      default: false,
      description: 'Whether popover should not focus contents on open',
    },
    {
      name: 'onOpenAutoFocus',
      type: `FocusScopeProps['onMountAutoFocus']`,
      default: false,
      description:
        'Event handler called when auto-focusing on open. Can be prevented.',
    },
    {
      name: 'onCloseAutoFocus',
      type: `FocusScopeProps['onUnmountAutoFocus'] | false`,
      default: false,
      description:
        'Event handler called when auto-focusing on close. Can be prevented.',
    },
    {
      name: 'lazyMount',
      required: false,
      type: `boolean`,
      description: `Removes all default Tamagui styles.`,
    },
  ]}
/>

### Popover.Anchor

Renders as YStack, see [Stacks](/docs/components/stacks).

When you want the Trigger to be in another location from where the Popover
attaches, use Anchor. When used, Anchor is where the Popover will attach, while
Trigger will open it.

### Popover.Sheet

When used with `Adapt`, Popover will render as a sheet when that breakpoint is
active.

See [Sheet](/docs/components/sheet) for more props.

Must use `Adapt.Contents` inside the `Popover.Sheet.Frame` to insert the
contents given to `Popover.Content`

### Popover.FocusScope

Provides access to the underlying FocusScope component used by Popover for focus
management. Can be used to control focus behavior from a parent component.

<PropsTable
  data={[
    {
      name: 'enabled',
      type: 'boolean',
      default: 'true',
      description: `Whether focus management is enabled`,
    },
    {
      name: 'loop',
      type: 'boolean',
      default: 'false',
      description: `When true, tabbing from last item will focus first tabbable and shift+tab from first item will focus last tabbable`,
    },
    {
      name: 'trapped',
      type: 'boolean',
      default: 'false',
      description: `When true, focus cannot escape the focus scope via keyboard, pointer, or programmatic focus`,
    },
    {
      name: 'focusOnIdle',
      type: 'boolean | number',
      default: 'false',
      description: `When true, waits for idle before focusing. When a number, waits that many ms. This prevents reflows during animations`,
    },
    {
      name: 'onMountAutoFocus',
      type: '(event: Event) => void',
      description: `Event handler called when auto-focusing on mount. Can be prevented`,
    },
    {
      name: 'onUnmountAutoFocus',
      type: '(event: Event) => void',
      description: `Event handler called when auto-focusing on unmount. Can be prevented`,
    },
  ]}
/>

### Popover.ScrollView

Must be nested inside Content. Renders as a plain React Native ScrollView. If
used alongside `<Adapt />` and Sheet, Tamagui will automatically know to remove
this ScrollView when swapping into the Sheet, as the Sheet must use its own
ScrollView that handles special logic for interactions with dragging.


## components/popover/1.43.0

---
title: Popover
description: A simple popover component
name: popover
component: Popover
package: popover
demoName: Popover
---

# Popover

<Description>Show content with a trigger in a floating pane</Description>

<HeroContainer showAnimationDriverControl>
  <PopoverDemo />
</HeroContainer>

```tsx hero template=Popover

```

<Highlights
  features={[
    `Optional arrow to point to content.`,
    `Keeps within bounds of page.`,
    `Can be placed into 12 anchor positions.`,
  ]}
/>

Popovers are a great way to show content that's only visible when trigger is pressed, floating above the current content.

<Notice>
  Support for native was contributed by a community member, but not guaranteed to work
  until we get time to fully test. We don't recommend the Popover pattern for phone-sized
  devices, and you can adapt it to a Sheet instead.
</Notice>

## Installation

Popover is already installed in `tamagui`, or you can install it independently:

```bash
npm install @tamagui/popover
```

## Anatomy

```tsx
import { Popover } from 'tamagui' // or '@tamagui/popover'

export default () => (
  <Popover>
    <Popover.Trigger />

    <Popover.Content>
      <Popover.Arrow />
      <Popover.Close />
      {/* ScrollView is optional, can just put any contents inside if not scrollable */}
      <Popover.ScrollView>{/* ... */}</Popover.ScrollView>
      {/* ... */}
    </Popover.Content>

    {/* optionally change to sheet when small screen */}
    <Popover.Adapt when="maxMd">
      <Popover.Sheet>
        <Popover.Sheet.Overlay />
        <Popover.Sheet.Frame>
          <Popover.Sheet.ScrollView>
            <Popover.Adapt.Contents />
          </Popover.Sheet.ScrollView>
        </Popover.Sheet.Frame>
      </Popover.Sheet>
    </Popover.Adapt>
  </Popover>
)
```

## API Reference

### Popover

Contains every component for the popover.

<PropsTable
  data={[
    {
      name: 'children',
      type: 'React.ReactNode',
      required: true,
      description: `Must contain Popover.Content`,
    },
    {
      name: 'size',
      type: 'SizeTokens',
      required: false,
      description: `Passes size down too all sub-components when set for padding, arrow, borderRadius`,
    },
    {
      name: 'placement',
      type: 'Placement',
      required: false,
      description: `'top' | 'right' | 'bottom' | 'left' | 'top-start' | 'top-end' | 'right-start' | 'right-end' | 'bottom-start' | 'bottom-end' | 'left-start' | 'left-end'`,
    },
    {
      name: 'open',
      type: 'boolean',
      required: false,
      description: ``,
    },
    {
      name: 'defaultOpen',
      type: 'boolean',
      required: false,
    },
    {
      name: 'onOpenChange',
      type: '(open: boolean) => void',
      required: false,
    },
    {
      name: 'modal',
      type: 'boolean',
      default: 'true',
      required: false,
      description: `Renders into root of app instead of inline.`,
    },
    {
      name: 'keepChildrenMounted',
      type: 'boolean',
      required: false,
      description: `By default, Popover removes children from DOM/rendering when fully hidden. Setting true will keep children mounted even when hidden. This can be beneficial for performance if your popover content is expensive to render.`,
    },
    {
      name: 'stayInFrame',
      type: 'ShiftProps | boolean',
      required: false,
      description: `Keeps the Popover inside the frame, see floating-ui shift().`,
    },
    {
      name: 'allowFlip',
      type: 'FlipProps | boolean',
      required: false,
      description: `Moves the Popover to other sides when space allows it, see floating-ui flip().`,
    },
    {
      name: 'offset',
      type: 'OffsetOptions',
      required: false,
      description: `Determines the distance the Popover appears from the target, see floating-ui offset().`,
    },
  ]}
/>

### Popover.Trigger

Used to trigger opening of the popover when uncontrolled, just renders a YStack, see [Stacks](/docs/components/stacks).

### Popover.Content

Renders as SizableStack which is just a YStack (see [Stacks](/docs/components/stacks)) with an extra `size` prop that accepts any `SizeTokens`.

Used to display the content of the popover.

<PropsTable
  data={[
    {
      name: 'size',
      type: 'SizeTokens',
      required: false,
    },
    {
      name: 'unstyled',
      required: false,
      type: `boolean`,
      description: `Removes all default Tamagui styles.`,
    },
  ]}
/>

### Popover.Anchor

Renders as YStack, see [Stacks](/docs/components/stacks).

When you want the Trigger to be in another location from where the Popover attaches, use Anchor. When used, Anchor is where the Popover will attach, while Trigger will open it.

### Popover.Sheet

When used with `Adapt`, Popover will render as a sheet when that breakpoint is active.

See [Sheet](/docs/components/sheet) for more props.

Must use `Adapt.Contents` inside the `Popover.Sheet.Frame` to insert the contents given to `Popover.Content`

### Popover.ScrollView

Must be nested inside Content. Renders as a plain React Native ScrollView. If used alongside `<Adapt />` and Sheet, Tamagui will automatically know to remove this ScrollView when swapping into the Sheet, as the Sheet must use it's own ScrollView that handles special logic for interactions with dragging.


## components/popover/1.83.0

---
title: Popover
description: A simple popover component
name: popover
component: Popover
package: popover
demoName: Popover
---

# Popover

<Description>Show content with a trigger in a floating pane</Description>

<HeroContainer showAnimationDriverControl>
  <PopoverDemo />
</HeroContainer>

```tsx hero template=Popover

```

<Highlights
  features={[
    `Optional arrow to point to content.`,
    `Keeps within bounds of page.`,
    `Can be placed into 12 anchor positions.`,
  ]}
/>

Popovers are a great way to show content that's only visible when trigger is pressed, floating above the current content.

## Installation

Popover is already installed in `tamagui`, or you can install it independently:

```bash
npm install @tamagui/popover
```

### 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 (
    <PortalProvider shouldAddRootHost>
      <YourApp />
    </PortalProvider>
  )
}

export default App
```

<PropsTable
  data={[
    {
      name: 'shouldAddRootHost',
      type: 'boolean',
      required: false,
      description: `Defines whether to add a default root host or not.`,
    },
  ]}
/>

## Anatomy

```tsx
import { Popover, Adapt } from 'tamagui' // or '@tamagui/popover'

export default () => (
  <Popover>
    <Popover.Trigger />

    <Popover.Content>
      <Popover.Arrow />
      <Popover.Close />
      {/* ScrollView is optional, can just put any contents inside if not scrollable */}
      <Popover.ScrollView>{/* ... */}</Popover.ScrollView>
      {/* ... */}
    </Popover.Content>

    {/* optionally change to sheet when small screen */}
    {/* you can also use <Popover.Adapt /> */}
    <Adapt when="maxMd">
      <Popover.Sheet>
        <Popover.Sheet.Overlay />
        <Popover.Sheet.Frame>
          <Popover.Sheet.ScrollView>
            <Adapt.Contents />
          </Popover.Sheet.ScrollView>
        </Popover.Sheet.Frame>
      </Popover.Sheet>
    </Adapt>
  </Popover>
)
```

## API Reference

### Popover

Contains every component for the popover.

<PropsTable
  data={[
    {
      name: 'children',
      type: 'React.ReactNode',
      required: true,
      description: `Must contain Popover.Content`,
    },
    {
      name: 'size',
      type: 'SizeTokens',
      required: false,
      description: `Passes size down too all sub-components when set for padding, arrow, borderRadius`,
    },
    {
      name: 'placement',
      type: 'Placement',
      required: false,
      description: `'top' | 'right' | 'bottom' | 'left' | 'top-start' | 'top-end' | 'right-start' | 'right-end' | 'bottom-start' | 'bottom-end' | 'left-start' | 'left-end'`,
    },
    {
      name: 'open',
      type: 'boolean',
      required: false,
      description: ``,
    },
    {
      name: 'defaultOpen',
      type: 'boolean',
      required: false,
    },
    {
      name: 'onOpenChange',
      type: '(open: boolean) => void',
      required: false,
    },
    {
      name: 'modal',
      type: 'boolean',
      default: 'true',
      required: false,
      description: `Renders into root of app instead of inline.`,
    },
    {
      name: 'keepChildrenMounted',
      type: 'boolean',
      required: false,
      description: `By default, Popover removes children from DOM/rendering when fully hidden. Setting true will keep children mounted even when hidden. This can be beneficial for performance if your popover content is expensive to render.`,
    },
    {
      name: 'stayInFrame',
      type: 'ShiftProps | boolean',
      required: false,
      description: `Keeps the Popover inside the frame, see floating-ui shift().`,
    },
    {
      name: 'allowFlip',
      type: 'FlipProps | boolean',
      required: false,
      description: `Moves the Popover to other sides when space allows it, see floating-ui flip().`,
    },
    {
      name: 'offset',
      type: 'OffsetOptions',
      required: false,
      description: `Determines the distance the Popover appears from the target, see floating-ui offset().`,
    },
    {
      name: 'hoverable',
      type: 'boolean | UseFloatingProps',
      required: false,
      description: `Allows hovering on the trigger to open the popover. See UseFloatingProps from floating-ui: accepts boolean or object of { delay: number, restMs: number, handleClose: Function, mouseOnly: boolean, move: boolean }`,
    },
  ]}
/>

<Notice>
  If using `modal={true}` (which is `true` by default), refer to the [PortalProvider installation](/ui/popover/1.83.0#portalprovider) for more information.
</Notice>

### Popover.Arrow

Popover.Arrow can be used to show an arrow that points at the Trigger element. In order for the Arrow to show you must have a Trigger element within your Popover. Arrows extend YStack, see [Stacks](/docs/components/stacks).

### Popover.Trigger

Used to trigger opening of the popover when uncontrolled, just renders a YStack, see [Stacks](/docs/components/stacks).

### Popover.Content

Renders as SizableStack which is just a YStack (see [Stacks](/docs/components/stacks)) with an extra `size` prop that accepts any `SizeTokens`.

Used to display the content of the popover.

<PropsTable
  data={[
    {
      name: 'size',
      type: 'SizeTokens',
      required: false,
    },
    {
      name: 'unstyled',
      required: false,
      type: `boolean`,
      description: `Removes all default Tamagui styles.`,
    },
  ]}
/>

### Popover.Anchor

Renders as YStack, see [Stacks](/docs/components/stacks).

When you want the Trigger to be in another location from where the Popover attaches, use Anchor. When used, Anchor is where the Popover will attach, while Trigger will open it.

### Popover.Sheet

When used with `Adapt`, Popover will render as a sheet when that breakpoint is active.

See [Sheet](/docs/components/sheet) for more props.

Must use `Adapt.Contents` inside the `Popover.Sheet.Frame` to insert the contents given to `Popover.Content`

### Popover.ScrollView

Must be nested inside Content. Renders as a plain React Native ScrollView. If used alongside `<Adapt />` and Sheet, Tamagui will automatically know to remove this ScrollView when swapping into the Sheet, as the Sheet must use it's own ScrollView that handles special logic for interactions with dragging.


## components/popover/2.0.0

---
title: Popover
description: Show content with a trigger in a floating pane.
name: popover
component: Popover
package: popover
demoName: Popover
---

<HeroContainer showAnimationDriverControl>
  <PopoverDemo />
</HeroContainer>

```tsx hero template=Popover

```

<Highlights
  features={[
    `Optional arrow to point to content.`,
    `Keeps within bounds of page.`,
    `Can be placed into 12 anchor positions.`,
  ]}
/>

Popovers are a great way to show content that's only visible when trigger is
pressed, floating above the current content. Be sure to open the code example
above for a copy-paste implementation.

<Notice>
  Note: Popovers are not a recommended pattern for mobile apps. Instead you can
  use Adapt and render them as a Sheet, or just conditionally render them to
  some native UI.
</Notice>

## Installation

Popover is already installed in `tamagui`, or you can install it independently:

```bash
npm install @tamagui/popover
```

### PortalProvider

_Only_ if you are not using `tamagui` and installing popover independently -
you'll need to set up the portal for it with 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 (
    <PortalProvider shouldAddRootHost>
      <YourApp />
    </PortalProvider>
  )
}

export default App
```

<PropsTable
  data={[
    {
      name: 'shouldAddRootHost',
      type: 'boolean',
      required: false,
      description: `Defines whether to add a default root host or not.`,
    },
  ]}
/>

<Notice theme="blue">
  For native apps, we recommend [setting up native portals](/docs/components/portal#native-portal-setup-recommended) to preserve React context inside Popover content.
</Notice>

## Anatomy

```tsx
import { Popover, Adapt, Sheet } from 'tamagui' // or '@tamagui/popover'

export default () => (
  <Popover>
    <Popover.Trigger />

    {/* Optional: Control focus behavior */}
    <Popover.FocusScope loop trapped focusOnIdle={true}>
      <Popover.Content>
        <Popover.Arrow />
        <Popover.Close />
        {/* ScrollView is optional, can just put any contents inside if not scrollable */}
        <Popover.ScrollView>{/* ... */}</Popover.ScrollView>
        {/* ... */}
      </Popover.Content>
    </Popover.FocusScope>

    {/* optionally change to sheet when small screen */}
    {/* you can also use <Popover.Adapt /> */}
    <Adapt when="maxMd">
      <Sheet>
        <Sheet.Overlay />
        <Sheet.Frame>
          <Sheet.ScrollView>
            <Adapt.Contents />
          </Sheet.ScrollView>
        </Sheet.Frame>
      </Sheet>
    </Adapt>
  </Popover>
)
```

## Scoping

Popover supports scoping which lets you mount one or more Popover instances at
the root of your app, while having a deeply nested child Trigger or Content
attach to the proper parent Popover instance.

In performance sensitive areas you may want to take advantage of this as it allows
you to only render the Popover.Trigger inside the sensitive area.
Popover isn't the cheapest component - it has a lot of functionality
inside of it like scroll management, focus management, and tracking position.

Here's the basic anatomy of using `scope` and placing your Popover higher up
for performance:

```tsx fileName=_layout.tsx
import { Popover } from 'tamagui'

// in your root layout:
export default ({ children }) => (
  <Popover scope="user-avatar">
    <Popover.Content>
      <Popover.Arrow />
      <Popover.Close />
      <Popover.ScrollView>{/* ... */}</Popover.ScrollView>
    </Popover.Content>

    {/* the rest of your app, note that it's inside of Popover */}
    {children}
  </Popover>
)
```

```tsx fileName=UserAvatar.tsx
export default () => (
  <Popover.Trigger scope="user-avatar">
    <Avatar />
  </Popover.Trigger>
)
```

Note that the `Trigger` scope ties to the `Popover` scope.

## API Reference

### Popover

Contains every component for the popover.

<PropsTable
  data={[
    {
      name: 'children',
      type: 'React.ReactNode',
      required: true,
      description: `Must contain Popover.Content`,
    },
    {
      name: 'size',
      type: 'SizeTokens',
      required: false,
      description: `Passes size down to all sub-components when set for padding, arrow, borderRadius.`,
    },
    {
      name: 'placement',
      type: 'Placement',
      required: false,
      description: `'top' | 'right' | 'bottom' | 'left' | 'top-start' | 'top-end' | 'right-start' | 'right-end' | 'bottom-start' | 'bottom-end' | 'left-start' | 'left-end'`,
    },
    {
      name: 'open',
      type: 'boolean',
      required: false,
      description: ``,
    },
    {
      name: 'defaultOpen',
      type: 'boolean',
      required: false,
    },
    {
      name: 'onOpenChange',
      type: '(open: boolean) => void',
      required: false,
    },
    {
      name: 'keepChildrenMounted',
      type: 'boolean | "lazy"',
      required: false,
      description: `By default, Popover removes children from DOM/rendering when fully hidden. Setting true will keep children mounted even when hidden. This can be beneficial for performance if your popover content is expensive to render. The "lazy" value will only initially mount the children after a React startTransition, and then keep them mounted thereafter.`,
    },
    {
      name: 'stayInFrame',
      type: 'ShiftProps | boolean',
      required: false,
      description: `Keeps the Popover inside the frame, see floating-ui shift().`,
    },
    {
      name: 'allowFlip',
      type: 'FlipProps | boolean',
      required: false,
      description: `Moves the Popover to other sides when space allows it, see floating-ui flip().`,
    },
    {
      name: 'offset',
      type: 'OffsetOptions',
      required: false,
      description: `Determines the distance the Popover appears from the target, see floating-ui offset().`,
    },
    {
      name: 'hoverable',
      type: 'boolean | UseFloatingProps',
      required: false,
      description: `Allows hovering on the trigger to open the popover. See UseFloatingProps from floating-ui: accepts boolean or object of { delay: number, restMs: number, handleClose: Function, mouseOnly: boolean, move: boolean }`,
    },
    {
      name: 'resize',
      type: 'SizeProps | boolean',
      required: false,
      description: `Will set maxWidth and maxHeight of Content to fit inside outer window when it won't fit, see floating-ui size().`,
    },
  ]}
/>

For most of these properties, you'll want to reference the
[floating-ui docs](https://floating-ui.com/docs/getting-started).

<Notice>
  If using `modal={true}` (which is `true` by default), refer to the
  [PortalProvider installation](#portalprovider) for more
  information.
</Notice>

### Popover.Arrow

Popover.Arrow can be used to show an arrow that points at the Trigger element.
In order for the Arrow to show you must have a Trigger element within your
Popover. Arrows extend YStack, see [Stacks](/docs/components/stacks).

### Popover.Trigger

Used to trigger opening of the popover when uncontrolled, just renders a YStack,
see [Stacks](/docs/components/stacks).

### Popover.Content

Extends PopperContent which extends SizableStack which extends a YStack (see
[Stacks](/docs/components/stacks)).

Also extends

Used to display the content of the popover.

<PropsTable
  data={[
    {
      name: 'enableAnimationForPositionChange',
      type: 'boolean',
      description:
        'Disabled animate presence animations in favor of regular animation, useful for doing sliding popovers.',
    },
    {
      name: 'size',
      type: 'SizeTokens',
      required: false,
      description:
        'Controls default padding/borderRadius when unstyled is false.',
    },
    {
      name: 'unstyled',
      required: false,
      type: `boolean`,
      default: false,
      description: `Removes all default Tamagui styles.`,
    },
    {
      name: 'trapFocus',
      type: 'boolean',
      default: false,
      description: 'Whether focus should be trapped within the `Popover`',
    },
    {
      name: 'disableFocusScope',
      type: 'boolean',
      default: false,
      description: 'Whether popover should not focus contents on open',
    },
    {
      name: 'onOpenAutoFocus',
      type: `FocusScopeProps['onMountAutoFocus']`,
      default: false,
      description:
        'Event handler called when auto-focusing on open. Can be prevented.',
    },
    {
      name: 'onCloseAutoFocus',
      type: `FocusScopeProps['onUnmountAutoFocus'] | false`,
      default: false,
      description:
        'Event handler called when auto-focusing on close. Can be prevented.',
    },
    {
      name: 'lazyMount',
      required: false,
      type: `boolean`,
      description: `Removes all default Tamagui styles.`,
    },
  ]}
/>

### Popover.Anchor

Renders as YStack, see [Stacks](/docs/components/stacks).

When you want the Trigger to be in another location from where the Popover
attaches, use Anchor. When used, Anchor is where the Popover will attach, while
Trigger will open it.

### Sheet (with Adapt)

When used with `Adapt`, you can render a Sheet when that breakpoint is
active. Import `Sheet` directly from `tamagui` or `@tamagui/sheet`.

See [Sheet](/docs/components/sheet) for more props.

Must use `Adapt.Contents` inside the `Sheet.Frame` to insert the
contents given to `Popover.Content`

### Popover.FocusScope

Provides access to the underlying FocusScope component used by Popover for focus
management. Can be used to control focus behavior from a parent component.

<PropsTable
  data={[
    {
      name: 'enabled',
      type: 'boolean',
      default: 'true',
      description: `Whether focus management is enabled`,
    },
    {
      name: 'loop',
      type: 'boolean',
      default: 'false',
      description: `When true, tabbing from last item will focus first tabbable and shift+tab from first item will focus last tabbable`,
    },
    {
      name: 'trapped',
      type: 'boolean',
      default: 'false',
      description: `When true, focus cannot escape the focus scope via keyboard, pointer, or programmatic focus`,
    },
    {
      name: 'focusOnIdle',
      type: 'boolean | number',
      default: 'false',
      description: `When true, waits for idle before focusing. When a number, waits that many ms. This prevents reflows during animations`,
    },
    {
      name: 'onMountAutoFocus',
      type: '(event: Event) => void',
      description: `Event handler called when auto-focusing on mount. Can be prevented`,
    },
    {
      name: 'onUnmountAutoFocus',
      type: '(event: Event) => void',
      description: `Event handler called when auto-focusing on unmount. Can be prevented`,
    },
  ]}
/>

### Popover.ScrollView

Must be nested inside Content. Renders as a plain React Native ScrollView. If
used alongside `<Adapt />` and Sheet, Tamagui will automatically know to remove
this ScrollView when swapping into the Sheet, as the Sheet must use its own
ScrollView that handles special logic for interactions with dragging.


## components/portal/1.0.0

---
title: Portal
description: Send items to other areas of the tree.
name: portal
component: Portal
package: portal
---

Portal is included in `tamagui` as it's used by a few components. For now, it's simply using [@gorhom/portal](https://github.com/gorhom/react-native-portal).


## components/portal/2.0.0

---
title: Portal
description: Send items to other areas of the tree.
name: portal
component: Portal
package: portal
---

Portal is included in `tamagui` and is used by [Sheet](/docs/components/sheet),
[Dialog](/docs/components/dialog), [Popover](/docs/components/popover),
[Select](/docs/components/select), and [Toast](/docs/components/toast).

## Native Portal Setup (Recommended)

On web, React's built-in `createPortal` preserves context automatically. On
native, the default default portal implementation implementation doesn't
preserve React context. Tamagui automatically re-propagates its own contexts
(theme, configuration), but your custom contexts like navigation or app state
won't be available inside portaled content. The re-propagation also adds some
overhead.

We recommend using
[react-native-teleport](https://github.com/nicksrandall/react-native-teleport)
to solve this. It uses React Native's native portal API to preserve context
automatically.

### Step 1: Install react-native-teleport

```bash
npm install react-native-teleport
```

### Step 2: Call setupNativePortal

In your app's entry file (index.js or App.tsx), before rendering:

```tsx
import { setupNativePortal } from '@tamagui/portal/setup-native'

setupNativePortal()
```

That's it! All portal-using components will now preserve context automatically
on native.

### Why This Matters

Without native portals, your custom context from parent components won't be
available inside portaled content:

```tsx
const MyContext = createContext('default')

function App() {
  return (
    <MyContext.Provider value="from-provider">
      <Sheet modal open>
        <Sheet.Frame>
          {/* Without native portal: logs "default" instead of "from-provider" */}
          <MyConsumer />
        </Sheet.Frame>
      </Sheet>
    </MyContext.Provider>
  )
}
```

With `setupNativePortal()`, all context flows through correctly - and it's
faster since Tamagui no longer needs to manually re-propagate its own contexts.

## Alternative Approaches

If you can't use react-native-teleport, there are other ways to handle context
in portals:

### Component Scoping

For Dialog, Popover, and Tooltip, use the `scope` prop to mount a single
instance at your app root. This avoids portals entirely on native:

```tsx
// _layout.tsx - mount once at root with all your providers
<TamaguiProvider>
  <Tooltip scope="global">
    <Tooltip.Content>
      <Tooltip.Arrow />
      <Paragraph>{/* label set by trigger */}</Paragraph>
    </Tooltip.Content>

    {/* rest of your app */}
    <Slot />
  </Tooltip>
</TamaguiProvider>

// anywhere in your app - just the trigger
<Tooltip.Trigger scope="global" aria-label="Settings">
  <Button icon={Settings} />
</Tooltip.Trigger>
```

This pattern is also a performance win for lists or tables with many interactive
elements.

### Manual Context Re-propagation

Wrap portal children with the necessary providers:

```tsx
const theme = useTheme()
const myValue = useContext(MyContext)

<Sheet>
  <Sheet.Frame>
    <ThemeProvider theme={theme}>
      <MyContext.Provider value={myValue}>
        {/* content that needs context */}
      </MyContext.Provider>
    </ThemeProvider>
  </Sheet.Frame>
</Sheet>
```

This is more verbose and error-prone as you need to remember to re-propagate
every context you use.

## Technical Details

[react-native-teleport](https://github.com/nicksrandall/react-native-teleport)
uses `ReactNativeFabricUIManager.createPortal` (Fabric) or
`UIManager.createPortal` (Paper) to create true native portals that preserve the
React fiber tree.

The default portal implementation, by contrast, uses a JS-based approach with
context providers and a reducer to manage portal state. While compatible with
older RN versions, it breaks React context because it re-renders content in a
separate provider tree.

Tamagui includes a `needsPortalRepropagation()` helper that returns `true` when
using the default portal implementation and `false` when using native portals,
so library authors can conditionally re-propagate context only when needed.


## components/progress/1.0.0

---
title: Progress
description: Display a bar to indicate percent completion.
name: progress
component: Progress
package: progress
demoName: Progress
---

# Progress

<Description>Show percent completion with a progress bar</Description>

<HeroContainer showAnimationDriverControl>
  <ProgressDemo />
</HeroContainer>

```tsx hero template=Progress

```

<Highlights
  features={[
    'Sizable, themeable, animatable.',
    'Compound component API for complete control.',
    <span>
      Adheres to the{' '}
      <a href="https://www.w3.org/TR/wai-aria-1.2/#progressbar">
        progressbar role requirements
      </a>
    </span>,
  ]}
/>

## Installation

Progress is already installed in `tamagui`, or you can install it independently:

```bash
npm install @tamagui/progress
```

## Usage

The `value` property controls the percent, but you can override it by adjusting the `x` property.

```tsx
import { Button, Progress } from 'tamagui'

export default () => (
  <Progress value={60}>
    <Progress.Indicator animation="bouncy" />
  </Progress>
)
```

## API Reference

### Progress

Progress extends [ThemeableStack](/docs/components/stacks#themeablestack), getting [Tamagui standard props](/docs/intro/props), plus:

<PropsTable
  data={[
    {
      name: 'size',
      required: false,
      type: '"small" | "large"',
    },
    {
      name: 'value',
      required: false,
      type: 'number | null',
      description: `Control the percent progress.`,
    },
    {
      name: 'max',
      required: false,
      type: 'number',
    },
  ]}
/>

### Progress.Indicator

`Progress.Indicator` extends [ThemeableStack](/docs/components/stacks#themeablestack), getting [Tamagui standard props](/docs/intro/props).


## components/progress/1.48.0

---
title: Progress
description: Show percent completion with a progress bar.
name: progress
component: Progress
package: progress
demoName: Progress
---

<HeroContainer showAnimationDriverControl>
  <ProgressDemo />
</HeroContainer>

```tsx hero template=Progress

```

<Highlights
  features={[
    'Sizable, themeable, animatable.',
    'Compound component API for complete control.',
    <span>
      Adheres to the{' '}
      <Link href="https://www.w3.org/TR/wai-aria-1.2/#progressbar">
        progressbar role requirements
      </Link>
    </span>,
  ]}
/>

## Installation

Progress is already installed in `tamagui`, or you can install it independently:

```bash
npm install @tamagui/progress
```

## Usage

The `value` property controls the percent, but you can override it by adjusting the `x` property.

```tsx
import { Button, Progress } from 'tamagui'

export default () => (
  <Progress value={60}>
    <Progress.Indicator animation="bouncy" />
  </Progress>
)
```

## API Reference

### Progress

Progress extends [ThemeableStack](/docs/components/stacks#themeablestack), getting [Tamagui standard props](/docs/intro/props), plus:

<PropsTable
  data={[
    {
      name: 'size',
      required: false,
      type: '"small" | "large"',
    },
    {
      name: 'value',
      required: false,
      type: 'number | null',
      description: `Control the percent progress.`,
    },
    {
      name: 'max',
      required: false,
      type: 'number',
    },
    {
      name: 'unstyled',
      required: false,
      type: 'boolean',
      description: `When true will remove all default styles`,
    },
  ]}
/>

### Progress.Indicator

`Progress.Indicator` extends [ThemeableStack](/docs/components/stacks#themeablestack), getting [Tamagui standard props](/docs/intro/props).

<PropsTable
  data={[
    {
      name: 'unstyled',
      required: false,
      type: 'boolean',
      description: `When true will remove all default styles`,
    },
  ]}
/>


## components/progress/2.0.0

---
title: Progress
description: Show percent completion with a progress bar.
name: progress
component: Progress
package: progress
demoName: Progress
---

<HeroContainer showAnimationDriverControl>
  <ProgressDemo />
</HeroContainer>

```tsx hero template=Progress

```

<Highlights
  features={[
    'Sizable, themeable, animatable.',
    'Compound component API for complete control.',
    <span>
      Adheres to the{' '}
      <Link href="https://www.w3.org/TR/wai-aria-1.2/#progressbar">
        progressbar role requirements
      </Link>
    </span>,
  ]}
/>

## Installation

Progress is already installed in `tamagui`, or you can install it independently:

```bash
npm install @tamagui/progress
```

## Usage

The `value` property controls the percent, but you can override it by adjusting the `x` property.

```tsx
import { Button, Progress } from 'tamagui'

export default () => (
  <Progress value={60}>
    <Progress.Indicator transition="bouncy" />
  </Progress>
)
```

## API Reference

### Progress

Progress extends [ThemeableStack](/docs/components/stacks#themeablestack), getting [Tamagui standard props](/docs/intro/props), plus:

<PropsTable
  data={[
    {
      name: 'size',
      required: false,
      type: '"small" | "large"',
    },
    {
      name: 'value',
      required: false,
      type: 'number | null',
      description: `Control the percent progress.`,
    },
    {
      name: 'max',
      required: false,
      type: 'number',
    },
    {
      name: 'unstyled',
      required: false,
      type: 'boolean',
      description: `When true will remove all default styles`,
    },
  ]}
/>

### Progress.Indicator

`Progress.Indicator` extends [ThemeableStack](/docs/components/stacks#themeablestack), getting [Tamagui standard props](/docs/intro/props).

<PropsTable
  data={[
    {
      name: 'unstyled',
      required: false,
      type: 'boolean',
      description: `When true will remove all default styles`,
    },
  ]}
/>


## components/radio-group/1.2.0

---
title: RadioGroup
description: A group of radio buttons
name: radio-group
component: RadioGroup
package: radio-group
demoName: RadioGroup
---

# RadioGroup

<Description>
Choose one option from a list in a form.
</Description>

<YStack className="is-sticky" />

<Tabs id="type" defaultValue="styled">
<Tabs.List>
  <TooltipSimple label="With Tamagui's default styles">
    <Tabs.Tab value="styled">Styled</Tabs.Tab>
  </TooltipSimple>
  <TooltipSimple label="No default styles, easy to customize">
    <Tabs.Tab value="unstyled">Unstyled</Tabs.Tab>
  </TooltipSimple>
  <TooltipSimple label="No dependency on Tamagui's core">
    <Tabs.Tab value="headless" label="No styles and no dependency on Tamagui's styling">Headless</Tabs.Tab>
  </TooltipSimple>
</Tabs.List>

<HeroContainer showAnimationDriverControl>
  <Tabs.Content
    value="styled"
    justifyContent="center"
    alignItems="center"
    width="100%"
  >
    <RadioGroupDemo />
  </Tabs.Content>
  <Tabs.Content
    value="unstyled"
    justifyContent="center"
    alignItems="center"
    width="100%"
  >
    <RadioGroupUnstyledDemo />
  </Tabs.Content>
  <Tabs.Content
    value="headless"
    justifyContent="center"
    alignItems="center"
    width="100%"
  >
    <RadioGroupHeadlessDemo />
  </Tabs.Content>
</HeroContainer>

<Tabs.Content value="styled">
  ```tsx hero template=RadioGroup

````
</Tabs.Content>
<Tabs.Content value="unstyled">
  ```tsx hero template=RadioGroupUnstyled

````
</Tabs.Content>
<Tabs.Content value="headless">
  ```tsx hero template=RadioGroupHeadless

````
</Tabs.Content>

<Highlights
features={[
  `Accessible, easy to compose and customize.`,
  `Sizable & works controlled or uncontrolled.`,
  `Ability to opt-out to native radio button on web.`,
]}
/>

## Installation

<Tabs.Content value="styled">
RadioGroup is already installed in `tamagui`, or you can install it independently:

```bash
npm install @tamagui/radio-group
````

</Tabs.Content>

<Tabs.Content value="unstyled">
RadioGroup is already installed in `tamagui`, or you can install it independently:

```bash
npm install @tamagui/radio-group
````

</Tabs.Content>

<Tabs.Content value="headless">

To use the headless radio group, you want to import it from the
`@tamagui/radio-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/radio-headless
```

</Tabs.Content>

## Usage

<Tabs.Content value="styled">

```tsx
import { RadioGroup } from 'tamagui'

export default () => (
  <RadioGroup value="foo" gap="$2">
    <RadioGroup.Item value="foo" id="foo-radio-item">
      <RadioGroup.Indicator />
    </RadioGroup.Item>
    <RadioGroup.Item value="bar" id="bar-radio-item">
      <RadioGroup.Indicator />
    </RadioGroup.Item>
  </RadioGroup>
)
```

</Tabs.Content>

<Tabs.Content value="unstyled">

Use the `createRadioGroup` export to create a fully custom RadioGroup 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 `createRadioGroup`.

```tsx template=RadioGroupUnstyled

```

</Tabs.Content>

<Tabs.Content value="headless">

Using the `useRadioGroup` and `useRadioGroupItem` API, you can make your own RadioGroup from scratch.

```tsx template=RadioGroupHeadless

```

</Tabs.Content>

<Tabs.Content value="styled">

## API Reference

### RadioGroup

RadioGroup extend Stack views inheriting all the [Tamagui standard props](/docs/intro/props), plus:

<PropsTable
  data={[
    {
      name: 'name',
      type: 'string',
      description: `Equivalent to input name.`,
    },
    {
      name: 'value',
      type: 'string',
      description: `Give it a value (for use in HTML forms).`,
    },
    {
      name: 'required',
      type: 'boolean',
      description: `Sets aria-required.`,
    },
    {
      name: 'disabled',
      type: 'boolean',
      description: `Set aria-disabled on web, and disable touch on native for all children items`,
    },
    {
      name: 'native',
      type: 'boolean',
      description: `Renders native radio button on web.`,
      default: false,
    },
    {
      name: 'onValueChange',
      type: '(value: string) => void',
    },
    {
      name: 'accentColor',
      type: 'string',
      description: 'Sets `accent-color` style when `native` prop is enabled',
    },
  ]}
/>

### RadioGroup.Item

<PropsTable
  data={[
    {
      name: 'labeledBy',
      type: 'string',
      description: `Set aria-labeled-by on web`,
    },
    {
      name: 'value',
      type: 'string',
      description: `Input value for the radio button.`,
    },
    {
      name: 'disabled',
      type: 'boolean',
      description: `Set aria-disabled on web, and disable touch on native`,
    },
    {
      name: 'id',
      type: 'string',
      description: `Id used on the web`,
    },
    {
      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',
      type: 'boolean',
      default: 'false',
      description: `When true, remove all default tamagui styling.`,
    },
  ]}
/>

### RadioGroup.Indicator

RadioGroup.Indicator appears only when the parent Item is checked. it extends [ThemeableStack](/docs/components/stacks#themeablestack), getting [Tamagui standard props](/docs/intro/props) adding:

<PropsTable
  data={[
    {
      name: 'unstyled',
      required: false,
      type: `boolean`,
      description: `Removes all default Tamagui styles.`,
    },
  ]}
/>

</Tabs.Content>
<Tabs.Content value="unstyled">

## API Reference

### RadioGroup

RadioGroup extend Stack views inheriting all the [Tamagui standard props](/docs/intro/props), plus:

<PropsTable
  data={[
    {
      name: 'name',
      type: 'string',
      description: `Equivalent to input name.`,
    },
    {
      name: 'value',
      type: 'string',
      description: `Give it a value (for use in HTML forms).`,
    },
    {
      name: 'required',
      type: 'boolean',
      description: `Sets aria-required.`,
    },
    {
      name: 'disabled',
      type: 'boolean',
      description: `Set aria-disabled on web, and disable touch on native for all children items`,
    },
    {
      name: 'native',
      type: 'boolean',
      description: `Renders native radio button on web.`,
      default: false,
    },
    {
      name: 'onValueChange',
      type: '(value: string) => void',
    },
    {
      name: 'accentColor',
      type: 'string',
      description: 'Sets `accent-color` style when `native` prop is enabled',
    },
  ]}
/>

### RadioGroup.Item

<PropsTable
  data={[
    {
      name: 'labeledBy',
      type: 'string',
      description: `Set aria-labeled-by on web`,
    },
    {
      name: 'value',
      type: 'string',
      description: `Input value for the radio button.`,
    },
    {
      name: 'disabled',
      type: 'boolean',
      description: `Set aria-disabled on web, and disable touch on native`,
    },
    {
      name: 'id',
      type: 'string',
      description: `Id used on the web`,
    },
    {
      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',
      type: 'boolean',
      default: 'false',
      description: `When true, remove all default tamagui styling.`,
    },
  ]}
/>

### RadioGroup.Indicator

RadioGroup.Indicator appears only when the parent Item is checked. it extends [ThemeableStack](/docs/components/stacks#themeablestack), getting [Tamagui standard props](/docs/intro/props) adding:

<PropsTable
  data={[
    {
      name: 'unstyled',
      required: false,
      type: `boolean`,
      description: `Removes all default Tamagui styles.`,
    },
  ]}
/>

</Tabs.Content>

</Tabs>

## components/radio-group/1.3.0

---
title: RadioGroup
description: Use in a form to allow selecting one option from multiple.
name: radio-group
component: RadioGroup
package: radio-group
demoName: RadioGroup
---

<HeroContainer>
  <RadioGroupDemo />
</HeroContainer>

```tsx hero template=RadioGroup

```

<Highlights
  features={[
    `Accessible, easy to compose and customize.`,
    `Sizable & works controlled or uncontrolled.`,
    `Ability to opt-out to native radio button on web.`,
  ]}
/>

## Installation

RadioGroup is already installed in `tamagui`, or you can install it independently:

```bash
npm install @tamagui/radio-group
```

## Usage

```tsx
import { RadioGroup } from 'tamagui'

export default () => (
  <RadioGroup value="foo" gap="$2">
    <RadioGroup.Item value="foo" id="foo-radio-item">
      <RadioGroup.Indicator />
    </RadioGroup.Item>
    <RadioGroup.Item value="bar" id="bar-radio-item">
      <RadioGroup.Indicator />
    </RadioGroup.Item>
  </RadioGroup>
)
```

## API Reference

### RadioGroup

RadioGroup extend Stack views inheriting all the [Tamagui standard props](/docs/intro/props), plus:

<PropsTable
  data={[
    {
      name: 'name',
      type: 'string',
      description: `Equivalent to input name.`,
    },
    {
      name: 'value',
      type: 'string',
      description: `Give it a value (for use in HTML forms).`,
    },
    {
      name: 'required',
      type: 'boolean',
      description: `Sets aria-required.`,
    },
    {
      name: 'disabled',
      type: 'boolean',
      description: `Set aria-disabled on web, and disable touch on native for all children items`,
    },
    {
      name: 'native',
      type: 'boolean',
      description: `Renders native radio button on web.`,
      default: false,
    },
    {
      name: 'onValueChange',
      type: '(value: string) => void',
    },
    {
      name: 'accentColor',
      type: 'string',
      description: 'Sets `accent-color` style when `native` prop is enabled',
    },
  ]}
/>

### RadioGroup.Item

<PropsTable
  data={[
    {
      name: 'labeledBy',
      type: 'string',
      description: `Set aria-labeled-by on web`,
    },
    {
      name: 'value',
      type: 'string',
      description: `Input value for the radio button.`,
    },
    {
      name: 'disabled',
      type: 'boolean',
      description: `Set aria-disabled on web, and disable touch on native`,
    },
    {
      name: 'id',
      type: 'string',
      description: `Id used on the web`,
    },
    {
      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',
      type: 'boolean',
      default: 'false',
      description: `When true, remove all default tamagui styling.`,
    },
  ]}
/>

### RadioGroup.Indicator

RadioGroup.Indicator appears only when the parent Item is checked. it extends [ThemeableStack](/docs/components/stacks#themeablestack), getting [Tamagui standard props](/docs/intro/props) adding:

<PropsTable
  data={[
    {
      name: 'unstyled',
      required: false,
      type: `boolean`,
      description: `Removes all default Tamagui styles.`,
    },
  ]}
/>


## components/radio-group/2.0.0

---
title: RadioGroup
description: Use in a form to allow selecting one option from multiple.
name: radio-group
component: RadioGroup
package: radio-group
demoName: RadioGroup
---

<YStack className="is-sticky" />

<Tabs id="type" defaultValue="styled">
<Tabs.List>
  <TooltipSimple label="With Tamagui's default styles">
    <Tabs.Tab value="styled">Styled</Tabs.Tab>
  </TooltipSimple>
  <TooltipSimple label="No default styles, easy to customize">
    <Tabs.Tab value="unstyled">Unstyled</Tabs.Tab>
  </TooltipSimple>
  <TooltipSimple label="No dependency on Tamagui's core">
    <Tabs.Tab value="headless" label="No styles and no dependency on Tamagui's styling">Headless</Tabs.Tab>
  </TooltipSimple>
</Tabs.List>

<HeroContainer>
  <Tabs.Content
    value="styled"
    justifyContent="center"
    alignItems="center"
    width="100%"
  >
    <RadioGroupDemo />
  </Tabs.Content>
  <Tabs.Content
    value="unstyled"
    justifyContent="center"
    alignItems="center"
    width="100%"
  >
    <RadioGroupDemo />
  </Tabs.Content>
  <Tabs.Content
    value="headless"
    justifyContent="center"
    alignItems="center"
    width="100%"
  >
    <RadioGroupHeadlessDemo />
  </Tabs.Content>
</HeroContainer>

<Tabs.Content value="styled">
```tsx hero template=RadioGroup

```
</Tabs.Content>
<Tabs.Content value="unstyled">
```tsx hero template=RadioGroup

```
</Tabs.Content>
<Tabs.Content value="headless">
```tsx hero template=RadioGroupHeadless

```
</Tabs.Content>

<Highlights
  features={[
    `Accessible, easy to compose and customize.`,
    `Sizable & works controlled or uncontrolled.`,
    `Ability to opt-out to native radio button on web.`,
  ]}
/>

## Installation

<Tabs.Content value="styled">
RadioGroup is already installed in `tamagui`, or you can install it independently:

```bash
npm install @tamagui/radio-group
```

</Tabs.Content>

<Tabs.Content value="unstyled">
RadioGroup is already installed in `tamagui`, or you can install it independently:

```bash
npm install @tamagui/radio-group
```

</Tabs.Content>

<Tabs.Content value="headless">

To use the headless radio group, import from the `@tamagui/radio-headless` package. This package has no dependency on `@tamagui/core` and provides hooks for building custom radio groups with any styling solution.

```bash
npm install @tamagui/radio-headless
```

</Tabs.Content>

## Usage

<Tabs.Content value="styled">

```tsx
import { RadioGroup } from 'tamagui'

export default () => (
  <RadioGroup value="foo" gap="$2">
    <RadioGroup.Item value="foo" id="foo-radio-item">
      <RadioGroup.Indicator />
    </RadioGroup.Item>
    <RadioGroup.Item value="bar" id="bar-radio-item">
      <RadioGroup.Indicator />
    </RadioGroup.Item>
  </RadioGroup>
)
```

</Tabs.Content>

<Tabs.Content value="unstyled">

Use the `createRadioGroup` export to create a fully custom radio group that still uses the Tamagui styling system. You provide your own styled components and get back a fully functional radio group component.

```tsx
import { createRadioGroup, RadioGroupContext } from '@tamagui/radio-group'
import { styled, View } from 'tamagui'

const CustomFrame = styled(View, {
  // your styles
})

const CustomItem = styled(View, {
  context: RadioGroupContext,
  // your styles
})

const CustomIndicator = styled(View, {
  context: RadioGroupContext,
  // your styles
})

export const CustomRadioGroup = createRadioGroup({
  Frame: CustomFrame,
  Item: CustomItem,
  Indicator: CustomIndicator,
})
```

</Tabs.Content>

<Tabs.Content value="headless">

The `@tamagui/radio-headless` package provides three hooks for building custom radio groups:

- `useRadioGroup` - for the container/group
- `useRadioGroupItem` - for individual radio items
- `useRadioGroupItemIndicator` - for the indicator (checked state visual)

### Basic Usage

```tsx
import {
  useRadioGroup,
  useRadioGroupItem,
  useRadioGroupItemIndicator,
  RadioGroupContextValue,
  RadioGroupItemContextValue,
} from '@tamagui/radio-headless'
import { createContext, useContext } from 'react'

// Create contexts for the group and item
const RadioGroupContext = createContext<RadioGroupContextValue>({})
const RadioGroupItemContext = createContext<RadioGroupItemContextValue>({ checked: false })

function RadioGroup({ children, ...props }) {
  const { providerValue, frameAttrs } = useRadioGroup({
    orientation: 'vertical',
    ...props,
  })

  return (
    <RadioGroupContext.Provider value={providerValue}>
      <div {...frameAttrs} style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
        {children}
      </div>
    </RadioGroupContext.Provider>
  )
}

function RadioItem({ value, children }) {
  const {
    checked,
    providerValue,
    frameAttrs,
    bubbleInput,
  } = useRadioGroupItem({
    radioGroupContext: RadioGroupContext,
    value,
  })

  return (
    <RadioGroupItemContext.Provider value={providerValue}>
      <button
        {...frameAttrs}
        style={{
          display: 'flex',
          alignItems: 'center',
          gap: 8,
          padding: 8,
          border: '1px solid #ccc',
          borderRadius: 4,
          background: checked ? '#e0f2fe' : 'white',
        }}
      >
        <RadioIndicator />
        {children}
      </button>
      {bubbleInput}
    </RadioGroupItemContext.Provider>
  )
}

function RadioIndicator() {
  const { checked, ...indicatorProps } = useRadioGroupItemIndicator({
    radioGroupItemContext: RadioGroupItemContext,
  })

  return (
    <div
      {...indicatorProps}
      style={{
        width: 16,
        height: 16,
        borderRadius: '50%',
        border: '2px solid #3b82f6',
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'center',
      }}
    >
      {checked && (
        <div style={{ width: 8, height: 8, borderRadius: '50%', background: '#3b82f6' }} />
      )}
    </div>
  )
}

// Usage
function App() {
  return (
    <RadioGroup defaultValue="option1" onValueChange={console.log}>
      <RadioItem value="option1">Option 1</RadioItem>
      <RadioItem value="option2">Option 2</RadioItem>
      <RadioItem value="option3">Option 3</RadioItem>
    </RadioGroup>
  )
}
```

</Tabs.Content>

## API Reference

### RadioGroup

<Tabs.Content value="styled">

RadioGroup extends Stack views inheriting all the [Tamagui standard props](/docs/intro/props), plus:

</Tabs.Content>
<Tabs.Content value="unstyled">

When using `createRadioGroup`, you provide styled components:

- `Frame`: The root container component
- `Item`: The radio item component
- `Indicator`: The indicator component

</Tabs.Content>
<Tabs.Content value="headless">

The `useRadioGroup` hook accepts these options:

</Tabs.Content>

<PropsTable
  data={[
    {
      name: 'name',
      type: 'string',
      description: `Equivalent to input name.`,
    },
    {
      name: 'value',
      type: 'string',
      description: `The controlled value of the selected radio item.`,
    },
    {
      name: 'defaultValue',
      type: 'string',
      description: `The default value for uncontrolled usage.`,
    },
    {
      name: 'required',
      type: 'boolean',
      description: `Sets aria-required.`,
    },
    {
      name: 'disabled',
      type: 'boolean',
      description: `Set aria-disabled on web, and disable touch on native for all children items`,
    },
    {
      name: 'native',
      type: 'boolean',
      description: `Renders native radio button on web.`,
      default: 'false',
    },
    {
      name: 'onValueChange',
      type: '(value: string) => void',
      description: 'Callback when the selected value changes.',
    },
    {
      name: 'orientation',
      type: '"horizontal" | "vertical"',
      description: 'The orientation of the radio group.',
    },
    {
      name: 'accentColor',
      type: 'string',
      description: 'Sets `accent-color` style when `native` prop is enabled',
    },
  ]}
/>

<Tabs.Content value="styled">

### RadioGroup.Item

<PropsTable
  data={[
    {
      name: 'labeledBy',
      type: 'string',
      description: `Set aria-labeled-by on web`,
    },
    {
      name: 'value',
      type: 'string',
      description: `Input value for the radio button.`,
    },
    {
      name: 'disabled',
      type: 'boolean',
      description: `Set aria-disabled on web, and disable touch on native`,
    },
    {
      name: 'id',
      type: 'string',
      description: `Id used on the web`,
    },
    {
      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',
      type: 'boolean',
      default: 'false',
      description: `When true, remove all default tamagui styling.`,
    },
  ]}
/>

### RadioGroup.Indicator

RadioGroup.Indicator appears only when the parent Item is checked. It extends [ThemeableStack](/docs/components/stacks#themeablestack), getting [Tamagui standard props](/docs/intro/props) adding:

<PropsTable
  data={[
    {
      name: 'unstyled',
      required: false,
      type: `boolean`,
      description: `Removes all default Tamagui styles.`,
    },
  ]}
/>

</Tabs.Content>

<Tabs.Content value="headless">

### useRadioGroup Return Value

| Property | Type | Description |
|----------|------|-------------|
| `providerValue` | `RadioGroupContextValue` | Value to pass to your context provider |
| `frameAttrs` | `object` | Props to spread on the group container (role, aria-orientation, etc.) |
| `rovingFocusGroupAttrs` | `object` | Props for roving focus behavior (optional) |

### useRadioGroupItem

Hook for individual radio items. Requires a context containing the group state.

```tsx
const {
  checked,
  isFormControl,
  providerValue,
  bubbleInput,
  native,
  frameAttrs,
  rovingFocusGroupAttrs,
} = useRadioGroupItem({
  radioGroupContext: YourRadioGroupContext,
  value: 'option1',
  id: 'option1-id',
  labelledBy: 'label-id',
  disabled: false,
  onPress: () => {},
  onKeyDown: () => {},
  onFocus: () => {},
})
```

| Property | Type | Description |
|----------|------|-------------|
| `checked` | `boolean` | Whether this item is currently selected |
| `providerValue` | `object` | Context value for the indicator |
| `frameAttrs` | `object` | Props to spread on the item element |
| `bubbleInput` | `ReactNode` | Hidden input for form compatibility |
| `native` | `boolean` | Whether using native radio |
| `isFormControl` | `boolean` | Whether inside a form |

### useRadioGroupItemIndicator

Hook for the visual indicator of checked state.

```tsx
const { checked, ...dataAttrs } = useRadioGroupItemIndicator({
  radioGroupItemContext: YourRadioGroupItemContext,
  disabled: false,
})
```

| Property | Type | Description |
|----------|------|-------------|
| `checked` | `boolean` | Whether the parent item is checked |
| `data-state` | `string` | 'checked' or 'unchecked' |
| `data-disabled` | `string \| undefined` | Present when disabled |

</Tabs.Content>

</Tabs>


## components/scroll-view/1.0.0

---
title: ScrollView
description: React Native ScrollView with Tamagui props.
name: scrollView
component: ScrollView
package: scroll-view
demoName: ScrollView
---

<HeroContainer>
  <ScrollViewDemo />
</HeroContainer>

```tsx hero template=ScrollView

```

<Highlights
  features={[
    `All the features of React Native ScrollView.`,
    `Adds all the style properties of Tamagui.`,
  ]}
/>

## Installation

ScrollView is already installed in `tamagui`, or you can install it independently:

```bash
npm install @tamagui/scroll-view
```

## Usage

```tsx
import { ScrollView, YStack, ListItem } from 'tamagui'

export default () => (
  <ScrollView>
    <YStack>
      <ListItem>1</ListItem>
      <ListItem>2</ListItem>
      <ListItem>3</ListItem>
      <ListItem>4</ListItem>
    </YStack>
  </ScrollView>
)
```

## API Reference

### ScrollView

See [React Native ScrollView](https://reactnative.dev/docs/scrollview) and [Tamagui style props](https://tamagui.dev/docs/intro/props).


## components/scroll-view/2.0.0

---
title: ScrollView
description: React Native ScrollView with Tamagui props.
name: scrollView
component: ScrollView
package: scroll-view
demoName: ScrollView
---

<HeroContainer>
  <ScrollViewDemo />
</HeroContainer>

```tsx hero template=ScrollView

```

<Highlights
  features={[
    `All the features of React Native ScrollView.`,
    `Adds all the style properties of Tamagui.`,
  ]}
/>

## Installation

ScrollView is already installed in `tamagui`, or you can install it independently:

```bash
npm install @tamagui/scroll-view
```

## Usage

```tsx
import { ScrollView, YStack, ListItem } from 'tamagui'

export default () => (
  <ScrollView>
    <YStack>
      <ListItem>1</ListItem>
      <ListItem>2</ListItem>
      <ListItem>3</ListItem>
      <ListItem>4</ListItem>
    </YStack>
  </ScrollView>
)
```

## API Reference

### ScrollView

See [React Native ScrollView](https://reactnative.dev/docs/scrollview) and [Tamagui style props](https://tamagui.dev/docs/intro/props).


## components/select/1.0.0

---
title: Select
description: A simple select component
name: select
component: Select
package: select
demoName: Select
---

# Select

<Description>Show a menu of items users can select from one of.</Description>

<HeroContainer>
  <SelectDemo />
</HeroContainer>

```tsx hero template=Select

```

<Highlights
  features={[
    `Comes with styling, yet completely customizable and themeable.`,
    `Accepts animations, themes, size props and more.`,
    `Accessible, keyboard navigable, full-featured.`,
  ]}
/>

### Anatomy

```tsx
import { Select } from 'tamagui' // or '@tamagui/select'

export default () => (
  <Select defaultValue="">
    <Select.Trigger>
      <Select.Value placeholder="Search..." />
    </Select.Trigger>
    <Select.Content>
      <Select.ScrollUpButton />
      <Select.Viewport>
        <Select.Group>
          <Select.Label />
          <Select.Item>
            <Select.ItemText />
          </Select.Item>
        </Select.Group>
      </Select.Viewport>
      <Select.ScrollDownButton />
    </Select.Content>
  </Select>
)
```

<Notice>
  Note that Select only works on Native using the Adapt component to adapt it to a Sheet.
  See below for docs.
</Notice>

### API

#### &lt;Select /&gt;

Contains every component for the select:

<PropsTable
  data={[
    {
      name: 'id',
      type: 'string',
      description: `Optional for usage with Label`,
    },
    {
      name: 'size',
      type: 'SizeTokens',
      description: `Set the size of itself and pass to all inner elements`,
    },
    {
      name: 'children',
      type: 'React.ReactNode',
      description: `Select children API components`,
    },
    {
      name: 'value',
      type: 'string',
      description: `Controlled value`,
    },
    {
      name: 'defaultValue',
      type: 'string',
      description: `Default value`,
    },
    {
      name: 'onValueChange',
      type: '(value: string) => void',
      description: `Callback on value change`,
    },
    {
      name: 'open',
      type: 'boolean',
      description: `Controlled open value`,
    },
    {
      name: 'defaultOpen',
      type: 'boolean',
      description: `Default open value`,
    },
    {
      name: 'onOpenChange',
      type: '(open: boolean) => void',
      description: `Callback on open change`,
    },
    {
      name: 'dir',
      type: 'Direction',
      description: `Direction of text display`,
    },
    {
      name: 'name',
      type: 'string',
      description: `For use in forms`,
    },
  ]}
/>

#### &lt;Trigger /&gt;

Extends [ListItem](/docs/components/list-item) to give sizing, icons, and more.

#### &lt;Value /&gt;

Extends [Paragraph](/docs/components/text), adding:

<PropsTable
  data={[
    {
      name: 'placeholder',
      type: 'string',
      description: `Optional placeholder to show when no value selected`,
    },
  ]}
/>

#### &lt;Content /&gt;

Main container for Select content, used to contain the up/down arrows, no API beyond children.

#### &lt;ScrollUpButton /&gt;

Inside Content first, displays when you can scroll up, stuck to the top.

Extends [YStack](/docs/components/stacks).

#### &lt;ScrollDownButton /&gt;

Inside Content last, displays when you can scroll down, stuck to the bottom.

Extends [YStack](/docs/components/stacks).

#### &lt;Viewport /&gt;

Extends [ThemeableStack](/docs/components/stacks#themeablestack). Contains scrollable content items as children.

<PropsTable
  data={[
    {
      name: 'disableScroll',
      type: 'boolean',
      description: `Removes ability to scroll and all style and functionality related to scrolling`,
    },
  ]}
/>

#### &lt;Group /&gt;

Extends [YStack](/docs/components/stacks). Use only when grouping together items, alongside a Label as the first child.

#### &lt;Label /&gt;

Extends [ListItem](/docs/components/list-item). Used to label Groups.

#### &lt;Item /&gt;

Extends [ListItem](/docs/components/list-item). Used to add selectable values ot the list. Must provide an index as React Native doesn't give any escape hatch for us to configure that automatically.

<PropsTable
  data={[
    {
      name: 'index',
      type: 'number',
      required: true,
      description: `Incrementally starting from 0, matching its appearance in the list.`,
    },

    {
      name: 'value',
      type: 'string',
      description: `Provide a value that will be passed on selection.`,
    },

]}
/>

#### &lt;ItemText /&gt;

Extends [Paragraph](/docs/components/text). Used inside Item to provide unselectable text that will show above once selected in the parent Select.

#### &lt;Sheet /&gt;

When used alongside `<Adapt />`, Select will render as a sheet when that breakpoint is active.

This is the only way to render a Select on Native for now, as mobile apps tend to show Select very differently from web and Tamagui wants to present the right abstractions for each platform.

See [Sheet](/docs/components/sheet) for more props.

Must use `Select.SheetContents` inside the `Select.Sheet.Frame` to insert the contents given to `Select.Content`

```tsx
import { Select } from 'tamagui' // or '@tamagui/select'

export default () => (
  <Select defaultValue="">
    <Select.Trigger>
      <Select.Value placeholder="Search..." />
    </Select.Trigger>

    <Adapt when="maxMd" platform="touch">
      {/* or <Select.Sheet> */}
      <Sheet>
        <Sheet.Frame>
          <SheetContents />
        </Sheet.Frame>
        <Sheet.Overlay />
      </Sheet>
    </Adapt>

    <Select.Content>
      <Select.ScrollUpButton />
      <Select.Viewport>
        <Select.Group>
          <Select.Label />
          <Select.Item>
            <Select.ItemText />
          </Select.Item>
        </Select.Group>
      </Select.Viewport>
      <Select.ScrollDownButton />
    </Select.Content>
  </Select>
)
```


## components/select/1.128.0

---
title: Select
description: Show a menu of items that users can select from.
name: select
component: Select
package: select
demoName: Select
---

<HeroContainer>
  <SelectDemo />
</HeroContainer>

```tsx hero template=Select

```

<Highlights
  features={[
    `Comes with styling, yet completely customizable and themeable.`,
    `Accepts animations, themes, size props and more.`,
    `Accessible, keyboard navigable, full-featured.`,
  ]}
/>

## Installation

Select is already installed in `tamagui`, or you can install it independently:

```bash
npm install @tamagui/select
```

## Anatomy

```tsx
import { Select } from 'tamagui' // or '@tamagui/select'

export default () => (
  <Select defaultValue="">
    <Select.Trigger>
      <Select.Value placeholder="Search..." />
    </Select.Trigger>
    {/* Optional: Control focus behavior */}
    <Select.FocusScope loop trapped focusOnIdle={true}>
      <Select.Content>
        <Select.ScrollUpButton />
        <Select.Viewport>
          <Select.Group>
            <Select.Label />
            <Select.Item>
              <Select.ItemText />
            </Select.Item>
          </Select.Group>
        </Select.Viewport>
        <Select.ScrollDownButton />
      </Select.Content>
    </Select.FocusScope>
  </Select>
)
```

<Notice>
  Note that Select only works on Native using the Adapt component to adapt it to
  a Sheet. See below for docs.
</Notice>

## API Reference

### Select

Contains every component for the select:

<PropsTable
  data={[
    {
      name: 'id',
      type: 'string',
      description: `Optional for usage with Label`,
    },
    {
      name: 'size',
      type: 'SizeTokens',
      description: `Set the size of itself and pass to all inner elements`,
    },
    {
      name: 'children',
      type: 'React.ReactNode',
      description: `Select children API components`,
    },
    {
      name: 'value',
      type: 'string',
      description: `Controlled value`,
    },
    {
      name: 'defaultValue',
      type: 'string',
      description: `Default value`,
    },
    {
      name: 'onValueChange',
      type: '(value: string) => void',
      description: `Callback on value change`,
    },
    {
      name: 'open',
      type: 'boolean',
      description: `Controlled open value`,
    },
    {
      name: 'defaultOpen',
      type: 'boolean',
      description: `Default open value`,
    },
    {
      name: 'onOpenChange',
      type: '(open: boolean) => void',
      description: `Callback on open change`,
    },
    {
      name: 'dir',
      type: 'Direction',
      description: `Direction of text display`,
    },
    {
      name: 'name',
      type: 'string',
      description: `For use in forms`,
    },
    {
      name: 'native',
      type: 'NativeValue',
      description: `If passed, will render a native component instead of the custom one. Currently only \`web\` is supported.`,
    },
  ]}
/>

### Select.Trigger

Extends [ListItem](/docs/components/list-item) to give sizing, icons, and more.

#### Select.Value

Extends [Paragraph](/docs/components/text), adding:

<PropsTable
  data={[
    {
      name: 'placeholder',
      type: 'string',
      description: `Optional placeholder to show when no value selected`,
    },
  ]}
/>

#### SelectContent

Main container for Select content, used to contain the up/down arrows, no API beyond children.

#### Select.ScrollUpButton

Inside Content first, displays when you can scroll up, stuck to the top.

Extends [YStack](/docs/components/stacks).

### Select.ScrollDownButton

Inside Content last, displays when you can scroll down, stuck to the bottom.

Extends [YStack](/docs/components/stacks).

### Select.Viewport

Extends [ThemeableStack](/docs/components/stacks#themeablestack). Contains scrollable content items as children.

<PropsTable
  data={[
    {
      name: 'disableScroll',
      type: 'boolean',
      description: `Removes ability to scroll and all style and functionality related to scrolling`,
    },
    {
      name: 'unstyled',
      type: 'boolean',
      description: `Removes all default styles`,
    },
  ]}
/>

<Notice>
  Make sure to not pass `height` prop as that is managed internally because of
  UX reasons and having a fixed height will break that behaviour
</Notice>

### Select.Group

Extends [YStack](/docs/components/stacks). Use only when grouping together items, alongside a Label as the first child.

### Select.Label

Extends [ListItem](/docs/components/list-item). Used to label Groups.

### Select.Item

Extends [ListItem](/docs/components/list-item). Used to add selectable values to the list. Must provide an index as React Native doesn't give any escape hatch for us to configure that automatically.

<PropsTable
  data={[
    {
      name: 'index',
      type: 'number',
      required: true,
      description: `Incrementally starting from 0, matching its appearance in the list.`,
    },

    {
      name: 'value',
      type: 'string',
      description: `Provide a value that will be passed on selection.`,
    },

]}
/>

### Select.ItemText

Extends [Paragraph](/docs/components/text). Used inside Item to provide unselectable text that will show above once selected in the parent Select.

### Select.FocusScope

Provides access to the underlying FocusScope component used by Select for focus management. Can be used to control focus behavior from a parent component.

<PropsTable
  data={[
    {
      name: 'enabled',
      type: 'boolean',
      default: 'true',
      description: `Whether focus management is enabled`,
    },
    {
      name: 'loop',
      type: 'boolean',
      default: 'false',
      description: `When true, tabbing from last item will focus first tabbable and shift+tab from first item will focus last tabbable`,
    },
    {
      name: 'trapped',
      type: 'boolean',
      default: 'false',
      description: `When true, focus cannot escape the focus scope via keyboard, pointer, or programmatic focus`,
    },
    {
      name: 'focusOnIdle',
      type: 'boolean | number',
      default: 'false',
      description: `When true, waits for idle before focusing. When a number, waits that many ms. This prevents reflows during animations`,
    },
    {
      name: 'onMountAutoFocus',
      type: '(event: Event) => void',
      description: `Event handler called when auto-focusing on mount. Can be prevented`,
    },
    {
      name: 'onUnmountAutoFocus',
      type: '(event: Event) => void',
      description: `Event handler called when auto-focusing on unmount. Can be prevented`,
    },
  ]}
/>

### Select.Sheet

When used alongside `<Adapt />`, Select will render as a sheet when that breakpoint is active.

This is the only way to render a Select on Native for now, as mobile apps tend to show Select very differently from web and Tamagui wants to present the right abstractions for each platform.

See [Sheet](/docs/components/sheet) for more props.

Must use `Select.Adapt.Contents` inside the `Select.Sheet.Frame` to insert the contents given to `Select.Content`

```tsx
import { Select } from 'tamagui' // or '@tamagui/select'

export default () => (
  <Select defaultValue="">
    <Select.Trigger>
      <Select.Value placeholder="Search..." />
    </Select.Trigger>

    <Adapt when="maxMd" platform="touch">
      {/* or <Select.Sheet> */}
      <Sheet>
        <Sheet.Frame>
          <Adapt.Contents />
        </Sheet.Frame>
        <Sheet.Overlay />
      </Sheet>
    </Adapt>

    <Select.Content>
      <Select.ScrollUpButton />
      <Select.Viewport>
        <Select.Group>
          <Select.Label />
          <Select.Item>
            <Select.ItemText />
          </Select.Item>
        </Select.Group>
      </Select.Viewport>
      <Select.ScrollDownButton />
    </Select.Content>
  </Select>
)
```


## components/select/1.141.0

---
title: Select
description: Show a menu of items that users can select from.
name: select
component: Select
package: select
demoName: Select
---

<HeroContainer>
  <SelectDemo />
</HeroContainer>

```tsx hero template=Select

```

<Highlights
  features={[
    `Comes with styling, yet completely customizable and themeable.`,
    `Accepts animations, themes, size props and more.`,
    `Accessible, keyboard navigable, full-featured.`,
  ]}
/>

## Installation

Select is already installed in `tamagui`, or you can install it independently:

```bash
npm install @tamagui/select
```

## Anatomy

```tsx
import { Select } from 'tamagui' // or '@tamagui/select'

export default () => (
  <Select defaultValue="">
    <Select.Trigger>
      <Select.Value placeholder="Search..." />
    </Select.Trigger>
    {/* Optional: Control focus behavior */}
    <Select.FocusScope loop trapped focusOnIdle={true}>
      <Select.Content>
        <Select.ScrollUpButton />
        <Select.Viewport>
          <Select.Group>
            <Select.Label />
            <Select.Item>
              <Select.ItemText />
            </Select.Item>
          </Select.Group>
        </Select.Viewport>
        <Select.ScrollDownButton />
      </Select.Content>
    </Select.FocusScope>
  </Select>
)
```

<Notice>
  Note that Select only works on Native using the Adapt component to adapt it to
  a Sheet. See below for docs.
</Notice>

## Server-Side Rendering (SSR)

By default, Select uses a portal-based mechanism to display the selected item's text in the trigger. This happens in a client-side effect, which can cause hydration mismatches when using SSR.

To fix this, use the `renderValue` prop to provide the label synchronously during render:

```tsx
const items = [
  { value: 'apple', label: 'Apple' },
  { value: 'orange', label: 'Orange' },
]

// Create a lookup function
const getLabel = (value: string) =>
  items.find((item) => item.value === value)?.label

export function MySelect() {
  const [value, setValue] = React.useState('apple')

  return (
    <Select value={value} onValueChange={setValue} renderValue={getLabel}>
      <Select.Trigger>
        <Select.Value placeholder="Select a fruit..." />
      </Select.Trigger>

      <Select.Content>
        <Select.Viewport>
          {items.map((item, i) => (
            <Select.Item key={item.value} value={item.value} index={i}>
              <Select.ItemText>{item.label}</Select.ItemText>
            </Select.Item>
          ))}
        </Select.Viewport>
      </Select.Content>
    </Select>
  )
}
```

When `renderValue` is provided, it's called synchronously during render, ensuring the server and client render the same content.

## API Reference

### Select

Contains every component for the select:

<PropsTable
  data={[
    {
      name: 'id',
      type: 'string',
      description: `Optional for usage with Label`,
    },
    {
      name: 'size',
      type: 'SizeTokens',
      description: `Set the size of itself and pass to all inner elements`,
    },
    {
      name: 'children',
      type: 'React.ReactNode',
      description: `Select children API components`,
    },
    {
      name: 'value',
      type: 'string',
      description: `Controlled value`,
    },
    {
      name: 'defaultValue',
      type: 'string',
      description: `Default value`,
    },
    {
      name: 'onValueChange',
      type: '(value: string) => void',
      description: `Callback on value change`,
    },
    {
      name: 'open',
      type: 'boolean',
      description: `Controlled open value`,
    },
    {
      name: 'defaultOpen',
      type: 'boolean',
      description: `Default open value`,
    },
    {
      name: 'onOpenChange',
      type: '(open: boolean) => void',
      description: `Callback on open change`,
    },
    {
      name: 'dir',
      type: 'Direction',
      description: `Direction of text display`,
    },
    {
      name: 'name',
      type: 'string',
      description: `For use in forms`,
    },
    {
      name: 'native',
      type: 'NativeValue',
      description: `If passed, will render a native component instead of the custom one. Currently only \`web\` is supported.`,
    },
    {
      name: 'renderValue',
      type: '(value: string) => React.ReactNode',
      description: `Render function for the selected value. Use this for SSR support to avoid hydration mismatches. When provided, this is called synchronously during render to display the selected value.`,
    },
  ]}
/>

### Select.Trigger

Extends [ListItem](/docs/components/list-item) to give sizing, icons, and more.

#### Select.Value

Extends [Paragraph](/docs/components/text), adding:

<PropsTable
  data={[
    {
      name: 'placeholder',
      type: 'string',
      description: `Optional placeholder to show when no value selected`,
    },
  ]}
/>

#### SelectContent

Main container for Select content, used to contain the up/down arrows, no API beyond children.

#### Select.ScrollUpButton

Inside Content first, displays when you can scroll up, stuck to the top.

Extends [YStack](/docs/components/stacks).

### Select.ScrollDownButton

Inside Content last, displays when you can scroll down, stuck to the bottom.

Extends [YStack](/docs/components/stacks).

### Select.Viewport

Extends [ThemeableStack](/docs/components/stacks#themeablestack). Contains scrollable content items as children.

<PropsTable
  data={[
    {
      name: 'disableScroll',
      type: 'boolean',
      description: `Removes ability to scroll and all style and functionality related to scrolling`,
    },
    {
      name: 'unstyled',
      type: 'boolean',
      description: `Removes all default styles`,
    },
  ]}
/>

<Notice>
  Make sure to not pass `height` prop as that is managed internally because of
  UX reasons and having a fixed height will break that behaviour
</Notice>

### Select.Group

Extends [YStack](/docs/components/stacks). Use only when grouping together items, alongside a Label as the first child.

### Select.Label

Extends [ListItem](/docs/components/list-item). Used to label Groups.

### Select.Item

Extends [ListItem](/docs/components/list-item). Used to add selectable values to the list. Must provide an index as React Native doesn't give any escape hatch for us to configure that automatically.

<PropsTable
  data={[
    {
      name: 'index',
      type: 'number',
      required: true,
      description: `Incrementally starting from 0, matching its appearance in the list.`,
    },

    {
      name: 'value',
      type: 'string',
      description: `Provide a value that will be passed on selection.`,
    },

]}
/>

### Select.ItemText

Extends [Paragraph](/docs/components/text). Used inside Item to provide unselectable text that will show above once selected in the parent Select.

### Select.FocusScope

Provides access to the underlying FocusScope component used by Select for focus management. Can be used to control focus behavior from a parent component.

<PropsTable
  data={[
    {
      name: 'enabled',
      type: 'boolean',
      default: 'true',
      description: `Whether focus management is enabled`,
    },
    {
      name: 'loop',
      type: 'boolean',
      default: 'false',
      description: `When true, tabbing from last item will focus first tabbable and shift+tab from first item will focus last tabbable`,
    },
    {
      name: 'trapped',
      type: 'boolean',
      default: 'false',
      description: `When true, focus cannot escape the focus scope via keyboard, pointer, or programmatic focus`,
    },
    {
      name: 'focusOnIdle',
      type: 'boolean | number',
      default: 'false',
      description: `When true, waits for idle before focusing. When a number, waits that many ms. This prevents reflows during animations`,
    },
    {
      name: 'onMountAutoFocus',
      type: '(event: Event) => void',
      description: `Event handler called when auto-focusing on mount. Can be prevented`,
    },
    {
      name: 'onUnmountAutoFocus',
      type: '(event: Event) => void',
      description: `Event handler called when auto-focusing on unmount. Can be prevented`,
    },
  ]}
/>

### Select.Sheet

When used alongside `<Adapt />`, Select will render as a sheet when that breakpoint is active.

This is the only way to render a Select on Native for now, as mobile apps tend to show Select very differently from web and Tamagui wants to present the right abstractions for each platform.

See [Sheet](/docs/components/sheet) for more props.

Must use `Select.Adapt.Contents` inside the `Select.Sheet.Frame` to insert the contents given to `Select.Content`

```tsx
import { Select } from 'tamagui' // or '@tamagui/select'

export default () => (
  <Select defaultValue="">
    <Select.Trigger>
      <Select.Value placeholder="Search..." />
    </Select.Trigger>

    <Adapt when="maxMd" platform="touch">
      {/* or <Select.Sheet> */}
      <Sheet>
        <Sheet.Frame>
          <Adapt.Contents />
        </Sheet.Frame>
        <Sheet.Overlay />
      </Sheet>
    </Adapt>

    <Select.Content>
      <Select.ScrollUpButton />
      <Select.Viewport>
        <Select.Group>
          <Select.Label />
          <Select.Item>
            <Select.ItemText />
          </Select.Item>
        </Select.Group>
      </Select.Viewport>
      <Select.ScrollDownButton />
    </Select.Content>
  </Select>
)
```


## components/select/1.19.0

---
title: Select
description: A simple select component
name: select
component: Select
package: select
demoName: Select
---

# Select

<Description>Show a menu of items users can select from one of</Description>

<HeroContainer>
  <SelectDemo />
</HeroContainer>

```tsx hero template=Select

```

<Highlights
  features={[
    `Comes with styling, yet completely customizable and themeable.`,
    `Accepts animations, themes, size props and more.`,
    `Accessible, keyboard navigable, full-featured.`,
  ]}
/>

## Anatomy

```tsx
import { Select } from 'tamagui' // or '@tamagui/select'

export default () => (
  <Select defaultValue="">
    <Select.Trigger>
      <Select.Value placeholder="Search..." />
    </Select.Trigger>
    <Select.Content>
      <Select.ScrollUpButton />
      <Select.Viewport>
        <Select.Group>
          <Select.Label />
          <Select.Item>
            <Select.ItemText />
          </Select.Item>
        </Select.Group>
      </Select.Viewport>
      <Select.ScrollDownButton />
    </Select.Content>
  </Select>
)
```

<Notice>
  Note that Select only works on Native using the Adapt component to adapt it to a Sheet.
  See below for docs.
</Notice>

## API Reference

### Select

Contains every component for the select:

<PropsTable
  data={[
    {
      name: 'id',
      type: 'string',
      description: `Optional for usage with Label`,
    },
    {
      name: 'size',
      type: 'SizeTokens',
      description: `Set the size of itself and pass to all inner elements`,
    },
    {
      name: 'children',
      type: 'React.ReactNode',
      description: `Select children API components`,
    },
    {
      name: 'value',
      type: 'string',
      description: `Controlled value`,
    },
    {
      name: 'defaultValue',
      type: 'string',
      description: `Default value`,
    },
    {
      name: 'onValueChange',
      type: '(value: string) => void',
      description: `Callback on value change`,
    },
    {
      name: 'open',
      type: 'boolean',
      description: `Controlled open value`,
    },
    {
      name: 'defaultOpen',
      type: 'boolean',
      description: `Default open value`,
    },
    {
      name: 'onOpenChange',
      type: '(open: boolean) => void',
      description: `Callback on open change`,
    },
    {
      name: 'dir',
      type: 'Direction',
      description: `Direction of text display`,
    },
    {
      name: 'name',
      type: 'string',
      description: `For use in forms`,
    },
    {
      name: 'native',
      type: 'NativeValue',
      description: `If passed, will render a native component instead of the custom one. Currently only \`web\` is supported.`,
    },
  ]}
/>

### Select.Trigger

Extends [ListItem](/docs/components/list-item) to give sizing, icons, and more.

#### Select.Value

Extends [Paragraph](/docs/components/text), adding:

<PropsTable
  data={[
    {
      name: 'placeholder',
      type: 'string',
      description: `Optional placeholder to show when no value selected`,
    },
  ]}
/>

#### SelectContent

Main container for Select content, used to contain the up/down arrows, no API beyond children.

#### Select.ScrollUpButton

Inside Content first, displays when you can scroll up, stuck to the top.

Extends [YStack](/docs/components/stacks).

### Select.ScrollDownButton

Inside Content last, displays when you can scroll down, stuck to the bottom.

Extends [YStack](/docs/components/stacks).

### Select.Viewport

Extends [ThemeableStack](/docs/components/stacks#themeablestack). Contains scrollable content items as children.

<PropsTable
  data={[
    {
      name: 'disableScroll',
      type: 'boolean',
      description: `Removes ability to scroll and all style and functionality related to scrolling`,
    },
  ]}
/>

### Select.Group

Extends [YStack](/docs/components/stacks). Use only when grouping together items, alongside a Label as the first child.

### Select.Label

Extends [ListItem](/docs/components/list-item). Used to label Groups.

### Select.Item

Extends [ListItem](/docs/components/list-item). Used to add selectable values ot the list. Must provide an index as React Native doesn't give any escape hatch for us to configure that automatically.

<PropsTable
  data={[
    {
      name: 'index',
      type: 'number',
      required: true,
      description: `Incrementally starting from 0, matching its appearance in the list.`,
    },

    {
      name: 'value',
      type: 'string',
      description: `Provide a value that will be passed on selection.`,
    },

]}
/>

### Select.ItemText

Extends [Paragraph](/docs/components/text). Used inside Item to provide unselectable text that will show above once selected in the parent Select.

### Select.Sheet

When used alongside `<Adapt />`, Select will render as a sheet when that breakpoint is active.

This is the only way to render a Select on Native for now, as mobile apps tend to show Select very differently from web and Tamagui wants to present the right abstractions for each platform.

See [Sheet](/docs/components/sheet) for more props.

Must use `Select.SheetContents` inside the `Select.Sheet.Frame` to insert the contents given to `Select.Content`

```tsx
import { Select } from 'tamagui' // or '@tamagui/select'

export default () => (
  <Select defaultValue="">
    <Select.Trigger>
      <Select.Value placeholder="Search..." />
    </Select.Trigger>

    <Adapt when="maxMd" platform="touch">
      {/* or <Select.Sheet> */}
      <Sheet>
        <Sheet.Frame>
          <SheetContents />
        </Sheet.Frame>
        <Sheet.Overlay />
      </Sheet>
    </Adapt>

    <Select.Content>
      <Select.ScrollUpButton />
      <Select.Viewport>
        <Select.Group>
          <Select.Label />
          <Select.Item>
            <Select.ItemText />
          </Select.Item>
        </Select.Group>
      </Select.Viewport>
      <Select.ScrollDownButton />
    </Select.Content>
  </Select>
)
```


## components/select/1.40.0

---
title: Select
description: Show a menu of items that users can select from.
name: select
component: Select
package: select
demoName: Select
---

<HeroContainer>
  <SelectDemo />
</HeroContainer>

```tsx hero template=Select

```

<Highlights
  features={[
    `Comes with styling, yet completely customizable and themeable.`,
    `Accepts animations, themes, size props and more.`,
    `Accessible, keyboard navigable, full-featured.`,
  ]}
/>

## Installation

Select is already installed in `tamagui`, or you can install it independently:

```bash
npm install @tamagui/select
```

## Anatomy

```tsx
import { Select } from 'tamagui' // or '@tamagui/select'

export default () => (
  <Select defaultValue="">
    <Select.Trigger>
      <Select.Value placeholder="Search..." />
    </Select.Trigger>
    <Select.Content>
      <Select.ScrollUpButton />
      <Select.Viewport>
        <Select.Group>
          <Select.Label />
          <Select.Item>
            <Select.ItemText />
          </Select.Item>
        </Select.Group>
      </Select.Viewport>
      <Select.ScrollDownButton />
    </Select.Content>
  </Select>
)
```

<Notice>
  Note that Select only works on Native using the Adapt component to adapt it to
  a Sheet. See below for docs.
</Notice>

## API Reference

### Select

Contains every component for the select:

<PropsTable
  data={[
    {
      name: 'id',
      type: 'string',
      description: `Optional for usage with Label`,
    },
    {
      name: 'size',
      type: 'SizeTokens',
      description: `Set the size of itself and pass to all inner elements`,
    },
    {
      name: 'children',
      type: 'React.ReactNode',
      description: `Select children API components`,
    },
    {
      name: 'value',
      type: 'string',
      description: `Controlled value`,
    },
    {
      name: 'defaultValue',
      type: 'string',
      description: `Default value`,
    },
    {
      name: 'onValueChange',
      type: '(value: string) => void',
      description: `Callback on value change`,
    },
    {
      name: 'open',
      type: 'boolean',
      description: `Controlled open value`,
    },
    {
      name: 'defaultOpen',
      type: 'boolean',
      description: `Default open value`,
    },
    {
      name: 'onOpenChange',
      type: '(open: boolean) => void',
      description: `Callback on open change`,
    },
    {
      name: 'dir',
      type: 'Direction',
      description: `Direction of text display`,
    },
    {
      name: 'name',
      type: 'string',
      description: `For use in forms`,
    },
    {
      name: 'native',
      type: 'NativeValue',
      description: `If passed, will render a native component instead of the custom one. Currently only \`web\` is supported.`,
    },
  ]}
/>

### Select.Trigger

Extends [ListItem](/docs/components/list-item) to give sizing, icons, and more.

#### Select.Value

Extends [Paragraph](/docs/components/text), adding:

<PropsTable
  data={[
    {
      name: 'placeholder',
      type: 'string',
      description: `Optional placeholder to show when no value selected`,
    },
  ]}
/>

#### SelectContent

Main container for Select content, used to contain the up/down arrows, no API beyond children.

#### Select.ScrollUpButton

Inside Content first, displays when you can scroll up, stuck to the top.

Extends [YStack](/docs/components/stacks).

### Select.ScrollDownButton

Inside Content last, displays when you can scroll down, stuck to the bottom.

Extends [YStack](/docs/components/stacks).

### Select.Viewport

Extends [ThemeableStack](/docs/components/stacks#themeablestack). Contains scrollable content items as children.

<PropsTable
  data={[
    {
      name: 'disableScroll',
      type: 'boolean',
      description: `Removes ability to scroll and all style and functionality related to scrolling`,
    },
    {
      name: 'unstyled',
      type: 'boolean',
      description: `Removes all default styles`,
    },
  ]}
/>

<Notice>
  Make sure to not pass `height` prop as that is managed internally because of
  UX reasons and having a fixed height will break that behaviour
</Notice>

### Select.Group

Extends [YStack](/docs/components/stacks). Use only when grouping together items, alongside a Label as the first child.

### Select.Label

Extends [ListItem](/docs/components/list-item). Used to label Groups.

### Select.Item

Extends [ListItem](/docs/components/list-item). Used to add selectable values ot the list. Must provide an index as React Native doesn't give any escape hatch for us to configure that automatically.

<PropsTable
  data={[
    {
      name: 'index',
      type: 'number',
      required: true,
      description: `Incrementally starting from 0, matching its appearance in the list.`,
    },

    {
      name: 'value',
      type: 'string',
      description: `Provide a value that will be passed on selection.`,
    },

]}
/>

### Select.ItemText

Extends [Paragraph](/docs/components/text). Used inside Item to provide unselectable text that will show above once selected in the parent Select.

### Select.Sheet

When used alongside `<Adapt />`, Select will render as a sheet when that breakpoint is active.

This is the only way to render a Select on Native for now, as mobile apps tend to show Select very differently from web and Tamagui wants to present the right abstractions for each platform.

See [Sheet](/docs/components/sheet) for more props.

Must use `Select.Adapt.Contents` inside the `Select.Sheet.Frame` to insert the contents given to `Select.Content`

```tsx
import { Select } from 'tamagui' // or '@tamagui/select'

export default () => (
  <Select defaultValue="">
    <Select.Trigger>
      <Select.Value placeholder="Search..." />
    </Select.Trigger>

    <Adapt when="maxMd" platform="touch">
      {/* or <Select.Sheet> */}
      <Sheet>
        <Sheet.Frame>
          <Adapt.Contents />
        </Sheet.Frame>
        <Sheet.Overlay />
      </Sheet>
    </Adapt>

    <Select.Content>
      <Select.ScrollUpButton />
      <Select.Viewport>
        <Select.Group>
          <Select.Label />
          <Select.Item>
            <Select.ItemText />
          </Select.Item>
        </Select.Group>
      </Select.Viewport>
      <Select.ScrollDownButton />
    </Select.Content>
  </Select>
)
```


## components/select/2.0.0

---
title: Select
description: Show a menu of items that users can select from.
name: select
component: Select
package: select
demoName: Select
---

<HeroContainer>
  <SelectDemo />
</HeroContainer>

```tsx hero template=Select

```

<Highlights
  features={[
    `Comes with styling, yet completely customizable and themeable.`,
    `Accepts animations, themes, size props and more.`,
    `Accessible, keyboard navigable, full-featured.`,
  ]}
/>

## Installation

Select is already installed in `tamagui`, or you can install it independently:

```bash
npm install @tamagui/select
```

<Notice theme="blue">
  For native apps, we recommend [setting up native portals](/docs/components/portal#native-portal-setup-recommended) to preserve React context inside Select content.
</Notice>

## Anatomy

```tsx
import { Select } from 'tamagui' // or '@tamagui/select'

export default () => (
  <Select defaultValue="">
    <Select.Trigger>
      <Select.Value placeholder="Search..." />
    </Select.Trigger>
    {/* Optional: Control focus behavior */}
    <Select.FocusScope loop trapped focusOnIdle={true}>
      <Select.Content>
        <Select.ScrollUpButton />
        <Select.Viewport>
          <Select.Group>
            <Select.Label />
            <Select.Item>
              <Select.ItemText />
            </Select.Item>
          </Select.Group>
        </Select.Viewport>
        <Select.ScrollDownButton />
      </Select.Content>
    </Select.FocusScope>
  </Select>
)
```

<Notice>
  Note that Select only works on Native using the Adapt component to adapt it to
  a Sheet. See below for docs.
</Notice>

## API Reference

### Select

Contains every component for the select:

<PropsTable
  data={[
    {
      name: 'id',
      type: 'string',
      description: `Optional for usage with Label`,
    },
    {
      name: 'size',
      type: 'SizeTokens',
      description: `Set the size of itself and pass to all inner elements`,
    },
    {
      name: 'children',
      type: 'React.ReactNode',
      description: `Select children API components`,
    },
    {
      name: 'value',
      type: 'string',
      description: `Controlled value`,
    },
    {
      name: 'defaultValue',
      type: 'string',
      description: `Default value`,
    },
    {
      name: 'onValueChange',
      type: '(value: string) => void',
      description: `Callback on value change`,
    },
    {
      name: 'open',
      type: 'boolean',
      description: `Controlled open value`,
    },
    {
      name: 'defaultOpen',
      type: 'boolean',
      description: `Default open value`,
    },
    {
      name: 'onOpenChange',
      type: '(open: boolean) => void',
      description: `Callback on open change`,
    },
    {
      name: 'dir',
      type: 'Direction',
      description: `Direction of text display`,
    },
    {
      name: 'name',
      type: 'string',
      description: `For use in forms`,
    },
    {
      name: 'native',
      type: 'NativeValue',
      description: `If passed, will render a native component instead of the custom one. Currently only \`web\` is supported.`,
    },
    {
      name: 'renderValue',
      type: '(value: string) => ReactNode',
      description: `Render function for the selected value. Useful for SSR when you need custom display text. By default, Select falls back to showing the raw value for SSR compatibility.`,
    },
  ]}
/>

### Select.Trigger

Extends [ListItem](/docs/components/list-item) to give sizing, icons, and more.

#### Select.Value

Extends [Paragraph](/docs/components/text), adding:

<PropsTable
  data={[
    {
      name: 'placeholder',
      type: 'string',
      description: `Optional placeholder to show when no value selected`,
    },
  ]}
/>

#### SelectContent

Main container for Select content, used to contain the up/down arrows, no API beyond children.

#### Select.ScrollUpButton

Inside Content first, displays when you can scroll up, stuck to the top.

Extends [YStack](/docs/components/stacks).

### Select.ScrollDownButton

Inside Content last, displays when you can scroll down, stuck to the bottom.

Extends [YStack](/docs/components/stacks).

### Select.Viewport

Extends [ThemeableStack](/docs/components/stacks#themeablestack). Contains scrollable content items as children.

<PropsTable
  data={[
    {
      name: 'disableScroll',
      type: 'boolean',
      description: `Removes ability to scroll and all style and functionality related to scrolling`,
    },
    {
      name: 'unstyled',
      type: 'boolean',
      description: `Removes all default styles`,
    },
  ]}
/>

<Notice>
  Make sure to not pass `height` prop as that is managed internally because of
  UX reasons and having a fixed height will break that behaviour
</Notice>

### Select.Group

Extends [YStack](/docs/components/stacks). Use only when grouping together items, alongside a Label as the first child.

### Select.Label

Extends [SizableText](/docs/components/text). Used to label Groups. Includes size-based padding and minHeight for consistent appearance with other Select items.

### Select.Item

Extends [ListItem](/docs/components/list-item). Used to add selectable values to the list. Must provide an index as React Native doesn't give any escape hatch for us to configure that automatically.

<PropsTable
  data={[
    {
      name: 'index',
      type: 'number',
      required: true,
      description: `Incrementally starting from 0, matching its appearance in the list.`,
    },

    {
      name: 'value',
      type: 'string',
      description: `Provide a value that will be passed on selection.`,
    },

]}
/>

### Select.ItemText

Extends [Paragraph](/docs/components/text). Used inside Item to provide unselectable text that will show above once selected in the parent Select.

### Select.Indicator

An animated indicator that highlights the currently focused item. Place it inside `Select.Viewport` to enable a smooth sliding highlight animation as users navigate through options.

```tsx
<Select.Viewport>
  <Select.Indicator transition="quick" />
  <Select.Group>
    {/* items */}
  </Select.Group>
</Select.Viewport>
```

Use the `transition` prop to control the animation speed. You can use any animation name from your config like `quick`, `quicker`, or `quickest`.

<Notice>
  By default, Select uses the item's `hoverStyle` and `pressStyle` for hover feedback. Add `Select.Indicator` for a smoother animated effect. If using the indicator, you may want to set items to have transparent hover styles to avoid visual conflict.
</Notice>

### Select.FocusScope

Provides access to the underlying FocusScope component used by Select for focus management. Can be used to control focus behavior from a parent component.

<PropsTable
  data={[
    {
      name: 'enabled',
      type: 'boolean',
      default: 'true',
      description: `Whether focus management is enabled`,
    },
    {
      name: 'loop',
      type: 'boolean',
      default: 'false',
      description: `When true, tabbing from last item will focus first tabbable and shift+tab from first item will focus last tabbable`,
    },
    {
      name: 'trapped',
      type: 'boolean',
      default: 'false',
      description: `When true, focus cannot escape the focus scope via keyboard, pointer, or programmatic focus`,
    },
    {
      name: 'focusOnIdle',
      type: 'boolean | number',
      default: 'false',
      description: `When true, waits for idle before focusing. When a number, waits that many ms. This prevents reflows during animations`,
    },
    {
      name: 'onMountAutoFocus',
      type: '(event: Event) => void',
      description: `Event handler called when auto-focusing on mount. Can be prevented`,
    },
    {
      name: 'onUnmountAutoFocus',
      type: '(event: Event) => void',
      description: `Event handler called when auto-focusing on unmount. Can be prevented`,
    },
  ]}
/>

### Select.Sheet

When used alongside `<Adapt />`, Select will render as a sheet when that breakpoint is active.

This is the only way to render a Select on Native for now, as mobile apps tend to show Select very differently from web and Tamagui wants to present the right abstractions for each platform.

See [Sheet](/docs/components/sheet) for more props.

Must use `Select.Adapt.Contents` inside the `Select.Sheet.Frame` to insert the contents given to `Select.Content`

```tsx
import { Select } from 'tamagui' // or '@tamagui/select'

export default () => (
  <Select defaultValue="">
    <Select.Trigger>
      <Select.Value placeholder="Search..." />
    </Select.Trigger>

    <Adapt when="maxMd" platform="touch">
      {/* or <Select.Sheet> */}
      <Sheet>
        <Sheet.Frame>
          <Adapt.Contents />
        </Sheet.Frame>
        <Sheet.Overlay />
      </Sheet>
    </Adapt>

    <Select.Content>
      <Select.ScrollUpButton />
      <Select.Viewport>
        <Select.Group>
          <Select.Label />
          <Select.Item>
            <Select.ItemText />
          </Select.Item>
        </Select.Group>
      </Select.Viewport>
      <Select.ScrollDownButton />
    </Select.Content>
  </Select>
)
```


## components/separator/1.0.0

---
title: Separator
description: Create borders between components.
name: separator
component: Separator
package: separator
demoName: Separator
---

<HeroContainer>
  <SeparatorDemo />
</HeroContainer>

```tsx hero template=Separator

```

<Highlights features={[`Supports horizontal and vertical orientation.`]} />

## Installation

Separator is already installed in `tamagui`, or you can install it independently:

```bash
npm install @tamagui/separator
```

## Usage

Separator uses the `borderWidth` and `borderColor` attribute for its style, so be sure to override those when extending it.

```tsx
export default () => (
  <XStack alignItems="center">
    <Paragraph>Blog</Paragraph>
    <Separator alignSelf="stretch" vertical />
    <Paragraph>Docs</Paragraph>
    <Separator alignSelf="stretch" vertical />
    <Paragraph>Source</Paragraph>
  </XStack>
)

```

## API Reference

### Separator props

Separators extend Stack views inheriting all the [Tamagui standard props](/docs/intro/props), plus:

<PropsTable
  data={[
    {
      name: 'vertical',
      required: false,
      type: 'boolean',
      description: `Show vertical separator.`,
    },
  ]}
/>


## components/separator/2.0.0

---
title: Separator
description: Create borders between components.
name: separator
component: Separator
package: separator
demoName: Separator
---

<HeroContainer>
  <SeparatorDemo />
</HeroContainer>

```tsx hero template=Separator

```

<Highlights features={[`Supports horizontal and vertical orientation.`]} />

## Installation

Separator is already installed in `tamagui`, or you can install it independently:

```bash
npm install @tamagui/separator
```

## Usage

Separator uses the `borderWidth` and `borderColor` attribute for its style, so be sure to override those when extending it.

```tsx
export default () => (
  <XStack alignItems="center">
    <Paragraph>Blog</Paragraph>
    <Separator alignSelf="stretch" vertical />
    <Paragraph>Docs</Paragraph>
    <Separator alignSelf="stretch" vertical />
    <Paragraph>Source</Paragraph>
  </XStack>
)

```

## API Reference

### Separator props

Separators extend Stack views inheriting all the [Tamagui standard props](/docs/intro/props), plus:

<PropsTable
  data={[
    {
      name: 'vertical',
      required: false,
      type: 'boolean',
      description: `Show vertical separator.`,
    },
  ]}
/>


## components/shapes/1.0.0

---
title: Shapes
description: Easy to use Square and Circle.
name: shapes
component: Square
package: shapes
demoName: Shapes
---

<HeroContainer>
  <ShapesDemo />
</HeroContainer>

```tsx hero template=Shapes

```

<Highlights
  features={[
    'Accepts size props as number or token value.',
    'Sets min and max width and height.',
  ]}
/>

## Installation

Shapes is already installed in `tamagui`, or you can install it independently:

```bash
npm install @tamagui/shapes
```

## Usage

Tamagui supports sizing shapes using your `size` tokens, or plain numbers.

```tsx
import { Circle, Square } from 'tamagui'

export default () => (
  <>
    <Square size="$4" />
    <Square size={100} />
    <Circle size="$4" />
    <Circle size={100} />
  </>
)
```

## API Reference

### Square

`Square` extends Stack views inheriting all the [Tamagui standard props](/docs/intro/props), plus:

<PropsTable
  data={[
    {
      name: 'size',
      required: false,
      type: 'string | tokens.size',
      description: `Set a size, number or one of the size token values.`,
    },
    {
      name: 'circular',
      required: false,
      type: 'boolean',
      description: `Rounds the border radius to be circular.`,
    },
  ]}
/>

### Circle

`Circle` extends [Square](#square), setting `circular` to `true`.


## components/shapes/2.0.0

---
title: Shapes
description: Easy to use Square and Circle.
name: shapes
component: Square
package: shapes
demoName: Shapes
---

<HeroContainer>
  <ShapesDemo />
</HeroContainer>

```tsx hero template=Shapes

```

<Highlights
  features={[
    'Accepts size props as number or token value.',
    'Sets min and max width and height.',
  ]}
/>

## Installation

Shapes is already installed in `tamagui`, or you can install it independently:

```bash
npm install @tamagui/shapes
```

## Usage

Tamagui supports sizing shapes using your `size` tokens, or plain numbers.

```tsx
import { Circle, Square } from 'tamagui'

export default () => (
  <>
    <Square size="$4" />
    <Square size={100} />
    <Circle size="$4" />
    <Circle size={100} />
  </>
)
```

## API Reference

### Square

`Square` extends Stack views inheriting all the [Tamagui standard props](/docs/intro/props), plus:

<PropsTable
  data={[
    {
      name: 'size',
      required: false,
      type: 'string | tokens.size',
      description: `Set a size, number or one of the size token values.`,
    },
    {
      name: 'circular',
      required: false,
      type: 'boolean',
      description: `Rounds the border radius to be circular.`,
    },
  ]}
/>

### Circle

`Circle` extends [Square](#square), setting `circular` to `true`.


## components/sheet/1.0.0

---
title: Sheet
description: A simple sheet component
name: sheet
component: Sheet
package: sheet
demoName: Sheet
---

# Sheet

<Description>A bottom sheet that slides up.</Description>

<HeroContainer showAnimationDriverControl>
  <SheetDemo />
</HeroContainer>

```tsx hero template=Sheet

```

<Highlights
  features={[
    `Lightweight implementation with dragging support.`,
    `Multiple snap point points and a handle.`,
    `Automatically adjusts to screen size.`,
    `Accepts animations, themes, size props and more.`,
  ]}
/>

### Anatomy

```tsx
import { Sheet } from 'tamagui' // or '@tamagui/sheet'

export default () => (
  <Sheet>
    <Sheet.Overlay />
    <Sheet.Handle />
    <Sheet.Frame>
      {/* ...inner contents */}
    </Sheet.Frame>
  </Sheet>
)
```

### API

#### &lt;Sheet /&gt;

Contains every component for the sheet.

<PropsTable
  data={[
    { name: 'open', type: 'boolean', description: `Set to use as controlled component.` },
    {
      name: 'defaultOpen',
      type: 'boolean',
      description: `Uncontrolled open state on mount.`,
    },
    {
      name: 'onOpenChange',
      type: '(open: boolean) => 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.`,
    },
  ]}
/>

#### &lt;Overlay /&gt;

Displays behind Frame. Extends [YStack](/docs/components/stacks).

#### &lt;Frame /&gt;

Contains the content. Extends [YStack](/docs/components/stacks).

#### &lt;Handle /&gt;

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).

#### &lt;Scrollview /&gt;

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

<Description>A bottom sheet that slides up</Description>

<HeroContainer showAnimationDriverControl>
  <SheetDemo />
</HeroContainer>

```tsx hero template=Sheet

```

<Highlights
  features={[
    `Lightweight implementation with dragging support.`,
    `Multiple snap point points and a handle.`,
    `Automatically adjusts to screen size.`,
    `Accepts animations, themes, size props and more.`,
  ]}
/>

## 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 (
    <PortalProvider shouldAddRootHost>
      <YourApp />
    </PortalProvider>
  )
}

export default App
```

<PropsTable
  data={[
    {
      name: 'shouldAddRootHost',
      type: 'boolean',
      required: false,
      description: `Defines whether to add a default root host or not.`,
    },
  ]}
/>

## Anatomy

```tsx
import { Sheet } from 'tamagui' // or '@tamagui/sheet'

export default () => (
  <Sheet>
    <Sheet.Overlay />
    <Sheet.Handle />
    <Sheet.Frame>{/* ...inner contents */}</Sheet.Frame>
  </Sheet>
)
```

## Snap points

By default, snap points are treated as percentages.

```tsx
<Sheet snapPoints={[85, 50]}> // 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 { Stack, styled } from '@tamagui/core'
import { createSheet } from '@tamagui/sheet'

const Handle = styled(Stack, {
  variants: {
    open: {
      true: {
        opacity: 0.35,
      },
      false: {
        opacity: 0.5,
      },
    },
  } as const,
})

const Overlay = styled(Stack, {
  variants: {
    open: {
      true: {
        opacity: 1,
        pointerEvents: 'auto',
      },
      false: {
        opacity: 0,
        pointerEvents: 'none',
      },
    },
  } as const,
})

const Frame = styled(Stack, {
  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 (
  <Sheet native>
    {/* ... the rest of your sheet */}
  </Sheet>
)
```

## API Reference

### Sheet

Contains every component for the sheet.

<PropsTable
  data={[
    { name: 'open', type: 'boolean', description: `Set to use as controlled component.` },
    {
      name: 'defaultOpen',
      type: 'boolean',
      description: `Uncontrolled open state on mount.`,
    },
    {
      name: 'onOpenChange',
      type: '(open: boolean) => 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.',
    },
  ]}
/>


<Notice>
  If using `modal={true}` (which is `true` by default), refer to the [PortalProvider installation](/ui/sheet/1.59.0#portalprovider) for more information.
</Notice>

### Sheet.Overlay

Displays behind Frame. Extends [YStack](/docs/components/stacks).

### Sheet.Frame

Contains the content. Extends [YStack](/docs/components/stacks).

<PropsTable
  data={[
    {
      name: 'disableHideBottomOverflow',
      type: 'boolean',
      description: `Disables Sheet cloning the Frame and positioning it below the frame, which helps to hide content that may appear underneath when spring animations bounce past 100%.`,
    },
  ]}
/>

### 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.

<PropsTable
  data={[
    { name: 'open', type: 'boolean', description: `Set to use as controlled component.` },
    {
      name: 'setOpen',
      type: 'Function',
      description: `Control the open state of the sheet.`,
    },
    {
      name: 'setPosition',
      type: '(index: number) => 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.123.18

---
title: Sheet
description: A bottom sheet that animates.
name: sheet
component: Sheet
package: sheet
demoName: Sheet
---

<HeroContainer showAnimationDriverControl>
  <SheetDemo />
</HeroContainer>

```tsx hero template=Sheet

```

<Highlights
  features={[
    `Lightweight implementation with dragging support.`,
    `Multiple snap point points and a handle.`,
    `Automatically adjusts to screen size.`,
    `Accepts animations, themes, size props and more.`,
  ]}
/>

## 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 () => (
  <Sheet>
    <Sheet.Overlay />
    <Sheet.Handle />
    <Sheet.Frame>{/* ...inner contents */}</Sheet.Frame>
  </Sheet>
)
```

### API

#### &lt;Sheet /&gt;

Contains every component for the sheet.

<PropsTable
  data={[
    { name: 'open', type: 'boolean', description: `Set to use as controlled component.` },
    {
      name: 'defaultOpen',
      type: 'boolean',
      description: `Uncontrolled open state on mount.`,
    },
    {
      name: 'onOpenChange',
      type: '(open: boolean) => 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.',
    },
  ]}
/>

#### &lt;Overlay /&gt;

Displays behind Frame. Extends [YStack](/docs/components/stacks).

#### &lt;Frame /&gt;

Contains the content. Extends [YStack](/docs/components/stacks).

#### &lt;Handle /&gt;

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).

#### &lt;Scrollview /&gt;

Allows scrolling within Sheet. Extends [Scrollview](/docs/components/scroll-view).

### Notes

For Android you need to manually re-propagate any context when using `modal`. This is because React Native doesn't support portals yet.

### Native support

We've deprecated the `native` prop in favor of using Adapt.


## components/sheet/1.130.0

---
title: Sheet
description: A bottom sheet that animates.
name: sheet
component: Sheet
package: sheet
demoName: Sheet
---

<HeroContainer showAnimationDriverControl>
  <SheetDemo />
</HeroContainer>

```tsx hero template=Sheet

```

<Highlights
  features={[
    `Lightweight implementation with dragging support.`,
    `Multiple snap points and a handle.`,
    `Automatically adjusts to screen size.`,
    `Accepts animations, themes, size props and more.`,
  ]}
/>

## 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 () => (
  <Sheet>
    <Sheet.Overlay />
    <Sheet.Handle />
    <Sheet.Frame>{/* ...inner contents */}</Sheet.Frame>
  </Sheet>
)
```

### API

#### &lt;Sheet /&gt;

Contains every component for the sheet.

<PropsTable
  data={[
    {
      name: 'open',
      type: 'boolean',
      description: `Set to use as controlled component.`,
    },
    {
      name: 'defaultOpen',
      type: 'boolean',
      description: `Uncontrolled open state on mount.`,
    },
    {
      name: 'onOpenChange',
      type: '(open: boolean) => 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.`,
    },
  ]}
/>

#### &lt;Overlay /&gt;

Displays behind Frame. Extends [YStack](/docs/components/stacks).

#### &lt;Frame /&gt;

Contains the content. Extends [YStack](/docs/components/stacks).

#### &lt;Handle /&gt;

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).

#### &lt;ScrollView /&gt;

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.21.0

---
title: Sheet
description: A simple sheet component
name: sheet
component: Sheet
package: sheet
demoName: Sheet
---

# Sheet

<Description>A bottom sheet that slides up</Description>

<HeroContainer showAnimationDriverControl>
  <SheetDemo />
</HeroContainer>

```tsx hero template=Sheet

```

<Highlights
  features={[
    `Lightweight implementation with dragging support.`,
    `Multiple snap point points and a handle.`,
    `Automatically adjusts to screen size.`,
    `Accepts animations, themes, size props and more.`,
  ]}
/>

### Anatomy

```tsx
import { Sheet } from 'tamagui' // or '@tamagui/sheet'

export default () => (
  <Sheet>
    <Sheet.Overlay />
    <Sheet.Handle />
    <Sheet.Frame>{/* ...inner contents */}</Sheet.Frame>
  </Sheet>
)
```

### API

#### &lt;Sheet /&gt;

Contains every component for the sheet.

<PropsTable
  data={[
    { name: 'open', type: 'boolean', description: `Set to use as controlled component.` },
    {
      name: 'defaultOpen',
      type: 'boolean',
      description: `Uncontrolled open state on mount.`,
    },
    {
      name: 'onOpenChange',
      type: '(open: boolean) => 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.',
    },
  ]}
/>

#### &lt;Overlay /&gt;

Displays behind Frame. Extends [YStack](/docs/components/stacks).

#### &lt;Frame /&gt;

Contains the content. Extends [YStack](/docs/components/stacks).

#### &lt;Handle /&gt;

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).

#### &lt;Scrollview /&gt;

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.27.0

---
title: Sheet
description: A simple sheet component
name: sheet
component: Sheet
package: sheet
demoName: Sheet
---

# Sheet

<Description>A bottom sheet that slides up</Description>

<HeroContainer showAnimationDriverControl>
  <SheetDemo />
</HeroContainer>

```tsx hero template=Sheet

```

<Highlights
  features={[
    `Lightweight implementation with dragging support.`,
    `Multiple snap point points and a handle.`,
    `Automatically adjusts to screen size.`,
    `Accepts animations, themes, size props and more.`,
  ]}
/>

## 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 () => (
  <Sheet>
    <Sheet.Overlay />
    <Sheet.Handle />
    <Sheet.Frame>{/* ...inner contents */}</Sheet.Frame>
  </Sheet>
)
```

## 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 { Stack, styled } from '@tamagui/core'
import { createSheet } from '@tamagui/sheet'

const Handle = styled(Stack, {
  variants: {
    open: {
      true: {
        opacity: 0.35,
      },
      false: {
        opacity: 0.5,
      },
    },
  } as const,
})

const Overlay = styled(Stack, {
  variants: {
    open: {
      true: {
        opacity: 1,
        pointerEvents: 'auto',
      },
      false: {
        opacity: 0,
        pointerEvents: 'none',
      },
    },
  } as const,
})

const Frame = styled(Stack, {
  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 (
  <Sheet native>
    {/* The rest of your sheet views, see Anatomy, example and props API */}
  </Sheet>
)
```

## API Reference

### Sheet

Contains every component for the sheet.

<PropsTable
  data={[
    { name: 'open', type: 'boolean', description: `Set to use as controlled component.` },
    {
      name: 'defaultOpen',
      type: 'boolean',
      description: `Uncontrolled open state on mount.`,
    },
    {
      name: 'onOpenChange',
      type: '(open: boolean) => 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).

<PropsTable
  data={[
    {
      name: 'disableHideBottomOverflow',
      type: 'boolean',
      description: `Disables Sheet cloning the Frame and positioning it below the frame, which helps to hide content that may appear underneath when spring animations bounce past 100%.`,
    },
  ]}
/>

### 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.

<PropsTable
  data={[
    { name: 'open', type: 'boolean', description: `Set to use as controlled component.` },
    {
      name: 'setOpen',
      type: 'Function',
      description: `Control the open state of the sheet.`,
    },
    {
      name: 'setPosition',
      type: '(index: number) => 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.59.0

---
title: Sheet
description: A simple sheet component
name: sheet
component: Sheet
package: sheet
demoName: Sheet
---

# Sheet

<Description>A bottom sheet that slides up</Description>

<HeroContainer showAnimationDriverControl>
  <SheetDemo />
</HeroContainer>

```tsx hero template=Sheet

```

<Highlights
  features={[
    `Lightweight implementation with dragging support.`,
    `Multiple snap point points and a handle.`,
    `Automatically adjusts to screen size.`,
    `Accepts animations, themes, size props and more.`,
  ]}
/>

## 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 (
    <PortalProvider shouldAddRootHost>
      <YourApp />
    </PortalProvider>
  )
}

export default App
```

<PropsTable
  data={[
    {
      name: 'shouldAddRootHost',
      type: 'boolean',
      required: false,
      description: `Defines whether to add a default root host or not.`,
    },
  ]}
/>

## Anatomy

```tsx
import { Sheet } from 'tamagui' // or '@tamagui/sheet'

export default () => (
  <Sheet>
    <Sheet.Overlay />
    <Sheet.Handle />
    <Sheet.Frame>{/* ...inner contents */}</Sheet.Frame>
  </Sheet>
)
```

## Snap points

By default, snap points are treated as percentages.

```tsx
<Sheet snapPoints={[85, 50]}> // 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 { Stack, styled } from '@tamagui/core'
import { createSheet } from '@tamagui/sheet'

const Handle = styled(Stack, {
  variants: {
    open: {
      true: {
        opacity: 0.35,
      },
      false: {
        opacity: 0.5,
      },
    },
  } as const,
})

const Overlay = styled(Stack, {
  variants: {
    open: {
      true: {
        opacity: 1,
        pointerEvents: 'auto',
      },
      false: {
        opacity: 0,
        pointerEvents: 'none',
      },
    },
  } as const,
})

const Frame = styled(Stack, {
  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 (
  <Sheet native>
    {/* The rest of your sheet views, see Anatomy, example and props API */}
  </Sheet>
)
```

## API Reference

### Sheet

Contains every component for the sheet.

<PropsTable
  data={[
    { name: 'open', type: 'boolean', description: `Set to use as controlled component.` },
    {
      name: 'defaultOpen',
      type: 'boolean',
      description: `Uncontrolled open state on mount.`,
    },
    {
      name: 'onOpenChange',
      type: '(open: boolean) => 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.',
    },
  ]}
/>


<Notice>
  If using `modal={true}` (which is `true` by default), refer to the [PortalProvider installation](/ui/sheet/1.59.0#portalprovider) for more information.
</Notice>

### Sheet.Overlay

Displays behind Frame. Extends [YStack](/docs/components/stacks).

### Sheet.Frame

Contains the content. Extends [YStack](/docs/components/stacks).

<PropsTable
  data={[
    {
      name: 'disableHideBottomOverflow',
      type: 'boolean',
      description: `Disables Sheet cloning the Frame and positioning it below the frame, which helps to hide content that may appear underneath when spring animations bounce past 100%.`,
    },
  ]}
/>

### 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.

<PropsTable
  data={[
    { name: 'open', type: 'boolean', description: `Set to use as controlled component.` },
    {
      name: 'setOpen',
      type: 'Function',
      description: `Control the open state of the sheet.`,
    },
    {
      name: 'setPosition',
      type: '(index: number) => 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

<Description>A bottom sheet that slides up.</Description>

<HeroContainer showAnimationDriverControl>
  <SheetDemo />
</HeroContainer>

```tsx hero template=Sheet

```

<Highlights
  features={[
    `Lightweight implementation with dragging support.`,
    `Multiple snap point points and a handle.`,
    `Automatically adjusts to screen size.`,
    `Accepts animations, themes, size props and more.`,
  ]}
/>

### Anatomy

```tsx
import { Sheet } from 'tamagui' // or '@tamagui/sheet'

export default () => (
  <Sheet>
    <Sheet.Overlay />
    <Sheet.Handle />
    <Sheet.Frame>
      {/* ...inner contents */}
    </Sheet.Frame>
  </Sheet>
)
```

### API

#### &lt;Sheet /&gt;

Contains every component for the sheet.

<PropsTable
  data={[
    { name: 'open', type: 'boolean', description: `Set to use as controlled component.` },
    {
      name: 'defaultOpen',
      type: 'boolean',
      description: `Uncontrolled open state on mount.`,
    },
    {
      name: 'onOpenChange',
      type: '(open: boolean) => 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.'
    }
  ]}
/>

#### &lt;Overlay /&gt;

Displays behind Frame. Extends [YStack](/docs/components/stacks).

#### &lt;Frame /&gt;

Contains the content. Extends [YStack](/docs/components/stacks).

#### &lt;Handle /&gt;

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).

#### &lt;Scrollview /&gt;

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/2.0.0

---
title: Sheet
description: A bottom sheet that animates.
name: sheet
component: Sheet
package: sheet
demoName: Sheet
---

<HeroContainer showAnimationDriverControl>
  <SheetDemo />
</HeroContainer>

```tsx hero template=Sheet

```

<Highlights
  features={[
    `Lightweight implementation with dragging support.`,
    `Multiple snap points and a handle.`,
    `Automatically adjusts to screen size.`,
    `Accepts animations, themes, size props and more.`,
  ]}
/>

## Installation

Sheet is already installed in `tamagui`, or you can install it independently:

```bash
npm install @tamagui/sheet
```

<Notice theme="blue">
  For native apps, we recommend [setting up native portals](/docs/components/portal#native-portal-setup-recommended) to preserve React context inside Sheet content.
</Notice>

### Anatomy

```tsx
import { Sheet } from 'tamagui' // or '@tamagui/sheet'

export default () => (
  <Sheet>
    <Sheet.Overlay />
    <Sheet.Handle />
    <Sheet.Frame>{/* ...inner contents */}</Sheet.Frame>
  </Sheet>
)
```

### API

#### &lt;Sheet /&gt;

Contains every component for the sheet.

<PropsTable
  data={[
    {
      name: 'open',
      type: 'boolean',
      description: `Set to use as controlled component.`,
    },
    {
      name: 'defaultOpen',
      type: 'boolean',
      description: `Uncontrolled open state on mount.`,
    },
    {
      name: 'onOpenChange',
      type: '(open: boolean) => 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: 'forceRemoveScrollEnabled',
      type: 'boolean',
      default: 'false',
      deprecated: true,
      description: `@deprecated Use \`disableRemoveScroll\` instead. 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.`,
    },
  ]}
/>

#### &lt;Overlay /&gt;

Displays behind Frame. Extends [YStack](/docs/components/stacks).

#### &lt;Frame /&gt;

Contains the content. Extends [YStack](/docs/components/stacks).

#### &lt;Handle /&gt;

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).

#### &lt;ScrollView /&gt;

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/slider/1.0.0

---
title: Slider
description: A simple slider component
name: slider
component: Slider
package: slider
demoName: Slider
---

# Slider

<Description>Drag to set values, vertically or horizontally</Description>

<HeroContainer>
  <SliderDemo />
</HeroContainer>

```tsx hero template=Slider

```

<Highlights
  features={[
    `Sizable, themed, works controlled or uncontrolled.`,
    `Multiple thumbs support.`,
    `Control steps and control with your keyboard.`,
    `Accessible, easy to compose and customize.`,
  ]}
/>

## Installation

Slider is already installed in `tamagui`, or you can install it independently:

```bash
npm install @tamagui/slider
```

## Usage

Slider comes as multiple components that ship with default styles and are sizable. The `size` prop on `<Slider />` will automatically pass size down to all the sub-components.

```tsx
import { Slider } from 'tamagui'

export default () => (
  <Slider size="$4" width={200} defaultValue={[50]} max={100} step={1}>
    <Slider.Track>
      <Slider.TrackActive />
    </Slider.Track>
    <Slider.Thumb circular index={0} />
  </Slider>
)
```

You can also optionally style any component, either using inline style props or by wrapping with `styled`:

```tsx
import { Slider, styled } from 'tamagui'

const CustomSliderTrack = styled(Slider.Track, {
  backgroundColor: 'red',
})

export default () => (
  <Slider size="$4" width={200} defaultValue={[50]} max={100} step={1}>
    <CustomSliderTrack>
      <Slider.TrackActive />
    </CustomSliderTrack>
    <Slider.Thumb circular index={0} />
  </Slider>
)
```

## API Reference

### Slider

Contains every component for the slider.

<PropsTable
  data={[
    {
      name: 'size',
      required: false,
      type: 'SizeTokens',
      description: `Control size of every component.`,
    },
    {
      name: 'name',
      required: false,
      type: 'string',
      description: `For usage with forms.`,
    },
    {
      name: 'value',
      required: false,
      type: 'number[]',
      description: `Controlled value.`,
    },
    {
      name: 'defaultValue',
      required: false,
      type: 'number[]',
      description: `Uncontrolled starting value.`,
    },
    {
      name: 'onValueChange',
      required: false,
      type: '(value: number[]): void',
      description: ``,
    },
    {
      name: 'disabled',
      required: false,
      type: 'boolean',
      description: `Disable interaction.`,
    },
    {
      name: 'orientation',
      required: false,
      type: `"horizontal" | "vertical"`,
      default: 'horizontal',
      description: `Direction of the slider.`,
    },
    {
      name: 'dir',
      required: false,
      type: '"ltr" | "rtl"',
      description: `Controls the side the active track appears on.`,
    },
    {
      name: 'min',
      required: false,
      type: 'number',
      description: `Minimum value.`,
    },
    {
      name: 'max',
      required: false,
      type: 'number',
      description: `Maximum value.`,
    },
    {
      name: 'step',
      required: false,
      type: 'number',
      description: `Minimum thumb move distance.`,
    },
    {
      name: 'minStepsBetweenThumbs',
      required: false,
      type: 'number',
      description: `Minimum steps between thumbs.`,
    },
  ]}
/>

### Slider.Track

`Slider.Track` Inherits `SizableStack`, extending all the default [props](/docs/intro/props).

### Slider.TrackActive

`Slider.Track` Inherits `Stack`, extending all the default [props](/docs/intro/props).

### Slider.Thumb

`Slider.Track` Inherits `SizableStack`, extending all the default [props](/docs/intro/props), adding:

<PropsTable
  data={[
    {
      name: 'index',
      required: true,
      type: 'number',
      description: `Corresponds to the index of \`value\` or \`defaultValue\`. Use to correlate thumbs to each value in the array.`,
    },
  ]}
/>


## components/slider/1.45.0

---
title: Slider
description: Drag to set values, vertically or horizontally.
name: slider
component: Slider
package: slider
demoName: Slider
---

<HeroContainer>
  <SliderDemo />
</HeroContainer>

```tsx hero template=Slider

```

<Highlights
  features={[
    `Sizable, themed, works controlled or uncontrolled.`,
    `Multiple thumbs support.`,
    `Control steps and control with your keyboard.`,
    `Accessible, easy to compose and customize.`,
  ]}
/>

## Installation

Slider is already installed in `tamagui`, or you can install it independently:

```bash
npm install @tamagui/slider
```

## Usage

Slider comes as multiple components that ship with default styles and are sizable. The `size` prop on `<Slider />` will automatically pass size down to all the sub-components.

```tsx
import { Slider } from 'tamagui'

export default () => (
  <Slider size="$4" width={200} defaultValue={[50]} max={100} step={1}>
    <Slider.Track>
      <Slider.TrackActive />
    </Slider.Track>
    <Slider.Thumb circular index={0} />
  </Slider>
)
```

You can also optionally style any component, either using inline style props or by wrapping with `styled`:

```tsx
import { Slider, styled } from 'tamagui'

const CustomSliderTrack = styled(Slider.Track, {
  backgroundColor: 'red',
})

export default () => (
  <Slider size="$4" width={200} defaultValue={[50]} max={100} step={1}>
    <CustomSliderTrack>
      <Slider.TrackActive />
    </CustomSliderTrack>
    <Slider.Thumb circular index={0} />
  </Slider>
)
```

## API Reference

### Slider

Contains every component for the slider.

<PropsTable
  data={[
    {
      name: 'size',
      required: false,
      type: 'SizeTokens',
      description: `Control size of every component.`,
    },
    {
      name: 'name',
      required: false,
      type: 'string',
      description: `For usage with forms.`,
    },
    {
      name: 'value',
      required: false,
      type: 'number[]',
      description: `Controlled value.`,
    },
    {
      name: 'defaultValue',
      required: false,
      type: 'number[]',
      description: `Uncontrolled starting value.`,
    },
    {
      name: 'onValueChange',
      required: false,
      type: '(value: number[]): void',
      description: ``,
    },
    {
      name: 'disabled',
      required: false,
      type: 'boolean',
      description: `Disable interaction.`,
    },
    {
      name: 'orientation',
      required: false,
      type: `"horizontal" | "vertical"`,
      default: 'horizontal',
      description: `Direction of the slider.`,
    },
    {
      name: 'dir',
      required: false,
      type: '"ltr" | "rtl"',
      description: `Controls the side the active track appears on.`,
    },
    {
      name: 'min',
      required: false,
      type: 'number',
      description: `Minimum value.`,
    },
    {
      name: 'max',
      required: false,
      type: 'number',
      description: `Maximum value.`,
    },
    {
      name: 'step',
      required: false,
      type: 'number',
      description: `Minimum thumb move distance.`,
    },
    {
      name: 'minStepsBetweenThumbs',
      required: false,
      type: 'number',
      description: `Minimum steps between thumbs.`,
    },
    {
      name: 'onSlideStart',
      required: false,
      type: `(event: GestureReponderEvent, value: number, target: 'thumb' | 'track') => void`,
      description: `Called on slide start.`,
    },
    {
      name: 'onSlideMove',
      required: false,
      type: `(event: GestureReponderEvent, value: number) => void`,
      description: `Called on slide move.`,
    },
    {
      name: 'onSlideEnd',
      required: false,
      type: `(event: GestureReponderEvent, value: number) => void`,
      description: `Called on slide end.`,
    },
  ]}
/>

### Slider.Track

`Slider.Track` Inherits `SizableStack`, extending all the default [props](/docs/intro/props).

### Slider.TrackActive

`Slider.TrackActive` Inherits `Stack`, extending all the default [props](/docs/intro/props).

### Slider.Thumb

`Slider.Thumb` Inherits `SizableStack`, extending all the default [props](/docs/intro/props), adding:

<PropsTable
  data={[
    {
      name: 'index',
      required: true,
      type: 'number',
      description: `Corresponds to the index of \`value\` or \`defaultValue\`. Use to correlate thumbs to each value in the array.`,
    },
  ]}
/>


## components/slider/2.0.0

---
title: Slider
description: Drag to set values, vertically or horizontally.
name: slider
component: Slider
package: slider
demoName: Slider
---

<HeroContainer>
  <SliderDemo />
</HeroContainer>

```tsx hero template=Slider

```

<Highlights
  features={[
    `Sizable, themed, works controlled or uncontrolled.`,
    `Multiple thumbs support.`,
    `Control steps and control with your keyboard.`,
    `Accessible, easy to compose and customize.`,
  ]}
/>

## Installation

Slider is already installed in `tamagui`, or you can install it independently:

```bash
npm install @tamagui/slider
```

## Usage

Slider comes as multiple components that ship with default styles and are sizable. The `size` prop on `<Slider />` will automatically pass size down to all the sub-components.

```tsx
import { Slider } from 'tamagui'

export default () => (
  <Slider size="$4" width={200} defaultValue={[50]} max={100} step={1}>
    <Slider.Track>
      <Slider.TrackActive />
    </Slider.Track>
    <Slider.Thumb circular index={0} />
  </Slider>
)
```

You can also optionally style any component, either using inline style props or by wrapping with `styled`:

```tsx
import { Slider, styled } from 'tamagui'

const CustomSliderTrack = styled(Slider.Track, {
  backgroundColor: 'red',
})

export default () => (
  <Slider size="$4" width={200} defaultValue={[50]} max={100} step={1}>
    <CustomSliderTrack>
      <Slider.TrackActive />
    </CustomSliderTrack>
    <Slider.Thumb circular index={0} />
  </Slider>
)
```

## Vertical Slider on iOS

When using a vertical slider on iOS, you need to pass safe area insets to `TamaguiProvider` for proper pointer position calculation. Without this, the slider thumb may not track your finger correctly due to iOS safe area offsets.

```tsx
import { TamaguiProvider } from 'tamagui'
import { useSafeAreaInsets } from 'react-native-safe-area-context'
import { config } from './tamagui.config'

function App() {
  const insets = useSafeAreaInsets()

  return (
    <TamaguiProvider config={config} insets={insets}>
      {/* Your app content */}
    </TamaguiProvider>
  )
}
```

Then you can use vertical sliders normally:

```tsx
import { Slider, XStack } from 'tamagui'

export default () => (
  <XStack height={200} alignItems="center" gap="$8">
    <Slider height={200} orientation="vertical" defaultValue={[50]} max={100} step={1}>
      <Slider.Track>
        <Slider.TrackActive />
      </Slider.Track>
      <Slider.Thumb size="$2" index={0} circular />
    </Slider>
  </XStack>
)
```

<Notice theme="blue">
  This is only required on iOS. On Android and web, the slider works correctly without passing insets.
</Notice>

## API Reference

### Slider

Contains every component for the slider.

<PropsTable
  data={[
    {
      name: 'size',
      required: false,
      type: 'SizeTokens',
      description: `Control size of every component.`,
    },
    {
      name: 'name',
      required: false,
      type: 'string',
      description: `For usage with forms.`,
    },
    {
      name: 'value',
      required: false,
      type: 'number[]',
      description: `Controlled value.`,
    },
    {
      name: 'defaultValue',
      required: false,
      type: 'number[]',
      description: `Uncontrolled starting value.`,
    },
    {
      name: 'onValueChange',
      required: false,
      type: '(value: number[]): void',
      description: ``,
    },
    {
      name: 'disabled',
      required: false,
      type: 'boolean',
      description: `Disable interaction.`,
    },
    {
      name: 'orientation',
      required: false,
      type: `"horizontal" | "vertical"`,
      default: 'horizontal',
      description: `Direction of the slider.`,
    },
    {
      name: 'dir',
      required: false,
      type: '"ltr" | "rtl"',
      description: `Controls the side the active track appears on.`,
    },
    {
      name: 'min',
      required: false,
      type: 'number',
      description: `Minimum value.`,
    },
    {
      name: 'max',
      required: false,
      type: 'number',
      description: `Maximum value.`,
    },
    {
      name: 'step',
      required: false,
      type: 'number',
      description: `Minimum thumb move distance.`,
    },
    {
      name: 'minStepsBetweenThumbs',
      required: false,
      type: 'number',
      description: `Minimum steps between thumbs.`,
    },
    {
      name: 'onSlideStart',
      required: false,
      type: `(event: GestureReponderEvent, value: number, target: 'thumb' | 'track') => void`,
      description: `Called on slide start.`,
    },
    {
      name: 'onSlideMove',
      required: false,
      type: `(event: GestureReponderEvent, value: number) => void`,
      description: `Called on slide move.`,
    },
    {
      name: 'onSlideEnd',
      required: false,
      type: `(event: GestureReponderEvent, value: number) => void`,
      description: `Called on slide end.`,
    },
  ]}
/>

### Slider.Track

`Slider.Track` Inherits `SizableStack`, extending all the default [props](/docs/intro/props).

### Slider.TrackActive

`Slider.TrackActive` Inherits `Stack`, extending all the default [props](/docs/intro/props).

### Slider.Thumb

`Slider.Thumb` Inherits `SizableStack`, extending all the default [props](/docs/intro/props), adding:

<PropsTable
  data={[
    {
      name: 'index',
      required: true,
      type: 'number',
      description: `Corresponds to the index of \`value\` or \`defaultValue\`. Use to correlate thumbs to each value in the array.`,
    },
  ]}
/>


## components/spinner/1.0.0

---
title: Spinner
description: Render a loading indicator.
name: spinner
component: Spinner
demoName: Spinner
---

<HeroContainer>
  <SpinnerDemo />
</HeroContainer>

```tsx hero template=Spinner

```

<Highlights
  features={[
    'Custom size "small" or "large".',
    'Accepts all theme colors.',
    'Accepts all YStack props.',
  ]}
/>

## Installation

Spinner is already installed in `tamagui`, or you can install it independently:

```bash
npm install @tamagui/spinner
```

Note that due to the fact that Spinner is an extension of React Native [ActivityIndicator](https://reactnative.dev/docs/activityindicator), and that only accepts size `small` or `large`, we are currently limited to just these sizes.

```tsx
import { Button, Spinner } from 'tamagui'

export default () => <Spinner size="large" color="$green10" />
```

## API Reference

### Spinner

Spinner extends [YStack](/docs/components/stacks), getting [Tamagui standard props](/docs/intro/props), plus:

<PropsTable
  data={[
    {
      name: 'size',
      required: false,
      type: '"small" | "large"',
    },
    {
      name: 'color',
      required: false,
      type: 'string | ColorTokens',
      description: `Give the spinner a color.`,
    },
  ]}
/>


## components/spinner/2.0.0

---
title: Spinner
description: Render a loading indicator.
name: spinner
component: Spinner
demoName: Spinner
---

<HeroContainer>
  <SpinnerDemo />
</HeroContainer>

```tsx hero template=Spinner

```

<Highlights
  features={[
    'Custom size "small" or "large".',
    'Accepts all theme colors.',
    'Accepts all YStack props.',
  ]}
/>

## Installation

Spinner is already installed in `tamagui`, or you can install it independently:

```bash
npm install @tamagui/spinner
```

Note that due to the fact that Spinner is an extension of React Native [ActivityIndicator](https://reactnative.dev/docs/activityindicator), and that only accepts size `small` or `large`, we are currently limited to just these sizes.

```tsx
import { Button, Spinner } from 'tamagui'

export default () => <Spinner size="large" color="$green10" />
```

## API Reference

### Spinner

Spinner extends [YStack](/docs/components/stacks), getting [Tamagui standard props](/docs/intro/props), plus:

<PropsTable
  data={[
    {
      name: 'size',
      required: false,
      type: '"small" | "large"',
    },
    {
      name: 'color',
      required: false,
      type: 'string | ColorTokens',
      description: `Give the spinner a color.`,
    },
  ]}
/>


## components/stacks/1.0.0

---
title: Stacks
description: An optional base for creating flex-based layouts.
name: stacks
component: Stacks
package: stacks
demoName: Stacks
---

<HeroContainer>
  <StacksDemo />
</HeroContainer>

```tsx hero template=Stacks

```

<Highlights
  features={[
    'X, Y, and Z stacks for easy flex layouts.',
    'Gap property to add space between elements.',
    'Handle press, focus, and layout events easily.',
  ]}
/>

Tamagui UI includes optional stack views - XStack, YStack and ZStack. They extend directly off the [View](/docs/core/stack-and-text) from `@tamagui/core`.

Stack props accept [every prop from react-native-web](https://necolas.github.io/react-native-web/docs/view/) View, as well as all [the style properties Tamagui supports](/docs/intro/props).

In this example you'd show three `YStack` elements spaced out.

## Installation

Stacks is already installed in `tamagui`, or you can install it independently:

```bash
npm install @tamagui/stacks
```

```tsx
import { XStack, YStack } from 'tamagui'

export default () => (
  <XStack gap="$2">
    <YStack />
    <YStack />
    <YStack />
  </XStack>
)
```

To see all the style properties supported, see the [Props](/docs/intro/props) documentation.

### Fuller example

An example using a wide variety of style properties:

```tsx
import { Text, XStack, YStack } from 'tamagui'

export default () => (
  <XStack
    flex={1}
    flexWrap="wrap"
    backgroundColor="#fff"
    hoverStyle={{
      backgroundColor: 'red',
    }}
    // media query
    $gtSm={{
      flexDirection: 'column',
      flexWrap: 'nowrap',
    }}
  >
    <YStack gap="$3">
      <Text>Hello</Text>
      <Text>World</Text>
    </YStack>
  </XStack>
)
```

## API Reference

### XStack, YStack, ZStack

Beyond the [Tamagui Props](/docs/intro/props), the stacks add just two variants:

<PropsTable
  data={[
    {
      name: 'fullscreen',
      required: false,
      type: 'boolean',
      description: (
        <span>Sets position: absolute, top: 0, left: 0, right: 0, bottom: 0.</span>
      ),
    },
    {
      name: 'elevation',
      required: false,
      type: 'number | tokens.size',
      description: (
        <span>
          Sets a natural looking shadow that expands out and away as the size gets bigger.
        </span>
      ),
    },
  ]}
/>


## components/stacks/2.0.0

---
title: Stacks
description: An optional base for creating flex-based layouts.
name: stacks
component: Stacks
package: stacks
demoName: Stacks
---

<HeroContainer>
  <StacksDemo />
</HeroContainer>

```tsx hero template=Stacks

```

<Highlights
  features={[
    'X, Y, and Z stacks for easy flex layouts.',
    'Gap property to add space between elements.',
    'Handle press, focus, and layout events easily.',
  ]}
/>

Tamagui UI includes optional stack views - XStack, YStack and ZStack. They extend directly off the [View](/docs/core/stack-and-text) from `@tamagui/core`.

Stack props accept [every prop from react-native-web](https://necolas.github.io/react-native-web/docs/view/) View, as well as all [the style properties Tamagui supports](/docs/intro/props).

In this example you'd show three `YStack` elements spaced out.

## Installation

Stacks is already installed in `tamagui`, or you can install it independently:

```bash
npm install @tamagui/stacks
```

```tsx
import { XStack, YStack } from 'tamagui'

export default () => (
  <XStack gap="$2">
    <YStack />
    <YStack />
    <YStack />
  </XStack>
)
```

To see all the style properties supported, see the [Props](/docs/intro/props) documentation.

### Fuller example

An example using a wide variety of style properties:

```tsx
import { Text, XStack, YStack } from 'tamagui'

export default () => (
  <XStack
    flex={1}
    flexWrap="wrap"
    backgroundColor="#fff"
    hoverStyle={{
      backgroundColor: 'red',
    }}
    // media query
    $gtSm={{
      flexDirection: 'column',
      flexWrap: 'nowrap',
    }}
  >
    <YStack gap="$3">
      <Text>Hello</Text>
      <Text>World</Text>
    </YStack>
  </XStack>
)
```

## API Reference

### XStack, YStack, ZStack

Beyond the [Tamagui Props](/docs/intro/props), the stacks add just two variants:

<PropsTable
  data={[
    {
      name: 'fullscreen',
      required: false,
      type: 'boolean',
      description: (
        <span>Sets position: absolute, top: 0, left: 0, right: 0, bottom: 0.</span>
      ),
    },
    {
      name: 'elevation',
      required: false,
      type: 'number | tokens.size',
      description: (
        <span>
          Sets a natural looking shadow that expands out and away as the size gets bigger.
        </span>
      ),
    },
  ]}
/>


## components/switch/1.0.0

---
title: Switch
description: A simple switch component
name: switch
component: Switch
package: switch
demoName: Switch
---

# Switch

<Description>Use in forms to toggle between two states.</Description>

<HeroContainer showAnimationDriverControl>
  <SwitchDemo />
</HeroContainer>

```tsx hero template=Switch

```

<Highlights
  features={[
    `Accessible, easy to compose and customize.`,
    `Style and animate both frame and thumb.`,
    `Sizable & works controlled or uncontrolled.`,
  ]}
/>

### Usage

```tsx
import { Switch } from 'tamagui'

export default () => (
  <Switch size="$4">
    <Switch.Thumb animation="bouncy" />
  </Switch>
)
```

### Switch props

Switchs extend Stack views inheriting all the [Tamagui standard props](/docs/intro/props), plus:

<PropsTable
  data={[
    {
      name: 'labeledBy',
      type: 'string',
      description: `Set aria-labeled-by.`,
    },
    {
      name: 'name',
      type: 'string',
      description: `Equivalent to input name.`,
    },
    {
      name: 'value',
      type: 'string',
      description: `Give it a value (for use in HTML forms).`,
    },
    {
      name: 'checked',
      type: 'boolean',
      description: `Control the input.`,
    },
    {
      name: 'defaultChecked',
      type: 'boolean',
      description: `Uncontrolled default value.`,
    },
    {
      name: 'required',
      type: 'boolean',
      description: `Sets aria-required.`,
    },
    {
      name: 'onCheckedChange',
      type: '(checked: boolean) => void',
    },
    {
      name: 'unstyled',
      type: 'boolean',
      default: 'false',
      description: `When true, remove all default tamagui styling.`,
    },
  ]}
/>


## components/switch/1.28.0

---
title: Switch
description: A simple switch component
name: switch
component: Switch
package: switch
demoName: Switch
---

# Switch

<Description>Use in forms to toggle between two states.</Description>

<HeroContainer showAnimationDriverControl>
  <SwitchDemo />
</HeroContainer>

```tsx hero template=Switch

```

<Highlights
  features={[
    `Accessible, easy to compose and customize.`,
    `Style and animate both frame and thumb.`,
    `Sizable & works controlled or uncontrolled.`,
    `Native prop that renders native Switch on mobile`,
  ]}
/>

## Installation

Switch is already installed in `tamagui`, or you can install it independently:

```bash
npm install @tamagui/switch
```

## Usage

```tsx
import { Switch } from 'tamagui' // or '@tamagui/switch'

export default () => (
  <Switch size="$4">
    <Switch.Thumb animation="bouncy" />
  </Switch>
)
```

## API Reference

### Switch

Switchs extend Stack views inheriting all the [Tamagui standard props](/docs/intro/props), plus:

<PropsTable
  data={[
    {
      name: 'labeledBy',
      type: 'string',
      description: `Set aria-labeled-by.`,
    },
    {
      name: 'name',
      type: 'string',
      description: `Equivalent to input name.`,
    },
    {
      name: 'value',
      type: 'string',
      description: `Give it a value (for use in HTML forms).`,
    },
    {
      name: 'checked',
      type: 'boolean',
      description: `Control the input.`,
    },
    {
      name: 'defaultChecked',
      type: 'boolean',
      description: `Uncontrolled default value.`,
    },
    {
      name: 'required',
      type: 'boolean',
      description: `Sets aria-required.`,
    },
    {
      name: 'onCheckedChange',
      type: '(checked: boolean) => void',
    },
    {
      name: 'unstyled',
      type: 'boolean',
      default: 'false',
      description: `When true, remove all default tamagui styling.`,
    },
    {
      name: 'native',
      type: 'NativeValue<"mobile" | "ios" | "android">',
      description: `Render to a native switch. (Not supported on web)`,
    },
    {
      name: 'nativeProps',
      type: 'SwitchProps (from `react-native`)',
      description: `Props to pass to the native Switch;`,
    },
  ]}
/>

### Switch.Thumb

`Switch.Thumb` extends Stack views inheriting all the [Tamagui standard props](/docs/intro/props), plus:

<PropsTable
  data={[
    {
      name: 'unstyled',
      type: 'boolean',
      default: 'false',
      description: `When true, remove all default tamagui styling.`,
    },
  ]}
/>


## components/switch/1.58.0

---
title: Switch
description: A simple switch component
name: switch
component: Switch
package: switch
demoName: Switch
---

# Switch

<Description>Use in forms to toggle between two states.</Description>

<HeroContainer showAnimationDriverControl>
  <SwitchDemo />
</HeroContainer>

```tsx hero template=Switch

```

<Highlights
  features={[
    `Accessible, easy to compose and customize.`,
    `Style and animate both frame and thumb.`,
    `Sizable & works controlled or uncontrolled.`,
    `Native prop that renders native Switch on mobile`,
  ]}
/>

## Installation

Switch is already installed in `tamagui`, or you can install it independently:

```bash
npm install @tamagui/switch
```

## Usage

```tsx
import { Switch } from 'tamagui' // or '@tamagui/switch'

export default () => (
  <Switch size="$4">
    <Switch.Thumb animation="bouncy" />
  </Switch>
)
```

## Headless with `createSwitch`

Using the `createSwitch` export, you can create a fully custom switch without using any of the default styles. This is similar to `unstyled`, but it doesn't assume the props `size` or `unstyled` exist, and it won't automatically apply the `active` theme.

You must pass `SwitchContext` as the `context` option to your Frame and Thumb styled components.

If you define a `checked` variant, it will apply those styles.

Here's an example:

```tsx
import { Stack, styled } from '@tamagui/core'
import { createSwitch } from '@tamagui/switch'

const Frame = styled(Stack, {
  variants: {
    checked: {
      true: {
        backgroundColor: 'yellow'
      },
      false: {
        backgroundColor: 'green',
      },
    },
  } as const,
})

const Thumb = styled(Stack, {
  variants: {
    checked: {
      true: {
        opacity: 1,
      },
      false: {
        opacity: 0.5,
      },
    },
  } as const,
})

export const Switch = createSwitch({
  Frame,
  Thumb,
})
```

## API Reference

### Switch

`Switch` extends Stack views inheriting all the [Tamagui standard props](/docs/intro/props), plus:

<PropsTable
  data={[
    {
      name: 'labeledBy',
      type: 'string',
      description: `Set aria-labeled-by.`,
    },
    {
      name: 'name',
      type: 'string',
      description: `Equivalent to input name.`,
    },
    {
      name: 'value',
      type: 'string',
      description: `Give it a value (for use in HTML forms).`,
    },
    {
      name: 'checked',
      type: 'boolean',
      description: `Control the input.`,
    },
    {
      name: 'defaultChecked',
      type: 'boolean',
      description: `Uncontrolled default value.`,
    },
    {
      name: 'required',
      type: 'boolean',
      description: `Sets aria-required.`,
    },
    {
      name: 'onCheckedChange',
      type: '(checked: boolean) => void',
    },
    {
      name: 'unstyled',
      type: 'boolean',
      default: 'false',
      description: `When true, remove all default tamagui styling.`,
    },
    {
      name: 'native',
      type: 'NativeValue<"mobile" | "ios" | "android">',
      description: `Render to a native switch. (Not supported on web)`,
    },
    {
      name: 'nativeProps',
      type: 'SwitchProps (from `react-native`)',
      description: `Props to pass to the native Switch;`,
    },
  ]}
/>

### Switch.Thumb

`Switch.Thumb` extends Stack views inheriting all the [Tamagui standard props](/docs/intro/props), plus:

<PropsTable
  data={[
    {
      name: 'unstyled',
      type: 'boolean',
      default: 'false',
      description: `When true, remove all default tamagui styling.`,
    },
  ]}
/>


## components/switch/1.89.0

---
title: Switch
description: A toggle between two states.
name: switch
component: Switch
package: switch
demoName: Switch
---

<YStack className="is-sticky" />

<Tabs id="type" defaultValue="styled">
<Tabs.List>
  <TooltipSimple label="With Tamagui's default styles">
    <Tabs.Tab value="styled">Styled</Tabs.Tab>
  </TooltipSimple>
  <TooltipSimple label="No default styles, easy to customize">
    <Tabs.Tab value="unstyled">Unstyled</Tabs.Tab>
  </TooltipSimple>
  <TooltipSimple label="No dependency on Tamagui's core">
    <Tabs.Tab value="headless" label="No styles and no dependency on Tamagui's styling">Headless</Tabs.Tab>
  </TooltipSimple>
</Tabs.List>

<HeroContainer showAnimationDriverControl>
  <Tabs.Content
    value="styled"
    justifyContent="center"
    alignItems="center"
    width="100%"
  >
    <SwitchDemo />
  </Tabs.Content>
  <Tabs.Content
    value="unstyled"
    justifyContent="center"
    alignItems="center"
    width="100%"
  >
    <SwitchUnstyledDemo />
  </Tabs.Content>
  <Tabs.Content
    value="headless"
    justifyContent="center"
    alignItems="center"
    width="100%"
  >
    <SwitchHeadlessDemo />
  </Tabs.Content>
</HeroContainer>

<Tabs.Content value="styled">
  ```tsx hero template=Switch

````
</Tabs.Content>
<Tabs.Content value="unstyled">
```tsx hero template=SwitchUnstyled

````

</Tabs.Content>
  <Tabs.Content value="headless">
  ```tsx hero template=SwitchHeadless

````
</Tabs.Content>



<Highlights
features={[
  `Accessible, easy to compose and customize.`,
  `Style and animate both frame and thumb.`,
  `Sizable & works controlled or uncontrolled.`,
  `Native prop that renders native Switch on mobile`,
]}
/>


## Installation

<Tabs.Content value="styled">

Switch is already installed in `tamagui`, or you can install it independently:

```bash
npm install @tamagui/switch
````

</Tabs.Content>

<Tabs.Content value="unstyled">

Switch is already installed in `tamagui`, or you can install it independently:

```bash
npm install @tamagui/switch
```

</Tabs.Content>

<Tabs.Content value="headless">

To use the headless switch, you want to import it from the
`@tamagui/switch-headless` package. This package has no dependency on
`@tamagui/core`, but still works off the react-native APIs.
This means you can bring your own style library.

```bash
npm install @tamagui/switch-headless
```

</Tabs.Content>

## Usage

<Tabs.Content value="styled">

```tsx
import { Switch } from 'tamagui' // or '@tamagui/switch'

export default () => (
  <Switch size="$4">
    <Switch.Thumb animation="bouncy" />
  </Switch>
)
```

</Tabs.Content>

<Tabs.Content value="unstyled">

Using the `createSwitch` export, you can create an unstyled switch without using
any of the default styles. This is similar to the `unstyled` prop, but it
doesn't assume the props `size` or `unstyled` exist, and it won't automatically
apply the `active` theme.

You must pass `SwitchContext` as the `context` option to your Frame and Thumb
styled components.

If you define a `checked` variant, it will apply those styles.

```tsx template=SwitchUnstyled

```

</Tabs.Content>

<Tabs.Content value="headless">

Using the `useSwitch` API, you can make your own Switch from scratch.

```tsx template=SwitchHeadless

```

</Tabs.Content>

## API Reference

### Switch

`Switch` extends Stack views inheriting all the
[Tamagui standard props](/docs/intro/props), plus:

<PropsTable
  data={[
    {
      name: 'labeledBy',
      type: 'string',
      description: `Set aria-labeled-by.`,
    },
    {
      name: 'name',
      type: 'string',
      description: `Equivalent to input name.`,
    },
    {
      name: 'value',
      type: 'string',
      description: `Give it a value (for use in HTML forms).`,
    },
    {
      name: 'checked',
      type: 'boolean',
      description: `Control the input.`,
    },
    {
      name: 'defaultChecked',
      type: 'boolean',
      description: `Uncontrolled default value.`,
    },
    {
      name: 'required',
      type: 'boolean',
      description: `Sets aria-required.`,
    },
    {
      name: 'onCheckedChange',
      type: '(checked: boolean) => void',
    },
    {
      name: 'unstyled',
      type: 'boolean',
      default: 'false',
      description: `When true, remove all default tamagui styling.`,
    },
    {
      name: 'native',
      type: 'NativeValue<"mobile" | "ios" | "android">',
      description: `Render to a native switch. (Not supported on web)`,
    },
    {
      name: 'nativeProps',
      type: 'SwitchProps (from `react-native`)',
      description: `Props to pass to the native Switch;`,
    },
  ]}
/>

### Switch.Thumb

`Switch.Thumb` extends Stack views inheriting all the
[Tamagui standard props](/docs/intro/props), plus:

<PropsTable
  data={[
    {
      name: 'unstyled',
      type: 'boolean',
      default: 'false',
      description: `When true, remove all default tamagui styling.`,
    },
  ]}
/>

</Tabs>

{/* TODO: document createSwitch and useSwitch's API */}


## components/switch/2.0.0

---
title: Switch
description: A toggle between two states.
name: switch
component: Switch
package: switch
demoName: Switch
---

<YStack className="is-sticky" />

<Tabs id="type" defaultValue="styled">
<Tabs.List>
  <TooltipSimple label="With Tamagui's default styles">
    <Tabs.Tab value="styled">Styled</Tabs.Tab>
  </TooltipSimple>
  <TooltipSimple label="No default styles, easy to customize">
    <Tabs.Tab value="unstyled">Unstyled</Tabs.Tab>
  </TooltipSimple>
  <TooltipSimple label="No dependency on Tamagui's core">
    <Tabs.Tab value="headless" label="No styles and no dependency on Tamagui's styling">Headless</Tabs.Tab>
  </TooltipSimple>
</Tabs.List>

<HeroContainer showAnimationDriverControl>
  <Tabs.Content
    value="styled"
    justifyContent="center"
    alignItems="center"
    width="100%"
  >
    <SwitchDemo />
  </Tabs.Content>
  <Tabs.Content
    value="unstyled"
    justifyContent="center"
    alignItems="center"
    width="100%"
  >
    <SwitchUnstyledDemo />
  </Tabs.Content>
  <Tabs.Content
    value="headless"
    justifyContent="center"
    alignItems="center"
    width="100%"
  >
    <SwitchHeadlessDemo />
  </Tabs.Content>
</HeroContainer>

<Tabs.Content value="styled">
  ```tsx hero template=Switch

````
</Tabs.Content>
<Tabs.Content value="unstyled">
```tsx hero template=SwitchUnstyled

````

</Tabs.Content>
  <Tabs.Content value="headless">
  ```tsx hero template=SwitchHeadless

````
</Tabs.Content>



<Highlights
features={[
  `Accessible, easy to compose and customize.`,
  `Style and animate both frame and thumb.`,
  `Sizable & works controlled or uncontrolled.`,
  `Native prop that renders native Switch on mobile`,
]}
/>


## Installation

<Tabs.Content value="styled">

Switch is already installed in `tamagui`, or you can install it independently:

```bash
npm install @tamagui/switch
````

</Tabs.Content>

<Tabs.Content value="unstyled">

Switch is already installed in `tamagui`, or you can install it independently:

```bash
npm install @tamagui/switch
```

</Tabs.Content>

<Tabs.Content value="headless">

To use the headless switch, you want to import it from the
`@tamagui/switch-headless` package. This package has no dependency on
`@tamagui/core`, but still works off the react-native APIs.
This means you can bring your own style library.

```bash
npm install @tamagui/switch-headless
```

</Tabs.Content>

## Usage

<Tabs.Content value="styled">

```tsx
import { Switch } from 'tamagui' // or '@tamagui/switch'

export default () => (
  <Switch size="$4">
    <Switch.Thumb transition="bouncy" />
  </Switch>
)
```

</Tabs.Content>

<Tabs.Content value="unstyled">

Using the `createSwitch` export, you can create an unstyled switch without using
any of the default styles. This is similar to the `unstyled` prop, but it
doesn't assume the props `size` or `unstyled` exist, and it won't automatically
apply the `active` theme.

You must pass `SwitchContext` as the `context` option to your Frame and Thumb
styled components.

If you define a `checked` variant, it will apply those styles.

```tsx template=SwitchUnstyled

```

</Tabs.Content>

<Tabs.Content value="headless">

The `useSwitch` hook provides all the state and accessibility props needed to build a custom switch with any styling solution.

```tsx template=SwitchHeadless

```

### Basic Usage

```tsx
import { useSwitch } from '@tamagui/switch-headless'
import { useState } from 'react'
import { Pressable, View } from 'react-native'

function MySwitch({ defaultChecked, onCheckedChange, ...props }) {
  const [checked, setChecked] = useState(defaultChecked || false)

  const { switchProps, switchRef, bubbleInput } = useSwitch(
    props,
    [checked, setChecked],
    null
  )

  return (
    <>
      <Pressable
        ref={switchRef}
        {...switchProps}
        style={{
          width: 50,
          height: 28,
          borderRadius: 14,
          backgroundColor: checked ? '#22c55e' : '#d1d5db',
          padding: 2,
        }}
      >
        <View
          style={{
            width: 24,
            height: 24,
            borderRadius: 12,
            backgroundColor: 'white',
            transform: [{ translateX: checked ? 22 : 0 }],
          }}
        />
      </Pressable>
      {bubbleInput}
    </>
  )
}
```

</Tabs.Content>

## API Reference

### Switch

`Switch` extends Stack views inheriting all the
[Tamagui standard props](/docs/intro/props), plus:

<PropsTable
  data={[
    {
      name: 'labeledBy',
      type: 'string',
      description: `Set aria-labeled-by.`,
    },
    {
      name: 'name',
      type: 'string',
      description: `Equivalent to input name.`,
    },
    {
      name: 'value',
      type: 'string',
      description: `Give it a value (for use in HTML forms).`,
    },
    {
      name: 'checked',
      type: 'boolean',
      description: `Control the input.`,
    },
    {
      name: 'defaultChecked',
      type: 'boolean',
      description: `Uncontrolled default value.`,
    },
    {
      name: 'required',
      type: 'boolean',
      description: `Sets aria-required.`,
    },
    {
      name: 'onCheckedChange',
      type: '(checked: boolean) => void',
    },
    {
      name: 'unstyled',
      type: 'boolean',
      default: 'false',
      description: `When true, remove all default tamagui styling.`,
    },
    {
      name: 'native',
      type: 'NativeValue<"mobile" | "ios" | "android">',
      description: `Render to a native switch. (Not supported on web)`,
    },
    {
      name: 'nativeProps',
      type: 'SwitchProps (from `react-native`)',
      description: `Props to pass to the native Switch.`,
    },
    {
      name: 'activeStyle',
      type: 'ViewStyle',
      description: `Styles to apply when the switch is checked/active.`,
    },
    {
      name: 'activeTheme',
      type: 'string | null',
      description: `Theme to apply when the switch is checked/active.`,
    },
  ]}
/>

### Switch.Thumb

`Switch.Thumb` extends Stack views inheriting all the
[Tamagui standard props](/docs/intro/props), plus:

<PropsTable
  data={[
    {
      name: 'unstyled',
      type: 'boolean',
      default: 'false',
      description: `When true, remove all default tamagui styling.`,
    },
    {
      name: 'activeStyle',
      type: 'ViewStyle',
      description: `Styles to apply to the thumb when the switch is checked/active.`,
    },
  ]}
/>

<Tabs.Content value="headless">

### useSwitch

The `useSwitch` hook accepts three arguments:

```tsx
const { switchProps, switchRef, bubbleInput } = useSwitch(
  props,        // SwitchProps
  state,        // [checked: boolean, setChecked: (checked: boolean) => void]
  ref           // React.Ref
)
```

#### Props (first argument)

<PropsTable
  data={[
    {
      name: 'labeledBy',
      type: 'string',
      description: `Set aria-labelledby for accessibility.`,
    },
    {
      name: 'disabled',
      type: 'boolean',
      description: `Whether the switch is disabled.`,
    },
    {
      name: 'name',
      type: 'string',
      description: `Form input name for the hidden input.`,
    },
    {
      name: 'value',
      type: 'string',
      description: `Form input value.`,
    },
    {
      name: 'required',
      type: 'boolean',
      description: `Whether the switch is required in a form.`,
    },
    {
      name: 'onPress',
      type: '(event) => void',
      description: `Called when switch is pressed (composed with internal handler).`,
    },
  ]}
/>

#### State (second argument)

A tuple of `[checked, setChecked]` where:
- `checked`: Current boolean state
- `setChecked`: React state setter function

#### Return Value

| Property | Type | Description |
|----------|------|-------------|
| `switchProps` | `object` | Props to spread on your switch element (role, aria-checked, onPress, etc.) |
| `switchRef` | `Ref` | Composed ref to attach to your switch element |
| `bubbleInput` | `ReactNode \| null` | Hidden input for form compatibility (render as sibling, web only) |

</Tabs.Content>

</Tabs>



## components/tabs/1.125.35

---
title: Tabs
description: Use in pages to manage sub-pages.
name: tabs
component: Tabs
package: tabs
demoName: Tabs
---

<Tabs id="type" defaultValue="styled">
  <Tabs.List>
    <TooltipSimple label="With Tamagui's default styles">
      <Tabs.Tab value="styled">Styled</Tabs.Tab>
    </TooltipSimple>
    <TooltipSimple label="No dependency on Tamagui's core">
      <Tabs.Tab value="headless">Headless</Tabs.Tab>
    </TooltipSimple>
  </Tabs.List>
</Tabs>

<Tabs.Content value="styled">
  <HeroContainer>
    <TabsDemo />
  </HeroContainer>

```tsx hero template=Tabs

```

<Highlights
  features={[
    `Accessible, easy to compose, customize and animate`,
    `Sizable & works controlled or uncontrolled`,
    `Supports automatic and manual activation modes for web`,
    `Full keyboard navigation`,
  ]}
/>

Note: Tabs have landed on v1.7 and not fully ready for runtime. Send us your feedback and we'll address it. We're marking it Beta a such as there may be hopefully minimal breaking changes as we get feedback on the API.

## Installation

Tabs is already installed in `tamagui`, or you can install it independently:

```bash
npm install @tamagui/tabs
```

## Usage

```tsx
import { SizableText, Tabs } from "tamagui";

export default () => (
  <Tabs defaultValue="tab1" width={400}>
    <Tabs.List space>
      <Tabs.Tab value="tab1">
        <SizableText>Tab 1</SizableText>
      </Tabs.Tab>
      <Tabs.Tab value="tab2">
        <SizableText>Tab 2</SizableText>
      </Tabs.Tab>
    </Tabs.List>

    <Tabs.Content value="tab1">
      <H5>Tab 1</H5>
    </Tabs.Content>
    <Tabs.Content value="tab2">
      <H5>Tab 2</H5>
    </Tabs.Content>
  </Tabs>
);
```

## API Reference

### Tabs

Root tabs component. Extends [Stack](/docs/components/stacks). Passing the `size` prop to this component will have effect on the descendants.

<PropsTable
  data={[
    {
      name: "value",
      type: "string",
      description: `The value for the selected tab, if controlled`,
    },
    {
      name: "defaultValue",
      type: "string",
      description: `The value of the tab to select by default, if uncontrolled`,
    },
    {
      name: "onValueChange",
      type: "(value: string) => void",
      description: `A function called when a new tab is selected`,
    },
    {
      name: "orientation",
      type: '"horizontal" | "vertical"',
      default: "horizontal",
      description: `The orientation the tabs are laid out.`,
    },
    {
      name: "dir",
      type: '"ltr" | "rtl"',
      description: `The direction of navigation between toolbar items`,
    },
    {
      name: "activationMode",
      type: '"manual" | "automatic"',
      default: "automatic",
      description: `Whether or not a tab is activated automatically or manually. Not applicable on mobile`,
    },
  ]}
/>

### Tabs.List

Container for the trigger buttons. Supports scrolling by extending [Group](/docs/components/group). You can disable passing border radius to children by passing `disablePassBorderRadius`.

<PropsTable
  data={[
    {
      name: "loop",
      type: "boolean",
      default: "true",
      description: `Whether or not to loop over after reaching the end or start of the items. Used mainly for managing keyboard navigation`,
    },
  ]}
/>

### Tabs.Tab

The clickable tab button that activates its corresponding content. Extends [ThemeableStack](/docs/components/stacks#themeablestack), adding:

<PropsTable
  data={[
    {
      name: "value",
      type: "string",
      description: `The value for the tabs state to be changed to after activation of the tab`,
    },
    {
      name: "onInteraction",
      type: `(type: InteractionType, layout: TabLayout | null) => void`,
      description: `Used for making custom indicators when tab is interacted with. InteractionType is 'select' | 'focus' | 'hover'`,
    },
    {
      name: "disabled",
      type: `boolean`,
      description: `When true, prevents user interaction with the tab`,
    },
    {
      name: "disableActiveTheme",
      type: `boolean`,
      description: `When true, disables applying the 'active' theme when the tab is selected`,
    },
    {
      name: "unstyled",
      type: `boolean`,
      description: `When true, remove all default tamagui styling`,
    },
  ]}
/>

### Tabs.Trigger

<Aside type="deprecated">
  Use `Tabs.Tab` instead. `Tabs.Trigger` is an alias kept for backwards compatibility.
</Aside>

Extends [ThemeableStack](/docs/components/stacks#themeablestack), adding:

<PropsTable
  data={[
    {
      name: "value",
      type: "string",
      description: `The value for the tabs state to be changed to after activation of the trigger`,
    },
    {
      name: "onInteraction",
      type: `(type: InteractionType, layout: TabLayout | null) => void`,
      description: `Used for making custom indicators when trigger interacted with`,
    },
    {
      name: "unstyled",
      type: `boolean`,
      description: `When true, remove all default tamagui styling`,
    },
  ]}
/>

### Tabs.Content

Where each tab's content will be shown. Extends [ThemeableStack](/docs/components/stacks#themeablestack), adding:

<PropsTable
  data={[
    {
      name: "value",
      type: "string",
      description: `Will show the content when the value matches the state of Tabs root`,
    },
    {
      name: "forceMount",
      type: "boolean",
      default: "false",
      description: `Used to force mounting when more control is needed. Useful when controlling animation with Tamagui animations`,
    },
  ]}
/>

## Examples

### Animations

Here is a demo with more advanced animations using [AnimatePresence](/docs/core/animations#animatepresence-and-exitstyle) and [Trigger](#trigger)'s `onInteraction` prop.

<HeroContainer noPad showAnimationDriverControl>
  <TabsAdvancedDemo />
</HeroContainer>

```tsx hero template=TabsAdvanced

```

</Tabs.Content>

<Tabs.Content value="headless">
  <HeroContainer>
    <TabsHeadlessDemo />
  </HeroContainer>

```tsx hero template=TabsHeadless

```

### Customization Options

When using `createTabs`, you need to provide three styled components:

- `TabsFrame`: The root container component
- `TabFrame`: The tab trigger component
- `ContentFrame`: The content container component

The created tabs component will maintain all the accessibility features and keyboard navigation while allowing you to have complete control over the visual presentation.

The headless API gives you the foundation to build completely custom tab interfaces while maintaining accessibility and interaction patterns. You can integrate it with your design system and add any custom behaviors you need.

</Tabs.Content>


## components/tabs/1.7.0

---
title: Tabs
description: Use in pages to manage sub-pages.
name: tabs
component: Tabs
package: tabs
demoName: Tabs
---

<HeroContainer>
  <TabsDemo />
</HeroContainer>

```tsx hero template=Tabs

```

<Highlights
  features={[
    `Accessible, easy to compose, customize and animate`,
    `Sizable & works controlled or uncontrolled`,
    `Supports automatic and manual activation modes for web`,
    `Full keyboard navigation`,
  ]}
/>

Note: Tabs have landed on v1.7 and not fully ready for runtime. Send us your feedback and we'll address it. We're marking it Beta a such as there may be hopefully minimal breaking changes as we get feedback on the API.

## Usage

```tsx
import { SizableText, Tabs } from 'tamagui'

export default () => (
  <Tabs defaultValue="tab1" width={400}>
    <Tabs.List gap="$4">
      <Tabs.Tab value="tab1">
        <SizableText>Tab 1</SizableText>
      </Tabs.Tab>
      <Tabs.Tab value="tab2">
        <SizableText>Tab 2</SizableText>
      </Tabs.Tab>
    </Tabs.List>

    <Tabs.Content value="tab1">
      <H5>Tab 1</H5>
    </Tabs.Content>
    <Tabs.Content value="tab2">
      <H5>Tab 2</H5>
    </Tabs.Content>
  </Tabs>
)
```

## API Reference

### Tabs

Root tabs component. Extends [Stack](/docs/components/stack). Passing the `size` prop to this component will have effect on the descendants.

<PropsTable
  data={[
    {
      name: 'value',
      type: 'string',
      description: `The value for the selected tab, if controlled`,
    },
    {
      name: 'defaultValue',
      type: 'string',
      description: `The value of the tab to select by default, if uncontrolled`,
    },
    {
      name: 'onValueChange',
      type: '(value: string) => void',
      description: `A function called when a new tab is selected`,
    },
    {
      name: 'orientation',
      type: '"horizontal" | "vertical"',
      default: 'horizontal',
      description: `The orientation the tabs are layed out`,
    },
    {
      name: 'dir',
      type: '"ltr" | "rtl"',
      description: `The direction of navigation between toolbar items`,
    },
    {
      name: 'activationMode',
      type: '"manual" | "automatic"',
      default: 'automatic',
      description: `Whether or not a tab is activated automatically or manually. Not applicable on mobile`,
    },
  ]}
/>

### Tabs.List

Container for the trigger buttons. Supports scrolling by extending [Group](/docs/components/group). You can disable passing border radius to children by passing `disablePassBorderRadius`.

<PropsTable
  data={[
    {
      name: 'loop',
      type: 'boolean',
      default: 'true',
      description: `Whether or not to loop over after reaching the end or start of the items. Used mainly for managing keyboard navigation`,
    },
  ]}
/>

### Tabs.Trigger

Extends [Button](/docs/components/button), adding:

<PropsTable
  data={[
    {
      name: 'value',
      type: 'string',
      description: `The value for the tabs state to be changed to after activation of the trigger`,
    },
    {
      name: 'onInteraction',
      type: `(type: InteractionType, layout: TabTriggerLayout | null) => void`,
      description: `Used for making custom indicators when trigger interacted with`,
    },
    {
      name: 'unstyled',
      type: `boolean`,
      description: `When true, remove all default tamagui styling`,
    },
  ]}
/>

### Tabs.Content

Where each tab's content will be shown. Extends [ThemeableStack](/docs/components/stacks#themeablestack), adding:

<PropsTable
  data={[
    {
      name: 'value',
      type: 'string',
      description: `Will show the content when the value matches the state of Tabs root`,
    },
    {
      name: 'forceMount',
      type: 'boolean',
      default: 'false',
      description: `Used to force mounting when more control is needed. Useful when controlling animation with Tamagui animations`,
    },
  ]}
/>

## Examples

### Animations

Here is a demo with more advanced animations using [AnimatePresence](/docs/core/animations#animatepresence-and-exitstyle) and [Trigger](#trigger)'s `onInteraction` prop.

<HeroContainer noPad showAnimationDriverControl>
  <TabsAdvancedDemo />
</HeroContainer>

```tsx hero template=TabsAdvanced

```


## components/tabs/2.0.0

---
title: Tabs
description: Use in pages to manage sub-pages.
name: tabs
component: Tabs
package: tabs
demoName: Tabs
---

<YStack className="is-sticky" />

<Tabs id="type" defaultValue="styled">
<Tabs.List>
  <TooltipSimple label="With Tamagui's default styles">
    <Tabs.Tab value="styled">Styled</Tabs.Tab>
  </TooltipSimple>
  <TooltipSimple label="No default styles, easy to customize">
    <Tabs.Tab value="unstyled">Unstyled</Tabs.Tab>
  </TooltipSimple>
  <TooltipSimple label="No dependency on Tamagui's core">
    <Tabs.Tab value="headless" label="No styles and no dependency on Tamagui's styling">Headless</Tabs.Tab>
  </TooltipSimple>
</Tabs.List>

<HeroContainer>
  <Tabs.Content
    value="styled"
    justifyContent="center"
    alignItems="center"
    width="100%"
  >
    <TabsDemo />
  </Tabs.Content>
  <Tabs.Content
    value="unstyled"
    justifyContent="center"
    alignItems="center"
    width="100%"
  >
    <TabsHeadlessDemo />
  </Tabs.Content>
  <Tabs.Content
    value="headless"
    justifyContent="center"
    alignItems="center"
    width="100%"
  >
    <TabsHeadlessDemo />
  </Tabs.Content>
</HeroContainer>

<Tabs.Content value="styled">
```tsx hero template=Tabs

```
</Tabs.Content>
<Tabs.Content value="unstyled">
```tsx hero template=TabsHeadless

```
</Tabs.Content>
<Tabs.Content value="headless">
```tsx hero template=TabsHeadless

```
</Tabs.Content>

<Highlights
  features={[
    `Accessible, easy to compose, customize and animate`,
    `Sizable & works controlled or uncontrolled`,
    `Supports automatic and manual activation modes`,
    `Full keyboard navigation`,
  ]}
/>

## Installation

<Tabs.Content value="styled">
Tabs is already installed in `tamagui`, or you can install it independently:

```bash
npm install @tamagui/tabs
```

</Tabs.Content>

<Tabs.Content value="unstyled">
Tabs is already installed in `tamagui`, or you can install it independently:

```bash
npm install @tamagui/tabs
```

</Tabs.Content>

<Tabs.Content value="headless">

To use the headless tabs, import from the `@tamagui/tabs-headless` package. This package has no dependency on `@tamagui/core` and provides hooks for building custom tab interfaces with any styling solution.

```bash
npm install @tamagui/tabs-headless
```

</Tabs.Content>

## Usage

<Tabs.Content value="styled">

```tsx
import { SizableText, Tabs } from "tamagui";

export default () => (
  <Tabs defaultValue="tab1" width={400}>
    <Tabs.List>
      <Tabs.Tab value="tab1">
        <SizableText>Tab 1</SizableText>
      </Tabs.Tab>
      <Tabs.Tab value="tab2">
        <SizableText>Tab 2</SizableText>
      </Tabs.Tab>
    </Tabs.List>

    <Tabs.Content value="tab1">
      <H5>Tab 1</H5>
    </Tabs.Content>
    <Tabs.Content value="tab2">
      <H5>Tab 2</H5>
    </Tabs.Content>
  </Tabs>
);
```

</Tabs.Content>

<Tabs.Content value="unstyled">

Use the `createTabs` export to create fully custom tabs that still use the Tamagui styling system. You provide your own styled frame components and get back a fully functional tabs component.

```tsx template=TabsHeadless

```

</Tabs.Content>

<Tabs.Content value="headless">

The `useTabs` hook provides all the state and accessibility props needed to build custom tabs with any styling solution.

```tsx
import { useTabs } from '@tamagui/tabs-headless'

function MyTabs() {
  const { tabsProps, listProps, getTabProps, getContentProps, value } = useTabs({
    defaultValue: 'tab1',
    orientation: 'horizontal',
  })

  return (
    <div {...tabsProps}>
      <div {...listProps}>
        <button {...getTabProps('tab1')}>Tab 1</button>
        <button {...getTabProps('tab2')}>Tab 2</button>
      </div>

      <div {...getContentProps('tab1')}>
        {value === 'tab1' && <p>Content 1</p>}
      </div>
      <div {...getContentProps('tab2')}>
        {value === 'tab2' && <p>Content 2</p>}
      </div>
    </div>
  )
}
```

### Component-Based API

For more complex use cases, you can use the context-based hooks:

```tsx
import {
  useTabs,
  TabsProvider,
  useTab,
  useTabContent
} from '@tamagui/tabs-headless'

function Tabs({ children, ...props }) {
  const { contextValue, tabsProps } = useTabs(props)
  return (
    <TabsProvider value={contextValue}>
      <div {...tabsProps}>{children}</div>
    </TabsProvider>
  )
}

function Tab({ value, children }) {
  const { isSelected, tabProps } = useTab({ value })
  return (
    <button {...tabProps} style={{ fontWeight: isSelected ? 'bold' : 'normal' }}>
      {children}
    </button>
  )
}

function TabContent({ value, children }) {
  const { shouldMount, contentProps } = useTabContent({ value })
  if (!shouldMount) return null
  return <div {...contentProps}>{children}</div>
}
```

</Tabs.Content>

## API Reference

### Tabs

<Tabs.Content value="styled">

Root tabs component. Extends [Stack](/docs/components/stacks). Passing the `size` prop to this component will affect the descendants.

</Tabs.Content>
<Tabs.Content value="unstyled">

When using `createTabs`, you provide three styled components:

- `TabsFrame`: The root container component
- `TabFrame`: The tab trigger component
- `ContentFrame`: The content container component

</Tabs.Content>
<Tabs.Content value="headless">

The `useTabs` hook accepts these options and returns props/helpers:

</Tabs.Content>

<PropsTable
  data={[
    {
      name: "value",
      type: "string",
      description: `The value for the selected tab, if controlled`,
    },
    {
      name: "defaultValue",
      type: "string",
      description: `The value of the tab to select by default, if uncontrolled`,
    },
    {
      name: "onValueChange",
      type: "(value: string) => void",
      description: `A function called when a new tab is selected`,
    },
    {
      name: "orientation",
      type: '"horizontal" | "vertical"',
      default: "horizontal",
      description: `The orientation the tabs are laid out.`,
    },
    {
      name: "dir",
      type: '"ltr" | "rtl"',
      description: `The direction of navigation between toolbar items`,
    },
    {
      name: "activationMode",
      type: '"manual" | "automatic"',
      default: "manual",
      description: `Whether a tab is activated automatically (on focus) or manually (on click/enter)`,
    },
    {
      name: "loop",
      type: "boolean",
      default: "true",
      description: `Whether keyboard navigation should loop from last to first and vice versa`,
    },
  ]}
/>

<Tabs.Content value="headless">

### useTabs Return Value

| Property | Type | Description |
|----------|------|-------------|
| `value` | `string` | The currently selected tab value |
| `setValue` | `(value: string) => void` | Function to change the selected tab |
| `direction` | `'ltr' \| 'rtl'` | The resolved text direction |
| `tabsProps` | `object` | Props to spread on the tabs container |
| `listProps` | `object` | Props to spread on the tab list |
| `getTabProps` | `(value: string, disabled?: boolean) => object` | Get props for a tab trigger |
| `getContentProps` | `(value: string) => object` | Get props for a tab content panel |
| `contextValue` | `TabsContextValue` | Context value for component-based API |

### useTab

Hook for individual tab triggers when using the component-based API.

```tsx
const { isSelected, tabProps } = useTab({
  value: 'tab1',
  disabled: false,
  onPress: () => {},
  onKeyDown: () => {},
  onFocus: () => {},
})
```

### useTabContent

Hook for tab content panels when using the component-based API.

```tsx
const { isSelected, shouldMount, contentProps } = useTabContent({
  value: 'tab1',
  forceMount: false,
})
```

</Tabs.Content>

<Tabs.Content value="styled">

### Tabs.List

Container for the trigger buttons. Supports scrolling by extending [Group](/docs/components/group). You can disable passing border radius to children by passing `disablePassBorderRadius`.

<PropsTable
  data={[
    {
      name: "loop",
      type: "boolean",
      default: "true",
      description: `Whether or not to loop over after reaching the end or start of the items. Used mainly for managing keyboard navigation`,
    },
  ]}
/>

<Notice>
  Since Tabs.List extends Group, the same limitation applies: automatic border radius detection only works when `Tabs.Tab` is a **direct child** of `Tabs.List`. If you wrap tabs in custom components, see the [Group docs on nested items](/docs/components/group#custom-components--nested-items) for workarounds.
</Notice>

### Tabs.Tab

Extends [ThemeableStack](/docs/components/stacks#themeablestack), adding:

<PropsTable
  data={[
    {
      name: "value",
      type: "string",
      description: `The value for the tabs state to be changed to after activation of the trigger`,
    },
    {
      name: "onInteraction",
      type: `(type: InteractionType, layout: TabLayout | null) => void`,
      description: `Used for making custom indicators when trigger interacted with`,
    },
    {
      name: "disabled",
      type: `boolean`,
      description: `Whether the tab is disabled`,
    },
    {
      name: "unstyled",
      type: `boolean`,
      description: `When true, remove all default tamagui styling`,
    },
    {
      name: "activeStyle",
      type: `StyleProp`,
      description: `Styles to apply when the tab is selected`,
    },
    {
      name: "activeTheme",
      type: `string | null`,
      description: `Theme to apply when the tab is selected. Set to null for no theme change.`,
    },
  ]}
/>

### Tabs.Content

Where each tab's content will be shown. Extends [ThemeableStack](/docs/components/stacks#themeablestack), adding:

<PropsTable
  data={[
    {
      name: "value",
      type: "string",
      description: `Will show the content when the value matches the state of Tabs root`,
    },
    {
      name: "forceMount",
      type: "boolean",
      default: "false",
      description: `Used to force mounting when more control is needed. Useful when controlling animation with Tamagui animations`,
    },
  ]}
/>

## Examples

### Animations

Here is a demo with more advanced animations using [AnimatePresence](/docs/core/animations#animatepresence-and-exitstyle) and [Tab](#tabstab)'s `onInteraction` prop.

<HeroContainer noPad showAnimationDriverControl>
  <TabsAdvancedDemo />
</HeroContainer>

```tsx hero template=TabsAdvanced

```

</Tabs.Content>

</Tabs>


## components/tamagui-image/1.0.0

---
title: Image
description:
  Web compatible and super light image component with Tamagui style props.
name: html
component: Image
package: image-next
demoName: WebNativeImageDemo
---

<HeroContainer noPad>
  <WebNativeImageDemo />
</HeroContainer>

```tsx hero template=WebNativeImage

```

<Highlights
  features={[
    'Web compatible.',
    'Supports SSR.',
    'Works on native and web.',
    'No react-native-web dependency',
    'Super light',
  ]}
/>

## Installation

Image is already installed in `tamagui`, or you can install it independently:

```bash
npm install @tamagui/image
```

## Usage

```tsx
export default () => <Image src="https://..." width={300} height={400} />
```

## API Reference

### Image

[Tamagui props](/docs/intro/props) +
[Web img props](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img) +
[React Native Image props](https://necolas.github.io/react-native-web/docs/image/).

<Notice>
  All web [img](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img)
  props are supported on web, and all native
  [Image](https://necolas.github.io/react-native-web/docs/image/) props are
  supported on native. on native we are still using web img APIs, but not all
  web img props are supported. like `decoding`
</Notice>

#### Some common props

<PropsTable
  data={[
    {
      name: 'src',
      required: true,
      type: 'string',
      description: `The image URL.`,
    },
    {
      name: 'alt',
      required: false,
      type: 'string',
      description: `mandatory and incredibly useful for accessibility`,
    },
    {
      name: 'objectFit',
      required: false,
      type: 'CSS.ObjectFit',
      description: `sets how the content of image is resized to fit its container. it's alternative to resizeMode prop`,
    },
    {
      name: 'unstyled',
      required: false,
      type: 'boolean',
      description: `When true will remove all default styles`,
    },
    {
      name: 'onLoad',
      required: false,
      type: 'function',
      description: `Callback when image is loaded`,
    },
    {
      name: 'onError',
      required: false,
      type: 'function',
      description: `Callback when image fails to load`,
    },
  ]}
/>


## components/tamagui-image/2.0.0

---
title: Image
description:
  Web compatible and super light image component with Tamagui style props.
name: html
component: Image
package: image
demoName: WebNativeImageDemo
---

<HeroContainer noPad>
  <WebNativeImageDemo />
</HeroContainer>

```tsx hero template=WebNativeImage

```

<Highlights
  features={[
    'Web compatible.',
    'Supports SSR.',
    'Works on native and web.',
    'No react-native-web dependency',
    'Super light',
  ]}
/>

## Installation

Image is already installed in `tamagui`, or you can install it independently:

```bash
npm install @tamagui/image
```

## Usage

```tsx
export default () => <Image src="https://..." width={300} height={400} />
```

## API Reference

### Image

[Tamagui props](/docs/intro/props) +
[Web img props](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img) +
[React Native Image props](https://necolas.github.io/react-native-web/docs/image/).

<Notice>
  All web [img](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img)
  props are supported on web, and all native
  [Image](https://necolas.github.io/react-native-web/docs/image/) props are
  supported on native. on native we are still using web img APIs, but not all
  web img props are supported. like `decoding`
</Notice>

#### Some common props

<PropsTable
  data={[
    {
      name: 'src',
      required: true,
      type: 'string',
      description: `The image URL.`,
    },
    {
      name: 'alt',
      required: false,
      type: 'string',
      description: `mandatory and incredibly useful for accessibility`,
    },
    {
      name: 'objectFit',
      required: false,
      type: 'CSS.ObjectFit',
      description: `sets how the content of image is resized to fit its container. it's alternative to resizeMode prop`,
    },
    {
      name: 'unstyled',
      required: false,
      type: 'boolean',
      description: `When true will remove all default styles`,
    },
    {
      name: 'onLoad',
      required: false,
      type: 'function',
      description: `Callback when image is loaded`,
    },
    {
      name: 'onError',
      required: false,
      type: 'function',
      description: `Callback when image fails to load`,
    },
  ]}
/>


## components/text/1.0.0-alpha

---
title: Text
description: Text, Sized Text and Paragraph show one way to build a design system.
name: text
component: Paragraph
demoName: Text
---

# Text

<Description>
  Text, Sized Text and Paragraph show one way to build a design system.
</Description>

<HeroContainer>
  <TextDemo />
</HeroContainer>

```tsx hero template=Text

```

<Highlights
  features={[
    'Supports all react-native-web props and Tamagui styling props.',
    'Media query styles, hoverStyle, pressStyle.',
    'Paragraph uses themes and spread variants for a nicer default.',
  ]}
/>

### Usage

Text in Tamagui matches to Text in react-native-web, just with the added [Tamagui Props](/docs/intro/props).

It explicitly doesn't inherit your theme color or other font properties, as it's meant to be plain and used for extension. Below, we'll show `SizableText` which extends Text, and `Paragraph` which extends SizableText. Generally, Paragraph is the useful view as it will use theme values, while you can extend Text if you'd like to derive your own design system.

```tsx
import { Text, XStack, YStack } from 'tamagui'

export default () => (
  <>
    <Text
      // can add theme values
      color="$white"
      fontFamily="$body"
      // or just use direct values
      fontSize={20}
      hoverStyle={{
        color: '$color2',
      }}
    >
      Lorem ipsum
    </Text>
  </>
)
```

<Notice>
  SizableText and Paragraph default to the "body" fontFamily defined in your config.
  Headings all default to "heading".
</Notice>

## SizableText

[Seeing how SizableText is defined](https://github.com/tamagui/tamagui/blob/master/packages/tamagui/src/views/SizableText.tsx) is helpful for understanding Tamagui. They serve as a good example of how you can extend and compose components.

SizableText simply adds a single `size` property to maniplulate all of:

- fontSize
- lineHeight
- fontWeight
- letterSpacing

Based on the values set in your tokens. It uses [spread variants](/docs/core/styled#spread-variants) feature. Then Paragraph extends that and ensures it defaults to values from your theme - fontSize, lineHeight, color and fontFamily.

## Paragraph

Finally Paragraph extends `SizableText` and simply sets some default values from your theme:

```tsx
export const Paragraph = styled(SizableText, {
  fontFamily: '$body',
  color: '$color',
  // note tamagui uses a generic "true" token that your sizes should set to be the same as the default on your scale
  size: '$true',
})
```


## components/text/1.0.0

---
title: Text
description: Text primitives with themes custom to each font.
name: text
component: Paragraph
package: text
demoName: Text
---

<HeroContainer demoMultiple>
  <TextDemo />
</HeroContainer>

```tsx hero template=Text

```

<Highlights
  features={[
    'Themes that give you control over spacing, weights, and sizes custom to each font.',
    'Size prop that automatically matches all theme values.',
    'Media query styles, hoverStyle, pressStyle, focusStyle.',
  ]}
/>

## Installation

Text is already installed in `tamagui`, or you can install it independently:

```bash
npm install @tamagui/text
```

## Usage

```tsx
export default () => (
  <>
    <Text>Text</Text>
    <SizableText>Sizable Text</SizableText>
    <Paragraph>Paragraph</Paragraph>
  </>
)
```

## Text

Text in Tamagui matches to Text in react-native-web, just with the added [Tamagui Props](/docs/intro/props).

It explicitly doesn't inherit your theme color or other font properties, as it's meant to be plain and used for extension. Below, we'll show `SizableText` which extends Text, and `Paragraph` which extends SizableText. Generally, Paragraph is the useful view as it will use theme values, while you can extend Text if you'd like to derive your own design system.

```tsx
import { Text, XStack, YStack } from 'tamagui'

export default () => (
  <>
    <Text
      // can add theme values
      color="$white"
      fontFamily="$body"
      // or just use direct values
      fontSize={20}
      hoverStyle={{
        color: '$colorHover',
      }}
    >
      Lorem ipsum
    </Text>
  </>
)
```

## SizableText

Tamagui lets you define font sizing, spacing, line height, letter spacing and other properties with `createFont`, of which you can have many different configurations. We've found a nice pattern is to "align" all your keys across these sub-objects.

SizableText adds a `size` property thats defined using a [spread variant](/docs/core/styled#spread-variants) which looks for a matching key on each of these properties (using `@tamagui/get-font-sized`):

- color
- fontStyle
- textTransform
- fontFamily
- fontWeight
- letterSpacing
- fontSize
- lineHeight

So, if you've defined `small`, `medium` and `large` keys on each createFont category, you can use it like so:

```tsx
<SizableText size="$small" />
```

[Source code for SizableText](https://github.com/tamagui/tamagui/blob/main/code/ui/text/src/SizableText.tsx).

## Paragraph

Paragraph extends SizableText and is defined as:

```tsx
export const Paragraph = styled(SizableText, {
  name: 'Paragraph',
  tag: 'p',
  userSelect: 'auto',
  color: '$color',
  size: '$true',
  whiteSpace: 'normal',
})
```

<Notice>
  Note: Paragraph renders to a `p` tag on web, which can cause issues when you nest them
  during SSR. If you don't mind rendering to a span, use `SizableText`, otherwise, be
  careful when nesting items inside a Paragraph.
</Notice>


## components/text/2.0.0

---
title: Text
description: Text primitives with themes custom to each font.
name: text
component: Paragraph
package: text
demoName: Text
---

<HeroContainer demoMultiple>
  <TextDemo />
</HeroContainer>

```tsx hero template=Text

```

<Highlights
  features={[
    'Themes that give you control over spacing, weights, and sizes custom to each font.',
    'Size prop that automatically matches all theme values.',
    'Media query styles, hoverStyle, pressStyle, focusStyle.',
  ]}
/>

<Notice theme="green">
  The base `Text` component is already included in `@tamagui/core` (and `tamagui`). This package is optional and adds `SizableText` and `Paragraph`, which extend `Text` with the `size` prop and theme-aware defaults.
</Notice>

## Installation

If you're using `tamagui`, `SizableText` and `Paragraph` are already included. Otherwise, install independently:

```bash
npm install @tamagui/text
```

## Usage

```tsx
export default () => (
  <>
    <Text>Text</Text>
    <SizableText>Sizable Text</SizableText>
    <Paragraph>Paragraph</Paragraph>
  </>
)
```

## Text

Text in Tamagui matches to Text in react-native-web, just with the added [Tamagui Props](/docs/intro/props).

It explicitly doesn't inherit your theme color or other font properties, as it's meant to be plain and used for extension. Below, we'll show `SizableText` which extends Text, and `Paragraph` which extends SizableText. Generally, Paragraph is the useful view as it will use theme values, while you can extend Text if you'd like to derive your own design system.

```tsx
import { Text, XStack, YStack } from 'tamagui'

export default () => (
  <>
    <Text
      // can add theme values
      color="$white"
      fontFamily="$body"
      // or just use direct values
      fontSize={20}
      hoverStyle={{
        color: '$colorHover',
      }}
    >
      Lorem ipsum
    </Text>
  </>
)
```

## SizableText

Tamagui lets you define font sizing, spacing, line height, letter spacing and other properties with `createFont`, of which you can have many different configurations. We've found a nice pattern is to "align" all your keys across these sub-objects.

SizableText adds a `size` property thats defined using a [spread variant](/docs/core/styled#spread-variants) which looks for a matching key on each of these properties (using `@tamagui/get-font-sized`):

- color
- fontStyle
- textTransform
- fontFamily
- fontWeight
- letterSpacing
- fontSize
- lineHeight

So, if you've defined `small`, `medium` and `large` keys on each createFont category, you can use it like so:

```tsx
<SizableText size="$small" />
```

[Source code for SizableText](https://github.com/tamagui/tamagui/blob/main/code/ui/text/src/SizableText.tsx).

## Paragraph

Paragraph extends SizableText and is defined as:

```tsx
export const Paragraph = styled(SizableText, {
  name: 'Paragraph',
  tag: 'p',
  userSelect: 'auto',
  color: '$color',
  size: '$true',
  whiteSpace: 'normal',
})
```

<Notice>
  Note: Paragraph renders to a `p` tag on web, which can cause issues when you nest them
  during SSR. If you don't mind rendering to a span, use `SizableText`, otherwise, be
  careful when nesting items inside a Paragraph.
</Notice>


## components/toast/1.11.3

---
title: Toast
description: A toast component with native features
name: toast
component: Toast
package: toast
demoName: Toast
---

# Toast <Beta />

<Description>Use to show feedback to user interactions</Description>

<HeroContainer showAnimationDriverControl>
  <ToastDemo />
</HeroContainer>

```tsx hero template=Toast

```

<Highlights
  features={[
    `Automatically closes`,
    `Pause closing on hover, focus, window blur and mobile touch`,
    `Supports closing via swipe gesture`,
    `Easily animatable with Tamagui's animation drivers`,
    `Native toasts included for Android, iOS and web (notification API)`,
  ]}
/>

## Installation

Run the following command:

```
yarn add @tamagui/toast burnt
```

then, rebuild your React Native app. React Native requires sub-dependencies with native dependencies always be hoisted to your apps package.json and Toast relies on the amazing [Burnt](https://github.com/nandorojo/burnt) library by Fernando Rojo to provide its native functionality.

## Usage

To display the toast natively, you should either pass an array of native platforms (`native: ["ios", "web"]`), a single platform or `true` for all platforms.

```tsx
import { Button } from 'tamagui' // or '@tamagui/button'
import { Toast, ToastProvider, useToast } from '@tamagui/toast'

export default () => (
  <ToastProvider native={['mobile']}>
    <CurrentToast />
    <MyPage />
    <ToastViewport />
  </ToastProvider>
)

const CurrentToast = () => {
  const { currentToast } = useToast()

  // only show the component if it's present and not handled by native toast
  if (!currentToast || currentToast.isHandledNatively) return null
  return (
    <Toast key={currentToast.id}>
      <Toast.Title>{currentToast.title}</Toast.Title>
      <Toast.Description>{currentToast.message}</Toast.Description>
    </Toast>
  )
}

const MyPage = () => {
  const { show } = useToast()

  return (
    <Button onPress={() => show('Done!', { message: 'Form submitted successfully.' })}>
      Show Toast
    </Button>
  )
}
```

## API

### ToastProvider

Your toasts should be wrapped within a `ToastProvider`. This is usually done at the root of your application.

<PropsTable
  data={[
    {
      name: 'label',
      required: false,
      type: 'string',
      description: `An author-localized label for each toast. Used to help screen reader users associate the interruption with a toast.`,
      default: 'Notification',
    },
    {
      name: 'duration',
      required: false,
      type: 'number',
      description: `Time in milliseconds that each toast should remain visible for. This could be overwritten at the toast level as well.`,
      default: 5000,
    },
    {
      name: 'swipeDirection',
      required: false,
      type: 'SwipeDirection',
      description: `Direction of pointer swipe that should close the toast.`,
      default: 'right',
    },
    {
      name: 'swipeThreshold',
      required: false,
      type: 'number',
      description: `Distance in pixels that the swipe must pass before a close is triggered.`,
      default: 50,
    },
    {
      name: 'id',
      required: false,
      type: 'string',
      default: 'A unique generated ID',
    },
    {
      name: 'native',
      required: false,
      type: 'boolean | ToastNativePlatform | ToastNativePlatform[]',
      description: `Will show a native toast if is true or is set to the current platform. On iOS, it wraps \`SPIndicator\` and \`SPAlert\`. On Android, it wraps \`ToastAndroid\`. On web, it wraps Notification API. Mobile's native features are handled by \`burnt\`.`,
    },
    {
      name: `burntOptions`,
      required: false,
      type: `Omit<BurntToastOptions, 'title' | 'message' | 'duration'>`,
      description: `Options for the burnt package if you're using native toasts on mobile`,
    },
    {
      name: `notificationOptions`,
      required: false,
      type: `NotificationOptions`,
      description: `Options for the notification API if you're using native toasts on web`,
    },
  ]}
/>

### ToastViewport

The portal for toasts to be directed to. Should be used inside [ToastProvider](#toastprovider). Beyond [Stack Props](/docs/components/stacks/1.0.0), adds:

<PropsTable
  data={[
    {
      name: 'hotkey',
      type: 'string[]',
      default: "['F8']",
      required: false,
      description: `The keys to use as the keyboard shortcut that will move focus to the toast viewport.`,
    },
    {
      name: 'label',
      type: 'string',
      default: 'Notifications ({hotkey})',
      required: false,
      description: `An author-localized label for the toast viewport to provide context for screen reader users when navigating page landmarks. The available \`{hotkey}\` placeholder will be replaced for you.`,
    },
    {
      name: 'name',
      type: 'string',
      required: false,
      description: `Used to reference the viewport if you want to have multiple viewports in the same provider.`,
    },
    {
      name: 'multipleToasts',
      type: 'boolean',
      required: false,
      description: `Pass this when you want to have multiple/duplicated toasts.`,
    },
  ]}
/>

### Toast

Contains the Title, Description, Action and Close component. Should be used inside [ToastProvider](#toastprovider). Extends [Stack](/docs/components/stack#api) and adds:

<PropsTable
  data={[
    {
      name: 'forceMount',
      type: 'boolean',
      required: false,
      description: `Used to force mounting when more control is needed. Useful when controlling animation with React animation libraries.`,
    },
    {
      name: 'type',
      type: "'foreground' | 'background'",
      required: false,
      description: `Control the sensitivity of the toast for accessibility purposes. For toasts that are the result of a user action, choose foreground. Toasts generated from background tasks should use background.`,
    },
    {
      name: 'duration',
      type: 'number',
      required: false,
      description: `Time in milliseconds that toast should remain visible for. Overrides value given to \`ToastProvider\`.`,
    },
    {
      name: 'defaultOpen',
      type: 'boolean',
      required: false,
      description: `The open state of the dialog when it is initially rendered. Use when you do not need to control its open state.`,
    },
    {
      name: 'open',
      type: 'boolean',
      required: false,
      description: `The controlled open state of the dialog. Must be used in conjunction with \`onOpenChange\`.`,
    },
    {
      name: 'onOpenChange',
      type: '(open: boolean): void',
      required: false,
      description: `Event handler called when the open state of the dialog changes.`,
    },
    {
      name: 'onEscapeKeyDown',
      type: "(): DismissableProps['onEscapeKeyDown']",
      required: false,
      description: `Event handler called when the escape key is down. It can be prevented by calling \`event.preventDefault\`.`,
    },
    {
      name: 'onPause',
      type: '(): void',
      required: false,
      description: `Event handler called when the dismiss timer is paused. On web, this occurs when the pointer is moved over the viewport, the viewport is focused or when the window is blurred. On mobile, this occurs when the toast is touched.`,
    },
    {
      name: 'onResume',
      type: '(): void',
      required: false,
      description: `Event handler called when the dismiss timer is resumed. On web, this occurs when the pointer is moved away from the viewport, the viewport is blurred or when the window is focused. On mobile, this occurs when the toast is released.`,
    },
    {
      name: 'onSwipeStart',
      type: '(event: SwipeEvent): void',
      required: false,
      description: `Event handler called when starting a swipe interaction. It can be prevented by calling \`event.preventDefault\`.`,
    },
    {
      name: 'onSwipeMove',
      type: '(event: SwipeEvent): void',
      required: false,
      description: `Event handler called during a swipe interaction. It can be prevented by calling \`event.preventDefault\`.`,
    },
    {
      name: 'onSwipeCancel',
      type: '(event: SwipeEvent): void',
      required: false,
      description: `Event handler called at the cancellation of a swipe interaction. It can be prevented by calling \`event.preventDefault\`.`,
    },
    {
      name: 'onSwipeEnd',
      type: '(event: SwipeEvent): void',
      required: false,
      description: `Event handler called at the end of a swipe interaction. It can be prevented by calling \`event.preventDefault\`.`,
    },
    {
      name: 'viewportName',
      type: 'string',
      required: false,
      description: `The viewport's name to send the toast to. Used when using multiple viewports and want to forward toasts to different ones.`,
      default: 'default',
    },
  ]}
/>

#### Toast.Title

Should be used inside [Toast](#toast). Extends [SizableText](/docs/components/text/1.0.0#sizabletext).

#### Toast.Description

Should be used inside [Toast](#toast). Extends [SizableText](/docs/components/text/1.0.0#sizabletext).

#### Toast.Close

Should be used inside [Toast](#toast). Extends [Stack](/docs/components/stacks/1.0.0). You can pass `asChild` to this component and use a custom `<Button>` inside.

#### Toast.Action

Should be used inside [Toast](#toast). Extends [Stack](/docs/components/stacks/1.0.0). You can pass `asChild` to this component and use a custom `<Button>` inside.

### useToast

Should be used inside [ToastProvider](#toastprovider).

<PropsTable
  title="Returns"
  data={[
    {
      name: 'currentToast',
      type: 'ToastData | null',
      description: `The information about the current toast to show such as title, message, duration, etc.`,
    },
    {
      name: `show`,
      type: '(title: string, showOptions?: ShowToastOptions): void',
      description: `Call it to show a new toast. If you're using native toasts, you can pass native options using \`burntOptions\` or \`notificationOptions\` depending on the native platform (mobile/web).`,
    },
    {
      name: `hide`,
      type: '(): void',
      description: `Call it to hide the currently displayed toast.`,
    },
  ]}
/>

## FAQ

#### How to change the placement of toasts?

Native toasts:

- iOS (burnt): Supports top or bottom placements. Adjustable by passing `from` to `burntOptions`:

```tsx
<ToastProvider burntOptions={{ from: 'bottom' }}>
```

- Android (burnt): Not supported.
- Web (Notification API): Not supported.

Non-native toasts:

You should change the positioning of your [`<ToastViewport>`](#toastviewport). For instance, if you want them to appear from top right:

```tsx
<ToastViewport flexDirection="column-reverse" top={0} right={0} />
```

Or for bottom center:

```tsx
<ToastViewport flexDirection="column" bottom={0} left={0} right={0} />
```

<Notice theme="blue">
  When using multiple toasts, you can change the order of toasts by setting
  `flexDirection` to `column` or `column-reverse`. Or even have them stack horizontally
  using `row` or `row-reverse`.
</Notice>

#### How to show non-native toasts within safe area on mobile?

Install `react-native-safe-area-context` if you haven't, wrap your app inside `<SafeAreaProvider>`, and use the safe area insets to position the viewport inside the safe area.

```tsx
import { useSafeAreaInsets } from 'react-native-safe-area-context'

const SafeToastViewport = () => {
  const { left, top, right } = useSafeAreaInsets()
  return (
    <ToastViewport flexDirection="column-reverse" top={top} left={left} right={right} />
  )
}
```

#### Can I send toasts to different toast viewports?

Yes, but you will have to name them and then reference the viewport name on the `<Toast>` component. for instance:

```tsx
const App = () => {
  return (
    <ToastProvider>
      <ToastViewport /> {/* name will be "default" */}
      <ToastViewport name="viewport-custom" />
    </ToastProvider>
  )
}

const MyComponent = () => {
  return <Toast>{/* goes to default viewport */}</Toast>
}

const MyComponent2 = () => {
  return <Toast viewportName="viewport-custom" />
}
```

#### How can I have more control over toasts?

You will have to opt-out of the native toasts and only use custom ones. Here are some examples:

##### Single Toast

```tsx
export default () => {
  const [open, setOpen] = React.useState(false)
  const timerRef = React.useRef(0)

  React.useEffect(() => {
    return () => clearTimeout(timerRef.current)
  }, [])

  return (
    <YStack ai="center">
      <Button
        onPress={() => {
          setOpen(false)
          window.clearTimeout(timerRef.current)
          timerRef.current = window.setTimeout(() => {
            setOpen(true)
          }, 150)
        }}
      >
        Single Toast
      </Button>
      <Toast
        onOpenChange={setOpen}
        open={open}
        animation="100ms"
        enterStyle={{ x: -20, opacity: 0 }}
        exitStyle={{ x: -20, opacity: 0 }}
        opacity={1}
        x={0}
      >
        <Toast.Title>Subscribed!</Toast.Title>
        <Toast.Description>We'll be in touch.</Toast.Description>
      </Toast>
    </YStack>
  )
}
```

##### Multiple Toasts

<Notice>
  To use multiple toasts, you should pass `multipleToasts` to your `ToastViewport`.
  Otherwise there'll be issues when swipe-dismissing or animating toasts.
</Notice>

```tsx
export default () => {
  const [savedCount, setSavedCount] = React.useState(0)

  return (
    <YStack ai="center">
      <Button
        onPress={() => {
          setSavedCount((old) => old + 1)
        }}
      >
        Show toast
      </Button>
      {[...Array(savedCount)].map((_, index) => (
        <Toast
          key={index}
          animation="100ms"
          enterStyle={{ x: -20, opacity: 0 }}
          exitStyle={{ x: -20, opacity: 0 }}
          opacity={1}
          x={0}
        >
          <Toast.Title>Subscribed!</Toast.Title>
          <Toast.Description>We'll be in touch.</Toast.Description>
        </Toast>
      ))}
    </YStack>
  )
}
```


## components/toast/1.13.0

---
title: Toast
description: A toast component with native features
name: toast
component: Toast
package: toast
demoName: Toast
---

# Toast <Beta />

<Description>Use to show feedback to user interactions</Description>

<HeroContainer showAnimationDriverControl>
  <ToastDemo />
</HeroContainer>

```tsx hero template=Toast

```

<Highlights
  features={[
    `Automatically closes`,
    `Pause closing on hover, focus, window blur and mobile touch`,
    `Supports closing via swipe gesture`,
    `Easily animatable with Tamagui's animation drivers`,
    `Native toasts included for Android, iOS and web (notification API)`,
  ]}
/>

## Installation

Run the following command:

```
yarn add @tamagui/toast burnt
```

then, if targetin native, rebuild your React Native app. React Native requires sub-dependencies with native dependencies always be hoisted to your apps package.json and Toast relies on the amazing [Burnt](https://github.com/nandorojo/burnt) library by Fernando Rojo to provide its native functionality.

## Usage

To display the toast natively, you should either pass an array of native platforms (`native: ["ios", "web"]`), a single platform or `true` for all platforms.

```tsx
import { Toast, ToastProvider, useToastController, useToastState } from '@tamagui/toast'
import { Button } from 'tamagui' // or '@tamagui/button'

export default () => (
  <ToastProvider native={['mobile']}>
    <CurrentToast />
    <MyPage />
    <ToastViewport />
  </ToastProvider>
)

const CurrentToast = () => {
  const toast = useToastState()

  // only show the component if it's present and not handled by native toast
  if (!toast || toast.isHandledNatively) {
    return null
  }

  return (
    <Toast key={toast.id}>
      <Toast.Title>{toast.title}</Toast.Title>
      <Toast.Description>{toast.message}</Toast.Description>
    </Toast>
  )
}

const MyPage = () => {
  const { show } = useToastController()

  return (
    <Button onPress={() => show('Done!', { message: 'Form submitted successfully.' })}>
      Show Toast
    </Button>
  )
}
```

## API

### ToastProvider

Your toasts should be wrapped within a `ToastProvider`. This is usually done at the root of your application.

<PropsTable
  data={[
    {
      name: 'label',
      required: false,
      type: 'string',
      description: `An author-localized label for each toast. Used to help screen reader users associate the interruption with a toast.`,
      default: 'Notification',
    },
    {
      name: 'duration',
      required: false,
      type: 'number',
      description: `Time in milliseconds that each toast should remain visible for. This could be overwritten at the toast level as well.`,
      default: 5000,
    },
    {
      name: 'swipeDirection',
      required: false,
      type: 'SwipeDirection',
      description: `Direction of pointer swipe that should close the toast.`,
      default: 'right',
    },
    {
      name: 'swipeThreshold',
      required: false,
      type: 'number',
      description: `Distance in pixels that the swipe must pass before a close is triggered.`,
      default: 50,
    },
    {
      name: 'id',
      required: false,
      type: 'string',
      default: 'A unique generated ID',
    },
    {
      name: 'native',
      required: false,
      type: 'boolean | ToastNativePlatform | ToastNativePlatform[]',
      description: `Will show a native toast if is true or is set to the current platform. On iOS, it wraps \`SPIndicator\` and \`SPAlert\`. On Android, it wraps \`ToastAndroid\`. On web, it wraps Notification API. Mobile's native features are handled by \`burnt\`.`,
    },
    {
      name: `burntOptions`,
      required: false,
      type: `Omit<BurntToastOptions, 'title' | 'message' | 'duration'>`,
      description: `Options for the burnt package if you're using native toasts on mobile`,
    },
    {
      name: `notificationOptions`,
      required: false,
      type: `NotificationOptions`,
      description: `Options for the notification API if you're using native toasts on web`,
    },
  ]}
/>

### ToastViewport

The portal for toasts to be directed to. Should be used inside [ToastProvider](#toastprovider). Beyond [Stack Props](/docs/components/stacks/1.0.0), adds:

<PropsTable
  data={[
    {
      name: 'hotkey',
      type: 'string[]',
      default: "['F8']",
      required: false,
      description: `The keys to use as the keyboard shortcut that will move focus to the toast viewport.`,
    },
    {
      name: 'label',
      type: 'string',
      default: 'Notifications ({hotkey})',
      required: false,
      description: `An author-localized label for the toast viewport to provide context for screen reader users when navigating page landmarks. The available \`{hotkey}\` placeholder will be replaced for you.`,
    },
    {
      name: 'name',
      type: 'string',
      required: false,
      description: `Used to reference the viewport if you want to have multiple viewports in the same provider.`,
    },
    {
      name: 'multipleToasts',
      type: 'boolean',
      required: false,
      description: `Pass this when you want to have multiple/duplicated toasts.`,
    },
  ]}
/>

### Toast

Contains the Title, Description, Action and Close component. Should be used inside [ToastProvider](#toastprovider). Extends [Stack](/docs/components/stack#api) and adds:

<PropsTable
  data={[
    {
      name: 'forceMount',
      type: 'boolean',
      required: false,
      description: `Used to force mounting when more control is needed. Useful when controlling animation with React animation libraries.`,
    },
    {
      name: 'type',
      type: "'foreground' | 'background'",
      required: false,
      description: `Control the sensitivity of the toast for accessibility purposes. For toasts that are the result of a user action, choose foreground. Toasts generated from background tasks should use background.`,
    },
    {
      name: 'duration',
      type: 'number',
      required: false,
      description: `Time in milliseconds that toast should remain visible for. Overrides value given to \`ToastProvider\`.`,
    },
    {
      name: 'defaultOpen',
      type: 'boolean',
      required: false,
      description: `The open state of the dialog when it is initially rendered. Use when you do not need to control its open state.`,
    },
    {
      name: 'open',
      type: 'boolean',
      required: false,
      description: `The controlled open state of the dialog. Must be used in conjunction with \`onOpenChange\`.`,
    },
    {
      name: 'onOpenChange',
      type: '(open: boolean): void',
      required: false,
      description: `Event handler called when the open state of the dialog changes.`,
    },
    {
      name: 'onEscapeKeyDown',
      type: "(): DismissableProps['onEscapeKeyDown']",
      required: false,
      description: `Event handler called when the escape key is down. It can be prevented by calling \`event.preventDefault\`.`,
    },
    {
      name: 'onPause',
      type: '(): void',
      required: false,
      description: `Event handler called when the dismiss timer is paused. On web, this occurs when the pointer is moved over the viewport, the viewport is focused or when the window is blurred. On mobile, this occurs when the toast is touched.`,
    },
    {
      name: 'onResume',
      type: '(): void',
      required: false,
      description: `Event handler called when the dismiss timer is resumed. On web, this occurs when the pointer is moved away from the viewport, the viewport is blurred or when the window is focused. On mobile, this occurs when the toast is released.`,
    },
    {
      name: 'onSwipeStart',
      type: '(event: SwipeEvent): void',
      required: false,
      description: `Event handler called when starting a swipe interaction. It can be prevented by calling \`event.preventDefault\`.`,
    },
    {
      name: 'onSwipeMove',
      type: '(event: SwipeEvent): void',
      required: false,
      description: `Event handler called during a swipe interaction. It can be prevented by calling \`event.preventDefault\`.`,
    },
    {
      name: 'onSwipeCancel',
      type: '(event: SwipeEvent): void',
      required: false,
      description: `Event handler called at the cancellation of a swipe interaction. It can be prevented by calling \`event.preventDefault\`.`,
    },
    {
      name: 'onSwipeEnd',
      type: '(event: SwipeEvent): void',
      required: false,
      description: `Event handler called at the end of a swipe interaction. It can be prevented by calling \`event.preventDefault\`.`,
    },
    {
      name: 'viewportName',
      type: 'string',
      required: false,
      description: `The viewport's name to send the toast to. Used when using multiple viewports and want to forward toasts to different ones.`,
      default: 'default',
    },
  ]}
/>

#### Toast.Title

Should be used inside [Toast](#toast). Extends [SizableText](/docs/components/text/1.0.0#sizabletext).

#### Toast.Description

Should be used inside [Toast](#toast). Extends [SizableText](/docs/components/text/1.0.0#sizabletext).

#### Toast.Close

Should be used inside [Toast](#toast). Extends [Stack](/docs/components/stacks/1.0.0). You can pass `asChild` to this component and use a custom `<Button>` inside.

#### Toast.Action

Should be used inside [Toast](#toast). Extends [Stack](/docs/components/stacks/1.0.0). You can pass `asChild` to this component and use a custom `<Button>` inside.

### useToastController

Used to control the display of toasts. Should be used inside [ToastProvider](#toastprovider).

<PropsTable
  title="Returns"
  data={[
    {
      name: `show`,
      type: '(title: string, showOptions?: ShowToastOptions): void',
      description: `Call it to show a new toast. If you're using native toasts, you can pass native options using \`burntOptions\` or \`notificationOptions\` depending on the native platform (mobile/web).`,
    },
    {
      name: `hide`,
      type: '(): void',
      description: `Call it to hide the currently displayed toast.`,
    },
  ]}
/>

### useToastState

Used to render out your toast contents. Should be used inside [ToastProvider](#toastprovider).

<PropsTable
  title="Hook for rendering toast"
  data={[
    {
      name: `title`,
      type: 'string',
    },
    {
      name: `id`,
      type: 'string',
    },
    {
      name: `message`,
      type: 'string',
    },
    {
      name: `duration`,
      type: 'number',
    },
  ]}
/>

## FAQ

#### How to change the placement of toasts?

Native toasts:

- iOS (burnt): Supports top or bottom placements. Adjustable by passing `from` to `burntOptions`:

```tsx
<ToastProvider burntOptions={{ from: 'bottom' }}>
```

- Android (burnt): Not supported.
- Web (Notification API): Not supported.

Non-native toasts:

You should change the positioning of your [`<ToastViewport>`](#toastviewport). For instance, if you want them to appear from top right:

```tsx
<ToastViewport flexDirection="column-reverse" top={0} right={0} />
```

Or for bottom center:

```tsx
<ToastViewport flexDirection="column" bottom={0} left={0} right={0} />
```

<Notice theme="blue">
  When using multiple toasts, you can change the order of toasts by setting
  `flexDirection` to `column` or `column-reverse`. Or even have them stack horizontally
  using `row` or `row-reverse`.
</Notice>

#### How to show non-native toasts within safe area on mobile?

Install `react-native-safe-area-context` if you haven't, wrap your app inside `<SafeAreaProvider>`, and use the safe area insets to position the viewport inside the safe area.

```tsx
import { useSafeAreaInsets } from 'react-native-safe-area-context'

const SafeToastViewport = () => {
  const { left, top, right } = useSafeAreaInsets()
  return (
    <ToastViewport flexDirection="column-reverse" top={top} left={left} right={right} />
  )
}
```

#### Can I send toasts to different toast viewports?

Yes, but you will have to name them and then reference the viewport name on the `<Toast>` component. for instance:

```tsx
const App = () => {
  return (
    <ToastProvider>
      <ToastViewport /> {/* name will be "default" */}
      <ToastViewport name="viewport-custom" />
    </ToastProvider>
  )
}

const MyComponent = () => {
  return <Toast>{/* goes to default viewport */}</Toast>
}

const MyComponent2 = () => {
  return <Toast viewportName="viewport-custom" />
}
```

#### How can I have more control over toasts?

You will have to opt-out of the native toasts and only use custom ones. Here are some examples:

##### Single Toast

```tsx
export default () => {
  const [open, setOpen] = React.useState(false)
  const timerRef = React.useRef(0)

  React.useEffect(() => {
    return () => clearTimeout(timerRef.current)
  }, [])

  return (
    <YStack ai="center">
      <Button
        onPress={() => {
          setOpen(false)
          window.clearTimeout(timerRef.current)
          timerRef.current = window.setTimeout(() => {
            setOpen(true)
          }, 150)
        }}
      >
        Single Toast
      </Button>
      <Toast
        onOpenChange={setOpen}
        open={open}
        animation="100ms"
        enterStyle={{ x: -20, opacity: 0 }}
        exitStyle={{ x: -20, opacity: 0 }}
        opacity={1}
        x={0}
      >
        <Toast.Title>Subscribed!</Toast.Title>
        <Toast.Description>We'll be in touch.</Toast.Description>
      </Toast>
    </YStack>
  )
}
```

##### Multiple Toasts

<Notice>
  To use multiple toasts, you should pass `multipleToasts` to your `ToastViewport`.
  Otherwise there'll be issues when swipe-dismissing or animating toasts.
</Notice>

```tsx
export default () => {
  const [savedCount, setSavedCount] = React.useState(0)

  return (
    <YStack ai="center">
      <Button
        onPress={() => {
          setSavedCount((old) => old + 1)
        }}
      >
        Show toast
      </Button>
      {[...Array(savedCount)].map((_, index) => (
        <Toast
          key={index}
          animation="100ms"
          enterStyle={{ x: -20, opacity: 0 }}
          exitStyle={{ x: -20, opacity: 0 }}
          opacity={1}
          x={0}
        >
          <Toast.Title>Subscribed!</Toast.Title>
          <Toast.Description>We'll be in touch.</Toast.Description>
        </Toast>
      ))}
    </YStack>
  )
}
```


## components/toast/1.15.15

---
title: Toast
description: A toast component with native features
name: toast
component: Toast
package: toast
demoName: Toast
---

# Toast <Beta />

<Description>Use to show feedback to user interactions</Description>

<HeroContainer showAnimationDriverControl>
  <ToastDemo />
</HeroContainer>

```tsx hero template=Toast

```

<Highlights
  features={[
    `Automatically closes`,
    `Pause closing on hover, focus, window blur and mobile touch`,
    `Supports closing via swipe gesture`,
    `Easily animatable with Tamagui's animation drivers`,
    `Native toasts included for Android, iOS and web (notification API)`,
  ]}
/>

## Installation

Run the following command:

```bash
yarn add @tamagui/toast burnt
```

Then, if targeting native, rebuild your React Native app. React Native requires sub-dependencies with native dependencies always be hoisted to your apps package.json and Toast relies on the amazing [Burnt](https://github.com/nandorojo/burnt) library by Fernando Rojo to provide its native functionality.

## Anatomy

```tsx
<ToastProvider>
  <Toast>
    <Toast.Title />
    <Toast.Description />
    <Toast.Action />
    <Toast.Close />
  </Toast>

  <ToastViewport />
</ToastProvider>
```

## API Reference

### ToastProvider

Your toasts should be wrapped within a `ToastProvider`. This is usually done at the root of your application.

<PropsTable
  data={[
    {
      name: 'label',
      required: false,
      type: 'string',
      description: `An author-localized label for each toast. Used to help screen reader users associate the interruption with a toast.`,
      default: 'Notification',
    },
    {
      name: 'duration',
      required: false,
      type: 'number',
      description: `Time in milliseconds that each toast should remain visible for. This could be overwritten at the toast level as well.`,
      default: 5000,
    },
    {
      name: 'swipeDirection',
      required: false,
      type: 'SwipeDirection',
      description: `Direction of pointer swipe that should close the toast.`,
      default: 'right',
    },
    {
      name: 'swipeThreshold',
      required: false,
      type: 'number',
      description: `Distance in pixels that the swipe must pass before a close is triggered.`,
      default: 50,
    },
    {
      name: 'id',
      required: false,
      type: 'string',
      default: 'A unique generated ID',
    },
    {
      name: 'native',
      required: false,
      type: 'boolean | ToastNativePlatform | ToastNativePlatform[]',
      description: `Will show a native toast if is true or is set to the current platform. On iOS, it wraps \`SPIndicator\` and \`SPAlert\`. On Android, it wraps \`ToastAndroid\`. On web, it wraps Notification API. Mobile's native features are handled by \`burnt\`.`,
    },
    {
      name: `burntOptions`,
      required: false,
      type: `Omit<BurntToastOptions, 'title' | 'message' | 'duration'>`,
      description: `Options for the burnt package if you're using native toasts on mobile`,
    },
    {
      name: `notificationOptions`,
      required: false,
      type: `NotificationOptions`,
      description: `Options for the notification API if you're using native toasts on web`,
    },
  ]}
/>

### ToastViewport

The portal for toasts to be directed to. Should be used inside [ToastProvider](#toastprovider). Beyond [Stack Props](/docs/components/stacks/1.0.0), adds:

<PropsTable
  data={[
    {
      name: 'hotkey',
      type: 'string[]',
      default: "['F8']",
      required: false,
      description: `The keys to use as the keyboard shortcut that will move focus to the toast viewport.`,
    },
    {
      name: 'label',
      type: 'string',
      default: 'Notifications ({hotkey})',
      required: false,
      description: `An author-localized label for the toast viewport to provide context for screen reader users when navigating page landmarks. The available \`{hotkey}\` placeholder will be replaced for you.`,
    },
    {
      name: 'name',
      type: 'string',
      required: false,
      description: `Used to reference the viewport if you want to have multiple viewports in the same provider.`,
    },
    {
      name: 'multipleToasts',
      type: 'boolean',
      required: false,
      description: `Pass this when you want to have multiple/duplicated toasts.`,
    },
    {
      name: 'unstyled',
      type: 'boolean',
      default: 'false',
      description: `When true, remove all default tamagui styling.`,
    },
  ]}
/>

### Toast

Contains the Title, Description, Action and Close component. Should be used inside [ToastProvider](#toastprovider). Extends [Stack](/docs/components/stack#api) and adds:

<PropsTable
  data={[
    {
      name: 'forceMount',
      type: 'boolean',
      required: false,
      description: `Used to force mounting when more control is needed. Useful when controlling animation with React animation libraries.`,
    },
    {
      name: 'type',
      type: "'foreground' | 'background'",
      required: false,
      description: `Control the sensitivity of the toast for accessibility purposes. For toasts that are the result of a user action, choose foreground. Toasts generated from background tasks should use background.`,
    },
    {
      name: 'duration',
      type: 'number',
      required: false,
      description: `Time in milliseconds that toast should remain visible for. Overrides value given to \`ToastProvider\`.`,
    },
    {
      name: 'defaultOpen',
      type: 'boolean',
      required: false,
      description: `The open state of the dialog when it is initially rendered. Use when you do not need to control its open state.`,
    },
    {
      name: 'open',
      type: 'boolean',
      required: false,
      description: `The controlled open state of the dialog. Must be used in conjunction with \`onOpenChange\`.`,
    },
    {
      name: 'onOpenChange',
      type: '(open: boolean): void',
      required: false,
      description: `Event handler called when the open state of the dialog changes.`,
    },
    {
      name: 'onEscapeKeyDown',
      type: "(): DismissableProps['onEscapeKeyDown']",
      required: false,
      description: `Event handler called when the escape key is down. It can be prevented by calling \`event.preventDefault\`.`,
    },
    {
      name: 'onPause',
      type: '(): void',
      required: false,
      description: `Event handler called when the dismiss timer is paused. On web, this occurs when the pointer is moved over the viewport, the viewport is focused or when the window is blurred. On mobile, this occurs when the toast is touched.`,
    },
    {
      name: 'onResume',
      type: '(): void',
      required: false,
      description: `Event handler called when the dismiss timer is resumed. On web, this occurs when the pointer is moved away from the viewport, the viewport is blurred or when the window is focused. On mobile, this occurs when the toast is released.`,
    },
    {
      name: 'onSwipeStart',
      type: '(event: SwipeEvent): void',
      required: false,
      description: `Event handler called when starting a swipe interaction. It can be prevented by calling \`event.preventDefault\`.`,
    },
    {
      name: 'onSwipeMove',
      type: '(event: SwipeEvent): void',
      required: false,
      description: `Event handler called during a swipe interaction. It can be prevented by calling \`event.preventDefault\`.`,
    },
    {
      name: 'onSwipeCancel',
      type: '(event: SwipeEvent): void',
      required: false,
      description: `Event handler called at the cancellation of a swipe interaction. It can be prevented by calling \`event.preventDefault\`.`,
    },
    {
      name: 'onSwipeEnd',
      type: '(event: SwipeEvent): void',
      required: false,
      description: `Event handler called at the end of a swipe interaction. It can be prevented by calling \`event.preventDefault\`.`,
    },
    {
      name: 'viewportName',
      type: 'string',
      required: false,
      description: `The viewport's name to send the toast to. Used when using multiple viewports and want to forward toasts to different ones.`,
      default: 'default',
    },
    {
      name: 'unstyled',
      type: 'boolean',
      default: 'false',
      description: `When true, remove all default tamagui styling.`,
    },
  ]}
/>

### Toast.Title

Should be used inside [Toast](#toast). Extends [SizableText](/docs/components/text/1.0.0#sizabletext), adding:

<PropsTable
  data={[
    {
      name: 'unstyled',
      type: 'boolean',
      default: 'false',
      description: `When true, remove all default tamagui styling.`,
    },
  ]}
/>

### Toast.Description

Should be used inside [Toast](#toast). Extends [SizableText](/docs/components/text/1.0.0#sizabletext), adding:

<PropsTable
  data={[
    {
      name: 'unstyled',
      type: 'boolean',
      default: 'false',
      description: `When true, remove all default tamagui styling.`,
    },
  ]}
/>

### Toast.Close

Should be used inside [Toast](#toast). Extends [Stack](/docs/components/stacks/1.0.0). You can pass `asChild` to this component and use a custom `<Button>` inside.

### Toast.Action

Should be used inside [Toast](#toast). Extends [Stack](/docs/components/stacks/1.0.0). You can pass `asChild` to this component and use a custom `<Button>` inside.

### useToastController

Used to control the display of toasts. Should be used inside [ToastProvider](#toastprovider).

<PropsTable
  title="Returns"
  data={[
    {
      name: `show`,
      type: '(title: string, showOptions?: ShowToastOptions): void',
      description: `Call it to show a new toast. If you're using native toasts, you can pass native options using \`burntOptions\` or \`notificationOptions\` depending on the native platform (mobile/web).`,
    },
    {
      name: `hide`,
      type: '(): void',
      description: `Call it to hide the currently displayed toast.`,
    },
    {
      name: `options`,
      type: `ToastOptions`,
      description: `You can use if need to access the toast options.`,
    },
  ]}
/>

### useToastState

Used to render out your toast contents. Should be used inside [ToastProvider](#toastprovider). Doesn't take in anything and returns `ToastData`.

```tsx
const CurrentToast = () => {
  const toast = useToastState()

  // don't show any toast if no toast is present or it's handled natively
  if (!toast || toast.isHandledNatively) {
    return null
  }

  return (
    <Toast key={toast.id} duration={toast.duration} viewport={toast.viewport}>
      <Toast.Title>{toast.title}</Toast.Title>
      <Toast.Description>{toast.message}</Toast.Description>
    </Toast>
  )
}
```

## Examples

### Position the viewport

To position the viewport on native toasts:

- iOS (burnt): Supports top or bottom placements. Adjustable by passing `from` to `burntOptions`:

```tsx
<ToastProvider burntOptions={{ from: 'bottom' }}>
```

- Android (burnt): Not supported.
- Web (Notification API): Not supported.

To position the viewport on custom toasts:

You should change the positioning of your [`<ToastViewport>`](#toastviewport). For instance, if you want them to appear from top right:

```tsx
<ToastViewport flexDirection="column-reverse" top={0} right={0} />
```

Or for bottom center:

```tsx
<ToastViewport flexDirection="column" bottom={0} left={0} right={0} />
```

<Notice theme="blue">
  When using multiple toasts, you can change the order of toasts by setting
  `flexDirection` to `column` or `column-reverse`. Or even have them stack horizontally
  using `row` or `row-reverse`.
</Notice>

### Mobile safe area

To show toasts inside device's safe area, install `react-native-safe-area-context` if you haven't, wrap your app inside `<SafeAreaProvider>`, and then use the safe area insets to position the viewport inside the safe area.

```tsx
import { useSafeAreaInsets } from 'react-native-safe-area-context'

const SafeToastViewport = () => {
  const { left, top, right } = useSafeAreaInsets()
  return (
    <ToastViewport flexDirection="column-reverse" top={top} left={left} right={right} />
  )
}
```

### Different viewports

To send toasts to different viewports, you can set up different viewports:

```tsx
const App = () => {
  return (
    <ToastProvider>
      <ToastViewport /> {/* default viewport */}
      <ToastViewport name="viewport-custom" />
    </ToastProvider>
  )
}
```

And then, use the viewport's name on the toasts.

```tsx
const MyComponent = () => {
  return <Toast /> // default viewport
}

const MyComponent2 = () => {
  return <Toast viewportName="viewport-custom" />
}

```

### Custom data

Just pass your custom data to the second parameter of the `show()` method.

```ts
const toastController = useToastController()
toastController.show("Title", { myPreset: 'error' }) // or toastController.show("Title", { customData: { myPreset: 'error' } })
```

then, when showing the toast, you can retrieve them like so:

```ts
const toastState = useToastState()
toastState.myPreset // or toastState.customData.myPreset
```

To add TypeScript auto-completion for your custom fields, you can use TS module augmentation:

```ts
declare module '@tamagui/toast' {
  interface CustomData {
    myPreset: 'error' | 'success' | 'warning'
  }
}

```

### Without hooks

You can also use toasts without the hooks.

<Notice>You can't use native toasts this way.</Notice>

#### Single Toast

```tsx
export default () => {
  const [open, setOpen] = React.useState(false)
  const timerRef = React.useRef(0)

  React.useEffect(() => {
    return () => clearTimeout(timerRef.current)
  }, [])

  return (
    <YStack ai="center">
      <Button
        onPress={() => {
          setOpen(false)
          window.clearTimeout(timerRef.current)
          timerRef.current = window.setTimeout(() => {
            setOpen(true)
          }, 150)
        }}
      >
        Single Toast
      </Button>
      <Toast
        onOpenChange={setOpen}
        open={open}
        animation="100ms"
        enterStyle={{ x: -20, opacity: 0 }}
        exitStyle={{ x: -20, opacity: 0 }}
        opacity={1}
        x={0}
      >
        <Toast.Title>Subscribed!</Toast.Title>
        <Toast.Description>We'll be in touch.</Toast.Description>
      </Toast>
    </YStack>
  )
}
```

#### Multiple Toasts

<Notice>
  To use multiple toasts, you should pass `multipleToasts` to your `ToastViewport`.
  Otherwise there'll be issues when swipe-dismissing or animating toasts.
</Notice>

```tsx
export default () => {
  const [savedCount, setSavedCount] = React.useState(0)

  return (
    <YStack ai="center">
      <Button
        onPress={() => {
          setSavedCount((old) => old + 1)
        }}
      >
        Show toast
      </Button>
      {[...Array(savedCount)].map((_, index) => (
        <Toast
          key={index}
          animation="100ms"
          enterStyle={{ x: -20, opacity: 0 }}
          exitStyle={{ x: -20, opacity: 0 }}
          opacity={1}
          x={0}
        >
          <Toast.Title>Subscribed!</Toast.Title>
          <Toast.Description>We'll be in touch.</Toast.Description>
        </Toast>
      ))}
    </YStack>
  )
}
```


## components/toast/1.8.0

---
title: Toast
description: A toast component with native features
name: toast
component: Toast
package: toast
demoName: Toast
---

# Toast <Beta />

<Description>Use to show feedback to user interactions</Description>

<HeroContainer showAnimationDriverControl>
  <ToastDemo />
</HeroContainer>

```tsx hero template=Toast

```

<Highlights
  features={[
    `Automatically closes`,
    `Pause closing on hover, focus, window blur and mobile touch`,
    `Supports closing via swipe gesture`,
    `Easily animatable with Tamagui's animation drivers`,
    `Native toasts included for Android, iOS and web`,
  ]}
/>

## Install

For native support, run `yarn add burnt` to add `burnt`, then rebuild your React Native app. React Native requires sub-dependencies with native dependencies always be hoisted to your apps package.json and Toast relies on the amazing [Burnt](https://github.com/nandorojo/burnt) library by Fernando Rojo to provide its native functionality.

## Anatomy

```tsx
import { Toast, ToastProvider, ToastViewport } from 'tamagui' // or '@tamagui/toast'

export default () => (
  <ToastProvider>
    <Toast>
      <Toast.Title />
      <Toast.Description />
      <Toast.Action />
      <Toast.Close />
    </Toast>

    <ToastViewport />
  </ToastProvider>
)
```

## Usage

### Single Toast

```tsx
export default () => {
  const [open, setOpen] = React.useState(false)
  const timerRef = React.useRef(0)

  React.useEffect(() => {
    return () => clearTimeout(timerRef.current)
  }, [])

  return (
    <YStack ai="center">
      <Button
        onPress={() => {
          setOpen(false)
          window.clearTimeout(timerRef.current)
          timerRef.current = window.setTimeout(() => {
            setOpen(true)
          }, 150)
        }}
      >
        Single Toast
      </Button>
      <Toast
        onOpenChange={setOpen}
        open={open}
        animation="100ms"
        enterStyle={{ x: -20, opacity: 0 }}
        exitStyle={{ x: -20, opacity: 0 }}
        opacity={1}
        x={0}
      >
        <Toast.Title>Subscribed!</Toast.Title>
        <Toast.Description>We'll be in touch.</Toast.Description>
      </Toast>
    </YStack>
  )
}
```

### Duplicate Toasts

```tsx
export default () => {
  const [savedCount, setSavedCount] = React.useState(0)

  return (
    <YStack ai="center">
      <Button
        onPress={() => {
          setSavedCount((old) => old + 1)
        }}
      >
        Show toast
      </Button>
      {[...Array(savedCount)].map((_, index) => (
        <Toast
          key={index}
          animation="100ms"
          enterStyle={{ x: -20, opacity: 0 }}
          exitStyle={{ x: -20, opacity: 0 }}
          opacity={1}
          x={0}
        >
          <Toast.Title>Subscribed!</Toast.Title>
          <Toast.Description>We'll be in touch.</Toast.Description>
        </Toast>
      ))}
    </YStack>
  )
}
```

### Using `createToast`

```tsx
import { Button } from 'tamagui' // or '@tamagui/button'
import { Toast, ToastProvider, createToast } from 'tamagui' // or '@tamagui/toast'

export const { ImperativeToastProvider, useToast } = createToast()

export default () => (
  <ToastProvider>
    <ImperativeToastProvider>
      <CurrentToast />
      <MyPage />
    </ImperativeToastProvider>

    <ToastViewport />
  </ToastProvider>
)

const CurrentToast = () => {
  const { currentToast } = useToast()

  if (!currentToast) return
  return (
    <Toast key={currentToast.id}>
      <Toast.Title>{currentToast.title}</Toast.Title>
      <Toast.Description>{currentToast.message}</Toast.Description>
    </Toast>
  )
}

const MyPage = () => {
  const { show } = useToast()

  return (
    <Button onPress={() => show('Done!', { message: 'Form submitted successfully.' })}>
      Show Toast
    </Button>
  )
}
```

## API

### ToastProvider

Your toasts should be wrapped within a `ToastProvider`. This is usually done at the root of your application.

<PropsTable
  data={[
    {
      name: 'label',
      required: false,
      type: 'string',
      description: `An author-localized label for each toast. Used to help screen reader users associate the interruption with a toast.`,
      default: 'Notification',
    },
    {
      name: 'duration',
      required: false,
      type: 'number',
      description: `Time in milliseconds that each toast should remain visible for. This could be overwritten at the toast level as well.`,
      default: 5000,
    },
    {
      name: 'swipeDirection',
      required: false,
      type: 'SwipeDirection',
      description: `Direction of pointer swipe that should close the toast.`,
      default: 'right',
    },
    {
      name: 'swipeThreshold',
      required: false,
      type: 'number',
      description: `Distance in pixels that the swipe must pass before a close is triggered.`,
      default: 50,
    },
    {
      name: 'id',
      required: false,
      type: 'string',
      default: 'A unique generated ID',
    },
  ]}
/>

### ToastViewport

The portal for toasts to be directed to. Should be inside [ToastProvider](#toastprovider). Beyond [Stack Props](/docs/components/stacks/1.0.0), adds:

<PropsTable
  data={[
    {
      name: 'hotkey',
      type: 'string[]',
      default: "['F8']",
      required: false,
      description: `The keys to use as the keyboard shortcut that will move focus to the toast viewport.`,
    },
    {
      name: 'label',
      type: 'string',
      default: 'Notifications ({hotkey})',
      required: false,
      description: `An author-localized label for the toast viewport to provide context for screen reader users when navigating page landmarks. The available \`{hotkey}\` placeholder will be replaced for you.`,
    },
    {
      name: 'name',
      type: 'string',
      required: false,
      description: `Used to reference the viewport if you want to have multiple viewports in the same provider.`,
    },
  ]}
/>

### Toast

Contains the Title, Description, Action and Close component. Should be inside [ToastProvider](#toastprovider). Extends [Stack](/docs/components/stack#api) and adds:

<PropsTable
  data={[
    {
      name: 'forceMount',
      type: 'boolean',
      required: false,
      description: `Used to force mounting when more control is needed. Useful when controlling animation with React animation libraries.`,
    },
    {
      name: 'type',
      type: "'foreground' | 'background'",
      required: false,
      description: `Control the sensitivity of the toast for accessibility purposes. For toasts that are the result of a user action, choose foreground. Toasts generated from background tasks should use background.`,
    },
    {
      name: 'duration',
      type: 'number',
      required: false,
      description: `Time in milliseconds that toast should remain visible for. Overrides value given to \`ToastProvider\`.`,
    },
    {
      name: 'defaultOpen',
      type: 'boolean',
      required: false,
      description: `The open state of the dialog when it is initially rendered. Use when you do not need to control its open state.`,
    },
    {
      name: 'open',
      type: 'boolean',
      required: false,
      description: `The controlled open state of the dialog. Must be used in conjunction with \`onOpenChange\`.`,
    },
    {
      name: 'onOpenChange',
      type: '(open: boolean): void',
      required: false,
      description: `Event handler called when the open state of the dialog changes.`,
    },
    {
      name: 'onEscapeKeyDown',
      type: "(): DismissableProps['onEscapeKeyDown']",
      required: false,
      description: `Event handler called when the escape key is down. It can be prevented by calling \`event.preventDefault\`.`,
    },
    {
      name: 'onPause',
      type: '(): void',
      required: false,
      description: `Event handler called when the dismiss timer is paused. On web, this occurs when the pointer is moved over the viewport, the viewport is focused or when the window is blurred. On mobile, this occurs when the toast is touched.`,
    },
    {
      name: 'onResume',
      type: '(): void',
      required: false,
      description: `Event handler called when the dismiss timer is resumed. On web, this occurs when the pointer is moved away from the viewport, the viewport is blurred or when the window is focused. On mobile, this occurs when the toast is released.`,
    },
    {
      name: 'onSwipeStart',
      type: '(event: SwipeEvent): void',
      required: false,
      description: `Event handler called when starting a swipe interaction. It can be prevented by calling \`event.preventDefault\`.`,
    },
    {
      name: 'onSwipeMove',
      type: '(event: SwipeEvent): void',
      required: false,
      description: `Event handler called during a swipe interaction. It can be prevented by calling \`event.preventDefault\`.`,
    },
    {
      name: 'onSwipeCancel',
      type: '(event: SwipeEvent): void',
      required: false,
      description: `Event handler called at the cancellation of a swipe interaction. It can be prevented by calling \`event.preventDefault\`.`,
    },
    {
      name: 'onSwipeEnd',
      type: '(event: SwipeEvent): void',
      required: false,
      description: `Event handler called at the end of a swipe interaction. It can be prevented by calling \`event.preventDefault\`.`,
    },
    {
      name: 'viewportName',
      type: 'string',
      required: false,
      description: `The viewport's name to send the toast to. Used when using multiple viewports and want to forward toasts to different ones.`,
      default: 'default',
    },
  ]}
/>

#### Toast.Title

Should be inside [Toast](#toast). Extends [SizableText](/docs/components/text/1.0.0#sizabletext).

#### Toast.Description

Should be inside [Toast](#toast). Extends [SizableText](/docs/components/text/1.0.0#sizabletext).

#### Toast.Close

Should be inside [Toast](#toast). Extends [Stack](/docs/components/stacks/1.0.0). You can pass `asChild` to this component and use a custom `<Button>` inside.

#### Toast.Action

Should be inside [Toast](#toast). Extends [Stack](/docs/components/stacks/1.0.0). You can pass `asChild` to this component and use a custom `<Button>` inside.

### createToast

An alternative way to work with toasts.

<PropsTable
  title="Arguments"
  data={[
    {
      name: 'native',
      type: "boolean | 'web' | 'mobile'",
      description: `Will show a native toast if is true or is set to the current platform. On iOS, it wraps \`SPIndicator\` and \`SPAlert\`. On Android, it wraps \`ToastAndroid\`. On web, it wraps Notification API. Mobile's native features are handled by \`burnt\`.`,
      default: 'false',
    },
  ]}
/>

This function, then returns the following:

#### ImperativeToastProvider

Wrap children within this provider so that they can use `useToast()`. Takes no params.

#### useToast

You may export this hook to use it throughout your app.

<PropsTable
  title="Returns"
  data={[
    {
      name: 'currentToast',
      type: 'ToastData | null',
      description: `The information about the current toast to showm such as title, message, duration, etc.`,
    },
    {
      name: `show`,
      type: '(title: string, showOptions?: ToastOptions): void',
      description: 'Call it to show a new toast.',
    },
  ]}
/>


## components/toast/1.83.0

---
title: Toast
description: Use to show feedback to user interactions.
name: toast
component: Toast
package: toast
demoName: Toast
---

<HeroContainer showAnimationDriverControl>
  <ToastDemo />
</HeroContainer>

```tsx hero template=Toast

```

<Highlights
  features={[
    `Automatically closes`,
    `Pause closing on hover, focus, window blur and mobile touch`,
    `Supports closing via swipe gesture`,
    `Easily animatable with Tamagui's animation drivers`,
    `Native toasts included for Android, iOS and web (notification API)`,
  ]}
/>

## Installation

Note that `@tamagui/toast` is the only UI package not included by default in `tamagui`. The reason for this is that Metro would force you to install the native `burnt` dependency, and that would mean `tamagui` wouldn't work with Expo Go and would require a native install step no matter if you used Toast or not. This is due to a limitation in Metro.

To install toast:

```bash
yarn add @tamagui/toast burnt
```

Then, if targeting native, rebuild your React Native app. React Native requires sub-dependencies with native dependencies always be hoisted to your apps package.json and Toast relies on the amazing [Burnt](https://github.com/nandorojo/burnt) library by Fernando Rojo to provide its native functionality.

We're open to a refactor that moves the burnt dependency into a sub-path like `@tamagui/toast/burnt` and forces setup through there, that likely fixes the Metro issue and allow us to include it by default.

## Anatomy

```tsx
<ToastProvider>
  <Toast>
    <Toast.Title />
    <Toast.Description />
    <Toast.Action />
    <Toast.Close />
  </Toast>

  <ToastViewport />
</ToastProvider>
```

## API Reference

### ToastProvider

Your toasts should be wrapped within a `ToastProvider`. This is usually done at the root of your application.

<PropsTable
  data={[
    {
      name: 'label',
      required: false,
      type: 'string',
      description: `An author-localized label for each toast. Used to help screen reader users associate the interruption with a toast.`,
      default: 'Notification',
    },
    {
      name: 'duration',
      required: false,
      type: 'number',
      description: `Time in milliseconds that each toast should remain visible for. This could be overwritten at the toast level as well.`,
      default: 5000,
    },
    {
      name: 'swipeDirection',
      required: false,
      type: 'SwipeDirection',
      description: `Direction of pointer swipe that should close the toast.`,
      default: 'right',
    },
    {
      name: 'swipeThreshold',
      required: false,
      type: 'number',
      description: `Distance in pixels that the swipe must pass before a close is triggered.`,
      default: 50,
    },
    {
      name: 'id',
      required: false,
      type: 'string',
      default: 'A unique generated ID',
    },
    {
      name: 'native',
      required: false,
      type: 'boolean | ToastNativePlatform | ToastNativePlatform[]',
      description: `Will show a native toast if is true or is set to the current platform. On iOS, it wraps \`SPIndicator\` and \`SPAlert\`. On Android, it wraps \`ToastAndroid\`. On web, it wraps Notification API. Mobile's native features are handled by \`burnt\`.`,
    },
    {
      name: `burntOptions`,
      required: false,
      type: `Omit<BurntToastOptions, 'title' | 'message' | 'duration'>`,
      description: `Options for the burnt package if you're using native toasts on mobile`,
    },
    {
      name: `notificationOptions`,
      required: false,
      type: `NotificationOptions`,
      description: `Options for the notification API if you're using native toasts on web`,
    },
  ]}
/>

### ToastViewport

The portal for toasts to be directed to. Should be used inside [ToastProvider](#toastprovider). Beyond [Stack Props](/docs/components/stacks), adds:

<PropsTable
  data={[
    {
      name: 'hotkey',
      type: 'string[]',
      default: "['F8']",
      required: false,
      description: `The keys to use as the keyboard shortcut that will move focus to the toast viewport.`,
    },
    {
      name: 'label',
      type: 'string',
      default: 'Notifications ({hotkey})',
      required: false,
      description: `An author-localized label for the toast viewport to provide context for screen reader users when navigating page landmarks. The available \`{hotkey}\` placeholder will be replaced for you.`,
    },
    {
      name: 'name',
      type: 'string',
      required: false,
      description: `Used to reference the viewport if you want to have multiple viewports in the same provider.`,
    },
    {
      name: 'multipleToasts',
      type: 'boolean',
      required: false,
      description: `Pass this when you want to have multiple/duplicated toasts.`,
    },
    {
      name: 'portalToRoot',
      type: 'boolean | number',
      default: 'false',
      description: `When true, uses a portal to render at the very top of the root TamaguiProvider.`,
    },
    {
      name: 'unstyled',
      type: 'boolean',
      default: 'false',
      description: `When true, remove all default tamagui styling.`,
    },
  ]}
/>

### Toast

Contains the Title, Description, Action and Close component. Should be used inside [ToastProvider](#toastprovider). Extends [Stack](/docs/components/stacks) and adds:

<PropsTable
  data={[
    {
      name: 'forceMount',
      type: 'boolean',
      required: false,
      description: `Used to force mounting when more control is needed. Useful when controlling animation with React animation libraries.`,
    },
    {
      name: 'type',
      type: "'foreground' | 'background'",
      required: false,
      description: `Control the sensitivity of the toast for accessibility purposes. For toasts that are the result of a user action, choose foreground. Toasts generated from background tasks should use background.`,
    },
    {
      name: 'duration',
      type: 'number',
      required: false,
      description: `Time in milliseconds that toast should remain visible for. Overrides value given to \`ToastProvider\`.`,
    },
    {
      name: 'defaultOpen',
      type: 'boolean',
      required: false,
      description: `The open state of the dialog when it is initially rendered. Use when you do not need to control its open state.`,
    },
    {
      name: 'open',
      type: 'boolean',
      required: false,
      description: `The controlled open state of the dialog. Must be used in conjunction with \`onOpenChange\`.`,
    },
    {
      name: 'onOpenChange',
      type: '(open: boolean): void',
      required: false,
      description: `Event handler called when the open state of the dialog changes.`,
    },
    {
      name: 'onEscapeKeyDown',
      type: "(): DismissableProps['onEscapeKeyDown']",
      required: false,
      description: `Event handler called when the escape key is down. It can be prevented by calling \`event.preventDefault\`.`,
    },
    {
      name: 'onPause',
      type: '(): void',
      required: false,
      description: `Event handler called when the dismiss timer is paused. On web, this occurs when the pointer is moved over the viewport, the viewport is focused or when the window is blurred. On mobile, this occurs when the toast is touched.`,
    },
    {
      name: 'onResume',
      type: '(): void',
      required: false,
      description: `Event handler called when the dismiss timer is resumed. On web, this occurs when the pointer is moved away from the viewport, the viewport is blurred or when the window is focused. On mobile, this occurs when the toast is released.`,
    },
    {
      name: 'onSwipeStart',
      type: '(event: SwipeEvent): void',
      required: false,
      description: `Event handler called when starting a swipe interaction. It can be prevented by calling \`event.preventDefault\`.`,
    },
    {
      name: 'onSwipeMove',
      type: '(event: SwipeEvent): void',
      required: false,
      description: `Event handler called during a swipe interaction. It can be prevented by calling \`event.preventDefault\`.`,
    },
    {
      name: 'onSwipeCancel',
      type: '(event: SwipeEvent): void',
      required: false,
      description: `Event handler called at the cancellation of a swipe interaction. It can be prevented by calling \`event.preventDefault\`.`,
    },
    {
      name: 'onSwipeEnd',
      type: '(event: SwipeEvent): void',
      required: false,
      description: `Event handler called at the end of a swipe interaction. It can be prevented by calling \`event.preventDefault\`.`,
    },
    {
      name: 'viewportName',
      type: 'string',
      required: false,
      description: `The viewport's name to send the toast to. Used when using multiple viewports and want to forward toasts to different ones.`,
      default: 'default',
    },
    {
      name: 'unstyled',
      type: 'boolean',
      default: 'false',
      description: `When true, remove all default tamagui styling.`,
    },
  ]}
/>

### Toast.Title

Should be used inside [Toast](#toast). Extends [SizableText](/docs/components/text#sizabletext), adding:

<PropsTable
  data={[
    {
      name: 'unstyled',
      type: 'boolean',
      default: 'false',
      description: `When true, remove all default tamagui styling.`,
    },
  ]}
/>

### Toast.Description

Should be used inside [Toast](#toast). Extends [SizableText](/docs/components/text#sizabletext), adding:

<PropsTable
  data={[
    {
      name: 'unstyled',
      type: 'boolean',
      default: 'false',
      description: `When true, remove all default tamagui styling.`,
    },
  ]}
/>

### Toast.Close

Should be used inside [Toast](#toast). Extends [Stack](/docs/components/stacks). You can pass `asChild` to this component and use a custom `<Button>` inside.

### Toast.Action

Should be used inside [Toast](#toast). Extends [Stack](/docs/components/stacks). You can pass `asChild` to this component and use a custom `<Button>` inside.

### useToastController

Used to control the display of toasts. Should be used inside [ToastProvider](#toastprovider).

<PropsTable
  title="Returns"
  data={[
    {
      name: `show`,
      type: '(title: string, showOptions?: ShowToastOptions): void',
      description: `Call it to show a new toast. If you're using native toasts, you can pass native options using \`burntOptions\` or \`notificationOptions\` depending on the native platform (mobile/web).`,
    },
    {
      name: `hide`,
      type: '(): void',
      description: `Call it to hide the currently displayed toast.`,
    },
    {
      name: `options`,
      type: `ToastOptions`,
      description: `You can use if need to access the toast options.`,
    },
  ]}
/>

### useToastState

Used to render out your toast contents. Should be used inside [ToastProvider](#toastprovider). Doesn't take in anything and returns `ToastData`.

```tsx
const CurrentToast = () => {
  const toast = useToastState()

  // don't show any toast if no toast is present or it's handled natively
  if (!toast || toast.isHandledNatively) {
    return null
  }

  return (
    <Toast key={toast.id} duration={toast.duration} viewport={toast.viewport}>
      <Toast.Title>{toast.title}</Toast.Title>
      <Toast.Description>{toast.message}</Toast.Description>
    </Toast>
  )
}
```

## Examples

### Position the viewport

To position the viewport on native toasts:

- iOS (burnt): Supports top or bottom placements. Adjustable by passing `from` to `burntOptions`:

```tsx
<ToastProvider burntOptions={{ from: 'bottom' }}>
```

- Android (burnt): Not supported.
- Web (Notification API): Not supported.

To position the viewport on custom toasts:

You should change the positioning of your [`<ToastViewport>`](#toastviewport). For instance, if you want them to appear from top right:

```tsx
<ToastViewport flexDirection="column-reverse" top={0} right={0} />
```

Or for bottom center:

```tsx
<ToastViewport flexDirection="column" bottom={0} left={0} right={0} />
```

<Notice theme="blue">
  When using multiple toasts, you can change the order of toasts by setting
  `flexDirection` to `column` or `column-reverse`. Or even have them stack horizontally
  using `row` or `row-reverse`.
</Notice>

### Mobile safe area

To show toasts inside device's safe area, install `react-native-safe-area-context` if you haven't, wrap your app inside `<SafeAreaProvider>`, and then use the safe area insets to position the viewport inside the safe area.

```tsx
import { useSafeAreaInsets } from 'react-native-safe-area-context'

const SafeToastViewport = () => {
  const { left, top, right } = useSafeAreaInsets()
  return (
    <ToastViewport flexDirection="column-reverse" top={top} left={left} right={right} />
  )
}
```

### Different viewports

To send toasts to different viewports, you can set up different viewports:

```tsx
const App = () => {
  return (
    <ToastProvider>
      <ToastViewport /> {/* default viewport */}
      <ToastViewport name="viewport-custom" />
    </ToastProvider>
  )
}
```

And then, use the viewport's name on the toasts.

```tsx
const MyComponent = () => {
  return <Toast /> // default viewport
}

const MyComponent2 = () => {
  return <Toast viewportName="viewport-custom" />
}

```

### Custom data

Just pass your custom data to the second parameter of the `show()` method.

```ts
const toastController = useToastController()
toastController.show("Title", { myPreset: 'error' }) // or toastController.show("Title", { customData: { myPreset: 'error' } })
```

then, when showing the toast, you can retrieve them like so:

```ts
const toastState = useToastState()
toastState.myPreset // or toastState.customData.myPreset
```

To add TypeScript auto-completion for your custom fields, you can use TS module augmentation:

```ts
declare module '@tamagui/toast' {
  interface CustomData {
    myPreset: 'error' | 'success' | 'warning'
  }
}

```

### Without hooks

You can also use toasts without the hooks.

<Notice>You can't use native toasts this way.</Notice>

#### Single Toast

```tsx
export default () => {
  const [open, setOpen] = React.useState(false)
  const timerRef = React.useRef(0)

  React.useEffect(() => {
    return () => clearTimeout(timerRef.current)
  }, [])

  return (
    <YStack ai="center">
      <Button
        onPress={() => {
          setOpen(false)
          window.clearTimeout(timerRef.current)
          timerRef.current = window.setTimeout(() => {
            setOpen(true)
          }, 150)
        }}
      >
        Single Toast
      </Button>
      <Toast
        onOpenChange={setOpen}
        open={open}
        animation="100ms"
        enterStyle={{ x: -20, opacity: 0 }}
        exitStyle={{ x: -20, opacity: 0 }}
        opacity={1}
        x={0}
      >
        <Toast.Title>Subscribed!</Toast.Title>
        <Toast.Description>We'll be in touch.</Toast.Description>
      </Toast>
    </YStack>
  )
}
```

#### Multiple Toasts

<Notice>
  To use multiple toasts, you should pass `multipleToasts` to your `ToastViewport`.
  Otherwise there'll be issues when swipe-dismissing or animating toasts.
</Notice>

```tsx
export default () => {
  const [savedCount, setSavedCount] = React.useState(0)

  return (
    <YStack ai="center">
      <Button
        onPress={() => {
          setSavedCount((old) => old + 1)
        }}
      >
        Show toast
      </Button>
      {[...Array(savedCount)].map((_, index) => (
        <Toast
          key={index}
          animation="100ms"
          enterStyle={{ x: -20, opacity: 0 }}
          exitStyle={{ x: -20, opacity: 0 }}
          opacity={1}
          x={0}
        >
          <Toast.Title>Subscribed!</Toast.Title>
          <Toast.Description>We'll be in touch.</Toast.Description>
        </Toast>
      ))}
    </YStack>
  )
}
```


## components/toast/1.9.1

---
title: Toast
description: A toast component with native features
name: toast
component: Toast
package: toast
demoName: Toast
---

# Toast <Beta />

<Description>Use to show feedback to user interactions</Description>

<HeroContainer showAnimationDriverControl>
  <ToastDemo />
</HeroContainer>

```tsx hero template=Toast

```

<Highlights
  features={[
    `Automatically closes`,
    `Pause closing on hover, focus, window blur and mobile touch`,
    `Supports closing via swipe gesture`,
    `Easily animatable with Tamagui's animation drivers`,
    `Native toasts included for Android, iOS and web (notification API)`,
  ]}
/>

## Usage

### Imperative API

This API offers a nice imperative interface, support for native toasts with a lot of control.

#### Installation

For native support on mobile, run `yarn add burnt` to add `burnt`, then rebuild your React Native app. React Native requires sub-dependencies with native dependencies always be hoisted to your apps package.json and Toast relies on the amazing [Burnt](https://github.com/nandorojo/burnt) library by Fernando Rojo to provide its native functionality.

Note: `Burnt` [will not work](https://github.com/nandorojo/burnt?tab=readme-ov-file#expo) with Expo Go.

#### Usage

To display the toast natively, you should either pass an array of native platforms (`native: ["ios", "web"]`), a single platform or `true` for all platforms.

```tsx
import { Button } from 'tamagui' // or '@tamagui/button'
import { Toast, ToastImperativeProvider, ToastProvider, useToast } from 'tamagui' // or '@tamagui/toast'

const options = { native: 'mobile' }

export default () => (
  <ToastProvider>
    <ToastImperativeProvider options={options}>
      <CurrentToast />
      <MyPage />
    </ToastImperativeProvider>

    <ToastViewport />
  </ToastProvider>
)

const CurrentToast = () => {
  const { currentToast } = useToast()

  // only show the component if it's present and not handled by native toast
  if (!currentToast || currentToast.isHandledNatively) return null
  return (
    <Toast key={currentToast.id}>
      <Toast.Title>{currentToast.title}</Toast.Title>
      <Toast.Description>{currentToast.message}</Toast.Description>
    </Toast>
  )
}

const MyPage = () => {
  const { show } = useToast()

  return (
    <Button onPress={() => show('Done!', { message: 'Form submitted successfully.' })}>
      Show Toast
    </Button>
  )
}
```

### Barebone API

<Notice>
  This API does not support native toasts. For native toasts, use the [Imperative
  API](#imperative-api).
</Notice>

#### Single Toast

```tsx
export default () => {
  const [open, setOpen] = React.useState(false)
  const timerRef = React.useRef(0)

  React.useEffect(() => {
    return () => clearTimeout(timerRef.current)
  }, [])

  return (
    <YStack ai="center">
      <Button
        onPress={() => {
          setOpen(false)
          window.clearTimeout(timerRef.current)
          timerRef.current = window.setTimeout(() => {
            setOpen(true)
          }, 150)
        }}
      >
        Single Toast
      </Button>
      <Toast
        onOpenChange={setOpen}
        open={open}
        animation="100ms"
        enterStyle={{ x: -20, opacity: 0 }}
        exitStyle={{ x: -20, opacity: 0 }}
        opacity={1}
        x={0}
      >
        <Toast.Title>Subscribed!</Toast.Title>
        <Toast.Description>We'll be in touch.</Toast.Description>
      </Toast>
    </YStack>
  )
}
```

#### Multiple Toast

<Notice>
  To use multiple toasts, you should pass `multipleToasts` to your `ToastViewport`.
  Otherwise there'll be issues when swipe-dismissing or animating toasts.
</Notice>

```tsx
export default () => {
  const [savedCount, setSavedCount] = React.useState(0)

  return (
    <YStack ai="center">
      <Button
        onPress={() => {
          setSavedCount((old) => old + 1)
        }}
      >
        Show toast
      </Button>
      {[...Array(savedCount)].map((_, index) => (
        <Toast
          key={index}
          animation="100ms"
          enterStyle={{ x: -20, opacity: 0 }}
          exitStyle={{ x: -20, opacity: 0 }}
          opacity={1}
          x={0}
        >
          <Toast.Title>Subscribed!</Toast.Title>
          <Toast.Description>We'll be in touch.</Toast.Description>
        </Toast>
      ))}
    </YStack>
  )
}
```

## API

### ToastProvider

Your toasts should be wrapped within a `ToastProvider`. This is usually done at the root of your application.

<PropsTable
  data={[
    {
      name: 'label',
      required: false,
      type: 'string',
      description: `An author-localized label for each toast. Used to help screen reader users associate the interruption with a toast.`,
      default: 'Notification',
    },
    {
      name: 'duration',
      required: false,
      type: 'number',
      description: `Time in milliseconds that each toast should remain visible for. This could be overwritten at the toast level as well.`,
      default: 5000,
    },
    {
      name: 'swipeDirection',
      required: false,
      type: 'SwipeDirection',
      description: `Direction of pointer swipe that should close the toast.`,
      default: 'right',
    },
    {
      name: 'swipeThreshold',
      required: false,
      type: 'number',
      description: `Distance in pixels that the swipe must pass before a close is triggered.`,
      default: 50,
    },
    {
      name: 'id',
      required: false,
      type: 'string',
      default: 'A unique generated ID',
    },
  ]}
/>

### ToastViewport

The portal for toasts to be directed to. Should be inside [ToastProvider](#toastprovider). Beyond [Stack Props](/docs/components/stacks/1.0.0), adds:

<PropsTable
  data={[
    {
      name: 'hotkey',
      type: 'string[]',
      default: "['F8']",
      required: false,
      description: `The keys to use as the keyboard shortcut that will move focus to the toast viewport.`,
    },
    {
      name: 'label',
      type: 'string',
      default: 'Notifications ({hotkey})',
      required: false,
      description: `An author-localized label for the toast viewport to provide context for screen reader users when navigating page landmarks. The available \`{hotkey}\` placeholder will be replaced for you.`,
    },
    {
      name: 'name',
      type: 'string',
      required: false,
      description: `Used to reference the viewport if you want to have multiple viewports in the same provider.`,
    },
    {
      name: 'multipleToasts',
      type: 'boolean',
      required: false,
      description: `Pass this when you want to have multiple/duplicated toasts.`,
    },
  ]}
/>

### Toast

Contains the Title, Description, Action and Close component. Should be inside [ToastProvider](#toastprovider). Extends [Stack](/docs/components/stack#api) and adds:

<PropsTable
  data={[
    {
      name: 'forceMount',
      type: 'boolean',
      required: false,
      description: `Used to force mounting when more control is needed. Useful when controlling animation with React animation libraries.`,
    },
    {
      name: 'type',
      type: "'foreground' | 'background'",
      required: false,
      description: `Control the sensitivity of the toast for accessibility purposes. For toasts that are the result of a user action, choose foreground. Toasts generated from background tasks should use background.`,
    },
    {
      name: 'duration',
      type: 'number',
      required: false,
      description: `Time in milliseconds that toast should remain visible for. Overrides value given to \`ToastProvider\`.`,
    },
    {
      name: 'defaultOpen',
      type: 'boolean',
      required: false,
      description: `The open state of the dialog when it is initially rendered. Use when you do not need to control its open state.`,
    },
    {
      name: 'open',
      type: 'boolean',
      required: false,
      description: `The controlled open state of the dialog. Must be used in conjunction with \`onOpenChange\`.`,
    },
    {
      name: 'onOpenChange',
      type: '(open: boolean): void',
      required: false,
      description: `Event handler called when the open state of the dialog changes.`,
    },
    {
      name: 'onEscapeKeyDown',
      type: "(): DismissableProps['onEscapeKeyDown']",
      required: false,
      description: `Event handler called when the escape key is down. It can be prevented by calling \`event.preventDefault\`.`,
    },
    {
      name: 'onPause',
      type: '(): void',
      required: false,
      description: `Event handler called when the dismiss timer is paused. On web, this occurs when the pointer is moved over the viewport, the viewport is focused or when the window is blurred. On mobile, this occurs when the toast is touched.`,
    },
    {
      name: 'onResume',
      type: '(): void',
      required: false,
      description: `Event handler called when the dismiss timer is resumed. On web, this occurs when the pointer is moved away from the viewport, the viewport is blurred or when the window is focused. On mobile, this occurs when the toast is released.`,
    },
    {
      name: 'onSwipeStart',
      type: '(event: SwipeEvent): void',
      required: false,
      description: `Event handler called when starting a swipe interaction. It can be prevented by calling \`event.preventDefault\`.`,
    },
    {
      name: 'onSwipeMove',
      type: '(event: SwipeEvent): void',
      required: false,
      description: `Event handler called during a swipe interaction. It can be prevented by calling \`event.preventDefault\`.`,
    },
    {
      name: 'onSwipeCancel',
      type: '(event: SwipeEvent): void',
      required: false,
      description: `Event handler called at the cancellation of a swipe interaction. It can be prevented by calling \`event.preventDefault\`.`,
    },
    {
      name: 'onSwipeEnd',
      type: '(event: SwipeEvent): void',
      required: false,
      description: `Event handler called at the end of a swipe interaction. It can be prevented by calling \`event.preventDefault\`.`,
    },
    {
      name: 'viewportName',
      type: 'string',
      required: false,
      description: `The viewport's name to send the toast to. Used when using multiple viewports and want to forward toasts to different ones.`,
      default: 'default',
    },
  ]}
/>

#### Toast.Title

Should be inside [Toast](#toast). Extends [SizableText](/docs/components/text/1.0.0#sizabletext).

#### Toast.Description

Should be inside [Toast](#toast). Extends [SizableText](/docs/components/text/1.0.0#sizabletext).

#### Toast.Close

Should be inside [Toast](#toast). Extends [Stack](/docs/components/stacks/1.0.0). You can pass `asChild` to this component and use a custom `<Button>` inside.

#### Toast.Action

Should be inside [Toast](#toast). Extends [Stack](/docs/components/stacks/1.0.0). You can pass `asChild` to this component and use a custom `<Button>` inside.

### ToastImperativeProvider

Wrap components within this provider to use `useToast()` inside them.

<PropsTable
  data={[
    {
      name: 'options',
      type: 'ToastImperativeOptions',
      required: false,
      description: `Used to provide defaults to imperative API. Options can be overwritten when calling \`show()\`.`,
    },
  ]}
/>

### useToast

Used when using the imperative API.

<PropsTable
  title="Returns"
  data={[
    {
      name: 'currentToast',
      type: 'ToastData | null',
      description: `The information about the current toast to show such as title, message, duration, etc.`,
    },
    {
      name: `show`,
      type: '(title: string, showOptions?: ShowToastOptions): void',
      description: `Call it to show a new toast. If you're using native toasts, you can pass native options using \`burntOptions\` or \`notificationOptions\` depending on the native platform (mobile/web).`,
    },
    {
      name: `hide`,
      type: '(): void',
      description: `Call it to hide the currently displayed toast.`,
    },
  ]}
/>

## FAQ

#### How to change the placement of toasts?

##### Native toasts

- iOS (burnt): Supports top or bottom placements. Adjustable by passing `from` to `burntOptions`:

```tsx
<ToastImperativeProvider options={{ burntOptions: { from: 'bottom' } }}>
```

- Android (burnt): Not supported.
- Web (Notification API): Not supported.

##### Non-native toasts

You should change the positioning of your [`<ToastViewport>`](#toastviewport). For instance, if you want them to appear from top right:

```tsx
<ToastViewport flexDirection="column-reverse" top={0} right={0} />
```

Or for bottom center:

```tsx
<ToastViewport flexDirection="column" bottom={0} left={0} right={0} />
```

<Notice theme="blue">
  When using multiple toasts, you can change the order of toasts by setting
  `flexDirection` to `column` or `column-reverse`. Or even have them stack horizontally
  using `row` or `row-reverse`.
</Notice>

#### How to show non-native toasts within safe area on mobile?

Install `react-native-safe-area-context` if you haven't, wrap your app inside `<SafeAreaProvider>`, and use the safe area insets to position the viewport inside the safe area.

```tsx
import { useSafeAreaInsets } from 'react-native-safe-area-context'

const SafeToastViewport = () => {
  const { left, top, right } = useSafeAreaInsets()
  return (
    <ToastViewport flexDirection="column-reverse" top={top} left={left} right={right} />
  )
}
```

#### Can I have multiple viewports?

Yes, but you will have to name them and then reference the viewport name on the `<Toast>` component. for instance:

```tsx
const App = () => {
  return (
    <ToastProvider>
      <ToastViewport /> // name will be "default"
      <ToastViewport name="viewport-custom" />
    </ToastProvider>
  )
}

const MyComponent = () => {
  return <Toast> // goes to default viewport // ...</Toast>
}

const MyComponent2 = () => {
  return <Toast viewportName="viewport-custom">// ...</Toast>
}
```


## components/toast/2.0.0

---
title: Toast
description: Use to show feedback to user interactions.
name: toast
component: Toast
package: toast
demoName: Toast
---

<HeroContainer showAnimationDriverControl>
  <ToastDemo />
</HeroContainer>

```tsx hero template=Toast

```

<Highlights
  features={[
    `Automatically closes`,
    `Pause closing on hover, focus, window blur and mobile touch`,
    `Supports closing via swipe gesture`,
    `Easily animatable with Tamagui's animation drivers`,
    `Native toasts included for Android, iOS and web (notification API)`,
  ]}
/>

## Installation

Note that `@tamagui/toast` is the only UI package not included by default in `tamagui`. The reason for this is that Metro would force you to install the native `burnt` dependency, and that would mean `tamagui` wouldn't work with Expo Go and would require a native install step no matter if you used Toast or not. This is due to a limitation in Metro.

To install toast:

```bash
yarn add @tamagui/toast burnt
```

Then, if targeting native, rebuild your React Native app. React Native requires sub-dependencies with native dependencies always be hoisted to your apps package.json and Toast relies on the amazing [Burnt](https://github.com/nandorojo/burnt) library by Fernando Rojo to provide its native functionality.

We're open to a refactor that moves the burnt dependency into a sub-path like `@tamagui/toast/burnt` and forces setup through there, that likely fixes the Metro issue and allow us to include it by default.

<Notice theme="blue">
  For native apps, we recommend [setting up native portals](/docs/components/portal#native-portal-setup-recommended) to preserve React context inside Toast content.
</Notice>

## Anatomy

```tsx
<ToastProvider>
  <Toast>
    <Toast.Title />
    <Toast.Description />
    <Toast.Action />
    <Toast.Close />
  </Toast>

  <ToastViewport />
</ToastProvider>
```

## API Reference

### ToastProvider

Your toasts should be wrapped within a `ToastProvider`. This is usually done at the root of your application.

<PropsTable
  data={[
    {
      name: 'label',
      required: false,
      type: 'string',
      description: `An author-localized label for each toast. Used to help screen reader users associate the interruption with a toast.`,
      default: 'Notification',
    },
    {
      name: 'duration',
      required: false,
      type: 'number',
      description: `Time in milliseconds that each toast should remain visible for. This could be overwritten at the toast level as well.`,
      default: 5000,
    },
    {
      name: 'swipeDirection',
      required: false,
      type: 'SwipeDirection',
      description: `Direction of pointer swipe that should close the toast.`,
      default: 'right',
    },
    {
      name: 'swipeThreshold',
      required: false,
      type: 'number',
      description: `Distance in pixels that the swipe must pass before a close is triggered.`,
      default: 50,
    },
    {
      name: 'id',
      required: false,
      type: 'string',
      default: 'A unique generated ID',
    },
    {
      name: 'native',
      required: false,
      type: 'boolean | ToastNativePlatform | ToastNativePlatform[]',
      description: `Will show a native toast if is true or is set to the current platform. On iOS, it wraps \`SPIndicator\` and \`SPAlert\`. On Android, it wraps \`ToastAndroid\`. On web, it wraps Notification API. Mobile's native features are handled by \`burnt\`.`,
    },
    {
      name: `burntOptions`,
      required: false,
      type: `Omit<BurntToastOptions, 'title' | 'message' | 'duration'>`,
      description: `Options for the burnt package if you're using native toasts on mobile`,
    },
    {
      name: `notificationOptions`,
      required: false,
      type: `NotificationOptions`,
      description: `Options for the notification API if you're using native toasts on web`,
    },
  ]}
/>

### ToastViewport

The portal for toasts to be directed to. Should be used inside [ToastProvider](#toastprovider). Beyond [Stack Props](/docs/components/stacks), adds:

<PropsTable
  data={[
    {
      name: 'hotkey',
      type: 'string[]',
      default: "['F8']",
      required: false,
      description: `The keys to use as the keyboard shortcut that will move focus to the toast viewport.`,
    },
    {
      name: 'label',
      type: 'string',
      default: 'Notifications ({hotkey})',
      required: false,
      description: `An author-localized label for the toast viewport to provide context for screen reader users when navigating page landmarks. The available \`{hotkey}\` placeholder will be replaced for you.`,
    },
    {
      name: 'name',
      type: 'string',
      required: false,
      description: `Used to reference the viewport if you want to have multiple viewports in the same provider.`,
    },
    {
      name: 'multipleToasts',
      type: 'boolean',
      required: false,
      description: `Pass this when you want to have multiple/duplicated toasts.`,
    },
    {
      name: 'portalToRoot',
      type: 'boolean | number',
      default: 'false',
      description: `When true, uses a portal to render at the very top of the root TamaguiProvider.`,
    },
    {
      name: 'unstyled',
      type: 'boolean',
      default: 'false',
      description: `When true, remove all default tamagui styling.`,
    },
  ]}
/>

### Toast

Contains the Title, Description, Action and Close component. Should be used inside [ToastProvider](#toastprovider). Extends [Stack](/docs/components/stacks) and adds:

<PropsTable
  data={[
    {
      name: 'forceMount',
      type: 'boolean',
      required: false,
      description: `Used to force mounting when more control is needed. Useful when controlling animation with React animation libraries.`,
    },
    {
      name: 'type',
      type: "'foreground' | 'background'",
      required: false,
      description: `Control the sensitivity of the toast for accessibility purposes. For toasts that are the result of a user action, choose foreground. Toasts generated from background tasks should use background.`,
    },
    {
      name: 'duration',
      type: 'number',
      required: false,
      description: `Time in milliseconds that toast should remain visible for. Overrides value given to \`ToastProvider\`.`,
    },
    {
      name: 'defaultOpen',
      type: 'boolean',
      required: false,
      description: `The open state of the dialog when it is initially rendered. Use when you do not need to control its open state.`,
    },
    {
      name: 'open',
      type: 'boolean',
      required: false,
      description: `The controlled open state of the dialog. Must be used in conjunction with \`onOpenChange\`.`,
    },
    {
      name: 'onOpenChange',
      type: '(open: boolean): void',
      required: false,
      description: `Event handler called when the open state of the dialog changes.`,
    },
    {
      name: 'onEscapeKeyDown',
      type: "(): DismissableProps['onEscapeKeyDown']",
      required: false,
      description: `Event handler called when the escape key is down. It can be prevented by calling \`event.preventDefault\`.`,
    },
    {
      name: 'onPause',
      type: '(): void',
      required: false,
      description: `Event handler called when the dismiss timer is paused. On web, this occurs when the pointer is moved over the viewport, the viewport is focused or when the window is blurred. On mobile, this occurs when the toast is touched.`,
    },
    {
      name: 'onResume',
      type: '(): void',
      required: false,
      description: `Event handler called when the dismiss timer is resumed. On web, this occurs when the pointer is moved away from the viewport, the viewport is blurred or when the window is focused. On mobile, this occurs when the toast is released.`,
    },
    {
      name: 'onSwipeStart',
      type: '(event: SwipeEvent): void',
      required: false,
      description: `Event handler called when starting a swipe interaction. It can be prevented by calling \`event.preventDefault\`.`,
    },
    {
      name: 'onSwipeMove',
      type: '(event: SwipeEvent): void',
      required: false,
      description: `Event handler called during a swipe interaction. It can be prevented by calling \`event.preventDefault\`.`,
    },
    {
      name: 'onSwipeCancel',
      type: '(event: SwipeEvent): void',
      required: false,
      description: `Event handler called at the cancellation of a swipe interaction. It can be prevented by calling \`event.preventDefault\`.`,
    },
    {
      name: 'onSwipeEnd',
      type: '(event: SwipeEvent): void',
      required: false,
      description: `Event handler called at the end of a swipe interaction. It can be prevented by calling \`event.preventDefault\`.`,
    },
    {
      name: 'viewportName',
      type: 'string',
      required: false,
      description: `The viewport's name to send the toast to. Used when using multiple viewports and want to forward toasts to different ones.`,
      default: 'default',
    },
    {
      name: 'unstyled',
      type: 'boolean',
      default: 'false',
      description: `When true, remove all default tamagui styling.`,
    },
  ]}
/>

### Toast.Title

Should be used inside [Toast](#toast). Extends [SizableText](/docs/components/text#sizabletext), adding:

<PropsTable
  data={[
    {
      name: 'unstyled',
      type: 'boolean',
      default: 'false',
      description: `When true, remove all default tamagui styling.`,
    },
  ]}
/>

### Toast.Description

Should be used inside [Toast](#toast). Extends [SizableText](/docs/components/text#sizabletext), adding:

<PropsTable
  data={[
    {
      name: 'unstyled',
      type: 'boolean',
      default: 'false',
      description: `When true, remove all default tamagui styling.`,
    },
  ]}
/>

### Toast.Close

Should be used inside [Toast](#toast). Extends [Stack](/docs/components/stacks). You can pass `asChild` to this component and use a custom `<Button>` inside.

### Toast.Action

Should be used inside [Toast](#toast). Extends [Stack](/docs/components/stacks). You can pass `asChild` to this component and use a custom `<Button>` inside.

### useToastController

Used to control the display of toasts. Should be used inside [ToastProvider](#toastprovider).

<PropsTable
  title="Returns"
  data={[
    {
      name: `show`,
      type: '(title: string, showOptions?: ShowToastOptions): void',
      description: `Call it to show a new toast. If you're using native toasts, you can pass native options using \`burntOptions\` or \`notificationOptions\` depending on the native platform (mobile/web).`,
    },
    {
      name: `hide`,
      type: '(): void',
      description: `Call it to hide the currently displayed toast.`,
    },
    {
      name: `options`,
      type: `ToastOptions`,
      description: `You can use if need to access the toast options.`,
    },
  ]}
/>

### useToastState

Used to render out your toast contents. Should be used inside [ToastProvider](#toastprovider). Doesn't take in anything and returns `ToastData`.

```tsx
const CurrentToast = () => {
  const toast = useToastState()

  // don't show any toast if no toast is present or it's handled natively
  if (!toast || toast.isHandledNatively) {
    return null
  }

  return (
    <Toast key={toast.id} duration={toast.duration} viewport={toast.viewport}>
      <Toast.Title>{toast.title}</Toast.Title>
      <Toast.Description>{toast.message}</Toast.Description>
    </Toast>
  )
}
```

## Examples

### Position the viewport

To position the viewport on native toasts:

- iOS (burnt): Supports top or bottom placements. Adjustable by passing `from` to `burntOptions`:

```tsx
<ToastProvider burntOptions={{ from: 'bottom' }}>
```

- Android (burnt): Not supported.
- Web (Notification API): Not supported.

To position the viewport on custom toasts:

You should change the positioning of your [`<ToastViewport>`](#toastviewport). For instance, if you want them to appear from top right:

```tsx
<ToastViewport flexDirection="column-reverse" top={0} right={0} />
```

Or for bottom center:

```tsx
<ToastViewport flexDirection="column" bottom={0} left={0} right={0} />
```

<Notice theme="blue">
  When using multiple toasts, you can change the order of toasts by setting
  `flexDirection` to `column` or `column-reverse`. Or even have them stack horizontally
  using `row` or `row-reverse`.
</Notice>

### Mobile safe area

To show toasts inside device's safe area, install `react-native-safe-area-context` if you haven't, wrap your app inside `<SafeAreaProvider>`, and then use the safe area insets to position the viewport inside the safe area.

```tsx
import { useSafeAreaInsets } from 'react-native-safe-area-context'

const SafeToastViewport = () => {
  const { left, top, right } = useSafeAreaInsets()
  return (
    <ToastViewport flexDirection="column-reverse" top={top} left={left} right={right} />
  )
}
```

### Different viewports

To send toasts to different viewports, you can set up different viewports:

```tsx
const App = () => {
  return (
    <ToastProvider>
      <ToastViewport /> {/* default viewport */}
      <ToastViewport name="viewport-custom" />
    </ToastProvider>
  )
}
```

And then, use the viewport's name on the toasts.

```tsx
const MyComponent = () => {
  return <Toast /> // default viewport
}

const MyComponent2 = () => {
  return <Toast viewportName="viewport-custom" />
}

```

### Custom data

Just pass your custom data to the second parameter of the `show()` method.

```ts
const toastController = useToastController()
toastController.show("Title", { myPreset: 'error' }) // or toastController.show("Title", { customData: { myPreset: 'error' } })
```

then, when showing the toast, you can retrieve them like so:

```ts
const toastState = useToastState()
toastState.myPreset // or toastState.customData.myPreset
```

To add TypeScript auto-completion for your custom fields, you can use TS module augmentation:

```ts
declare module '@tamagui/toast' {
  interface CustomData {
    myPreset: 'error' | 'success' | 'warning'
  }
}

```

### Without hooks

You can also use toasts without the hooks.

<Notice>You can't use native toasts this way.</Notice>

#### Single Toast

```tsx
export default () => {
  const [open, setOpen] = React.useState(false)
  const timerRef = React.useRef(0)

  React.useEffect(() => {
    return () => clearTimeout(timerRef.current)
  }, [])

  return (
    <YStack ai="center">
      <Button
        onPress={() => {
          setOpen(false)
          window.clearTimeout(timerRef.current)
          timerRef.current = window.setTimeout(() => {
            setOpen(true)
          }, 150)
        }}
      >
        Single Toast
      </Button>
      <Toast
        onOpenChange={setOpen}
        open={open}
        transition="100ms"
        enterStyle={{ x: -20, opacity: 0 }}
        exitStyle={{ x: -20, opacity: 0 }}
        opacity={1}
        x={0}
      >
        <Toast.Title>Subscribed!</Toast.Title>
        <Toast.Description>We'll be in touch.</Toast.Description>
      </Toast>
    </YStack>
  )
}
```

#### Multiple Toasts

<Notice>
  To use multiple toasts, you should pass `multipleToasts` to your `ToastViewport`.
  Otherwise there'll be issues when swipe-dismissing or animating toasts.
</Notice>

```tsx
export default () => {
  const [savedCount, setSavedCount] = React.useState(0)

  return (
    <YStack ai="center">
      <Button
        onPress={() => {
          setSavedCount((old) => old + 1)
        }}
      >
        Show toast
      </Button>
      {[...Array(savedCount)].map((_, index) => (
        <Toast
          key={index}
          transition="100ms"
          enterStyle={{ x: -20, opacity: 0 }}
          exitStyle={{ x: -20, opacity: 0 }}
          opacity={1}
          x={0}
        >
          <Toast.Title>Subscribed!</Toast.Title>
          <Toast.Description>We'll be in touch.</Toast.Description>
        </Toast>
      ))}
    </YStack>
  )
}
```


## components/toggle-group/1.10.0

---
title: ToggleGroup
description: Two-state buttons that can be toggled on or off.
name: toggleGroup
component: ToggleGroup
package: toggle-group
demoName: ToggleGroup
---

<HeroContainer>
  <ToggleGroupDemo />
</HeroContainer>

```tsx hero template=ToggleGroup

```

<Highlights
  features={[
    `Full keyboard navigation.`,
    `Supports horizontal/vertical orientation.`,
    `Support single and multiple pressed buttons.`,
    `Can be controlled or uncontrolled.`,
  ]}
/>

## Installation

ToggleGroup is already installed in `tamagui`, or you can install it independently:

```bash
npm install @tamagui/toggle-group
```

## Usage

```tsx
import { ToggleGroup } from 'tamagui'

export default () => {
  return (
    <ToggleGroup type="single">
      <ToggleGroup.Item value="foo"></ToggleGroup.Item>
      <ToggleGroup.Item value="bar"></ToggleGroup.Item>
    </ToggleGroup>
  )
}
```

## API Reference

### ToggleGroup

`ToggleGroup` extends the [Group](/docs/components/group) component. You can disable passing border radius to children by passing `disablePassBorderRadius`. plus:

<PropsTable
  data={[
    {
      name: 'asChild',
      type: 'boolean',
      description: `When true, Tamagui expects a single child element. Instead of rendering its own element, it will pass all props to that child, merging together any event handling props.`,
      default: `false`,
    },
    {
      name: 'type',
      type: 'enum',
      description: `Determines whether a single or multiple items can be pressed at a time.`,
    },
    {
      name: 'value',
      type: 'string',
      description: `The controlled value of the pressed item when type is "single". Must be used in conjunction with onValueChange.`,
    },
    {
      name: 'defaultValue',
      description: 'The values of the items to show as pressed when initially rendered.',
      type: 'string',
      default: ``,
    },
    {
      name: 'orientation',
      type: 'enum',
      description: `The orientation of the component, which determines how focus moves: horizontal for left/right arrows and vertical for up/down arrows.`,
      default: `horizontal`,
    },
    {
      name: 'disabled',
      type: 'boolean',
      description: `When true, prevents the user from interacting with the toggle group and all its items.`,
      default: `false`,
    },
    {
      name: 'onValueChange',
      type: '(value: string[]) => void',
      description: `Event handler called when the pressed state of an item changes and type is "multiple".`,
    },
    {
      name: 'loop',
      type: 'boolean',
      description: `Whether or not to loop over after reaching the end or start of the items. Used mainly for managing keyboard navigation.`,
      default: `true`,
    },
    {
      name: 'disableDeactivation',
      type: 'boolean',
      description: `Won't let the user turn the active item off. Only applied to single toggle group.`,
      default: 'false',
    },
    {
      name: 'unstyled',
      type: 'boolean',
      default: 'false',
      description: `When true, remove all default tamagui styling.`,
    },
    {
      name: 'sizeAdjust',
      type: 'number',
      description: `Adjust the component's size scaling by this number.`,
    },
  ]}
/>

### ToggleGroup.Item

`ToggleGroup.Item` extend Stack views inheriting all the [Tamagui standard props](notion://www.notion.so/docs/intro/props), plus:

<PropsTable
  data={[
    {
      name: 'asChild',
      type: 'boolean',
      description: `When true, Tamagui expects a single child element. Instead of rendering its own element, it will pass all props to that child, merging together any event handling props.`,
      default: `false`,
    },
    {
      name: 'value',
      type: 'string',
      description: `The controlled value of the pressed item when type is "single".`,
    },
    {
      name: 'disabled',
      type: 'boolean',
      description: `When true, prevents the user from interacting with the toggle group item.`,
      default: `false`,
    },
    {
      name: 'unstyled',
      type: 'boolean',
      default: 'false',
      description: `When true, remove all default tamagui styling.`,
    },
  ]}
/>

When it is active, it will receive an `active` prop set to true. This means you can customize the active styles like so:

```tsx
import { ToggleGroup } from '@tamagui/toggle-group'
import { styled } from 'tamagui'

const MyToggleGroupItem = styled(ToggleGroup.Item, {
  variants: {
    active: {
      true: {
        backgroundColor: 'red'
      },
    },
  },
})
```


## components/toggle-group/2.0.0

---
title: ToggleGroup
description: Two-state buttons that can be toggled on or off.
name: toggleGroup
component: ToggleGroup
package: toggle-group
demoName: ToggleGroup
---

<HeroContainer>
  <ToggleGroupDemo />
</HeroContainer>

```tsx hero template=ToggleGroup

```

<Highlights
  features={[
    `Full keyboard navigation.`,
    `Supports horizontal/vertical orientation.`,
    `Support single and multiple pressed buttons.`,
    `Can be controlled or uncontrolled.`,
  ]}
/>

## Installation

ToggleGroup is already installed in `tamagui`, or you can install it independently:

```bash
npm install @tamagui/toggle-group
```

## Usage

```tsx
import { ToggleGroup } from 'tamagui'

export default () => {
  return (
    <ToggleGroup type="single">
      <ToggleGroup.Item value="foo"></ToggleGroup.Item>
      <ToggleGroup.Item value="bar"></ToggleGroup.Item>
    </ToggleGroup>
  )
}
```

## API Reference

### ToggleGroup

`ToggleGroup` extends the [Group](/docs/components/group) component. You can disable passing border radius to children by passing `disablePassBorderRadius`.

<Notice>
  Since ToggleGroup extends Group, automatic border radius detection only works when `ToggleGroup.Item` is a **direct child**. If you wrap items in custom components, see the [Group docs on nested items](/docs/components/group#custom-components--nested-items) for workarounds.
</Notice>

Props:

<PropsTable
  data={[
    {
      name: 'asChild',
      type: 'boolean',
      description: `When true, Tamagui expects a single child element. Instead of rendering its own element, it will pass all props to that child, merging together any event handling props.`,
      default: `false`,
    },
    {
      name: 'type',
      type: 'enum',
      description: `Determines whether a single or multiple items can be pressed at a time.`,
    },
    {
      name: 'value',
      type: 'string',
      description: `The controlled value of the pressed item when type is "single". Must be used in conjunction with onValueChange.`,
    },
    {
      name: 'defaultValue',
      description: 'The values of the items to show as pressed when initially rendered.',
      type: 'string',
      default: ``,
    },
    {
      name: 'orientation',
      type: 'enum',
      description: `The orientation of the component, which determines how focus moves: horizontal for left/right arrows and vertical for up/down arrows.`,
      default: `horizontal`,
    },
    {
      name: 'disabled',
      type: 'boolean',
      description: `When true, prevents the user from interacting with the toggle group and all its items.`,
      default: `false`,
    },
    {
      name: 'onValueChange',
      type: '(value: string[]) => void',
      description: `Event handler called when the pressed state of an item changes and type is "multiple".`,
    },
    {
      name: 'loop',
      type: 'boolean',
      description: `Whether or not to loop over after reaching the end or start of the items. Used mainly for managing keyboard navigation.`,
      default: `true`,
    },
    {
      name: 'disableDeactivation',
      type: 'boolean',
      description: `Won't let the user turn the active item off. Only applied to single toggle group.`,
      default: 'false',
    },
    {
      name: 'unstyled',
      type: 'boolean',
      default: 'false',
      description: `When true, remove all default tamagui styling.`,
    },
    {
      name: 'sizeAdjust',
      type: 'number',
      description: `Adjust the component's size scaling by this number.`,
    },
  ]}
/>

### ToggleGroup.Item

`ToggleGroup.Item` extend Stack views inheriting all the [Tamagui standard props](notion://www.notion.so/docs/intro/props), plus:

<PropsTable
  data={[
    {
      name: 'asChild',
      type: 'boolean',
      description: `When true, Tamagui expects a single child element. Instead of rendering its own element, it will pass all props to that child, merging together any event handling props.`,
      default: `false`,
    },
    {
      name: 'value',
      type: 'string',
      description: `The controlled value of the pressed item when type is "single".`,
    },
    {
      name: 'disabled',
      type: 'boolean',
      description: `When true, prevents the user from interacting with the toggle group item.`,
      default: `false`,
    },
    {
      name: 'activeStyle',
      type: 'StyleProp',
      description: `Style object applied when the item is in its active/pressed state. Accepts standard style properties like backgroundColor, color, etc.`,
    },
    {
      name: 'activeTheme',
      type: 'string | null',
      description: `Theme to apply when the item is active. Set to null for no theme change.`,
    },
    {
      name: 'unstyled',
      type: 'boolean',
      default: 'false',
      description: `When true, remove all default tamagui styling.`,
    },
  ]}
/>

### Styling Active State

You can customize the active/pressed state using `activeStyle`:

```tsx
// Inline usage
<ToggleGroup.Item
  value="left"
  activeStyle={{ backgroundColor: '$green9', color: '$yellow9' }}
>
  Left
</ToggleGroup.Item>

// Or via styled()
const GreenItem = styled(ToggleGroup.Item, {
  activeStyle: {
    backgroundColor: '$green9',
    color: '$yellow9',
  },
})
```

You can also use `activeTheme` to apply a theme when active:

```tsx
<ToggleGroup.Item value="option1" activeTheme="green">
  Option 1
</ToggleGroup.Item>
```

### useToggleGroupItem

For custom components inside `ToggleGroup.Item` that need to know the active state, use the `useToggleGroupItem` hook:

```tsx
import { useToggleGroupItem, ToggleGroup } from '@tamagui/toggle-group'

function CustomLabel({ children }) {
  const { active, size, color } = useToggleGroupItem()
  return (
    <Text color={active ? '$green10' : '$color'}>
      {children}
    </Text>
  )
}

// Usage
<ToggleGroup.Item value="option1">
  <CustomLabel>Option 1</CustomLabel>
</ToggleGroup.Item>
```


## components/tooltip/1.0.0

---
title: Tooltip
description: A simple tooltip component
name: tooltip
component: Tooltip
package: tooltip
demoName: Tooltip
---

# Tooltip

<Description>A tooltip on web, with only accessibility output on native</Description>

<HeroContainer showAnimationDriverControl>
  <TooltipDemo />
</HeroContainer>

```tsx hero template=Tooltip

```

<Highlights
  features={[
    `Doesn't open until your mouse stops moving.`,
    `Easy to animate enter and exit.`,
    `Sizable, positionable, unstyled or styled.`,
  ]}
/>

<Notice>
  Note that Tooltip doesn't render on native platforms.
</Notice>

## Installation

Tooltip is already installed in `tamagui`, or you can install it independently:

```bash
npm install @tamagui/tooltip
```

### PortalProvider

When rendering into root of app instead of inline, you'll first need to install the `@tamagui/portal` package: 

```bash
npm install @tamagui/portal
```

Then add `PortalProvider` to the root of your app:

```tsx fileName="App.tsx"
import { PortalProvider } from '@tamagui/portal'
import YourApp from './components/YourApp'

function App() {
  return (
    <PortalProvider shouldAddRootHost>
      <YourApp />
    </PortalProvider>
  )
}

export default App
```

<PropsTable
  data={[
    {
      name: 'shouldAddRootHost',
      type: 'boolean',
      required: false,
      description: `Defines whether to add a default root host or not.`,
    },
  ]}
/>

## Anatomy

```tsx
import { Tooltip } from 'tamagui' // or '@tamagui/tooltip'

export default () => (
  <Tooltip>
    <Tooltip.Trigger />
    <Tooltip.Content>
      <Tooltip.Arrow />
      {/* ... */}
    </Tooltip.Content>
  </Tooltip>
)
```

## API Reference

### Tooltip

Contains every component for the tooltip.

<PropsTable
  data={[
    {
      name: 'children',
      type: 'React.ReactNode',
      required: true,
      description: `Must contain Popover.Content`,
    },
    {
      name: 'groupId',
      type: 'string',
      required: false,
      description: `If given, will work alongside TooltipGroup to ensure only one tooltip in the groups stays open.`,
    },
    {
      name: 'restMs',
      type: 'number',
      required: false,
      description: `Time needed for cursor to rest before showing.`,
    },
    {
      name: 'delay',
      type: `number | { open?: number; close?: number }`,
      required: false,
      description: `Maximum time before showing (can be set independently for open/close).`,
    },
    {
      name: 'size',
      type: 'SizeTokens',
      required: false,
      description: `Passes size down too all sub-components when set for padding, arrow, borderRadius`,
    },
    {
      name: 'placement',
      type: 'Placement',
      required: false,
      description: `'top' | 'right' | 'bottom' | 'left' | 'top-start' | 'top-end' | 'right-start' | 'right-end' | 'bottom-start' | 'bottom-end' | 'left-start' | 'left-end'`,
    },
    {
      name: 'open',
      type: 'boolean',
      required: false,
      description: ``,
    },
    {
      name: 'defaultOpen',
      type: 'boolean',
      required: false,
    },
    {
      name: 'onOpenChange',
      type: '(open: boolean) => void',
      required: false,
    },
    {
      name: 'modal',
      type: 'boolean',
      default: 'true',
      required: false,
      description: `Renders into root of app instead of inline`,
    },
    {
      name: 'stayInFrame',
      type: 'ShiftProps',
      required: false,
      description: `See floating-ui shift()`,
    },
    {
      name: 'allowFlip',
      type: 'FlipProps',
      required: false,
      description: `See floating-ui flip`,
    },
    {
      name: 'offset',
      type: 'OffsetOptions',
      required: false,
      description: `Determines the distance the Popover appears from the target, see floating-ui offset().`,
    },
  ]}
/>

<Notice>
  If using `modal={true}` (which is `true` by default), refer to the [PortalProvider installation](/ui/tooltip/1.0.0#portalprovider) for more information.
</Notice>

### Tooltip.Trigger

Used to trigger opening of the popover when uncontrolled, see YStack in [Stacks](/docs/components/stacks).

### Tooltip.Content

Renders as SizableStack (see [Stacks](/docs/components/stacks)) with an extra `size` prop that accepts any `SizeTokens`.

### Tooltip.Anchor

Renders as YStack, see [Stacks](/docs/components/stacks).

When you want the Trigger to be in another location from where the Tooltip attaches, use Anchor. When used, Anchor is where the Tooltip will attach, while Trigger will open it.


## components/tooltip/1.105.0

---
title: Tooltip
description: A simple tooltip component
name: tooltip
component: Tooltip
package: tooltip
demoName: Tooltip
---

# Tooltip

<Description>A tooltip on web, with only accessibility output on native</Description>

<HeroContainer showAnimationDriverControl>
  <TooltipDemo />
</HeroContainer>

```tsx hero template=Tooltip

```

<Highlights
  features={[
    `Doesn't open until your mouse stops moving.`,
    `Easy to animate enter and exit.`,
    `Sizable, positionable, unstyled or styled.`,
  ]}
/>

<Notice>
  Note that Tooltip doesn't render on native platforms.
</Notice>

## Installation

Tooltip is already installed in `tamagui`, or you can install it independently:

```bash
npm install @tamagui/tooltip
```

### PortalProvider

Only if you aren't not using `tamagui` (but rather `@tamagui/core`) you'll need to install the `@tamagui/portal` package: 

```bash
npm install @tamagui/portal
```

Then add `PortalProvider` to the root of your app:

```tsx fileName="App.tsx"
import { PortalProvider } from '@tamagui/portal'
import YourApp from './components/YourApp'

function App() {
  return (
    <PortalProvider shouldAddRootHost>
      <YourApp />
    </PortalProvider>
  )
}

export default App
```

<PropsTable
  data={[
    {
      name: 'shouldAddRootHost',
      type: 'boolean',
      required: false,
      description: `Defines whether to add a default root host or not.`,
    },
  ]}
/>

## Anatomy

```tsx
import { Tooltip } from 'tamagui' // or '@tamagui/tooltip'

export default () => (
  <Tooltip>
    <Tooltip.Trigger />
    <Tooltip.Content>
      <Tooltip.Arrow />
      {/* ... */}
    </Tooltip.Content>
  </Tooltip>
)
```

## API Reference

### Tooltip

Contains every component for the tooltip.

<PropsTable
  data={[
    {
      name: 'children',
      type: 'React.ReactNode',
      required: true,
      description: `Must contain Popover.Content`,
    },
    {
      name: 'groupId',
      type: 'string',
      required: false,
      description: `If given, will work alongside TooltipGroup to ensure only one tooltip in the groups stays open.`,
    },
    {
      name: 'restMs',
      type: 'number',
      required: false,
      description: `Time needed for cursor to rest before showing.`,
    },
    {
      name: 'delay',
      type: `number | { open?: number; close?: number }`,
      required: false,
      description: `Maximum time before showing (can be set independently for open/close).`,
    },
    {
      name: 'size',
      type: 'SizeTokens',
      required: false,
      description: `Passes size down too all sub-components when set for padding, arrow, borderRadius`,
    },
    {
      name: 'placement',
      type: 'Placement',
      required: false,
      description: `'top' | 'right' | 'bottom' | 'left' | 'top-start' | 'top-end' | 'right-start' | 'right-end' | 'bottom-start' | 'bottom-end' | 'left-start' | 'left-end'`,
    },
    {
      name: 'open',
      type: 'boolean',
      required: false,
      description: ``,
    },
    {
      name: 'defaultOpen',
      type: 'boolean',
      required: false,
    },
    {
      name: 'onOpenChange',
      type: '(open: boolean) => void',
      required: false,
    },
    {
      name: 'modal',
      type: 'boolean',
      default: 'true',
      required: false,
      description: `Renders into root of app instead of inline`,
    },
    {
      name: 'stayInFrame',
      type: 'ShiftProps',
      required: false,
      description: `See floating-ui shift()`,
    },
    {
      name: 'allowFlip',
      type: 'FlipProps',
      required: false,
      description: `See floating-ui flip`,
    },
    {
      name: 'offset',
      type: 'OffsetOptions',
      required: false,
      description: `Determines the distance the Popover appears from the target, see floating-ui offset().`,
    },
  ]}
/>

<Notice>
  If using `modal={true}` (which is `true` by default), refer to the [PortalProvider installation](/ui/tooltip/1.0.0#portalprovider) for more information.
</Notice>

### Tooltip.Trigger

Used to trigger opening of the popover when uncontrolled, see YStack in [Stacks](/docs/components/stacks).

### Tooltip.Content

Renders as SizableStack (see [Stacks](/docs/components/stacks)) with an extra `size` prop that accepts any `SizeTokens`.

### Tooltip.Anchor

Renders as YStack, see [Stacks](/docs/components/stacks).

When you want the Trigger to be in another location from where the Tooltip attaches, use Anchor. When used, Anchor is where the Tooltip will attach, while Trigger will open it.

### TooltipGroup

This allows you to logically group any tooltips rendered below this component. You can control their delay props, and components inside a TooltipGroup will be smart about opening quicker if you are moving between targets with tooltips, ensuring that subsequent tooltips show immediately rather than after a delay.

See the [Floating UI docs](https://floating-ui.com/docs/floatingdelaygroup) for full details on how this works.

<PropsTable
  data={[
    {
      name: 'delay',
      type: 'number | { open?: number; close?: number }',
      default: 0,
      required: false,
      description: `The delay to use for the group.`,
    },
    {
      name: 'timeoutMs',
      type: 'number',
      default: 0,
      required: false,
      description: `An optional timeout to use for the group, which represents when grouping logic will no longer be active after the close delay completes. Useful if you want grouping to last longer than the close delay, for example if there is no close delay at all.`,
    },
    {
      name: 'preventAnimation',
      type: 'boolean',
      default: false,
      required: false,
      description: `Will disable the enter/exit animations while the group is active, but not for the initial enter animation of the first hovered tooltip.`,
    },
  ]}
/>


## components/tooltip/1.122.0

---
title: Tooltip
description: A tooltip on web, with only accessibility output on native.
name: tooltip
component: Tooltip
package: tooltip
demoName: Tooltip
---

<HeroContainer showAnimationDriverControl>
  <TooltipDemo />
</HeroContainer>

```tsx hero template=Tooltip

```

<Highlights
  features={[
    `Doesn't open until your mouse stops moving.`,
    `Easy to animate enter and exit.`,
    `Sizable, positionable, unstyled or styled.`,
  ]}
/>

<Notice>
  Note that Tooltip doesn't render on native platforms.
</Notice>

## Installation

Tooltip is already installed in `tamagui`, or you can install it independently:

```bash
npm install @tamagui/tooltip
```

### PortalProvider

If you are not using `tamagui` (but rather `@tamagui/core`) you'll need to install the `@tamagui/portal` package: 

```bash
npm install @tamagui/portal
```

Then add `PortalProvider` to the root of your app:

```tsx fileName="App.tsx"
import { PortalProvider } from '@tamagui/portal'
import YourApp from './components/YourApp'

function App() {
  return (
    <PortalProvider shouldAddRootHost>
      <YourApp />
    </PortalProvider>
  )
}

export default App
```

<PropsTable
  data={[
    {
      name: 'shouldAddRootHost',
      type: 'boolean',
      required: false,
      description: `Defines whether to add a default root host or not.`,
    },
  ]}
/>

## Anatomy

```tsx
import { Tooltip } from 'tamagui' // or '@tamagui/tooltip'

export default () => (
  <Tooltip>
    <Tooltip.Trigger />
    <Tooltip.Content>
      <Tooltip.Arrow />
      {/* ... */}
    </Tooltip.Content>
  </Tooltip>
)
```

## API Reference

### Tooltip

Contains every component for the tooltip.

<PropsTable
  data={[
    {
      name: 'children',
      type: 'React.ReactNode',
      required: true,
      description: `Must contain Popover.Content`,
    },
    {
      name: 'groupId',
      type: 'string',
      required: false,
      description: `If given, will work alongside TooltipGroup to ensure only one tooltip in the groups stays open.`,
    },
    {
      name: 'restMs',
      type: 'number',
      required: false,
      description: `Time needed for cursor to rest before showing.`,
    },
    {
      name: 'delay',
      type: `number | { open?: number; close?: number }`,
      required: false,
      description: `Maximum time before showing (can be set independently for open/close).`,
    },
    {
      name: 'size',
      type: 'SizeTokens',
      required: false,
      description: `Passes size down to all sub-components when set for padding, arrow, borderRadius.`,
    },
    {
      name: 'placement',
      type: 'Placement',
      required: false,
      description: `'top' | 'right' | 'bottom' | 'left' | 'top-start' | 'top-end' | 'right-start' | 'right-end' | 'bottom-start' | 'bottom-end' | 'left-start' | 'left-end'`,
    },
    {
      name: 'open',
      type: 'boolean',
      required: false,
      description: ``,
    },
    {
      name: 'defaultOpen',
      type: 'boolean',
      required: false,
    },
    {
      name: 'onOpenChange',
      type: '(open: boolean) => void',
      required: false,
    },
    {
      name: 'modal',
      type: 'boolean',
      default: 'true',
      required: false,
      description: `Renders into root of app instead of inline`,
    },
    {
      name: 'stayInFrame',
      type: 'ShiftProps',
      required: false,
      description: `See floating-ui shift()`,
    },
    {
      name: 'allowFlip',
      type: 'FlipProps',
      required: false,
      description: `See floating-ui flip`,
    },
    {
      name: 'offset',
      type: 'OffsetOptions',
      required: false,
      description: `Determines the distance the Popover appears from the target, see floating-ui offset().`,
    },
  ]}
/>

<Notice>
  If using `modal={true}` (which is `true` by default), refer to the [PortalProvider installation](#portalprovider) for more information.
</Notice>

### Tooltip.Trigger

Used to trigger opening of the popover when uncontrolled, see YStack in [Stacks](/docs/components/stacks).

### Tooltip.Content

Renders as SizableStack (see [Stacks](/docs/components/stacks)) with an extra `size` prop that accepts any `SizeTokens`.

### Tooltip.Anchor

Renders as YStack, see [Stacks](/docs/components/stacks).

When you want the Trigger to be in another location from where the Tooltip attaches, use Anchor. When used, Anchor is where the Tooltip will attach, while Trigger will open it.

### TooltipGroup

This allows you to logically group any tooltips rendered below this component. You can control their delay props, and components inside a TooltipGroup will be smart about opening quicker if you are moving between targets with tooltips, ensuring that subsequent tooltips show immediately rather than after a delay.

See the [Floating UI docs](https://floating-ui.com/docs/floatingdelaygroup) for full details on how this works.

<PropsTable
  data={[
    {
      name: 'delay',
      type: 'number | { open?: number; close?: number }',
      default: 0,
      required: false,
      description: `The delay to use for the group.`,
    },
    {
      name: 'timeoutMs',
      type: 'number',
      default: 0,
      required: false,
      description: `An optional timeout to use for the group, which represents when grouping logic will no longer be active after the close delay completes. Useful if you want grouping to last longer than the close delay, for example if there is no close delay at all.`,
    },
    {
      name: 'preventAnimation',
      type: 'boolean',
      default: false,
      required: false,
      description: `Will disable the enter/exit animations while the group is active, but not for the initial enter animation of the first hovered tooltip.`,
    },
  ]}
/>

### closeOpenTooltips

A small helper function of type `() => void` that will close any open tooltips.


## components/tooltip/2.0.0

---
title: Tooltip
description: A tooltip on web, with only accessibility output on native.
name: tooltip
component: Tooltip
package: tooltip
demoName: Tooltip
---

<HeroContainer showAnimationDriverControl>
  <TooltipDemo />
</HeroContainer>

```tsx hero template=Tooltip

```

<Highlights
  features={[
    `Doesn't open until your mouse stops moving.`,
    `Easy to animate enter and exit.`,
    `Sizable, positionable, unstyled or styled.`,
  ]}
/>

<Notice>
  Note that Tooltip doesn't render on native platforms.
</Notice>

## Installation

Tooltip is already installed in `tamagui`, or you can install it independently:

```bash
npm install @tamagui/tooltip
```

### PortalProvider

If you are not using `tamagui` (but rather `@tamagui/core`) you'll need to install the `@tamagui/portal` package: 

```bash
npm install @tamagui/portal
```

Then add `PortalProvider` to the root of your app:

```tsx fileName="App.tsx"
import { PortalProvider } from '@tamagui/portal'
import YourApp from './components/YourApp'

function App() {
  return (
    <PortalProvider shouldAddRootHost>
      <YourApp />
    </PortalProvider>
  )
}

export default App
```

<PropsTable
  data={[
    {
      name: 'shouldAddRootHost',
      type: 'boolean',
      required: false,
      description: `Defines whether to add a default root host or not.`,
    },
  ]}
/>

## Anatomy

```tsx
import { Tooltip } from 'tamagui' // or '@tamagui/tooltip'

export default () => (
  <Tooltip>
    <Tooltip.Trigger />
    <Tooltip.Content>
      <Tooltip.Arrow />
      {/* ... */}
    </Tooltip.Content>
  </Tooltip>
)
```

## API Reference

### Tooltip

Contains every component for the tooltip.

<PropsTable
  data={[
    {
      name: 'children',
      type: 'React.ReactNode',
      required: true,
      description: `Must contain Popover.Content`,
    },
    {
      name: 'groupId',
      type: 'string',
      required: false,
      description: `If given, will work alongside TooltipGroup to ensure only one tooltip in the groups stays open.`,
    },
    {
      name: 'restMs',
      type: 'number',
      required: false,
      description: `Time needed for cursor to rest before showing.`,
    },
    {
      name: 'delay',
      type: `number | { open?: number; close?: number }`,
      required: false,
      description: `Maximum time before showing (can be set independently for open/close).`,
    },
    {
      name: 'size',
      type: 'SizeTokens',
      required: false,
      description: `Passes size down to all sub-components when set for padding, arrow, borderRadius.`,
    },
    {
      name: 'placement',
      type: 'Placement',
      required: false,
      description: `'top' | 'right' | 'bottom' | 'left' | 'top-start' | 'top-end' | 'right-start' | 'right-end' | 'bottom-start' | 'bottom-end' | 'left-start' | 'left-end'`,
    },
    {
      name: 'open',
      type: 'boolean',
      required: false,
      description: ``,
    },
    {
      name: 'defaultOpen',
      type: 'boolean',
      required: false,
    },
    {
      name: 'onOpenChange',
      type: '(open: boolean) => void',
      required: false,
    },
    {
      name: 'modal',
      type: 'boolean',
      default: 'true',
      required: false,
      description: `Renders into root of app instead of inline`,
    },
    {
      name: 'stayInFrame',
      type: 'ShiftProps',
      required: false,
      description: `See floating-ui shift()`,
    },
    {
      name: 'allowFlip',
      type: 'FlipProps',
      required: false,
      description: `See floating-ui flip`,
    },
    {
      name: 'offset',
      type: 'OffsetOptions',
      required: false,
      description: `Determines the distance the Popover appears from the target, see floating-ui offset().`,
    },
  ]}
/>

<Notice>
  If using `modal={true}` (which is `true` by default), refer to the [PortalProvider installation](#portalprovider) for more information.
</Notice>

### Tooltip.Trigger

Used to trigger opening of the popover when uncontrolled, see YStack in [Stacks](/docs/components/stacks).

### Tooltip.Content

Renders as SizableStack (see [Stacks](/docs/components/stacks)) with an extra `size` prop that accepts any `SizeTokens`.

### Tooltip.Anchor

Renders as YStack, see [Stacks](/docs/components/stacks).

When you want the Trigger to be in another location from where the Tooltip attaches, use Anchor. When used, Anchor is where the Tooltip will attach, while Trigger will open it.

### TooltipGroup

This allows you to logically group any tooltips rendered below this component. You can control their delay props, and components inside a TooltipGroup will be smart about opening quicker if you are moving between targets with tooltips, ensuring that subsequent tooltips show immediately rather than after a delay.

See the [Floating UI docs](https://floating-ui.com/docs/floatingdelaygroup) for full details on how this works.

<PropsTable
  data={[
    {
      name: 'delay',
      type: 'number | { open?: number; close?: number }',
      default: 0,
      required: false,
      description: `The delay to use for the group.`,
    },
    {
      name: 'timeoutMs',
      type: 'number',
      default: 0,
      required: false,
      description: `An optional timeout to use for the group, which represents when grouping logic will no longer be active after the close delay completes. Useful if you want grouping to last longer than the close delay, for example if there is no close delay at all.`,
    },
    {
      name: 'preventAnimation',
      type: 'boolean',
      default: false,
      required: false,
      description: `Will disable the enter/exit animations while the group is active, but not for the initial enter animation of the first hovered tooltip.`,
    },
  ]}
/>

### closeOpenTooltips

A small helper function of type `() => void` that will close any open tooltips.

## Scoping

Tooltip supports scoping which lets you mount a single Tooltip instance at
the root of your app, while having deeply nested Trigger components anywhere
in your tree attach to that single parent Tooltip.

This is useful for performance - you only render `Tooltip.Trigger` in your
list items or buttons, while the heavier `Tooltip` and `Tooltip.Content` live
at the root. It also lets you style all your tooltips consistently in one place.

```tsx fileName=_layout.tsx
import { Tooltip } from 'tamagui'

export default ({ children }) => (
  <Tooltip scope="tooltip">
    <Tooltip.Content>
      <Tooltip.Arrow />
      {/* your tooltip content renders here */}
    </Tooltip.Content>

    {/* the rest of your app renders inside */}
    {children}
  </Tooltip>
)
```

```tsx fileName=MyButton.tsx
export default () => (
  <Tooltip.Trigger scope="tooltip" aria-label="Settings">
    <Button icon={Settings} />
  </Tooltip.Trigger>
)
```

The `scope` prop on `Trigger` connects it to the `Tooltip` with the matching scope.


## components/unspaced/1.0.0

---
title: Unspaced
description: Avoids spacing for children inside a spacing container.
name: html
component: Unspaced
---

While the `space` property is deprecated, Unspaced lives on in a better form, one which is easily back-ported to add Unspaced support (as well as adding `gap` support).

When using the `space` style prop, you may want some children to not be spaced.

Use Unspaced:

```tsx
import { Text, Unspaced, View } from '@tamagui/core'

export default () => (
  <View position="relative" gap="$4">
    <View width={20} height={20} />
    {/* space */}
    <View width={20} height={20} />
    {/* no */}
    <Unspaced>
      <Text position="absolute">Some absolute positioned text</Text>
    </Unspaced>
  </View>
)
```

If you want the item to be visually hidden as well as unspaced, see
[VisuallyHidden](/docs/components/visually-hidden), which uses Unspaced but also hides the contents in an accessible manner.


## components/unspaced/2.0.0

---
title: Unspaced
description: Avoids spacing for children inside a spacing container.
name: html
component: Unspaced
---

While the `space` property is deprecated, Unspaced lives on in a better form, one which is easily back-ported to add Unspaced support (as well as adding `gap` support).

When using the `space` style prop, you may want some children to not be spaced.

Use Unspaced:

```tsx
import { Text, Unspaced, View } from '@tamagui/core'

export default () => (
  <View position="relative" space>
    <View width={20} height={20} />
    {/* space */}
    <View width={20} height={20} />
    {/* no */}
    <Unspaced>
      <Text position="absolute">Some absolute positioned text</Text>
    </Unspaced>
  </View>
)
```

If you want the item to be visually hidden as well as unspaced, see
[VisuallyHidden](/docs/components/visually-hidden), which uses Unspaced but also hides the contents in an accessible manner.


## components/visually-hidden/1.0.0

---
title: Visually Hidden
description: Hide content accessibly.
name: html
component: VisuallyHidden
---

VisuallyHidden hides an item but ensures it remains visible to accessibility readers.

<Highlights
  features={[
    'Keeps content hidden on screen but visible to assistive tech.',
    'Works with "space" prop to not disturb spacing.',
  ]}
/>

## Installation

VisuallyHidden is already installed in `tamagui`, or you can install it independently:

```bash
npm install @tamagui/visually-hidden
```

## Usage

Simply wrap the content you want hidden in VisuallyHidden:

```tsx
import { Text, VisuallyHidden } from 'tamagui'

export default () => (
  <VisuallyHidden>
    <Text>Add annotations here</Text>
  </VisuallyHidden>
)
```

When using with the `space` property, it will avoid double-spacing:

```tsx
import { H1, Text, VisuallyHidden, YStack } from 'tamagui'

export default () => (
  <YStack gap="$4">
    <H1>Title</H1>

    <VisuallyHidden>
      <Text>Add annotations here</Text>
    </VisuallyHidden>
  </YStack>
)
```


## components/visually-hidden/2.0.0

---
title: Visually Hidden
description: Hide content accessibly.
name: html
component: VisuallyHidden
---

VisuallyHidden hides an item but ensures it remains visible to accessibility readers.

<Highlights
  features={[
    'Keeps content hidden on screen but visible to assistive tech.',
    'Works with "space" prop to not disturb spacing.',
  ]}
/>

## Installation

VisuallyHidden is already installed in `tamagui`, or you can install it independently:

```bash
npm install @tamagui/visually-hidden
```

## Usage

Simply wrap the content you want hidden in VisuallyHidden:

```tsx
import { Text, VisuallyHidden } from 'tamagui'

export default () => (
  <VisuallyHidden>
    <Text>Add annotations here</Text>
  </VisuallyHidden>
)
```

When using with the `space` property, it will avoid double-spacing:

```tsx
import { H1, Text, VisuallyHidden, YStack } from 'tamagui'

export default () => (
  <YStack space>
    <H1>Title</H1>

    <VisuallyHidden>
      <Text>Add annotations here</Text>
    </VisuallyHidden>
  </YStack>
)
```


## core/animations-css

---
title: CSS Driver
description: Lightweight CSS transition-based animations
---

The `@tamagui/animations-css` package works with the tamagui compiler and
runtime to give you simple ways to share typed animations across all your
components.

## Installation

```bash
yarn add @tamagui/animations-css
```

## Configuration

Add it to your [Tamagui config](/docs/core/configuration):

```tsx
import { createAnimations } from '@tamagui/animations-css'
import { createTamagui } from 'tamagui'

export default createTamagui({
  animations: createAnimations({
    fast: 'ease-in 150ms',
    medium: 'ease-in 300ms',
    slow: 'ease-in 450ms',
  }),
  // ...
})
```

## How it Works

At runtime, the plugin does very little except to set the `transition` property
in CSS. At compile-time, the compiler does the same, ensuring you get all the
benefits of prop removal and view flattening even when using animations.

## Animation Configuration

CSS animations use CSS transition syntax:

```tsx
createAnimations({
  quick: 'ease-out 100ms',
  bouncy: 'cubic-bezier(0.68, -0.55, 0.265, 1.55) 300ms',
  slow: 'ease-in-out 500ms',
})
```

The format is: `<easing-function> <duration>`

### Delay

You can add animation delays using the array syntax:

```tsx
<Square transition={['quick', { delay: 200 }]} />
```

The delay (in milliseconds) is applied as CSS `transition-delay`.

### Supported Easing Functions

- `ease` - Default easing (equivalent to `cubic-bezier(0.25, 0.1, 0.25, 1)`)
- `linear` - No easing, constant speed
- `ease-in` - Slow start
- `ease-out` - Slow end
- `ease-in-out` - Slow start and end
- `cubic-bezier(x1, y1, x2, y2)` - Custom cubic bezier curve

## When to Use

**Advantages:**
- **Smallest bundle size** - Minimal JavaScript overhead
- **Best compatibility** - Works everywhere CSS works
- **Compiler optimizations** - Benefits from static extraction
- **Simple** - Easiest to understand and configure

**Limitations:**
- No spring physics (only easing curves)
- Limited to CSS animatable properties
- Less control over animation lifecycle

## Example

```tsx
import { createAnimations } from '@tamagui/animations-css'
import { YStack, createTamagui } from 'tamagui'

const animations = createAnimations({
  quick: 'ease-out 150ms',
  bouncy: 'cubic-bezier(0.68, -0.55, 0.265, 1.55) 400ms',
})

export default createTamagui({
  animations,
  // ... rest of config
})

// Usage in components
export const MyComponent = () => (
  <YStack
    transition="quick"
    hoverStyle={{
      scale: 1.1,
    }}
  />
)
```

## See Also

- [Animations Introduction](/docs/core/animations)
- [React Native Driver](/docs/core/animations-react-native)
- [Motion Driver](/docs/core/animations-motion)
- [Reanimated Driver](/docs/core/animations-reanimated)


## core/animations-motion

---
title: Motion Driver
description: High-performance web animations with WAAPI
---

[Motion](https://motion.dev) is a modern animation library built on the Web Animations API (WAAPI). It features a unique hybrid engine that combines JavaScript animations with native browser APIs to deliver high-performance, GPU-accelerated animations.

## Installation

```bash
yarn add @tamagui/animations-motion motion
```

## Configuration

Add your animations to your [Tamagui config](/docs/core/configuration):

```tsx
import { createAnimations } from '@tamagui/animations-motion'
import { createTamagui } from 'tamagui'

export default createTamagui({
  animations: createAnimations({
    '75ms': { duration: 75 },
    '100ms': { duration: 100 },
    '200ms': { duration: 200 },
    superBouncy: {
      type: 'spring',
      damping: 5,
      mass: 0.7,
      stiffness: 200,
    },
    bouncy: {
      type: 'spring',
      damping: 9,
      mass: 0.9,
      stiffness: 150,
    },
    medium: {
      damping: 15,
      stiffness: 120,
      mass: 1,
    },
    quick: {
      type: 'spring',
      damping: 20,
      mass: 1.2,
      stiffness: 250,
    },
  }),
  // ...
})
```

## How it Works

At runtime, the Motion driver uses the `useAnimate()` hook from the Motion library to create animation scopes and orchestrate animations. It intelligently batches style changes and only animates properties that have changed, optimizing performance.

The driver leverages the Web Animations API (WAAPI) which runs animations on the compositor thread when possible, keeping animations smooth even when the main thread is busy.

## Animation Configuration

Motion animations support both spring and tween (timing) animations:

### Spring Animations

```tsx
{
  type: 'spring',
  damping: 10,     // Higher = less bouncy
  mass: 0.9,       // Higher = more inertia
  stiffness: 100,  // Higher = faster
}
```

### Tween Animations

```tsx
{
  duration: 200,   // Duration in milliseconds
}
```

### Delay

You can add animation delays using the array syntax:

```tsx
<Square transition={['bouncy', { delay: 200 }]} />
```

The delay is specified in milliseconds and is converted to seconds for Motion internally.

## Key Features

### Hybrid Engine

Motion is the only animation library with a hybrid engine that can dynamically run animations either via `requestAnimationFrame` or the Web Animations API. This allows it to animate any value while retaining the ability to run animations with hardware acceleration on the GPU.

### WAAPI Performance

The Web Animations API runs animations on the compositor thread when possible, keeping animations smooth even when the main thread is busy. This provides off-thread performance comparable to Reanimated, but for web-only applications.

### Excellent Spring Physics

Motion provides natural, physics-based spring animations with intuitive controls via `damping`, `stiffness`, and `mass` parameters. The spring implementation is excellent and feels very natural.

### Independent Transform Animations

Unlike standard WAAPI, Motion allows you to animate individual transform properties like `x`, `y`, `scale`, `rotate` separately with different animation configurations.

### Lightweight

Motion comes in two sizes - mini (2.3kb) for basic WAAPI animations, and hybrid (17kb) for full featured animations. The Tamagui integration uses the hybrid engine for maximum flexibility - still much smaller than Reanimated.

## Platform-Specific Configuration

For cross-platform apps, use Motion on web and Reanimated on native:

```tsx
// animations.ts (web)
import { createAnimations } from '@tamagui/animations-motion'

export const animations = createAnimations({
  bouncy: {
    type: 'spring',
    damping: 10,
    stiffness: 100,
  },
})
```

```tsx
// animations.native.ts
import { createAnimations } from '@tamagui/animations-reanimated'

export const animations = createAnimations({
  bouncy: {
    type: 'spring',
    damping: 10,
    stiffness: 100,
  },
})
```

Then import without the extension:

```tsx
import { animations } from './animations'
```

Your bundler will automatically pick the right file based on the platform.

## Example

```tsx
import { createAnimations } from '@tamagui/animations-motion'
import { YStack, createTamagui } from 'tamagui'

const animations = createAnimations({
  bouncy: {
    type: 'spring',
    damping: 9,
    mass: 0.9,
    stiffness: 150,
  },
  quick: {
    type: 'spring',
    damping: 20,
    mass: 1.2,
    stiffness: 250,
  },
  '100ms': {
    duration: 100,
  },
})

export default createTamagui({
  animations,
  // ... rest of config
})

// Usage in components
export const MyComponent = () => (
  <YStack
    transition="bouncy"
    hoverStyle={{
      scale: 1.05,
    }}
    enterStyle={{
      opacity: 0,
      y: 10,
    }}
  />
)
```

## Advanced Features

### GPU Acceleration

Motion automatically uses GPU acceleration for transforms and opacity, ensuring smooth 60fps animations even during heavy JavaScript execution.

## See Also

- [Animations Introduction](/docs/core/animations)
- [CSS Driver](/docs/core/animations-css)
- [React Native Driver](/docs/core/animations-react-native)
- [Reanimated Driver](/docs/core/animations-reanimated)
- [Motion Documentation](https://motion.dev)


## core/animations-react-native

---
title: React Native Driver
description: Spring-based animations using React Native Animated
---

[React Native's Animated library](https://reactnative.dev/docs/animated) is the
animation library that comes built into React Native and React Native Web.

## Installation

```bash
yarn add @tamagui/animations-react-native
```

## Configuration

Add it to your [Tamagui config](/docs/core/configuration):

```tsx
import { createAnimations } from '@tamagui/animations-react-native'
import { createTamagui } from 'tamagui'

export default createTamagui({
  animations: createAnimations({
    fast: {
      damping: 20,
      mass: 1.2,
      stiffness: 250,
    },
    medium: {
      damping: 10,
      mass: 0.9,
      stiffness: 100,
    },
    slow: {
      damping: 20,
      stiffness: 60,
    },
  }),
  // ...
})
```

## Animation Configuration

React Native animations use spring physics with these parameters:

### Spring Parameters

- **`damping`** - Defines how quickly the spring settles. Higher = less bouncy
- **`mass`** - Mass of the object attached to the spring. Higher = more inertia
- **`stiffness`** - Spring stiffness coefficient. Higher = faster animation

```tsx
createAnimations({
  bouncy: {
    damping: 10,
    mass: 0.9,
    stiffness: 100,
  },
  quick: {
    damping: 20,
    mass: 1.2,
    stiffness: 250,
  },
})
```

### Delay

You can add animation delays using the array syntax:

```tsx
<Square transition={['bouncy', { delay: 200 }]} />
```

The delay is specified in milliseconds and uses `Animated.delay()` internally to sequence animations.

## When to Use

**Advantages:**
- **Cross-platform** - Works on web and React Native
- **Built-in** - No additional animation library needed
- **Spring physics** - Natural, physics-based motion
- **Mature** - Battle-tested in React Native ecosystem

**Limitations:**
- Larger bundle than CSS driver
- Less advanced than Reanimated
- Web-only apps might prefer lighter alternatives

## Platform-Specific Configuration

You can use different drivers on web vs native using platform-specific file extensions:

```tsx
// animations.ts (web)
import { createAnimations } from '@tamagui/animations-css'

export const animations = createAnimations({
  quick: 'ease-out 150ms',
})
```

```tsx
// animations.native.ts (native)
import { createAnimations } from '@tamagui/animations-react-native'

export const animations = createAnimations({
  quick: {
    damping: 20,
    stiffness: 250,
  },
})
```

Then import without the extension:

```tsx
import { animations } from './animations'
```

Your bundler will automatically pick the right file based on the platform.

## Example

```tsx
import { createAnimations } from '@tamagui/animations-react-native'
import { YStack, createTamagui } from 'tamagui'

const animations = createAnimations({
  bouncy: {
    damping: 10,
    mass: 0.9,
    stiffness: 100,
  },
  quick: {
    damping: 20,
    mass: 1.2,
    stiffness: 250,
  },
})

export default createTamagui({
  animations,
  // ... rest of config
})

// Usage in components
export const MyComponent = () => (
  <YStack
    transition="bouncy"
    enterStyle={{
      opacity: 0,
      scale: 0.9,
    }}
  />
)
```

## See Also

- [Animations Introduction](/docs/core/animations)
- [CSS Driver](/docs/core/animations-css)
- [Motion Driver](/docs/core/animations-motion)
- [Reanimated Driver](/docs/core/animations-reanimated)


## core/animations-reanimated

---
title: Reanimated Driver
description: Advanced off-thread animations with Reanimated
---

[Reanimated](https://docs.swmansion.com/react-native-reanimated/) is an
animation library that targets React Native and React Native Web. It runs
off-thread animations and provides smooth spring and timing animations.

## Installation

```bash
yarn add @tamagui/animations-reanimated react-native-reanimated
```

Follow the [Reanimated installation guide](https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/getting-started/) to complete setup for your platform.

## Configuration

Add your animations to your [Tamagui config](/docs/core/configuration):

```tsx
import { createAnimations } from '@tamagui/animations-reanimated'
import { createTamagui } from 'tamagui'

export default createTamagui({
  animations: createAnimations({
    fast: {
      type: 'spring',
      damping: 20,
      mass: 1.2,
      stiffness: 250,
    },
    medium: {
      type: 'spring',
      damping: 10,
      mass: 0.9,
      stiffness: 100,
    },
    slow: {
      type: 'spring',
      damping: 20,
      stiffness: 60,
    },
  }),
  // ...
})
```

## How it Works

At runtime, this driver parses animatable style properties and hands them over
to Reanimated using worklets. Animations run on the UI thread for smooth 60fps performance.

## Animation Configuration

Reanimated animations support both spring and timing (tween) animations:

### Spring Animations

```tsx
{
  type: 'spring',
  damping: 10,     // Higher = less bouncy
  mass: 0.9,       // Higher = more inertia
  stiffness: 100,  // Higher = faster
}
```

### Timing Animations

```tsx
{
  type: 'timing',
  duration: 300,   // Duration in milliseconds
}
```

### Delay

You can add animation delays using the array syntax:

```tsx
<Square transition={['bouncy', { delay: 200 }]} />
```

The delay is specified in milliseconds.

## When to Use

**Advantages:**
- **Off-thread animations** - Runs on UI thread for 60fps performance
- **Cross-platform** - Works on web and React Native
- **Advanced features** - Worklets, gesture integration, shared values
- **React Native optimized** - Best performance on native platforms
- **Powerful** - Most feature-rich animation driver

**Considerations:**
- Larger bundle size
- Requires Reanimated setup and configuration
- More complex than simpler drivers
- Web-only apps might prefer Motion or CSS drivers

## Platform-Specific Configuration

You can use Reanimated on native and a lighter driver on web:

```tsx
// animations.native.ts
import { createAnimations } from '@tamagui/animations-reanimated'

export const animations = createAnimations({
  bouncy: {
    type: 'spring',
    damping: 10,
    stiffness: 100,
  },
})
```

```tsx
// animations.ts (web)
import { createAnimations } from '@tamagui/animations-motion'

export const animations = createAnimations({
  bouncy: {
    type: 'spring',
    damping: 10,
    stiffness: 100,
  },
})
```

Then import without the extension:

```tsx
import { animations } from './animations'
```

## Example

```tsx
import { createAnimations } from '@tamagui/animations-reanimated'
import { YStack, createTamagui } from 'tamagui'

const animations = createAnimations({
  bouncy: {
    type: 'spring',
    damping: 10,
    mass: 0.9,
    stiffness: 100,
  },
  quick: {
    type: 'spring',
    damping: 20,
    mass: 1.2,
    stiffness: 250,
  },
})

export default createTamagui({
  animations,
  // ... rest of config
})

// Usage in components
export const MyComponent = () => (
  <YStack
    transition="bouncy"
    enterStyle={{
      opacity: 0,
      scale: 0.9,
    }}
  />
)
```

## Advanced Features

Reanimated provides advanced features:

- **Gesture integration** - Animate based on gesture values
- **Shared values** - Share animated values between components
- **Worklets** - JavaScript functions that run on the UI thread
- **Custom animations** - Full control over animation curves

See the [Reanimated documentation](https://docs.swmansion.com/react-native-reanimated/) for more details.

## See Also

- [Animations Introduction](/docs/core/animations)
- [CSS Driver](/docs/core/animations-css)
- [React Native Driver](/docs/core/animations-react-native)
- [Motion Driver](/docs/core/animations-motion)
- [Reanimated Documentation](https://docs.swmansion.com/react-native-reanimated/)


## core/animations

---
title: Animations
description: Swap out animation drivers per-platform or at runtime
---

<HeroContainer>
  <AnimationsDemo />
</HeroContainer>

```tsx hero template=Animations

```

<Highlights
  disableLinks
  features={[
    `Animate any style prop with animation config per-prop.`,
    `Can animate across all states (media queries, hover, etc).`,
    `Multiple drivers you can swap out with type safety.`,
    `SSR safe mount animations.`,
    `Enter and exit animations with AnimatePresence.`,
  ]}
/>

Add animations to Tamagui with an animation driver. Animation drivers are designed to be swappable, so you can use lightweight CSS animations or other web-focused animation libraries on the web, while using larger but more advanced libraries like Reanimated on native - all without having to change a line outside of configuration.

For this guide, we'll use the React Native driver as an example, but you can choose different animation drivers: [CSS](/docs/core/animations-css), [React Native](/docs/core/animations-react-native), [Reanimated](/docs/core/animations-reanimated), or [Motion](/docs/core/animations-motion). See the [Choosing a Driver](#choosing-a-driver) section below for a comparison.

## Installation

```bash
yarn add @tamagui/animations-react-native
```

Then add it to your [Tamagui config](/docs/core/configuration):

```tsx
import { createAnimations } from '@tamagui/animations-react-native'
import { createTamagui } from 'tamagui'

export default createTamagui({
  animations: createAnimations({
    bouncy: {
      damping: 10,
      mass: 0.9,
      stiffness: 100,
    },
    lazy: {
      damping: 18,
      stiffness: 50,
    },
    quick: {
      damping: 20,
      mass: 1.2,
      stiffness: 250,
    },
  }),
  // ...
})
```

## Choosing a Driver

Tamagui supports four animation drivers, each with different strengths. The animation keys match between all drivers, so you can swap them out based on your platform needs.

<SimpleTable
  headers={['Driver', 'Platform', 'Bundle Impact', 'Performance', 'Spring Physics']}
  rows={[
    ['**[CSS](/docs/core/animations-css)**', 'Web only', 'Lightest', 'Fast (CSS transitions)', 'No (easing only)'],
    ['**[React Native](/docs/core/animations-react-native)**', 'Web + Native', 'No extra hit beyond RNW', 'On-thread', 'Yes (basic)'],
    ['**[Reanimated](/docs/core/animations-reanimated)**', 'Web + Native', 'Larger', 'Off-thread (native), slower (web)', 'Yes (advanced)'],
    ['**[Motion](/docs/core/animations-motion)**', 'Web only', 'Medium', 'Off-thread (WAAPI)', 'Yes (excellent)'],
  ]}
/>

- **[CSS](/docs/core/animations-css)** - Lightest bundle size, but lacks spring physics (easing curves only). Best for simple web-only apps.
- **[React Native](/docs/core/animations-react-native)** - No extra bundle hit beyond React Native Web, but runs on-thread. Good for basic cross-platform animations.
- **[Reanimated](/docs/core/animations-reanimated)** - Wide support for both native and web with advanced features, but larger bundle size and slower web performance (though better than React Native driver).
- **[Motion](/docs/core/animations-motion)** - Off-thread performance via WAAPI with excellent spring physics, medium bundle hit. Note: if you're also using Reanimated, this creates duplicate bundle weight.

## Platform-Specific Configuration

You can use different animation drivers per-platform using platform-specific file extensions. This allows you to optimize for each platform:

```tsx
// animations.ts (web)
import { createAnimations } from '@tamagui/animations-motion'

export const animations = createAnimations({
  bouncy: {
    type: 'spring',
    damping: 10,
    stiffness: 100,
  },
})
```

```tsx
// animations.native.ts
import { createAnimations } from '@tamagui/animations-reanimated'

export const animations = createAnimations({
  bouncy: {
    type: 'spring',
    damping: 10,
    stiffness: 100,
  },
})
```

Then import without the extension:

```tsx
// tamagui.config.ts
import { animations } from './animations' // Automatically picks .native.ts on native

export default createTamagui({
  animations,
  // ...
})
```

Your bundler (Metro, Webpack, Vite) will automatically select the correct file based on the platform.

### Multiple Animation Drivers

Configure multiple drivers at the root and select per-component with `animatedBy`. This has two benefits:

1. **Mix drivers** - Use CSS for simple transitions and Reanimated for complex springs
2. **Compiler optimization** - The compiler can make smarter decisions when it knows which driver a component uses statically

```tsx
import { createAnimations as createCSS } from '@tamagui/animations-css'
import { createAnimations as createSpring } from '@tamagui/animations-moti'

export default createTamagui({
  animations: {
    default: createCSS({ bouncy: 'ease-in 200ms' }),
    spring: createSpring({ bouncy: { type: 'spring', damping: 10 } }),
  },
})
```

```tsx
<Square transition="bouncy" />                    {/* uses default */}
<Square animatedBy="spring" transition="bouncy" /> {/* uses spring */}
```

Driver names are inferred automatically for TypeScript.

### Lazy Loading Animation Drivers

For apps that start static (like a marketing site) but need richer animations after login, you can lazy load drivers to reduce initial bundle size.

**Swap the default driver** with `Configuration`:

```tsx
// Inner layout for authenticated users
import { Configuration } from 'tamagui'
import { createAnimations } from '@tamagui/animations-moti'

const springDriver = createAnimations({ bouncy: { type: 'spring', damping: 10 } })

export function AuthenticatedLayout({ children }) {
  return (
    <Configuration animationDriver={springDriver}>
      {children}
    </Configuration>
  )
}
```

**Add multiple drivers** with `loadAnimationDriver`:

```tsx
import { loadAnimationDriver } from 'tamagui'
import { createAnimations } from '@tamagui/animations-moti'

const driver = createAnimations({ bouncy: { type: 'spring', damping: 10 } })
loadAnimationDriver('spring', driver)

// Now components can use animatedBy="spring"
```

When using `loadAnimationDriver`, add type hints for autocomplete:

```tsx
declare module 'tamagui' {
  interface TypeOverride {
    animationDrivers(): 'spring'
  }
}
```

### Optimizing Bundle Size

<Notice theme="green">
  **Experimental:** Use our `react-native-web-lite` library to tree-shake animation drivers on web. Set `useReactNativeWebLite: true` in any Tamagui plugin configuration. This lets you completely remove React Native Web's animation system when using CSS or Motion drivers, saving significant bundle size.
</Notice>

```tsx
// next.config.js
const { withTamagui } = require('@tamagui/next-plugin')

module.exports = withTamagui({
  config: './tamagui.config.ts',
  components: ['tamagui'],
  useReactNativeWebLite: true, // Experimental: tree-shake RNW on web
})
```

## Usage

The `transition` prop accepts the name of an animation you've configured. By default, animations will
apply to all animatable styles, similar to setting `all` in a CSS `transition`.

Here's an example animating `hoverStyle`:

<HeroContainer>
  <AnimationsHoverDemo />
</HeroContainer>

```tsx hero template=AnimationsHover

```

### The transition prop rules

If you add a `transition` prop, you must always keep the prop. If you need the animation to be disabled,
pass false, null or even undefined if it suits you.

The spring-based animation drivers have expensive hooks that would degrade runtime
performance if present on every component. As a workaround, the animation hooks
are called conditionally on whether the transition key is present in the props object.

So, `<Square transition={isActive ? 'bouncy' : null} />` rather than
`<Square {...isActive && { transition: 'bouncy' }} />`.

If you'd like to remove or add a transition prop after a component has already rendered, you'd have
to change the key.

### enterStyle

Setting `enterStyle` styles on any component tell it to start with those styles,
and after mount animate to their flat styles:

<HeroContainer>
  <AnimationsEnterDemo />
</HeroContainer>

```tsx hero template=AnimationsEnter

```

### Granular animations

The `transition` prop accepts a string or a more complex object to customize
animations per-property.

You can specify animations for specific properties using an object:

```tsx
import { YStack } from 'tamagui'

export default () => (
  <YStack
    transition={{
      // only x and y will apply animations
      x: 'bouncy',
      y: {
        type: 'bouncy',
        overshootClamping: true,
      },
    }}
  />
)
```

Note that values can either map to `AnimationKey` as a string, or to
`{ type: AnimationKey, ...configuration }`

You can set a default value using a two-arity array with the default in the
first position:

```tsx
import { YStack } from 'tamagui'

export default () => (
  <YStack
    transition={[
      // all attributes get "bouncy"
      'bouncy',
      // these are customized
      {
        y: 'slow',
        scale: {
          type: 'fast',
          repeat: 2,
        },
      },
    ]}
  />
)
```

### Delay

You can add a delay before animations start using the array syntax with a `delay` property (in milliseconds):

```tsx
import { Square, XStack } from 'tamagui'

export default () => (
  <XStack gap="$2">
    {[0, 1, 2, 3].map((i) => (
      <Square
        key={i}
        transition={['bouncy', { delay: i * 100 }]}
        enterStyle={{ opacity: 0, scale: 0.5, y: 20 }}
        size={50}
        bg="$color10"
      />
    ))}
  </XStack>
)
```

This creates a staggered animation effect where each square animates 100ms after the previous one. The delay works with all animation drivers and applies to enter, exit, and style change animations.

### Enter/Exit Transitions

You can specify different animations for enter (mount) and exit (unmount) transitions. This is useful when you want elements to enter slowly but exit quickly, or vice versa:

```tsx
import { AnimatePresence, View } from 'tamagui'

export default ({ show }) => (
  <AnimatePresence>
    {show && (
      <View
        key="panel"
        transition={{ enter: 'lazy', exit: 'quick' }}
        enterStyle={{ opacity: 0, y: 20 }}
        exitStyle={{ opacity: 0, y: -20 }}
      />
    )}
  </AnimatePresence>
)
```

The `enter` and `exit` keys accept any animation name from your config. You can also combine them with a `default` for property changes that happen while the element is mounted:

```tsx
// enter slowly, exit quickly, property changes use medium speed
<View
  transition={{ enter: 'lazy', exit: 'quick', default: 'bouncy' }}
  enterStyle={{ opacity: 0 }}
  exitStyle={{ opacity: 0 }}
/>
```

Or use enter/exit with the array syntax to include delay and per-property animations:

```tsx
// enter with lazy, exit with quick, delay 200ms, x uses its own animation
<View
  transition={['bouncy', { enter: 'lazy', exit: 'quick', delay: 200, x: 'slow' }]}
  enterStyle={{ opacity: 0, x: -100 }}
  exitStyle={{ opacity: 0, x: 100 }}
/>
```

This works with all four animation drivers (CSS, React Native, Reanimated, Motion).

### animateOnly

The `animateOnly` prop will limit your animation config to certain keys. It
accepts an array of strings that correspond to style property names.

## AnimatePresence

### exitStyle

AnimatePresence animates direct children before they unmount. It's inspired by
and forked from [Framer Motion](https://github.com/framer/motion), but works
with any animation in Tamagui.

To use with `@tamagui/core`, install and import `@tamagui/animate-presence`.
It's already bundled and exported from `tamagui`.

You can use it simply with `enterStyle` + `exitStyle`:

```tsx
import { AnimatePresence, View } from 'tamagui'

export const MyComponent = ({ isVisible }) => (
  <AnimatePresence>
    {isVisible && (
      <View
        key="my-square"
        transition="bouncy"
        backgroundColor="green"
        size={50}
        enterStyle={{
          opacity: 0,
          y: 10,
          scale: 0.9,
        }}
        exitStyle={{
          opacity: 0,
          y: -10,
          scale: 0.9,
        }}
      />
    )}
  </AnimatePresence>
)
```

<Notice theme="blue">
  Note you don't even need to set `opacity` on the base style. Tamagui knows to
  normalize styles like opacity and scale to 1 (and y to 0) if it's not defined
  on the base styles but is defined on `enterStyle` or `exitStyle`.
</Notice>

Wrap one or more tamagui components with AnimatePresence. This component will
animate on enter and exit.

<Notice theme="blue">
  Animated child components must each have a unique key prop so AnimatePresence
  can track their presence in the tree.
</Notice>

### The `custom` prop

<HeroContainer noPad>
  <AnimationsPresenceDemo />
</HeroContainer>

```tsx hero template=AnimationsPresence

```

AnimatePresence also takes a `custom` property that allows you to update a
variant of the animated child before it runs it's exit animation. This is useful
for animating a child out of the screen before it unmounts in a different
direction, like the example above:

```tsx
import { AnimatePresence } from '@tamagui/animate-presence'
import { ArrowLeft, ArrowRight } from '@tamagui/lucide-icons'
import { useState } from 'react'
import { Button, Image, XStack, YStack, styled } from 'tamagui'

const GalleryItem = styled(YStack, {
  zIndex: 1,
  x: 0,
  opacity: 1,
  fullscreen: true,

  variants: {
    // 1 = right, 0 = nowhere, -1 = left
    going: {
      ':number': (going) => ({
        enterStyle: {
          x: going > 0 ? 1000 : -1000,
          opacity: 0,
        },
        exitStyle: {
          zIndex: 0,
          x: going < 0 ? 1000 : -1000,
          opacity: 0,
        },
      }),
    },
  } as const,
})

const photos = [
  'https://picsum.photos/500/300',
  'https://picsum.photos/501/300',
  'https://picsum.photos/502/300',
]

const wrap = (min: number, max: number, v: number) => {
  const rangeSize = max - min
  return ((((v - min) % rangeSize) + rangeSize) % rangeSize) + min
}

export function Demo() {
  const [[page, going], setPage] = useState([0, 0])

  const imageIndex = wrap(0, photos.length, page)
  const paginate = (going: number) => {
    setPage([page + going, going])
  }

  return (
    <XStack
      overflow="hidden"
      backgroundColor="#000"
      position="relative"
      height={300}
      width="100%"
      alignItems="center"
    >
      <AnimatePresence initial={false} custom={{ going }}>
        <GalleryItem key={page} transition="slowest" going={going}>
          <Image src={photos[imageIndex]} width={500} height={300} />
        </GalleryItem>
      </AnimatePresence>

      <Button
        accessibilityLabel="Carousel left"
        icon={ArrowLeft}
        size="$5"
        position="absolute"
        left="$4"
        circular
        elevate
        onPress={() => paginate(-1)}
        zi={100}
      />
      <Button
        accessibilityLabel="Carousel right"
        icon={ArrowRight}
        size="$5"
        position="absolute"
        right="$4"
        circular
        elevate
        onPress={() => paginate(1)}
        zi={100}
      />
    </XStack>
  )
}
```

### What to know when animating

#### Conditional animations and HMR

The animation hooks are heavy, which initially meant we either had to choose
great performance or animations. We've settled on a trade-off. We track if the
`transition` prop is set, and if so, we enable the hook. If it _is ever set, even
just once_, then the hooks will continue to run for the remainder of the
component lifecycle. This means if you ever plan to animate a component you
should keep `transition` always set on the component props. You can disable it
like so:

```tsx
<View transition={condition ? 'animation-name' : null} />
```

Note that because of this constraint, you also will see an error if you add the
`transition` prop to a component in dev mode during an HMR. Often just saving once more will remove the error, or reloading at worst.


## core/config-v4

---
title: Quick Config
description: Easy config and themes with @tamagui/config/v4
---

<IntroParagraph>
  Config v4 simplifies configuration, generates much more useful themes, and
  adds a new helper function for generating your own theme suite called
  `createThemes`.
</IntroParagraph>

Tamagui is incredibly customizable, but if you want to get started faster and
enjoy using a configuration that is standardized and has been tested in many
apps, we recommend our `@tamagui/config` package.

First you'll want to add it:

```bash
npm install @tamagui/config
```

Using the new `defaultConfig` is the easiest way to start:

```tsx fileName="tamagui.config.ts"
import { defaultConfig } from '@tamagui/config/v4'
import { createTamagui } from 'tamagui'

export const config = createTamagui(defaultConfig)

type CustomConfig = typeof config

// ensure types work
declare module 'tamagui' {
  interface TamaguiCustomConfig extends CustomConfig {}
}
```

It gives you a robust suite of themes, a few shorthands that mostly align with
Tailwind, and default settings that prevent bugs.

If you like it but want to make some changes, you can
[copy and paste the source code](https://github.com/tamagui/tamagui/blob/5b0c075e92688ac85737fe97c98cd77322f034b0/code/core/config/src/v4.ts)
into your app and customize to your taste.

<Notice theme="green" title="Custom Themes">
  If you want to learn to create your own custom theme suite rather than use the
  included defaults, check out the [Creating Custom Themes
  Guide](/docs/guides/theme-builder).
</Notice>

We recommend adding `settings.styleCompat` to the default config, setting it to
be "react-native". This will align the default flexBasis of `flex` to be `0`,
not `auto`, matching the default in React Native. This setting will become the
default in the upcoming Tamagui v2 release. We left it out of Config v4 because
we wanted an easier transition for existing users, but if you are a new user, we
recommend this setting.

---

### Default Configuration

You can take or leave as much of the default configuration as you please. Each
part is exported separately, or all together as `defaultConfig`.

#### Themes

Config v4 has a new helper function `createThemes`, an opinionated way to
generate a suite of themes. It also comes with default themes that include
minimal grayscale light and dark themes, and an inverted "accent" theme.

It's easiest to get a feel for both createThemes the default themes by reading
the source for how `defaultThemes` are defined:

```tsx fileName=themes.ts
import * as Colors from '@tamagui/colors'
import { createThemes, defaultComponentThemes } from '@tamagui/config/v4'

const darkPalette = [
  '#050505',
  '#151515',
  '#191919',
  '#232323',
  '#282828',
  '#323232',
  '#424242',
  '#494949',
  '#545454',
  '#626262',
  '#a5a5a5',
  '#fff',
]

const lightPalette = [
  '#fff',
  '#f8f8f8',
  'hsl(0, 0%, 96.3%)',
  'hsl(0, 0%, 94.1%)',
  'hsl(0, 0%, 92.0%)',
  'hsl(0, 0%, 90.0%)',
  'hsl(0, 0%, 88.5%)',
  'hsl(0, 0%, 81.0%)',
  'hsl(0, 0%, 56.1%)',
  'hsl(0, 0%, 50.3%)',
  'hsl(0, 0%, 42.5%)',
  'hsl(0, 0%, 9.0%)',
]

const lightShadows = {
  shadow1: 'rgba(0,0,0,0.04)',
  shadow2: 'rgba(0,0,0,0.08)',
  shadow3: 'rgba(0,0,0,0.16)',
  shadow4: 'rgba(0,0,0,0.24)',
  shadow5: 'rgba(0,0,0,0.32)',
  shadow6: 'rgba(0,0,0,0.4)',
}

const darkShadows = {
  shadow1: 'rgba(0,0,0,0.2)',
  shadow2: 'rgba(0,0,0,0.3)',
  shadow3: 'rgba(0,0,0,0.4)',
  shadow4: 'rgba(0,0,0,0.5)',
  shadow5: 'rgba(0,0,0,0.6)',
  shadow6: 'rgba(0,0,0,0.7)',
}

const extraColors = {
  black1: darkPalette[0],
  black2: darkPalette[1],
  black3: darkPalette[2],
  black4: darkPalette[3],
  black5: darkPalette[4],
  black6: darkPalette[5],
  black7: darkPalette[6],
  black8: darkPalette[7],
  black9: darkPalette[8],
  black10: darkPalette[9],
  black11: darkPalette[10],
  black12: darkPalette[11],
  white1: lightPalette[0],
  white2: lightPalette[1],
  white3: lightPalette[2],
  white4: lightPalette[3],
  white5: lightPalette[4],
  white6: lightPalette[5],
  white7: lightPalette[6],
  white8: lightPalette[7],
  white9: lightPalette[8],
  white10: lightPalette[9],
  white11: lightPalette[10],
  white12: lightPalette[11],
}

const generatedThemes = createThemes({
  componentThemes: defaultComponentThemes,

  base: {
    palette: {
      dark: darkPalette,
      light: lightPalette,
    },

    // for values we don't want being inherited onto sub-themes
    extra: {
      light: {
        ...Colors.blue,
        ...Colors.green,
        ...Colors.red,
        ...Colors.yellow,
        ...lightShadows,
        ...extraColors,
        shadowColor: lightShadows.shadow1,
      },
      dark: {
        ...Colors.blueDark,
        ...Colors.greenDark,
        ...Colors.redDark,
        ...Colors.yellowDark,
        ...darkShadows,
        ...extraColors,
        shadowColor: darkShadows.shadow1,
      },
    },
  },

  // inverse accent theme
  accent: {
    palette: {
      dark: lightPalette,
      light: darkPalette,
    },
  },

  childrenThemes: {
    blue: {
      palette: {
        dark: Object.values(Colors.blueDark),
        light: Object.values(Colors.blue),
      },
    },
    red: {
      palette: {
        dark: Object.values(Colors.redDark),
        light: Object.values(Colors.red),
      },
    },
    yellow: {
      palette: {
        dark: Object.values(Colors.yellowDark),
        light: Object.values(Colors.yellow),
      },
    },
    green: {
      palette: {
        dark: Object.values(Colors.greenDark),
        light: Object.values(Colors.green),
      },
    },
  },
})

export type TamaguiThemes = typeof generatedThemes

/**
 * This is an optional production optimization: themes JS can get to 20Kb or more.
 * Tamagui has ~1Kb of logic to hydrate themes from CSS, so you can remove the JS.
 * So long as you server render your Tamagui CSS, this will save you bundle size:
 */
export const themes: TamaguiThemes =
  // we define this in our bundler plugins, but you can also use your framework/bundlers
  // built in environment variables to ensure this is shaken away on client js bundles
  process.env.TAMAGUI_ENVIRONMENT === 'client' &&
  process.env.NODE_ENV === 'production'
    ? {}
    : (generatedThemes as any)
```

So what have we done?

- `base` will always generate `light` and `dark` as your root themes. We plan to
  add support for generating just one base theme, but you can manually remove
  either light or dark if you please.
- Our `base` themes are grayscale, defined by their palettes.
- The `accent` option is an opinionated way to have a "contrasting theme". When
  set, it generates a few things:
  - `light_accent` and `dark_accent` themes
  - `$accent1` through `$accent12` on the base themes
- `childrenThemes` allows us to set up some sub-themes easily, this is
  especially useful for when you want to make an area look like an error,
  success, or warning state. That's why we default to setting `green`, `yellow`,
  and `red`. Of course, you can swap these out for whatever you like.
- We also are setting `componentThemes` to `defaultComponentThemes`, which will
  generate for us default styling for the Tamagui UI components. If you prefer
  to not have default component themes, you can leave this out.
- You can use the `getTheme` callback to customize any generated theme. See the
  [theme-builder guide](/docs/guides/theme-builder#customizing-themes-with-gettheme)
  for details.

#### Settings

<PropsTable
  data={[
    { name: 'mediaQueryDefaultActive', description: 'See Media' },
    { name: 'defaultFont', description: 'body' },
    {
      name: 'fastSchemeChange',
      description: true,
      description:
        'On iOS, leverages DynamicColorIOS for fast light/dark theme changes.',
    },
    {
      name: 'shouldAddPrefersColorThemes',
      description: true,
      description:
        'Generates CSS for prefers-color-scheme, so even with JS off you have proper light/dark styling.',
    },
    {
      name: 'allowedStyleDescriptions',
      description: 'somewhat-strict-web',
      description:
        'More strict than just a string, but still allows web-only style descriptions like vh, vw.',
    },
    {
      name: 'themeClassNameOnRoot',
      description: true,
      description:
        'Defaults to putting your root theme className onto the root html tag so you can use your CSS variables on your body tag.',
    },
    {
      name: 'onlyAllowShorthands',
      description: true,
      description:
        'For any shorthand defined, remove the types for the longhand.',
    },
  ]}
/>

#### Media

The new media queries in v4 are also more simplified and aligned to Tailwind:

<PropsTable
  data={[
    { name: '2xl', description: `minWidth: 1536` },
    { name: 'xl', description: `minWidth: 1280` },
    { name: 'lg', description: `minWidth: 1024` },
    { name: 'md', description: `minWidth: 768` },
    { name: 'sm', description: `minWidth: 640` },
    { name: 'xs', description: `minWidth: 460` },
    { name: '2xs', description: `minWidth: 340` },
  ]}
/>

On the server, only xs and 2xs default to `true` to align generally give you a
[mobile-first design](https://en.wikipedia.org/wiki/Responsive_web_design)
setup, but of course you can change that. This only really affects `useMedia()`,
as Tamagui generates proper SSR-safe CSS for media queries on the server for any
style properties.

#### Shorthands

Config v4 has fewer shorthands, and aligns shorthands to Tailwind for
familiarity:

<PropsTable
  data={[
    { name: 'text', description: 'textAlign' },
    { name: 'b', description: 'bottom' },
    { name: 'bg', description: 'backgroundColor' },
    { name: 'content', description: 'alignContent' },
    { name: 'flex', description: 'flexDirection' },
    { name: 'grow', description: 'flexGrow' },
    { name: 'items', description: 'alignItems' },
    { name: 'justify', description: 'justifyContent' },
    { name: 'l', description: 'left' },
    { name: 'm', description: 'margin' },
    { name: 'maxH', description: 'maxHeight' },
    { name: 'maxW', description: 'maxWidth' },
    { name: 'mb', description: 'marginBottom' },
    { name: 'minH', description: 'minHeight' },
    { name: 'minW', description: 'minWidth' },
    { name: 'ml', description: 'marginLeft' },
    { name: 'mr', description: 'marginRight' },
    { name: 'mt', description: 'marginTop' },
    { name: 'mx', description: 'marginHorizontal' },
    { name: 'my', description: 'marginVertical' },
    { name: 'p', description: 'padding' },
    { name: 'pb', description: 'paddingBottom' },
    { name: 'pl', description: 'paddingLeft' },
    { name: 'pr', description: 'paddingRight' },
    { name: 'pt', description: 'paddingTop' },
    { name: 'px', description: 'paddingHorizontal' },
    { name: 'py', description: 'paddingVertical' },
    { name: 'r', description: 'right' },
    { name: 'rounded', description: 'borderRadius' },
    { name: 'select', description: 'userSelect' },
    { name: 'self', description: 'alignSelf' },
    { name: 'shrink', description: 'flexShrink' },
    { name: 't', description: 'top' },
    { name: 'z', description: 'zIndex' },
  ]}
/>

#### Tokens

Tokens default tokens are defined as:

```tsx
export const size = {
  $0: 0,
  '$0.25': 2,
  '$0.5': 4,
  '$0.75': 8,
  $1: 20,
  '$1.5': 24,
  $2: 28,
  '$2.5': 32,
  $3: 36,
  '$3.5': 40,
  $4: 44,
  $true: 44,
  '$4.5': 48,
  $5: 52,
  $6: 64,
  $7: 74,
  $8: 84,
  $9: 94,
  $10: 104,
  $11: 124,
  $12: 144,
  $13: 164,
  $14: 184,
  $15: 204,
  $16: 224,
  $17: 224,
  $18: 244,
  $19: 264,
  $20: 284,
}

export const space = {
  // ... space is the same as size, but also defines "negative" values for every size, like:
  '$-1': -20,
}

export const zIndex = {
  0: 0,
  1: 100,
  2: 200,
  3: 300,
  4: 400,
  5: 500,
}

export const radius = {
  0: 0,
  1: 3,
  2: 5,
  3: 7,
  4: 9,
  true: 9,
  5: 10,
  6: 16,
  7: 19,
  8: 22,
  9: 26,
  10: 34,
  11: 42,
  12: 50,
}
```

Note that `color` tokens aren't defined, instead we are just using themes.

Also note that the "true" value on tokens is the "default" - Tamagui UI for now
uses defaults to determine how to size various things out of the box. This isn't
mandatory, and in version 2 of Tamagui we are working on a more clear solution.

#### Fonts

Only a `body` and `heading` font are exported, which both use "system" font
families:

<PropsTable
  data={[
    {
      name: 'web',
      description:
        '-apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif',
    },
    { name: 'native', description: 'System' },
  ]}
/>

You can create your own system font using the exported `createSystemFont`
helper, or of course swap out your own fonts altogether.


## core/config-v5

---
title: 'Config v5'
description: Modern config and themes with @tamagui/config/v5
---

<IntroParagraph>
  The recommended Tamagui configuration gives you a complete design system,
  flexible theme generation, Tailwind aligned shorthands, multiple animation
  drivers, and easy customization.
</IntroParagraph>

V5 is largely compatible with v4 - it's a superset that adds more colors/tokens
and makes theme customization easier. The main differences are modern defaults
for `styleCompat` and `position`, but everything can be customized to your
preferences.

First install the config package:

```bash
npm install @tamagui/config
```

## Choosing an Animation Driver

V5 provides separate entry points for different animation drivers, so you only
bundle what you need:

```tsx fileName="tamagui.config.ts"
import { defaultConfig } from '@tamagui/config/v5'
import { animations } from '@tamagui/config/v5-css'
import { createTamagui } from 'tamagui'

export const config = createTamagui({
  ...defaultConfig,
  animations,
})
```

Available animation drivers:

- **`@tamagui/config/v5-css`** - CSS animations (smallest bundle, great for web)
- **`@tamagui/config/v5-motion`** - Motion animations (spring physics, smooth)
- **`@tamagui/config/v5-rn`** - React Native Animated API
- **`@tamagui/config/v5-reanimated`** - Reanimated (best native performance)

<Notice>
  The base `@tamagui/config/v5` export includes no animations. Import animations
  separately from one of the driver entry points above.
</Notice>

### Cross-Platform Animation Setup

For apps targeting both web and native, you can use different animation drivers
per platform:

```tsx fileName="tamagui.config.ts"
import { defaultConfig } from '@tamagui/config/v5'
import { animations as animationsCSS } from '@tamagui/config/v5-css'
import { animations as animationsReanimated } from '@tamagui/config/v5-reanimated'
import { createTamagui, isWeb } from 'tamagui'

export const config = createTamagui({
  ...defaultConfig,
  animations: isWeb ? animationsCSS : animationsReanimated,
})
```

This gives you CSS animations on web (smaller bundle, better performance) and
Reanimated on native (smooth 60fps animations).

### Available Animations

All v5 animation drivers include these preset animations:

**Timing animations** (fixed duration):

<PropsTable
  data={[
    { name: '0ms', description: 'Instant (0ms)' },
    { name: '30ms', description: '30 milliseconds' },
    { name: '50ms', description: '50 milliseconds' },
    { name: '75ms', description: '75 milliseconds' },
    { name: '100ms', description: '100 milliseconds' },
    { name: '200ms', description: '200 milliseconds' },
    { name: '300ms', description: '300 milliseconds' },
  ]}
/>

**Spring animations** (physics-based):

<PropsTable
  data={[
    { name: 'superBouncy', description: 'Very bouncy, playful spring' },
    { name: 'bouncy', description: 'Bouncy spring with natural feel' },
    { name: 'kindaBouncy', description: 'Subtle bounce, good for UI elements' },
    { name: 'superLazy', description: 'Very slow, heavy spring' },
    { name: 'lazy', description: 'Slow, relaxed spring' },
    { name: 'medium', description: 'Balanced spring for general use' },
    { name: 'slowest', description: 'Slowest spring animation' },
    { name: 'slow', description: 'Slow spring animation' },
    { name: 'quick', description: 'Fast, responsive spring' },
    { name: 'quickLessBouncy', description: 'Quick with minimal overshoot' },
    { name: 'quicker', description: 'Faster spring' },
    { name: 'quickerLessBouncy', description: 'Faster with minimal overshoot' },
    { name: 'quickest', description: 'Fastest spring' },
    {
      name: 'quickestLessBouncy',
      description: 'Fastest with minimal overshoot',
    },
    { name: 'tooltip', description: 'Tuned for tooltip enter/exit' },
  ]}
/>

---

## What's Different from v4

- **More color themes** out of the box: blue, gray, green, orange, pink, purple,
  red, tan, teal, yellow, neutral
- **`createV5Theme` helper** for easy customization of themes without manually
  calling `createThemes`
- **`styleCompat: 'react-native'`** is the default, so `flex` uses
  `flexBasis: 0` (matches React Native behavior)
- **`defaultPosition: 'static'`** instead of `'relative'` (modern web default)
- **Improved color palettes** using
  [Radix Colors](https://www.radix-ui.com/colors) for better accessibility

All settings can be overridden - if you prefer v4 behavior, set
`styleCompat: 'legacy'` and `defaultPosition: 'relative'` in your settings.

---

## Customizing Themes with createV5Theme

The `createV5Theme` function lets you customize the default v5 themes. The
easiest way to add or change colors is using `@tamagui/colors` which provides
Radix color palettes:

```tsx fileName="tamagui.config.ts"
import {
  createV5Theme,
  defaultChildrenThemes,
  defaultConfig,
} from '@tamagui/config/v5'
import { cyan, cyanDark, amber, amberDark } from '@tamagui/colors'
import { createTamagui } from 'tamagui'

const themes = createV5Theme({
  childrenThemes: {
    // include defaults (blue, red, green, yellow, etc.)
    ...defaultChildrenThemes,
    // add new colors
    cyan: { light: cyan, dark: cyanDark },
    amber: { light: amber, dark: amberDark },
  },
})

export const config = createTamagui({
  ...defaultConfig,
  themes,
})
```

Or use only the colors you need:

```tsx fileName="tamagui.config.ts"
import { createV5Theme } from '@tamagui/config/v5'
import { blue, blueDark, gray, grayDark } from '@tamagui/colors'

// minimal theme with just blue and gray
const themes = createV5Theme({
  childrenThemes: {
    blue: { light: blue, dark: blueDark },
    gray: { light: gray, dark: grayDark },
  },
})
```

### createV5Theme Options

<PropsTable
  data={[
    {
      name: 'childrenThemes',
      type: 'Record<string, { light: ColorObject, dark: ColorObject }>',
      description:
        'Color themes to include. Use defaultChildrenThemes to include defaults, or pass only the colors you need. Works directly with @tamagui/colors.',
    },
    {
      name: 'darkPalette',
      type: 'string[]',
      description:
        'Override the dark base palette (12 colors from darkest to lightest)',
    },
    {
      name: 'lightPalette',
      type: 'string[]',
      description:
        'Override the light base palette (12 colors from lightest to darkest)',
    },
    {
      name: 'grandChildrenThemes',
      type: 'Record<string, { template: string }>',
      description: 'Override grandChildren themes (alt1, alt2, surface1, etc.)',
    },
    {
      name: 'componentThemes',
      type: 'false | ComponentThemes',
      default: 'false',
      description:
        'Component themes (defaults to false in v5). We recommend using defaultProps instead.',
    },
  ]}
/>

### Subtle Theme Variant

For a more muted look, use the subtle variant which has desaturated colors:

```tsx fileName="tamagui.config.ts"
import { defaultConfig, themes } from '@tamagui/config/v5-subtle'
import { createTamagui } from 'tamagui'

export const config = createTamagui({
  ...defaultConfig,
  themes,
})
```

### Custom Color Adjustments

Use `adjustPalette` to transform colors with a callback function. The callback
receives each color's HSL values and its 1-based index (1-12):

```tsx fileName="tamagui.config.ts"
import {
  adjustPalette,
  adjustPalettes,
  defaultChildrenThemes,
  createV5Theme,
} from '@tamagui/config/v5'

// adjust a single palette - desaturate and lighten
const mutedBlue = adjustPalette(blue, (hsl, i) => ({
  ...hsl,
  s: hsl.s * 0.7,
  l: Math.min(100, hsl.l * 1.1),
}))

// adjust multiple palettes at once with adjustPalettes
const mutedThemes = adjustPalettes(defaultChildrenThemes, {
  // 'default' applies to all colors not explicitly listed
  default: {
    light: (hsl, i) => ({ ...hsl, s: hsl.s * 0.8 }),
    dark: (hsl, i) => ({ ...hsl, s: hsl.s * 0.6, l: hsl.l * 0.9 }),
  },
  // override specific colors
  yellow: {
    light: (hsl) => ({ ...hsl, s: hsl.s * 0.5 }),
    dark: (hsl, i) => ({ ...hsl, s: hsl.s * (i <= 4 ? 0.3 : 0.8) }),
  },
  // skip adjustment for specific colors
  gray: undefined,
})

const themes = createV5Theme({ childrenThemes: mutedThemes })
```

The callback receives:

- `hsl` - object with `h` (hue 0-360), `s` (saturation 0-100), `l` (lightness
  0-100)
- `i` - 1-based index in the palette (1-12)

Use the index to apply different adjustments to background colors (1-4),
mid-tones (5-8), and foreground colors (9-12).

---

## Theme Values

V5 themes include a rich set of color values accessible via `$theme-*` tokens.

### Base Colors (color1-12)

Every theme includes 12 base colors that shift based on the theme:

```tsx
<View bg="$color1" />  // Lightest in light mode, darkest in dark mode
<View bg="$color6" />  // Mid-range
<View bg="$color12" /> // Darkest in light mode, lightest in dark mode
```

### Semantic Colors

Standard semantic values that adapt to each theme:

- `background`, `backgroundHover`, `backgroundPress`, `backgroundFocus`
- `color`, `colorHover`, `colorPress`, `colorFocus`
- `borderColor`, `borderColorHover`, `borderColorPress`, `borderColorFocus`
- `placeholderColor`, `outlineColor`

### Opacity Variants

Foreground color with opacity (useful for subtle overlays and borders):

- `color01` (10% opacity), `color0075`, `color005`, `color0025`, `color002`,
  `color001`

Background color with opacity:

- `background01` (10% opacity), `background0075`, `background005`,
  `background0025`, `background002`, `background001`

### Interpolated Colors

In-between shades for fine-grained control:

- `color0pt5` - Between color0 and color1
- `color1pt5` - Between color1 and color2
- `color2pt5` - Between color2 and color3

### White & Black (Fixed)

Always available regardless of theme, with opacity variants:

- `white`, `white0` (transparent), `white02`, `white04`, `white06`, `white08`
- `black`, `black0` (transparent), `black02`, `black04`, `black06`, `black08`
- `white1`-`white12`, `black1`-`black12` (12-step scales)

### Shadow Colors

Pre-tuned shadow opacities for light and dark modes:

- `shadow1` through `shadow6` (light shadows are subtler, dark are stronger)
- `shadowColor` - Default shadow color for the theme

### Radix Color Scales

All color themes (gray, blue, red, etc.) expose their full 12-step scale:

```tsx
<View bg="$blue5" />
<Text color="$red11" />
<View borderColor="$green7" />
```

Available scales: `gray1-12`, `blue1-12`, `red1-12`, `green1-12`, `yellow1-12`

### Neutral Colors

The `neutral` scale maintains sufficient contrast on both light and dark
backgrounds - useful for text or UI that needs to work regardless of theme:

- `neutral1`-`neutral12`

### Accent Colors

Every theme includes an inverted accent scale for contrast elements:

- `accent1`-`accent12` - Inverted palette (light colors in dark mode, vice
  versa)
- `accentBackground`, `accentColor` - Quick access to accent bg/fg

```tsx
<Button theme="accent">Primary Action</Button>
<View bg="$accentBackground" color="$accentColor" />
```

---

## Color Themes

V5 includes these color themes by default:

- **Grayscale**: gray, neutral
- **Colors**: blue, green, red, yellow
- **Fixed**: black, white (don't change between light/dark)

Each color theme has 12 steps following the
[Radix Colors](https://www.radix-ui.com/colors) pattern.

---

## Templates

V5 includes default templates that control how theme values map to palette
indices. Available templates: `base`, `surface1`, `surface2`, `surface3`,
`alt1`, `alt2`, `inverse`.

For more details on how templates work, see the
[Creating Custom Themes Guide](/docs/guides/theme-builder).

## Component Themes (Removed in V5)

V5 removes keeps our component themes as they solve a hard problem — how do you
change the theme of an entire area (like from `dark` to `dark_green`) but have
the component inside it still keep their relative shades?

One of the great things in Tamagui is being able to set component themes, and
sub-themes, and have an entire area of your UI re-theme and look perfect.

We'd exported just having the ability to set a default theme, like "surface1 =>
4", but then if you try and re-theme a component to `green`, that would
conflict. For example, if you made your default `<Button theme="surface3" />`,
then if someone uses it `<Button theme="green" />` now it's green, but not the
Button green that is typically stronger than your base green.

So for now we've left them on! We do want to move away from component themes in
general as they can be tricky to understand. But the benefits are also strong,
and until we find a great solution we're leaving them. You can always set
`componentThemes: false` in your `createV5Themes` function to turn it off.

---

## Settings

V5 config includes these default settings:

<PropsTable
  data={[
    { name: 'defaultFont', description: '"body"' },
    {
      name: 'fastSchemeChange',
      description:
        'true - Uses DynamicColorIOS on iOS for fast light/dark theme changes.',
    },
    {
      name: 'shouldAddPrefersColorThemes',
      description: 'true - Generates CSS for prefers-color-scheme.',
    },
    {
      name: 'allowedStyleValues',
      description:
        '"somewhat-strict-web" - Allows web-only values like vh, vw.',
    },
    {
      name: 'addThemeClassName',
      description:
        '"html" - Adds theme className to html tag for CSS variable access.',
    },
    {
      name: 'onlyAllowShorthands',
      description: 'true - Removes types for longhand versions of shorthands.',
    },
    {
      name: 'styleCompat',
      description:
        '"react-native" - Aligns flex defaults to React Native (flexBasis: 0).',
    },
  ]}
/>

---

## Media Queries

V5 includes an expanded set of media queries with height-based, max-width, and
min-width variants.

### Min-Width (Mobile-First)

<PropsTable
  data={[
    { name: 'xxxs', description: 'minWidth: 260' },
    { name: 'xxs', description: 'minWidth: 340' },
    { name: 'xs', description: 'minWidth: 460' },
    { name: 'sm', description: 'minWidth: 640' },
    { name: 'md', description: 'minWidth: 768' },
    { name: 'lg', description: 'minWidth: 1024' },
    { name: 'xl', description: 'minWidth: 1280' },
    { name: 'xxl', description: 'minWidth: 1536' },
  ]}
/>

### Max-Width (Desktop-First)

<PropsTable
  data={[
    { name: 'maxXXXS', description: 'maxWidth: 260' },
    { name: 'maxXXS', description: 'maxWidth: 340' },
    { name: 'maxXS', description: 'maxWidth: 460' },
    { name: 'maxSM', description: 'maxWidth: 640' },
    { name: 'maxMD', description: 'maxWidth: 768' },
    { name: 'maxLG', description: 'maxWidth: 1024' },
    { name: 'maxXL', description: 'maxWidth: 1280' },
    { name: 'maxXXL', description: 'maxWidth: 1536' },
  ]}
/>

### Height-Based

<PropsTable
  data={[
    { name: 'heightXXXS', description: 'minHeight: 260' },
    { name: 'heightXXS', description: 'minHeight: 340' },
    { name: 'heightXS', description: 'minHeight: 460' },
    { name: 'heightSM', description: 'minHeight: 640' },
    { name: 'heightMD', description: 'minHeight: 768' },
    { name: 'heightLG', description: 'minHeight: 1024' },
  ]}
/>

### Other

<PropsTable data={[{ name: 'pointerTouch', description: 'pointer: coarse' }]} />

---

## Shorthands

V5 uses the same Tailwind-aligned shorthands as v4. See the
[v4 config docs](/docs/core/config-v4#shorthands) for the full list.

---

## Tree Shaking Themes (Production Optimization)

Theme JS can grow to 20KB or more. Since Tamagui can hydrate themes from CSS
variables, you can remove the themes JS from your client bundle for better
Lighthouse scores.

This works because Tamagui generates CSS variables for all theme values. On the
client, it reads these from the DOM instead of needing the JS object.

### Setup

```tsx fileName="tamagui.config.ts"
import { defaultConfig, themes } from '@tamagui/config/v5'
import { createTamagui } from 'tamagui'

export const config = createTamagui({
  ...defaultConfig,
  // only load themes on server - client hydrates from CSS
  // for non-One Vite apps, use import.meta.env.SSR instead
  themes:
    process.env.VITE_ENVIRONMENT === 'client' ? ({} as typeof themes) : themes,
})
```

<Notice theme="green">
  This optimization requires server-side rendering. The CSS must be rendered on
  the server for the client to hydrate from it. Make sure you're using
  `config.getCSS()` or the bundler plugin's `outputCSS` option.
</Notice>

---

## Migrating from v4

Key differences when migrating:

1. **More colors**: v5 includes many more color themes out of the box
2. **`styleCompat`**: v5 defaults to `'react-native'`, so `flex` behaves like
   React Native
3. **Color palettes**: v5 uses Radix colors with darker grays for dark mode
4. **`createV5Theme`**: New helper function for customizing themes


## core/configuration

---
title: Configuration
description: Set up media queries, tokens, themes, and more.
---

Configuration in Tamagui controls things like tokens, themes, media queries,
animations, shorthands, and settings.

First, create a `tamagui.config.ts` file.

<Notice theme="blue">
  We recommend starting with [`@tamagui/config/v5`](/docs/core/config-v5), our
  preset configuration with sensible defaults, Tailwind-aligned shorthands, and
  a complete theme system.
</Notice>

```bash
npm install @tamagui/config
```

```tsx fileName="tamagui.config.ts"
import { defaultConfig } from '@tamagui/config/v5'
import { createTamagui } from 'tamagui'

export const config = createTamagui({
  ...defaultConfig,
  media: {
    ...defaultConfig.media,
    // add your own media queries here, if wanted
  },
})

type OurConfig = typeof config

declare module 'tamagui' {
  interface TamaguiCustomConfig extends OurConfig {}
}
```

We find most people are best off with this as it saves a lot of time, shares
shorthands with Tailwind, and has a lot of refinement. For more on our default
configuration, go to [the next page](/docs/core/config-v5).

Here's an example of a stripped down configuration to get a feel for the most
common concepts:

```tsx fileName=tamagui.config.ts
import { createTamagui, getConfig } from 'tamagui'

export const config = createTamagui({
  // tokens work like CSS Variables (and compile to them on the web)
  // accessible from anywhere, never changing dynamically:
  tokens: {
    // width="$sm"
    size: { sm: 8, md: 12, lg: 20 },
    // margin="$sm"
    space: { sm: 4, md: 8, lg: 12 },
    // radius="$none"
    radius: { none: 0, sm: 3 },
    color: { white: '#fff', black: '#000' },
  },

  // themes are like CSS Variables that you can change anywhere in the tree
  // you use <Theme name="light" /> to change the theme
  themes: {
    light: {
      bg: '#f2f2f2',
      color: '#fff',
    },
    dark: {
      bg: '#111',
      color: '#000',
    },
    // sub-themes are a powerful feature of tamagui, explained later in the docs
    // user theme like <Theme name="dark"><Theme name="blue">
    // or just <Theme name="dark_blue">
    dark_blue: {
      bg: 'darkblue',
      color: '#fff',
    },
  },

  // media query definitions can be used as style props or with the useMedia hook
  // but also are added to "group styles", which work like Container Queries from CSS
  media: {
    sm: { maxWidth: 860 },
    gtSm: { minWidth: 860 + 1 },
    short: { maxHeight: 820 },
    hoverNone: { hover: 'none' },
    pointerCoarse: { pointer: 'coarse' },
  },

  shorthands: {
    // <View px={20} />
    px: 'paddingHorizontal',
  },

  // there are more settings, explained below:
  settings: {
    disableSSR: true,
    allowedStyleValues: 'somewhat-strict-web',
  },
})

// now, make your types flow nicely back to your `tamagui` import:
type OurConfig = typeof config

declare module 'tamagui' {
  interface TamaguiCustomConfig extends OurConfig {}
}
```

<Notice theme="green">
  In this guide we import everything from `tamagui`, but if you are using the
  lower-level style library only, you can change the imports in this guide out
  for `@tamagui/core`, which is a strict subset.
</Notice>

Finally, pass your `config` export to a `<TamaguiProvider />` at the root of
your app:

```tsx
import { TamaguiProvider, View } from 'tamagui'
import { config } from './tamagui.config.ts'

export default () => (
  <TamaguiProvider config={config}>
    <View margin="$sm" />
  </TamaguiProvider>
)
```

You're all set up!

<Notice>
  To avoid issues with hot reloading and circular imports, make sure to only
  import your `tamagui.config.ts` once near the root of your app. If you need to
  access the configuration in other files, you generally do this just by using
  props on your styled components, or with hooks like `useMedia` or `useTheme`.
  We do have some getters like `getTokenValue`, and if you really need to access
  the full configuration object (though we don't find it necessary in most
  cases), you can use `getConfig`.
</Notice>

## In more detail

Let's start with an example of a more complete `tamagui.config.ts`:

```tsx fileName="tamagui.config.ts"
import { createFont, createTamagui, createTokens, isWeb } from 'tamagui'

// To work with the tamagui UI kit styled components (which is optional)
// you'd want the keys used for `size`, `lineHeight`, `weight` and
// `letterSpacing` to be consistent. The `createFont` function
// will fill-in any missing values if `lineHeight`, `weight` or
// `letterSpacing` are subsets of `size`.

const systemFont = createFont({
  family: isWeb ? 'Helvetica, Arial, sans-serif' : 'System',
  size: {
    1: 12,
    2: 14,
    3: 15,
  },
  lineHeight: {
    // 1 will be 22
    2: 22,
  },
  weight: {
    1: '300',
    // 2 will be 300
    3: '600',
  },
  letterSpacing: {
    1: 0,
    2: -1,
    // 3 will be -1
  },
  // (native only) swaps out fonts by face/style
  face: {
    300: { normal: 'InterLight', italic: 'InterItalic' },
    600: { normal: 'InterBold' },
  },
})

// Set up tokens

// The keys can be whatever you want, but if using `tamagui` you'll want 1-10:

const size = {
  0: 0,
  1: 5,
  2: 10,
  // ....
}

export const tokens = createTokens({
  size,
  space: { ...size, '-1': -5, '-2': -10 },
  radius: { 0: 0, 1: 3 },
  zIndex: { 0: 0, 1: 100, 2: 200 },
  color: {
    white: '#fff',
    black: '#000',
  },
})

const config = createTamagui({
  fonts: {
    heading: systemFont,
    body: systemFont,
  },
  tokens,

  themes: {
    light: {
      bg: '#f2f2f2',
      color: tokens.color.black,
    },
    dark: {
      bg: '#111',
      color: tokens.color.white,
    },
  },

  media: {
    sm: { maxWidth: 860 },
    gtSm: { minWidth: 860 + 1 },
    short: { maxHeight: 820 },
    hoverNone: { hover: 'none' },
    pointerCoarse: { pointer: 'coarse' },
  },

  // Shorthands
  // Adds <View m={10} /> to <View margin={10} />
  // See Settings section on this page to only allow shorthands
  // Be sure to have `as const` at the end
  shorthands: {
    px: 'paddingHorizontal',
    f: 'flex',
    m: 'margin',
    w: 'width',
  } as const,

  // Change the default props for any styled() component with a name.
  // We are discouraging the use of this and have deprecated it, prefer to use
  // styled() on any component to change it's styles.
  defaultProps: {
    Text: {
      color: 'green',
    },
  },
})

type AppConfig = typeof config

// this will give you types for your components
// note - if using your own design system, put the package name here instead of tamagui
declare module 'tamagui' {
  interface TamaguiCustomConfig extends AppConfig {}

  // if you want types for group styling props, define them like so:
  interface TypeOverride {
    groupNames(): 'card'
  }
}

export default config
```

The `createTamagui` function receives a configuration object with properties:

- [`animations`](#animations): Configurable animation drivers.
- [`media`](#media): Cross-platform, typed
  [media queries](/docs/core/use-media).
- [`themes`](#themes): Define [themes](/docs/intro/themes) to style contextually
  anywhere in the tree, much like CSS variables.
- [`tokens`](#tokens): Your base tokens are much like static CSS variables.
- [`settings`](#settings): Many options for strictness and style behavior.
- [`shorthands`](#shorthands): Define shorter names for any style property.

<Notice theme="blue">
  On Android you need to set the `face` option in `createFont` or else fonts
  won't pick up different weights, due to a React Native restriction.
</Notice>

Note, for `tamagui` (not core), it expects you to define a `true` token that
maps to your default size, this way it knows what token to use by default. So
you'd do something like this:

```tsx
export const tokens = createTokens({
  size: {
    small: 20,
    medium: 30,
    true: 30, // note true = 30 just like medium, your default size token
    large: 40,
  },
  space: {
    small: 10,
    medium: 20,
    true: 20, // same goes for space and other token categories
    large: 30,
  },
})
```

<Notice theme="green">
  If using the compiler, your tamagui.config.ts is parsed at build-time. For
  this reason, we recommend keeping it relatively simple. Avoid importing heavy
  dependencies.
</Notice>

## TamaguiProvider

With your config set up, import it near the root of your app and pass it to
`TamaguiProvider`:

```tsx line=3-5 fileName="App.tsx"
import { TamaguiProvider } from 'tamagui'
import { config } from './tamagui.config'

export default function App() {
  return (
    <TamaguiProvider config={config} defaultTheme="light">
      <AppContents />
    </TamaguiProvider>
  )
}
```

`TamaguiProvider` accepts a few properties:

<PropsTable
  data={[
    {
      name: 'defaultTheme',
      required: true,
      type: 'string',
      description: `The initial top level theme.`,
    },
    {
      name: 'disableInjectCSS',
      required: false,
      type: 'boolean',
      description: `By default Tamagui inserts CSS with a useInsertionEffect on load. But if you're setting up SSR you'll want to use getCSS() on the server instead and then turn on this setting.`,
    },
    {
      name: 'insets',
      required: false,
      type: '{ top?: number; bottom?: number; left?: number; right?: number }',
      description: `Safe area insets for iOS. Required for proper vertical Slider behavior on iOS. Pass the value from useSafeAreaInsets() hook.`,
    },
  ]}
/>

By default, Tamagui injects the CSS for your configuration on the client-side
into `document.head`, but you probably will want something better for
production. To do this, pass true to `disableInjectCSS` on `TamaguProvider`, and
then do one of the following three options:

If your framework has server-side layouts, you can just render it inline:

```tsx fileName=app/_layout.tsx
import { config } from './tamagui.config'

export default () => (
  <html>
    <head>
      <style
        dangerouslySetInnerHTML={{
          __html: config.getCSS(),
        }}
      />
    </head>
    <body>
      <Slot />
    </body>
  </html>
)
```

To optimize a bit more so you share a single CSS file between all pages, you can
use one of our bundler plugins' `outputCSS` setting, like so:

```tsx fileName=vite.config.ts
import { tamaguiPlugin } from '@tamagui/vite-plugin'

export default {
  plugins: [
    tamaguiPlugin({
      config: './src/tamagui.config.ts',
      outputCSS: './src/tamagui.css',
    }),
  ],
}
```

And then you'll want to import the resulting `tamagui.css` into your app.

As final option, you can also generate it yourself with the CLI. First create a
`tamagui.build.ts`:

```tsx fileName=tamagui.build.ts
import type { TamaguiBuildOptions } from 'tamagui'

export default {
  components: ['tamagui'],
  config: './config/tamagui.config.ts',
  outputCSS: './tamagui.css',
} satisfies TamaguiBuildOptions
```

And then run:

```bash
npx @tamagui/cli generate
```

See the [CLI Guide](/docs/guides/cli#generate) for more information on the
`generate` command and other CLI tools.

## Tokens

Tokens are inspired by the [Theme UI spec](https://theme-ui.com/theme-spec/).
They are mapped to CSS variables at build time. You can read about them in more
depth on [the tokens page](/docs/core/tokens).

### Font tokens

The font tokens are a bit special and are created with `createFont`:

```tsx
import { isWeb } from 'tamagui'

const interFont = createFont({
  family: isWeb ? 'Inter, Helvetica, Arial, sans-serif' : 'Inter',
  size: {
    1: 12,
    2: 14,
    3: 15,
    // ...
  },
  lineHeight: {
    1: 17,
    2: 22,
    3: 25,
    // ...
  },
  weight: {
    4: '300',
    6: '600',
  },
  letterSpacing: {
    4: 0,
    8: -1,
  },

  // because android handles fonts differently, you need to map the weight
  // to the actual name of the font in the font-file
  // you can get the name with `otfinfo`: otfinfo --family Inter.ttf
  face: {
    400: { normal: 'Inter', italic: 'Inter-Italic' },
    500: { normal: 'InterBold', italic: 'InterBold-Italic' },
  },
})
```

<Notice theme="blue">
  We use numbered keys as an example, but you can use any strings you'd like.
  The optional default styles in `tamagui` make use of number keys 1-10.
</Notice>

This gives you a lot of power over customizing every aspect of your design based
on each font family. In other styling libraries that follow the Theme UI spec,
you generally don't group your size/lineHeight/weight/etc tokens by the family,
which means you are forced to choose a single vertical rhythm no matter the
font.

#### Custom Fonts on Native

If you are using a custom font for native, you need to load your fonts for React
Native to recognize them. Tamagui doesn't really touch this area, instead you'll
use Expo or
[React Native directly](https://stackoverflow.com/questions/35255645/how-to-set-default-font-family-in-react-native),
something like this:

```tsx fileName="App.tsx"
import { useFonts } from 'expo-font'

function App() {
  const [loaded] = useFonts({
    Inter: require('@tamagui/font-inter/otf/Inter-Medium.otf'),
    InterBold: require('@tamagui/font-inter/otf/Inter-Bold.otf'),
  })

  useEffect(() => {
    if (loaded) {
      // can hide splash screen here
    }
  }, [loaded])

  if (!loaded) {
    return null
  }

  return <MyApp />
}
```

### Non-font tokens

The rest of the tokens categories besides font are flatter. The `space` and
`size` generally share keys, and that space can generally use negative keys as
well.

```tsx
// passed into createTamagui
const tokens = createTokens({
  color: {
    white: '#fff',
    black: '#000',
  },
})
```

You access tokens then by using `$` prefixes in your values. Tamagui knows which
tokens to use based on the style property you use.

```tsx
const App = () => (
  <Text fontSize="$lg" lineHeight="$lg" fontFamily="$mono" color="$white">
    Hello world
  </Text>
)
```

One final note: using tokens with themes. Tokens are considered a "fallback" to
themes, so any values you define in your theme will override the token. The next
section will explain this further.

### Configuring tokens

There are a few settings that control how strict your style values are allowed
to be, which are handled by the `settings` option of `createTamagui`. See the
[settings](#settings) below.

### Themes

Themes live one level below tokens. Tokens are your variables, where themes use
those tokens to create consistent, generic properties that you then typically
use in shareable components. Themes should generally only deal with colors.

Tamagui components in general expect a set of theme keys to be defined like the
following, but you can deviate if you create your own design system.

```tsx
const config = createTamagui({
  themes: {
    light: {
      background: '#fff',
      backgroundHover: tokens.color.gray2,
      // ...
      color: tokens.color.gray10,
      colorHover: tokens.color.gray9,
      colorPress: tokens.color.gray8,
      // ...
      color1: tokens.color.gray1,
    },
    dark: {
      background: '#000',
      // ... matching the properties for light ^
    },
  },
  // ... the rest of your configuration
})
```

Passing tokens to themes will be smart about sharing CSS, but is not requred.

You can then access theme values for any style value, either through styled() or
through a Styled Compoent like View or Text:

```tsx
const P = styled(Text, {
  color: '$color12'
})

// or directly
<Text color="$color11" />
```

One of the more powerful features in Tamagui is nesting themes, you just define
them like so:

```tsx
const config = createTamagui({
  themes: {
    light: {
      color1: '#fff',
      // ...
    },
    dark: {
      color1: '#000',
      // ...
    },

    light_alert: {
      background: '#e6ebbd',
    },
    dark_alert: {
      background: '#3e3f33',
    },
  },
  // ... the rest of your configuration
})
```

Themes work just like CSS variables, they can be changed anywhere in the tree.
Sub-themes can be subsets of parent themes likewise as their values will fall
back to the parent theme. They can also be nested as deeply as you like - so
even `light_alert_subtle` is possible.

For more on themes, see the [themes docs](/docs/core/themes), and more
[advanced theme building library](/docs/guides/theme-builder).

### Media

For more full docs on media queries, see the
[useMedia docs page](/docs/core/use-media).

### Animations

Tamagui supports four animation drivers that can be swapped out per-platform:

- [`@tamagui/animations-css`](/docs/core/animations-css) - CSS transition based
  animations (lightest)
- [`@tamagui/animations-react-native`](/docs/core/animations-react-native) -
  React Native Animated
- [`@tamagui/animations-reanimated`](/docs/core/animations-reanimated) -
  Reanimated for advanced animations
- [`@tamagui/animations-motion`](/docs/core/animations-motion) - Motion with
  WAAPI for web

See the [Animations documentation](/docs/core/animations) for a full comparison
and guide on choosing the right driver.

Add `animations` to `createTamagui`:

```tsx
import { createAnimations } from '@tamagui/animations-react-native'

// pass this exported `animations` to your `createTamagui` call:
export const animations = createAnimations({
  bouncy: {
    damping: 9,
    mass: 0.9,
    stiffness: 150,
  },
  lazy: {
    damping: 18,
    stiffness: 50,
  },
})
```

You can use different drivers per-platform - see the
[Platform-Specific Configuration](/docs/core/animations#platform-specific-configuration)
section.

### Shorthands

Shorthands are defined on `createTamagui`. Here's an example of a partial
shorthands configuration:

```tsx
// the as const ensures types work with the optional `onlyAllowShorthands` option
const shorthands = {
  ac: 'alignContent',
  ai: 'alignItems',
  als: 'alignSelf',
  bblr: 'borderBottomLeftRadius',
  bbrr: 'borderBottomRightRadius',
  bg: 'backgroundColor',
  br: 'borderRadius',
  btlr: 'borderTopLeftRadius',
  btrr: 'borderTopRightRadius',
  f: 'flex',
  // ...
} as const

export default createTamagui({
  shorthands,
})
```

Which will enable usage like:

```tsx
<View br="$myToken" />
```

where `br` expands into `borderRadius`.

### Settings

You can pass a `settings` object to `createTamagui`:

<PropsTable
  data={[
    {
      name: `disableSSR`,
      type: `boolean`,
      description: `For SSR compatibility on the web, Tamagui will render once with the settings from mediaQueryDefaultActive set for all media queries. Then, it will render again after the initial render using the proper media query values. This is so that the hydrating components will match the server. Setting disableSSR to true will avoid this and instead immediately render using the up to date media state, which is the preferrable behavior for client-side only (SPA) style apps. See the [Server Rendering](/docs/core/server-rendering) docs for more details on SSR, the ClientOnly component, and related hooks.`,
    },
    {
      name: `defaultFont`,
      required: false,
      type: `string`,
      description: `Map it to the regular key of the font given to createTamagui, so "body" or "heading" make sense as values here. This will ensure this font is the fallback for any views that don't define it.`,
    },
    {
      name: 'mediaQueryDefaultActive',
      required: false,
      type: 'Record<string, boolean>',
      description: `For the first render, determines which media queries are true (useful for SSR).`,
    },
    {
      name: 'addThemeClassName',
      required: false,
      type: "false | 'html' | 'body'",
      default: false,
      description: `If you want to style your <body> tag to use theme CSS variables on web, you must place the theme className  onto the body element or above. This will do so. If disabled, Tamagui will place the className onto the element rendered by the TamaguiProvider`,
    },
    {
      name: 'disableRootThemeClass',
      required: false,
      type: 'boolean',
      description:
        'On web, TamaguiProvider will add your top level theme className to the root of your document, the html tag. This lets you do things like style your body tag using Tamagui CSS variables that are generated by themes. If disabled, the theme className will instead by applied to the span inside the TamaguiProvider directly.',
    },
    {
      name: 'selectionStyles',
      required: false,
      type: '(theme) => ({ backgroundColor: Variable | string; color: Variable | string })',
      default: false,
      description: `On the web, will generate ::selection styles for text selection.`,
    },
    {
      name: 'shouldAddPrefersColorThemes',
      required: false,
      type: 'boolean',
      default: `true`,
      description: `Generates @media queries based on prefers-color-scheme for you if you have light/dark themes, causes larger total CSS sizes.`,
    },
    {
      name: 'onlyAllowShorthands',
      required: false,
      type: 'boolean',
      default: false,
      description: `Will remove the type for the long-form versions of any shorthands you define.`,
    },
    {
      name: `allowedStyleValues`,
      required: false,
      type: 'AllowedStyleValuesSetting',
      description: `Set up allowed values on style props, this is only a type-level validation (see below).`,
    },
    {
      name: `autocompleteSpecificTokens`,
      required: false,
      type: `boolean | 'except-special'`,
      description: `Set up if "specific tokens" ($color.name) are added to the types where tokens are allowed (see below).`,
    },
    {
      name: 'fastSchemeChange',
      required: false,
      type: 'boolean',
      default: false,
      description: `On iOS, this enables a mode where Tamagui returns color values using DynamicColorIOS significantly speeding up dark/light re-renders. Note: When enabled you must make sure the defaultTheme you set on TamaguiProvider matches the current system color scheme.`,
    },
    {
      name: 'webContainerType',
      required: false,
      type: 'string',
      default: 'inline-size',
      description: `Only for web, when using group styles Tamagui uses container queries. When using a group on a containing element, Tamagui defaults to setting "container-type" in CSS to "inline-size".`,
    },
  ]}
/>

#### Type strictness

#### `allowedStyleValues`

- `false (default)`: allows any string (or number for styles that accept
  numbers)
- `strict`: only allows tokens for any token-enabled properties
- `strict-web`: same as strict but allows for web-specific tokens like
  auto/inherit
- `somewhat-strict`: allow tokens or:
  - for `space/size`: string% or numbers
  - for `radius`: number
  - for `zIndex`: number
  - for `color`: named colors or rgba/hsla strings
- `somewhat-strict-web`: same as somewhat-strict but allows for web-specific
  tokens

```tsx
type AllowedValueSetting =
  | boolean
  | 'strict'
  | 'somewhat-strict'
  | 'strict-web'
  | 'somewhat-strict-web'

type AllowedStyleValuesSetting =
  | AllowedValueSetting
  | {
      space?: AllowedValueSetting
      size?: AllowedValueSetting
      radius?: AllowedValueSetting
      zIndex?: AllowedValueSetting
      color?: AllowedValueSetting
    }
```

#### `autocompleteSpecificTokens`

The VSCode autocomplete puts specific tokens above the regular ones, which leads
to worse DX. If true this setting removes the specific token from types for the
defined categories.

If set to `except-special`, specific tokens will autocomplete only if they don't
normally use one of the special token groups: space, size, radius, zIndex,
color.

### Environment Settings

A few things in Tamagui can be configuring via environment variables. Doing it
this way lets us avoid extra code being shipped to the client, as we your
bundler can tree shake the unused code easily when using envronment variables.
You should be sure to either use the Vite or Webpack Tamagui plugins, or
configure your bundler to define the environment variables for tree shaking. On
native, it is not a concern as the extra size is minimal.

## Tree Shaking Themes (Production Optimization)

Theme JS can grow to 20KB or more. Since Tamagui can hydrate themes from CSS
variables, you can remove the themes JS from your client bundle for better
Lighthouse scores.

This works because Tamagui generates CSS variables for all theme values. On the
client, it reads these from the DOM instead of needing the JS object.

### Setup

```tsx fileName="tamagui.config.ts"
import { defaultConfig, themes } from '@tamagui/config/v5'
import { createTamagui } from 'tamagui'

export const config = createTamagui({
  ...defaultConfig,
  // only load themes on server - client hydrates from CSS
  // for non-One Vite apps, use import.meta.env.SSR instead
  themes: process.env.VITE_ENVIRONMENT === 'client' ? ({} as typeof themes) : themes,
})
```

<Notice theme="green">
  This optimization requires server-side rendering. The CSS must be rendered on
  the server for the client to hydrate from it. Make sure you're using
  `config.getCSS()` or the bundler plugin's `outputCSS` option.
</Notice>


## core/exports

---
title: 'Other exports'
description: Helpful functions and constants
---

Let's take a quick look through some of the useful other exports in
`@tamagui/core`.

## Constants

[Constants are re-exported from @tamagui/constants](https://github.com/tamagui/tamagui/blob/main/code/core/constants/src/constants.ts):

- `isWeb` - True if targeting the web. Will be true both for SSR and client.
- `isWindowDefined` - `typeof window !== 'undefined'`
- `isServer` - `isWeb && !isWindowDefined`.
- `isClient` - `isWeb && isWindowDefined`.
- `useIsomorphicLayoutEffect` - `isServer ? useEffect : useLayoutEffect`. Helper
  for SSR rendering without warnings.
- `isChrome` - client-side Chrome
- `isWebTouchable` - web-only touch-device (client side only)
- `isTouchable` - True for any touch device (client side only).

---

## Helpers

### getConfig

The exported `getConfig` will return your fully parsed Tamagui config. This is
safer than importing it directly as it avoids circular imports. In general you
want to use `useTheme` to access themes, and
`getTokens`/`getToken`/`getTokenValue` for tokens, but for other parts of the
config this can be useful.

### insertFont

```tsx
type insertFont = (name: string, fontIn: GenericFont) => ParsedFont
```

Will add a new font after initial createTamagui call. Where `GenericFont` is the
same as a font passed to createTamagui, and `ParsedFont` is the font with the
subkeys prefixed with `$` and turned into variable objects.

### updateFont

The same as insertFont, but will update an existing font.

### isTamaguiComponent

```tsx
type isTamaguiComponent (component: any; name?: string) => boolean
```

If no name given, true if a Tamagui component, if name given ensures it's the
specific named Tamagui component.

### isTamaguiElement

```tsx
type isTamaguiElement (child: any; name?: string) => boolean
```

If no name given, true if a Tamagui ReactElement, if name given ensures it's the
specific named Tamagui component element.

### getTokens

```tsx
;() => TokensParsed
```

Returns the parsed Tamagui config object of all your tokens, can be used at
runtime to get values from tokens. If you are looking to get a single token
value, prefer `getToken` or `getTokenValue`.

### getToken

```tsx
;(name: Token, group?: keyof Tokens) => any
```

Given the specific name of a token or a name + group, will return the value as
either a variable on the web, or raw value on native. So if you define a size
token with key `small` and value of `14`:

```tsx
getToken('$size.small') // returns on web var(--size-small), native 14
getToken('$small', 'size') // returns on web var(--size-small), native 14
```

### getTokenValue

```tsx
;(name: Token, group?: keyof Tokens) => any
```

Similar to getToken, but always returns the raw value rather than the variable
name. If you define a size token with key `small` and value of `14`:

```tsx
getTokenValue('$size.small') // returns 14
getTokenValue('$small', 'size') // returns 14
```

### themeable

```tsx
themeable<Comp extends ReactComponentLike>(component: Comp): Comp
```

A higher order component that accepts `theme` and `themeInverse`, rendering them
onto `Theme` before rendering your component.

### getVariable

```tsx
type getVariable = (value: Variable) => string
```

If given a `Variable` - which comes from a parsed theme or token returned from
`createTamagui`, `useTheme` or `getTokens` (read more
[on the useTheme docs](/docs/core/use-theme)).

Calling `getVariable(useTheme().color)` returns `var(--color)` on the web, and
`#fff` on other platforms.

### setOnLayoutStrategy

This is a low-level utility to control `onLayout` performance. By default,
Tamagui `onLayout` on the web uses the `"async"` strategy (see below), which
gives a good balance of accuracy and performance. For extremely accurate to
native `onLayout`, you can use `"sync"`, but it can cause performance issues in
typical usage.

Call `setOnLayoutStrategy` with a single string argument:

- `"off"` will pause all `onLayout` callbacks from running.
- `"sync"` will run every frame and update every frame if necessary.
- `"async"` will use an async getBoundingClientRect strategy that avoids
  repaints/reflows, and also waits 2 frames of debouncing before triggering the
  callback.

The `async` strategy generally has great performance on web, and it will also
ensure to call `onLayout` sync on mount the first time, so you avoid jitters.

---

## Hooks

### useProps

Pass in props that include media styles and shorthands, get back a flat object
of props with the current media styles merged and shorthands expanded. This is
an advanced pattern that should be used sparingly, as it will trigger updates on
every media query used in the props object. Helpful for more complex components
that need to pass down props they are given to children (a common example being
the `size` prop, if it's controlling children that also accept size).

```tsx
// if props is:
//   { size: '$2', $sm: { size: '$4' } }
// and $sm is active, activeProps will be:
//   { size: '$4' }

// if shorthands like m => margin, then m always expands to margin
function MyButton(props) {
  const activeProps = useProps(props)
}
```

You can pass an option to disable the shorthands expansion, and another option
to resolve any token strings into their respective theme or token value:

```tsx
function MyButton(props) {
  const activeProps = useProps(props)
}
```

useProps takes a second argument, which is the same as useStyle, documented in
the next section.

### useStyle

Pass in props that include media styles and shorthands, get back a flat style
object with the current media styles merged, shorthands expanded, and all
theme/token values resolved (into CSS variables on the web or values on native
by default). This will remove any non-style values and will by default expand
all variants and resolve all values.

```tsx
// if props is:
//   { color: '$background', $sm: { color: '$color' } }
// and $sm is active, activeProps will be on web:
//   { color: 'var(--color)' }
// and on native will resolve to the actual theme or token value:
//   { color: '#fff' }
// this resolve behavior can be controlled, see the docs just below.

// if shorthands like m => margin, then m expands to margin

function MyButton(props) {
  const style = useStyle(props)
}
```

It can take a couple options to control how it resolves values and handles
shorthands:

```tsx
function MyButton(props) {
  const style = useStyle(props, {
    // option to disable the shorthand expansion
    disableExpandShorthands: true,
    // option to change how values are resolved
    // 'auto' (default) resolves to optimized value on all platforms (CSS variable on web, DynamicColor on iOS, not optimized on android)
    // 'value' resolves to the raw theme/token string on all platforms (the same as useTheme()[name].val)
    resolveValues: 'value',
    // pass a Tamagui component to resolve any custom variants:
    forComponent: CustomComponent,
  })
}
```

### usePropsAndStyle

Similar to the above two hooks, this returns your fully resolved/merged/media
query flattened styles, but instead of returning _just_ props or _just_ style,
it returns both of them split out, as well as returning the result of useTheme()
and useMedia() that it uses internally:

```tsx
function MyButton(props) {
  const [props, style, theme, media] = usePropsAndStyle(props, opts)
  // see useStyle to for documentation on opts
  // we return theme, media as they are used internally already in the hook
}
```

### useThemeName

Returns the string name of the current theme.

### useIsTouchDevice

SSR-friendly, only true on if native touchable or web touchable device (client
side, not server side).

### useDidFinishSSR

SSR-friendly, returns true if SSR has completed on the client, false before
hydration done. On server it's always false.

### ClientOnly

A component that opts its children out of SSR. When wrapped in `ClientOnly`,
Tamagui avoids double-renders in various places which results in faster
client-side rendering.

```tsx
import { ClientOnly } from 'tamagui' // or '@tamagui/core'

// Children will render only on the client, skipping SSR entirely
<ClientOnly>
  <MyClientOnlyComponent />
</ClientOnly>

// Can be conditionally enabled
<ClientOnly enabled={shouldSkipSSR}>
  <MyComponent />
</ClientOnly>
```

---

## Type Helpers

### GetProps

Fetches the type of props for a Tamagui component:

```tsx
import { View, GetProps, styled } from '@tamagui/core'

const X = styled(View, {})

type XProps = GetProps<typeof X>
```

### GetRef

Fetches the type of a ref for a Tamagui component, or any React component:

```tsx
import { View, GetRef, styled } from '@tamagui/core'

const X = styled(View, {})

const MyComponent = () => {
  const ref = useRef<GetRef<typeof X>>()
  return <X ref={ref} />
}
```


## core/font-language

---
title: FontLanguage
description: Using custom fonts for each language
---

The `FontLanguage` component works with the Tamagui design system, allowing you to define custom fonts per-language.

### Rationale

Before `FontLanguage`, you could get custom fonts with some effort. Simply create a `body` font, and a `body_french` font, and switch it out like so on relevant text components: `<Text fontFamily={isFrench ? '$body_french' : '$body'} />`.

This would work, but having to update every Text component on page with a conditional would be terribly verbose and slow.

With `FontLanguage`, you can do:

```tsx
<FontLanguage body={isFrench ? 'french' : 'default'}>
  <Text fontfamily="$body">Hello world</Text>
</FontLanguage>
```

Even better, with `styled`:

```tsx
const P = styled(Text, { fontFamily: '$body' })

<FontLanguage body={isFrench ? 'french' : 'default'}>
  <P>Hello world</P>
</FontLanguage>
```

On the web, FontLanguage uses CSS Variables to avoid re-renders even when changing languages.

### Usage

In your `tamagui.config.ts`, you can add a language variant for any font by adding a suffix with a custom name for that language. Here's a stripped down config showing how you can add the custom `_cn` suffix for a Chinese language font:

```tsx
import { createFont, createTamagui, createTokens } from 'tamagui'

export default createTamagui({
  fonts: {
    body: createFont({
      family: 'Inter, Helvetica, Arial, sans-serif',
      // ...
    }),
    body_cn: createFont({
      family: 'Inter-CN',
      // ...
    }),
  },
})
```

You'll then need to load the `Inter` and `Inter-CN` fonts for the platforms you support. Once you do, you'll get fully typed support for changing any `body` font to `cn` in a component:

```tsx
import { FontLanguage, Text } from 'tamagui' // or '@tamagui/core'

export default (
  <FontLanguage body="cn">
    <Text fontFamily="$body">
      你好
    </Text>
  </FontLanguage>
)
```

The name you choose for the suffix is up to you, and Tamagui will treat any font with a `_` separator as a language variant.

To reset your font back to your base `body` font, you can use the reserved key `default`:

```tsx
import { FontLanguage, Text } from 'tamagui' // or '@tamagui/core'

export default (
  <FontLanguage body="cn">
    <Text fontFamily="$body">
      你好
    </Text>
    <FontLanguage body="default">
      <Text fontFamily="$body">
        Hello
      </Text>
    </FontLanguage>
  </FontLanguage>
)
```


## core/server-rendering

---
title: Server Rendering
description: Advanced SSR, hydration, and client-only rendering in Tamagui
---

Tamagui takes a unique approach to server-side rendering that goes beyond typical SSR solutions, ensuring perfect hydration with zero flickering even when using spring animations.

## How It Works

Unlike other libraries that simply render default values and re-render on the client, Tamagui uses a three-phase approach:

1. **Server**: Renders with proper CSS media queries for everything, including components using spring animation drivers (which normally require hard-coded values)
2. **Client (first render)**: Renders with CSS to match the server output perfectly
3. **Client (after hydration)**: Seamlessly swaps to springs and animations

This approach delivers perfect hydration with zero flickering, even for complex responsive layouts with animations.

## Opting Out

You can opt out of SSR entirely or for specific parts of your app.

### Wholesale: disableSSR

For single-page applications that don't need SSR, disable it entirely in your config:

```tsx
import { createTamagui } from 'tamagui'

export const config = createTamagui({
  // ... other config
  settings: {
    disableSSR: true, // Automatically wraps app with <ClientOnly enabled>
  },
})
```

When `disableSSR` is true:
- Media queries immediately use actual values
- No double render occurs
- Your entire app is wrapped with `<ClientOnly enabled>`

### Partial: ClientOnly Component

For fine-grained control, wrap specific parts of your tree:

```tsx
import { ClientOnly } from '@tamagui/use-did-finish-ssr'

function MyComponent() {
  return (
    <YStack>
      {/* Server-rendered for SEO */}
      <ProductInfo />

      {/* Client-only for complex interactions */}
      <ClientOnly enabled>
        <InteractiveChart />
      </ClientOnly>
    </YStack>
  )
}
```

## Configuration Component

The `Configuration` component is essentially a shorthand for `ClientOnly`. It accepts [configuration settings](/docs/core/configuration) like `disableSSR`:

```tsx
import { Configuration } from 'tamagui'

function MyApp() {
  return (
    <Configuration disableSSR>
      {/* Everything here renders client-only */}
      <ComplexComponent />
    </Configuration>
  )
}
```

## useMedia and SSR

The `useMedia` hook automatically leverages Tamagui's smart SSR rendering. During server render, it returns appropriate defaults that match CSS media queries, then updates after hydration without flickering.

```tsx
import { useMedia } from 'tamagui'

function ResponsiveComponent() {
  const media = useMedia() // Automatically handles SSR correctly

  return media.gtSm ? <DesktopView /> : <MobileView />
}
```

## SSR Hooks

### useDidFinishSSR

Returns `true` when hydration is complete:

```tsx
import { useDidFinishSSR } from '@tamagui/use-did-finish-ssr'

function MyComponent() {
  const isClient = useDidFinishSSR()

  return isClient ? <BrowserFeature /> : <Placeholder />
}
```

**Behavior:**
- Server/before hydration: `false`
- After hydration: `true`
- Inside `<ClientOnly enabled>`: always `true`

### useIsClientOnly

Checks if you're in a client-only context:

```tsx
import { useIsClientOnly } from '@tamagui/use-did-finish-ssr'

function MyComponent() {
  const isClientOnly = useIsClientOnly()

  if (isClientOnly) {
    // Safe to use browser APIs
    return <ComponentWithBrowserAPIs />
  }

  return <SSRSafeComponent />
}
```

### useClientValue

Returns `undefined` during SSR, your value after hydration:

```tsx
import { useClientValue } from '@tamagui/use-did-finish-ssr'

function WindowInfo() {
  const width = useClientValue(() => window.innerWidth)

  return <Text>{width ? `${width}px` : 'Loading...'}</Text>
}
```

## Best Practices

### When to Use disableSSR

**Use it for:**
- Single-page applications
- Client-only web apps
- Maximum performance without SSR overhead

**Avoid for:**
- SEO-critical content
- Server-rendered frameworks (Next.js SSR/SSG)
- Fast initial page loads

### When to Use ClientOnly

**Use it for:**
- Expensive client-only components (charts, editors)
- Browser API dependencies
- Non-critical UI that can load after initial render

**Example:**
```tsx
function ProductPage({ product }) {
  return (
    <YStack>
      {/* SEO-critical content */}
      <ProductHeader product={product} />

      {/* Defer complex UI */}
      <ClientOnly enabled>
        <InteractiveGallery />
      </ClientOnly>
    </YStack>
  )
}
```

### When to Use Hooks

- `useDidFinishSSR`: Conditional rendering based on hydration state
- `useIsClientOnly`: Checking client-only context for browser APIs
- `useClientValue`: Shorthand for client-only values

## Package Reference

```tsx
import {
  ClientOnly,        // Component for client-only subtrees
  useDidFinishSSR,   // Hook for hydration state
  useIsClientOnly,   // Hook for client-only context
  useClientValue,    // Hook for client-only values
} from '@tamagui/use-did-finish-ssr'
```

## See Also

- [Configuration](/docs/core/configuration) - Learn more about settings
- [useMedia](/docs/core/use-media) - Responsive media queries
- [Themes](/docs/intro/themes) - Theme system and context

## core/stack-and-text

---
title: View & Text
description: Your base components
---

View and Text are functionally equivalent to React Native `View` and `Text`, they just accept the superset of props that Tamagui supports.

### Props

See [the Props docs](/docs/intro/props) for the full list of properties View and Text accept.

### Usage

You can use them directly:

```tsx
import { View, Text } from 'tamagui' // or '@tamagui/core'

export default () => (
  <View margin={10}>
    <Text color="$color">Hello</Text>
  </View>
)
```

We encourage you to use inline styles. Combined with [shorthands](/docs/core/configuration#shorthands) they can lead to really easy styling, and the Tamagui optimizing compiler will take care of optimizing everything for you so that there is little to no extra cost in performance:

```tsx
import { View, Text } from 'tamagui' // or '@tamagui/core'

export default () => (
  <View mx="$sm" scale={1.2}>
    <Text c="$color">Hello</Text>
  </View>
)
```

<Notice theme="blue">
  One really important and useful thing to note about Tamagui style properties: the order
  is important! [Read more here](/docs/core/styled#order-is-important)
</Notice>

### With styled()

You can also use them [with styled](/docs/core/styled) to create your own lower level views that are meant to be re-usable:

```tsx
import { View, styled } from 'tamagui' // or '@tamagui/core'

export const Circle = styled(View, {
  borderRadius: 100_000_000,

  variants: {
    pin: {
      top: {
        position: 'absolute',
        top: 0,
      },
    },

    centered: {
      true: {
        alignItems: 'center',
        justifyContent: 'center',
      },
    },

    size: {
      '...size': (size, { tokens }) => {
        return {
          width: tokens.size[size] ?? size,
          height: tokens.size[size] ?? size,
        }
      },
    },
  } as const,
})
```

Inline styles and `styled()` both are optimized by the compiler, so you can author styles using both depending on the use case.


## core/styled

---
title: styled()
description: Extend and build custom and optimizable components
---

<Notice theme="green">
  If you're looking for a full list of style properties accepted by Tamagui, see
  the [Styles page](/docs/intro/styles).
</Notice>

Create a new component by extending an existing one:

```tsx
import { GetProps, View, styled } from '@tamagui/core'

export const RoundedSquare = styled(View, {
  borderRadius: 20,
})

// helper to get props for any TamaguiComponent
export type RoundedSquareProps = GetProps<typeof RoundedSquare>
```

Usage:

```tsx
<RoundedSquare x={10} y={10} backgroundColor="red" />
```

You can pass any prop that is supported by the component you are wrapping in styled.

One really important and useful thing to note about Tamagui style properties: the order is important! [Read more below](#order-is-important)

## Variants

Let's add some variants:

```tsx
import { View, styled } from '@tamagui/core'

export const RoundedSquare = styled(View, {
  borderRadius: 20,

  variants: {
    pin: {
      top: {
        position: 'absolute',
        top: 0,
      },
    },

    centered: {
      true: {
        alignItems: 'center',
        justifyContent: 'center',
      },
    },

    size: {
      '...size': (size, { tokens }) => {
        return {
          width: tokens.size[size] ?? size,
          height: tokens.size[size] ?? size,
        }
      },
    },
  } as const,
})
```

<Notice theme="blue">
  Please use `as const` for the variants definition until Typescript gains the
  ability to infer generics as const .
</Notice>

We can use these like so:

```tsx
<RoundedSquare pin="top" centered size="$lg" />
```

To learn more about how to use them and all the special types, [see the docs on variants](/docs/core/variants).

### Non-working React Native views

You can assume all "utility" views in React Native are not supported: Pressable, TouchableOpacity, and others. They have specific logic for handling events that conflicts with Tamagui. We could support these in the future, but we don't plan on it - you can get all of Pressable functionality for the most part within Tamagui itself, and if you need something outside of it, you can use Pressable directly.

## Using on the web

The `styled()` function supports Tamagui views, React Native views, and any other React component that accepts a `style` prop. If you wrap an external component that Tamagui doesn't recognize, Tamagui will assume it only supports the `style` prop and not optimize it.

If it does accept `className`, you can opt-in to className, CSS media queries, and compile-time optimization by adding `acceptsClassName`:

```tsx
import { SomeCustomComponent } from 'some-library'
import { styled } from 'tamagui' // or '@tamagui/core'

export const TamaguiCustomComponent = styled(SomeCustomComponent, {
  acceptsClassName: true,
})
```

## render

The `render` prop lets you control which element or component is rendered. It accepts three forms:

### String (HTML element)

Render as a specific HTML element on web while maintaining native View on mobile:

```tsx
const Button = styled(View, {
  render: 'button',
  padding: '$4',
  backgroundColor: '$background',
})

const Anchor = styled(Text, {
  render: 'a',
  color: '$blue10',
})

const Nav = styled(View, {
  render: 'nav',
})
```

This is the recommended approach for semantic HTML and accessibility. String render props are optimized by the Tamagui compiler.

### JSX Element

Pass a JSX element to clone with Tamagui's computed props:

```tsx
<Stack
  render={<a href="/about" target="_blank" />}
  padding="$4"
  backgroundColor="$background"
>
  About Page
</Stack>
```

The JSX element's props are merged with Tamagui's computed styles, classNames, and event handlers. This is useful when you need to pass element-specific props like `href` or `target`.

<Notice theme="yellow">
  JSX element render props are not optimized by the compiler - they will cause a deopt.
</Notice>

### Function

For full control, pass a render function that receives props and component state:

```tsx
import { TamaguiComponentState } from '@tamagui/core'

<Stack
  render={(props, state: TamaguiComponentState) => (
    <CustomButton
      {...props}
      isHovered={state.hover}
      isPressed={state.press}
    />
  )}
  padding="$4"
  hoverStyle={{ backgroundColor: '$blue5' }}
>
  Custom Button
</Stack>
```

The state object includes:
- `hover` - true when hovered (web)
- `press` - true when pressed
- `focus` - true when focused
- `disabled` - true when disabled

<Notice theme="yellow">
  Function render props are not optimized by the compiler - they will cause a deopt.
</Notice>

### Runtime override

You can also override the render prop at runtime:

```tsx
const Box = styled(View, {
  padding: '$4',
})

// Use as a button
<Box render="button">Click me</Box>

// Use as a link
<Box render="a" href="/about">About</Box>
```

## styleable

Any component created with `styled()` has a new static property on it called `.styleable()`.

If you want a functional component that renders a Tamagui-styled component inside of it to *also* be able to be `styled()`, you need to wrap it with `styleable`. This is a mouthful, let's see an example:

```tsx
// 1. you create a `styled` component as usual:
const StyledText = styled(Text)

// 2. you create a wrapper component that adds some logic
//    but still returns a styled component that receives the props:
const HigherOrderStyledText = (props) => <StyledText {...props} />

// 3. you want that wrapper component itself to be able to use with `styled`:
const StyledHigherOrderStyledText = styled(HigherOrderStyledText, {
  variants: {
    // oops, variants will merge incorrectly
  },
})
```

The above code will generally cause weird issues, because Tamagui can't know that it needs to just forward some props down. Instead, Tamagui tries to "resolve" all the style props from `StyledHigherOrderStyledText` before passing them down to `HigherOrderStyledText`. But that causes problems, because now `HigherOrderStyledText` will merge things differently than you'd expect.

The way to fix this is to add a `.styleable` around your `HigherOrderStyledText`. You'll also want to forward the ref, which is forwarded for you:

```tsx
const StyledText = styled(Text)

// note the styleable wrapper here:
const HigherOrderStyledText = StyledText.styleable((props, ref) => (
  <StyledText ref={ref} {...props} />
))

const StyledHigherOrderStyledText = styled(HigherOrderStyledText, {
  variants: {
    // variants now merge correctly
  },
})
```

Now your component will handle everything properly, even if a theme is changed on `HigherOrderStyledText`, it will be applied.

A final note: you must pass all Tamagui style props given to `HigherOrderStyledText` down to a single `StyledText`, at least if you want everything to work fully correctly.

And if you'd like to add new props on top of the existing props, you can pass them in for the first generic type argument of styleable:

```tsx
import { View, ViewProps } from '@tamagui/core'

type ExtraProps = {
  someCustomProp: boolean
}

export type CustomProps = ViewProps & ExtraProps

const Custom = View.styleable<ExtraProps>((props) => {
  // ...
  return null
})
```

### createStyledContext

When building a "Compound Component API", you need a way to pass properties down to multiple related components at once.

What is a Compound Component API? It looks like this:

```tsx
export default () => (
  <Button size="$large">
    <Button.Icon>
      <Icon />
    </Button.Icon>
    <Button.Text>Lorem ipsum</Button.Text>
  </Button>
)
```

Note how the `size="$large"` is set on the outer Button frame. We'd expect this size property to pass down to both the Icon and Text so that our frame size always matches the icon and text size. It would be cumbersome and bug-prone to have to always pass the size to every sub-component.

Tamagui solves this with `createStyledContext` which acts much like React `createContext`, except it only works with styled components and only controls their variants (for now, we're exploring if it can do more).

You can set it up as follows:

```tsx
import {
  SizeTokens,
  View,
  Text,
  createStyledContext,
  styled,
  withStaticProperties,
} from '@tamagui/core'

export const ButtonContext = createStyledContext<{
  size: SizeTokens
}>({
  size: '$medium',
})

export const ButtonFrame = styled(View, {
  name: 'Button',
  context: ButtonContext,

  variants: {
    size: {
      '...size': (name, { tokens }) => {
        return {
          height: tokens.size[name],
          borderRadius: tokens.radius[name],
          gap: tokens.space[name].val * 0.2,
        }
      },
    },
  } as const,

  defaultVariants: {
    size: '$medium',
  },
})

export const ButtonText = styled(Text, {
  name: 'ButtonText',
  context: ButtonContext,

  variants: {
    size: {
      '...fontSize': (name, { font }) => ({
        fontSize: font?.size[name],
      }),
    },
  } as const,
})

export const Button = withStaticProperties(ButtonFrame, {
  Props: ButtonContext.Provider,
  Text: ButtonText,
})
```

A few things to note here:

- ButtonContext should only be typed and given properties that work across both components. Since they both define a `size` variant, this works.
- But note that one defines `...size` while the other defines `...fontSize`. This works in this case only if your design system has consistent naming for token sizes across `size` and `fontSize` (and is why we highly recommend this pattern).
- You can use `<Button.Props size="$large"><Button /></Button.Props>` now to set default props for a Button from above.
- As of today, using `context` pattern does not work with the optimizing compiler flattening functionality. So we recommend not using this for your most common components like Stacks or Text. But for Button or anything higher level it's totally fine - it will still extract CSS and remove some logic from the render function. We've mapped out how this can work with flattening eventually and it shouldn't be too much effort.

### Order is important

Finally, it's important to note that the order of style properties is significant. This is really important for two reasons:

1. You want to control which styles are overridden.
2. You have a variant that expands into multiple style properties, and you need to control it.

Lets see how it lets us control overriding styles:

```tsx
import { View, ViewProps } from '@tamagui/core'

export default (props: ViewProps) => (
  <View
    background="red"
    {...props}
    width={200}
  />
)
```

In this case we set a default `background` to red, but it can be overridden by props. But we set `width` _after_ the prop spread, so width is _always_ going to be set to 200.

It also is necessary for variants to make sense. Say we have a variant `huge` that sets `scale` to 2 and `borderRadius` to 100:

```tsx
// this will be scale = 3
export default (props: ViewProps) => (
  <MyView
    huge
    scale={3}
  />
)

// this will be scale = 2
export default (props: ViewProps) => (
  <MyView
    scale={3}
    huge
  />
)
```

If order wasn't important, how would you expect these two different usages to work? You'd have to make order important _somewhere_. If you do it in the `styled()` helper somewhere, you end up having no flexibility and would end up with boilerplate. Making the prop order important gives us maximum expressiveness and is easy to understand.

---

## Advanced

You can skip this section unless you're building out very rich components that are nested multiple levels and need variants at each level.

### Custom props that accepts tokens with `accept`

If you are wrapping something like an SVG, you may want it to accept theme and token values on certain props, for example `fill`. You can do so using `accept`:

```tsx
const StyledSVG = styled(SVG, {}, {
  accept: {
    fill: 'color'
  } as const
})
```

Note the `as const`, until we can drop TypeScript 4 support. Now, your StyledSVG will properly type the `fill` property to accept token and theme values and will pass the resolved colors to the SVG component.

You can also use `accept` to take in Tamagui style objects and output React Native style objects. This is useful for things like the `contentContainerStyle` prop on `ScrollView`, which expects a style object:

```tsx
const MyScrollView = styled(ScrollView, {}, {
  accept: {
    contentContainerStyle: 'style' // or 'textStyle'
  } as const
})
```


## core/theme

---
title: Theme
description: Change themes contextually
---

Changing themes in Tamagui is as easy as passing their name to the Theme
component.

### Usage

Change themes anywhere in your app like so:

```tsx
import { Square, Theme } from 'tamagui' // or '@tamagui/core'

export default () => (
  <Theme name="dark">
    <Button>I'm a dark button</Button>
    <Button theme="subtle">I'm using dark_subtle theme</Button>
  </Theme>
)
```

### Defining themes

Themes live one level below tokens. Tokens are your static variables, where
themes are more dynamic like CSS variables.

You can define themes however you want, but for the `tamagui` UI kit, the
components default styling will use a set of pre-defined keys. Those keys are:

- `background`, `backgroundHover`,`backgroundPress`, `backgroundFocus`
- `borderColor`, `borderColorHover`,`borderColorPress`, `borderColorFocus`
- `shadowColor`

These are optional, as you can always set `unstyled` on any Tamagui component to
use your own styles.

Here's a simplified theme definition:

```tsx
import { createTamagui, createTokens } from 'tamagui'

const tokens = createTokens({
  color: {
    pinkDark: '#610c62',
    pinkLight: '#f17efc',
  },
  // ... see configuration docs for required tokens
})

export default createTamagui({
  tokens,
  themes: {
    dark: {
      background: '#000',
      color: '#fff',
    },
    light: {
      color: '#000',
      background: '#fff',
    },
    dark_pink: {
      background: tokens.color.pinkDark,
      color: tokens.color.pinkLight,
    },
    light_pink: {
      background: tokens.color.pinkLight,
      color: tokens.color.pinkDark,
    },
  },
})
```

Passing tokens to themes will reduce CSS, but is not requred.

You can then access theme values for any style value, either through styled() or
through a Styled Compoent like View or Text:

```tsx
const P = styled(Text, {
  color: '$color12'
})

// or directly
<Text color="$color11" />
```

Because we defined sub-themes `light_pink` and `dark_pink`, the `Theme`
component will now let us do this:

```tsx
import { Button, Theme } from 'tamagui'

export default () => {
  return (
    <Theme name="dark">
      <Button>I have the theme dark</Button>
      <Theme name="pink">
        <Button>I have the theme pink_dark</Button>
      </Theme>
    </Theme>
  )
}
```

Notice we just use the name `pink`, easy!

For more on themes see our [advanced theme guide](/docs/guides/theme-builder)
which also introduces the `createThemes` and `createThemeBuilder`, both of which
help generate larger and richer, typed themes more easily.


## core/tokens

---
title: Tokens
description: Accessing and using tokens
---

Tamagui lets you create tokens using `createTokens`, which is then passed to the `createTamagui` function as part of the [configuration](/docs/core/configuration) object.

### Getting tokens

For example if you define some tokens:

```tsx
const tokens = createTokens({
  size: {
    small: 10
  }
})
```

After you pass that into `createTamagui`, you can access your tokens from anywhere using `getTokens`:

```tsx
import { getTokens } from '@tamagui/core'

getTokens().size.small
```

or

```tsx
getTokens().size['$small']
```

If you'd like just an object containing the prefixed (starting with `$`) or non-prefixed values, you can use the `prefixed` option:

```tsx
// only non-$
getTokens({ prefixed: false }).size.small
// only $
getTokens({ prefixed: true }).['$size'].small
```

What is returned is of type `Variable`, which is what Tamagui turns all tokens and theme values into internally in order to give them CSS variable names, as well as other things:

```tsx
getTokens().size.small.val // returns 10
getTokens().size.small.variable // returns something like (--size-small), which matches the CSS rule inserted
```

Tamagui has some helpers that make working with variables easier, which are documented in [Exports](/docs/core/exports), namely [`getVariable`](/docs/core/exports#getvariable) which will return the CSS variable on web, but raw value on native, and `getVariableValue` which always returns the raw value.

#### Color tokens as fallback values for themes

Color tokens are available as fallback values when you access a theme. So when you `useTheme()` and then access a value that isn't in the theme, it will check for a `tokens.color` with the matching name.

Think of it this way:

- Tokens are static and are your base values.
- Themes are dynamic, they can change in your React tree, and live above tokens.

If you are confused by Tamagui themes, don't be. You can avoid them altogether, or avoid learning them until later. Instead, you can just build your app using regular style props and leave out themes altogether. Or, simply use a `light` and a `dark` theme if you want light and dark mode in your app, but avoid using any nested themes.

### Using tokens with components

When using `styled` or any Tamagui component like `Stack`, you can access tokens directly. Just like with `useTheme`, it will first look for a theme value that matches, and if not it will fall back to a token.

Tokens are automatically applied to certain properties. For example, `size` tokens are applied to width and height. And of course `radius` to borderRadius.

Here's how they all apply:

<PropsTable
  title="How tokens apply to attributes"
  data={[
    {
      name: 'Size',
      description: 'width, height, minWidth, minHeight, maxWidth, maxHeight',
    },
    {
      name: 'zIndex',
      description: 'zIndex',
    },
    {
      name: 'Radius',
      description:
        'borderRadius, borderTopLeftRadius, borderTopRightRadius, borderBottomLeftRadius, borderBottomRightRadius',
    },
    {
      name: 'Color',
      description:
        'color, backgroundColor, borderColor, borderBottomColor, borderTopColor, borderLeftColor, borderRightColor',
    },
    {
      name: 'Space',
      description: 'All properties not matched by the above.',
    },
  ]}
/>

### Specific tokens

As of version 1.34, you can also define any custom token values you'd like:

```tsx
const tokens = createTokens({
  // ...other tokens
  icon: {
    small: 16,
    medium: 24,
    large: 32,
  },
})
```

And then access them using the new "specific tokens" syntax:

```tsx
export default () => (
  <Stack
    // access with the category first:
    width="$icon.small"
  />
)
```

This, like all token values, works the same with `styled`:

```tsx
import { styled, View } from '@tamagui/core'

export const MyView = styled(View, {
  width: '$icon.small'
})
```

When creating custom tokens, you can use the `px` helper to ensure values get proper pixel units on web while remaining as raw numbers on native:

```tsx
import { createTokens, px } from '@tamagui/core'

const tokens = createTokens({
  customSize: {
    small: px(100),   // → "100px" on web, 100 on native
    medium: px(200),
    large: px(300),
  },
  opacity: {
    low: 0.25,        // → 0.25 (unitless on both platforms)
    medium: 0.5,
    high: 0.75,
  },
})
```

<Notice theme="blue">
  The predefined token categories like `size`, `space`, and `radius` automatically add pixel units where appropriate, so you don't need to use the `px` helper for them.
</Notice>

## core/use-media

---
title: useMedia
description: Respond to different screen sizes
---

Define your media rules in the media object of your `tamagui.config.ts`:

```tsx line=2-6
export default createTamagui({
  media: {
    xs: { maxWidth: 660 },
    gtXs: { minWidth: 660 + 1 },
    sm: { maxWidth: 860 },
    gtSm: { minWidth: 860 + 1 },
    md: { maxWidth: 980 },
    gtMd: { minWidth: 980 + 1 },
    lg: { maxWidth: 1120 },
    gtLg: { minWidth: 1120 + 1 },
    short: { maxHeight: 820 },
    tall: { minHeight: 820 },
    hoverNone: { hover: 'none' },
    pointerCoarse: { pointer: 'coarse' },
  },
})
```

<Notice theme="blue">
  Choose the naming convention you prefer. We use `sm`, `md`, `lg` and `gtSm` etc. Where "gt" means "greater than".
</Notice>

The order here is important: items defined further down will override items before. In this example, `xs` is the weakest, `gtXs` will override it, and so on.

Behind the scenes, we convert this object syntax into media query syntax with a simple loop over your object, turning camelCase into hyphen-case, and adding `@media()` around it.

### Usage

Now in any component you may import and use `useMedia` or `$` prefixed media prop styles.

### Inline props

```tsx
import { Button, XStack, useMedia } from 'tamagui' // note: design system can use @tamagui/core

export default () => {
  const [x, setX] = useState(0)
  return (
    <XStack
      backgroundColor="red"
      $gtSm={{
        backgroundColor: 'blue',
      }}
      $gtMd={{
        backgroundColor: x > 0.5 ? 'green' : 'yellow',
      }}
    >
      <Button onPress={() => setX(Math.random())}>Hello</Button>
    </XStack>
  )
}
```

In this example we are doing mobile-first design, where the base props will be overridden as the viewport gets wider. Notice the ternary logic in `$gtMd`: this is extractable with Tamagui, and will still output simple CSS with no leftover style props in your component's render function.

### Hooks

```tsx
import { Button, XStack, useMedia } from 'tamagui' // note: design system can use @tamagui/core

export default () => {
  const media = useMedia()

  return (
    <XStack
      // can be used as a ternary
      backgroundColor={media.sm ? 'red' : 'blue'}
      // can be used as a spread
      {...(media.lg && {
        x: 10,
        y: 10,
      })}
    >
      <Button>Hello</Button>
    </XStack>
  )
}
```

As long as **all** of your usages of useMedia are extractable, Tamagui will actually generate your CSS and then fully remove the hook from the output code. You can check this by adding `// debug` to the top of your component.

Of course, you may sometimes use `useMedia` for other purposes than styling. In fact, that's the nice part about Tamagui - if it can't extract, it always falls back to runtime gracefully.

Tamagui runtime `useMedia` usage will track which keys are accessed, and only update if the media query matching that key updates. This granular updating is nice for performance.

#### Limitations

The `useMedia` hook uses proxies so it can track which keys you are accessing and only re-render components that need re-rendering based on those keys. The Tamagui compiler also understands straightforward uses of the hook during compile-time and can remove it entirely when targeting the web, in favor of CSS.

- In order to keep things lightweight and simple, the proxied object returned by `useMedia` is not iterable, and you can't check keys on it using `in` or get them using `Object.keys`.
- It's encouraged to write `const media = useMedia()` and then access your keys like `media.sm` to work best with the compiler. The compiler supports de-structuring, but not re-naming.


## core/use-theme

---
title: useTheme
description: Creating and using theme values
---

Access the current theme in context with `useTheme`. Tamagui themes operate much
the same as
[CSS variables](https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties)
do, and so
[they can nest and override each other contextually](/docs/intro/themes).

Note that the object that useTheme returns is the current theme, proxied upwards
to every parent theme, finally proxying back to your tokens. This means it
behaves just like CSS variables as well, at runtime.

A short example:

```tsx
import { YStack, useTheme } from 'tamagui'

const App = () => {
  const theme = useTheme()

  return <YStack backgroundColor={theme.color1.val} />
}
```

The `useTheme` hook returns your Theme turned into a `ThemeParsed`, which means
it turns all values into a `Variable`:

```tsx
{
  background: {
    val: '#000',
    variable: 'var(--background)',
    name: 'background',
    isVar: true,
  },
  color: {
    val: '#fff',
    variable: 'var(--color)',
    name: 'color',
    isVar: true,
  },
}
```

The `useTheme` hook further adds a `.get()` helper function on each Variable for
extra performance. On the web, this will return the `.variable` property and
avoid re-rendering if values change, as it assumes you are using the CSS
variables it generates to update styles. Meanwhile on native it will return
`.val` and re-render always. But - if you enable the
[`fastSchemeChange` setting on createTamagui](/docs/core/configuration#settings)
then `get()` will return a
[DynamicColorIOS](https://reactnative.dev/docs/dynamiccolorios) on iOS and avoid
re-rendering there too - unless you do `get('web')` in which case it will only
optimize for web.

```tsx
import { View, useTheme } from '@tamagui/core'
import { SomeExternalComponent } from 'some-external-component'

const App = () => {
  const theme = useTheme()

  // on the web this is something like var(--background) and will avoid re-renders
  // on native it will be something like #fff and will re-render
  const background = theme.background.get()

  // if you needed to access it in a way that always returns the raw value
  const backgroundValue = theme.background.val

  return (
    <SomeExternalComponent
      style={{
        backgroundColor: background,
      }}
    />
  )
}
```

You can mix and match `useMedia` and `useTheme`, and the compiler understands
most basic usages, even with nested logic or constants within the file or
imports [white-listed in your build setup](/docs/compiler/install):

```tsx
import { YStack, useMedia, useTheme } from 'tamagui'

const App = () => {
  const theme = useTheme()
  const media = useMedia()

  return (
    <YStack
      y={media.sm ? 10 : 0}
      backgroundColor={media.lg ? theme.red : theme.blue}
      {...(media.xl && {
        y: theme.space2,
      })}
    />
  )
}
```

This will compile on the web to:

```tsx
const _cn =
  ' _alignItems-1oszu61 _boxSizing-deolkf _display-6koalj _flexBasis-1mlwlqe _flexDirection-eqz5dr _flexShrink-1q142lx _transform-_sm_1exagq _transform-_sm0_1wpzndr _backgroundColor-_lg_no4z4g _backgroundColor-_lg0_1qoifqd _transform-_xl_gqa6p0'

import { YStack, useMedia, useTheme } from 'tamagui'

const App = () => {
  return <div className={_cn} />
}
```

And the following CSS:

```css
._alignItems-1oszu61 {
  -ms-flex-align: stretch;
  -webkit-align-items: stretch;
  -webkit-box-align: stretch;
  align-items: stretch;
}
._boxSizing-deolkf {
  box-sizing: border-box;
}
._display-6koalj {
  display: -webkit-box;
  display: -moz-box;
  display: -ms-flexbox;
  display: -webkit-flex;
  display: flex;
}
._flexBasis-1mlwlqe {
  -ms-flex-preferred-size: auto;
  -webkit-flex-basis: auto;
  flex-basis: auto;
}
._flexDirection-eqz5dr {
  -ms-flex-direction: column;
  -webkit-box-direction: normal;
  -webkit-box-orient: vertical;
  -webkit-flex-direction: column;
  flex-direction: column;
}
._flexShrink-1q142lx {
  -ms-flex-negative: 0;
  -webkit-flex-shrink: 0;
  flex-shrink: 0;
}
@media (max-width: 860px) {
  :root:root ._transform-_sm_1exagq {
    -webkit-transform: translateY(10px);
    transform: translateY(10px);
  }
}
@media not all and (max-width: 860px) {
  :root:root ._transform-_sm0_1wpzndr {
    -webkit-transform: translateY(0px);
    transform: translateY(0px);
  }
}
@media (min-width: 1120px) {
  :root:root:root ._backgroundColor-_lg_no4z4g {
    background-color: var(--red);
  }
}
@media not all and (min-width: 1120px) {
  :root:root:root ._backgroundColor-_lg0_1qoifqd {
    background-color: var(--blue);
  }
}
@media (min-width: 1280px) {
  :root:root:root:root ._transform-_xl_gqa6p0 {
    -webkit-transform: translateY(var(--space2));
    transform: translateY(var(--space2));
  }
}
```

## Using outside of styling

You can `useTheme()` (and `useMedia()`) at runtime. Like useMedia, useTheme will
only re-render when it has to, and often you can skip re-renders altogether by
either passing the values to a Tamagui styled component, or by using the helper
`getVariable`:

### getVariable

If you access `theme.bg.val` in your render function, the component will only
re-render when theme.bg changes.

```tsx
import { theme, View } from '@tamagui/core'

export default () => {
  const theme = useTheme()

  // access the value
  console.log(theme.bg.val)

  return (
    <View backgroundColor={theme.color1} />
  )
}
```

## Changing the theme at the hook level

This is a more advanced use for building custom hooks or components, but you can
pass in a theme name and a component theme name (as
[discussed here](/docs/intro/themes#component-subset-themes)) to grab the right
subset theme:

```tsx
function MyComponent(props) {
  const theme = useTheme(props.theme, 'MyComponent')
}
```

For example, if you have the following themes:

- `dark`
- `dark_green`
- `dark_green_MyComponent`

And this code:

```tsx
<Theme name="dark">
  <MyComponent theme="green" />
</Theme>
```

The above `useTheme` hook would return the `dark_green_MyComponent` theme.


## core/variants

---
title: Variants
description: Simple typed prop styles through styled()
---

Variants allow for a nice balance between simplicity and power, with affordances
for both the compiler and the type system.

In a style library you want to be able to succinctly add on conditional values
that expand into groups of styles. Variants do just that. Before explaining more
on why and how they work, an example:

```tsx
import { View, styled } from 'tamagui' // or '@tamagui/core'

export const Circle = styled(View, {
  borderRadius: 100_000_000,

  variants: {
    pin: {
      top: {
        position: 'absolute',
        top: 0,
      },
    },

    centered: {
      true: {
        alignItems: 'center',
        justifyContent: 'center',
      },
    },

    size: {
      '...size': (size, { tokens }) => {
        return {
          width: tokens.size[size] ?? size,
          height: tokens.size[size] ?? size,
        }
      },
    },
  } as const,
})
```

<Notice theme="blue">
  Notice the `as const` on the variant definition object. This is necessary to
  please TypeScript until it gains the ability to infer constant objects. If
  left out, your types may break.
</Notice>

We can use this styled component like so:

```tsx
<Circle pin="top" centered size="$lg" />
```

This component uses a few different types of variants. Below we'll expand on
each.

But first, why variants?

## Why Variants?

You have two basic options for sharing styles across components: share style
objects and then combine them directly into your render function, or allow
abstraction in some form.

You can think of `StyleSheet.create` as "a lot of style objects". While this is
nice in that it's simple, it doesn't enforce any rules, which can get you in
trouble with both a compiler and a type system.

You end up basically typing props by hand and then doing somewhat arbitrary
logic to glue it all together inside a functional component. The types may not
map to the actual output, and a compiler will almost certainly get confused or
be unable to optimize with any easily unforeseen abstraction.

Variants force you out of the React render function, which means no hooks and a
much clearer contract of limitations: at most you take in a value, your design
system, and props, and you output a group of styles.

It's nice having this special area just for styling. It keeps your types correct
by definition. And it ensures the optimizing compiler can understand your styled
components and people on your team won't "de-opt" it on accident.

And because Variants work with the styled function, they nest without adding an
extra depth to your render tree. Doing `styled(styled())` results in a single
React component, and can be optimized and flattened by the compiler as well.
Whereas it's quite easy for developers to take an existing functional component
and throw in a new one around it, further de-optimizing a compiler and leading
to a less clear separation of styled components and regular ones.

## Variants

### Typed Variants

#### `true` or `false`

The special keys `true` and `false` will map to a boolean. So the `centered`
prop will be typed to accept true or false, and when true it will apply its
styles.

```tsx
import { View, styled } from 'tamagui' // or '@tamagui/core'

export const MyView = styled(View, {
  variants: {
    selectable: {
      true: {
        userSelect: 'auto',
      },
      false: {
        userSelect: 'none',
      },
    },
  } as const,
})
```

#### String, Boolean, Number Variants

You can use a pseudo Typescript syntax for other variants:

- `:string` - Accepts a `string`
- `:boolean` - Accepts a `boolean` (less precedence than `true` or `false`)
- `:number` - Accepts a `number`

```tsx
import { View, styled } from 'tamagui' // or '@tamagui/core'

export const ColorfulView = styled(View, {
  variants: {
    color: {
      ':string': (color) => {
        // color is of type "string"
        return {
          color,
          borderColor: color,
        }
      },
    },
  } as const,
})
```

### Spread Variants

When you write variants, you have to be explicit so TypeScript and the runtime
know exactly which props you accept. This can be especially cumbersome when you
want to "gather" all the values of a specific token. For example, without spread
variants, if you wanted to have a `pad` property that accepted all the keys from
`tokens.size`, you'd have to write this:

```tsx
// in your tamagui.config.ts:
const tokens = createTokens({
  size: {
    sm: 10,
    md: 15,
    lg: 25,
    // ...
  }
  // ... see configuration docs for required tokens
})

export default createTamagui({
  tokens
})

// somewhere in your app:
const MyButton = styled(View, {
  variants: {
    pad: {
      sm: {
        padding: tokens.size.sm,
      },
      md: {
        padding: tokens.size.md,
      },
      lg: {
        padding: tokens.size.lg,
      },
      // ...
    }
  } as const
})

// now you can
<MyButton pad="$lg" />
```

This is verbose, and only gets more verbose if you add more sizes. It would
require always updating every component every time you change the tokens.

Spread variants solve this problem. Instead, we can write:

```tsx
// in your tamagui.config.ts:
const tokens = createTokens({
  size: {
    sm: 10,
    md: 15,
    lg: 25,
    // ...
  }
  // ... see configuration docs for required tokens
})

export default createTamagui({
  tokens
})

// somewhere in your app:
const MyButton = styled(View, {
  variants: {
    pad: {
      '...size': (val, { tokens }) => ({
        padding: tokens.size[val]
      }),
    }
  } as const
})

// now you can
<MyButton pad="$lg" />
```

Spread variants save you from having to define hardcoded styles for every key
(`sm`, `md`, `lg`) in your token object. They collect values from any of your
top level token categories. So you can only use `...color`, `...size`,
`...space`, `...font`, `...fontSize`, `...lineHeight`, `...radius`,
`...letterSpace`, or `...zIndex`. They must be prefixed with `...` as that is
how they are typed properly and assembled for runtime.

#### Extra properties passed to functional variants

There's a second argument passed to all variant functions that is a
bag-o-goodies that help you use the current tokens, theme, props, and fonts
easily.

```tsx
const SizableText = styled(Text, {
  variants: {
    size: {
      '...size': (size, { tokens, font }) => {
        return {
          fontSize: font?.size,
          lineHeight: font?.lineHeight,
          height: tokens.size[size] ?? size,
        }
      },
    },
  } as const,
})
```

Which you can use:

```tsx
<SizableText size="$md">Hello world</SizableText>
```

The Spread variant function will receive two arguments: the first is the value
given to the property (`"$lg"`), and the second is an object with
`{ theme, tokens, props, font, context }`.

<PropsTable
  data={[
    {
      name: 'theme',
      type: 'ThemeParsed',
      description: `A proxy to your theme that lets you access all theme values using normal keys, or with a "$" prefix.`,
    },
    {
      name: 'tokens',
      type: 'TokensParsed',
      description: `All your tokens given to createTamagui, ensuring the keys all start with "$".`,
    },
    {
      name: 'props',
      type: 'Props',
      description: `All props passed to the styled component.`,
    },
    {
      name: 'font',
      type: 'Font',
      description: `Maybe undefined. A single resolved Font that you passed to you fonts config.`,
    },
    {
      name: 'fontFamily',
      type: 'string',
      description: `Maybe undefined. The name of the current fontFamily.`,
    },
    {
      name: 'fonts',
      type: 'string',
      description: `All your fonts passed to createTamagui.`,
    },
    {
      name: 'context',
      type: 'StyledContext',
      description: `The styledContext values from the parent component, useful for compound components.`,
    },
  ]}
/>

#### Catch-all variants

Much like a dynamic variant, except it lets you use it alongside the other typed
variants you need. Use '...' and it will grab all variants that don't match:

```tsx
import { View, styled } from 'tamagui' // or '@tamagui/core'

export const ColorfulView = styled(View, {
  variants: {
    colorful: {
      true: {
        color: 'red',
      },
      '...': (val: string) => {
        // this will catch any other values that don't match
        return {
          color: val,
        }
      },
    },
  } as const,
})
```

#### Dynamic variants

If you need more complex types, or simply prefer a shorter syntax, you can use a
single function instead of using the object syntax for variants:

```tsx
import { View, styled } from 'tamagui' // or '@tamagui/core'

export const MyView = styled(View, {
  variants: {
    doubleMargin: (val: number) => ({
      margin: val * 2,
    }),
  } as const,
})
```

Tamagui also provides a few types of other variant definition patterns that work
with tokens or types.

### defaultVariants

Sometimes you'd like to set a default value for a variant you've just set on
your styled() component. Due to the way Typescript types parse from left to
right, we can't properly type variants directly on the object you define them
on.

The `defaultVariants` option allows you to set these, properly typed:

```tsx
const Square = styled(View, {
  variants: {
    size: {
      '...size': (size, { tokens }) => {
        // size === '$lg'
        // tokens.size.$lg === 25
        return {
          width: tokens.size[size] ?? size,
          height: tokens.size[size] ?? size,
        }
      },
    },
  } as const,

  // <Square /> will get size '$10' from size tokens automatically
  defaultVariants: {
    size: '$10',
  },
})
```

### Variants and Pseudos, Media Queries

Variants have the full power of the Tamagui styling system, including pseudo and
media styles:

```tsx
const SizedText = styled(Text, {
  variants: {
    size: {
      md: {
        fontSize: '$sm',

        $gtMd: {
          fontSize: '$md',
        },

        $gt2xl: {
          fontSize: '$lg',
        },
      },
    },
  } as const,
})
```

### Variants and Parent Variants

Styled components can access their parent components variants, even in their
variants:

```tsx
const ColorfulText = styled(Text, {
  variants: {
    colored: {
      true: {
        color: '$color',
      },
    },

    large: {
      true: {
        fontSize: '$8',
      },
    },
  } as const,
})

const MyParagraph = styled(ColorfulText, {
  colored: true,

  variants: {
    hero: {
      true: {
        large: true,
      },
    },
  } as const,
})
```


## guides/cli

---
title: Tamagui CLI
description: Command-line tools for building, checking, and managing Tamagui projects
---

<IntroParagraph>
  The Tamagui CLI provides a suite of command-line tools for optimizing components,
  managing dependencies, adding fonts and icons, and more.
</IntroParagraph>

<Notice theme="blue">
  **New to Tamagui?** We recommend skipping this section if it's your first time setting up Tamagui. Everything in the CLI is optional and can be learned later.
</Notice>

## Installation

Install the CLI as a development dependency:

```bash
yarn add -D @tamagui/cli
```

## Commands

### build

Pre-compile Tamagui components in-place for production builds. This is useful for bundlers that don't have a Tamagui plugin yet (like Turbopack) or when you want a simple setup that works with any bundler.

```bash
# Build all components in a directory (web + native by default)
npx tamagui build ./src

# Build for web only
npx tamagui build --target web ./src

# Build for native only
npx tamagui build --target native ./src

# Build a specific file
npx tamagui build ./src/components/MyComponent.tsx

# Include/exclude patterns
npx tamagui build --include "components/**" --exclude "**/*.test.tsx" ./src
```

**Flags:**

- `--target <platform>` - Target platform: `web`, `native`, or `both` (default: `both`)
- `--include <pattern>` - Glob pattern to include files
- `--exclude <pattern>` - Glob pattern to exclude files
- `--expect-optimizations <number>` - Fail if fewer than this minimum number of components are optimized (useful for CI)
- `--debug` - Enable debug output
- `--verbose` - Enable verbose debug output

**Platform-Specific File Handling:**

The CLI automatically handles platform-specific files:

- Files with `.web.tsx` or `.ios.tsx` extensions are optimized for web only
- Files with `.native.tsx` or `.android.tsx` extensions are optimized for native only
- Base files (`.tsx`) without platform-specific versions are optimized for all platforms
- If both `.web.tsx` and `.native.tsx` exist, the base `.tsx` file is skipped

**Configuration:**

Create a `tamagui.build.ts` config file in your project root:

```ts
import type { TamaguiBuildOptions } from 'tamagui'

export default {
  config: './tamagui.config.ts',
  components: ['tamagui'],
  importsWhitelist: ['constants.js', 'colors.js'],
  outputCSS: './public/tamagui.css',
} satisfies TamaguiBuildOptions
```

**Integration Examples:**

The CLI can wrap your build command using `--`, which optimizes files beforehand and automatically restores them after:

```json
{
  "scripts": {
    "build": "tamagui build --target web ./src -- next build"
  }
}
```

The `--` separator tells the CLI to run `next build` after optimization, then restore your source files automatically. This is the recommended approach as it keeps your source files unchanged.

Alternatively, run `tamagui build` separately (files will remain modified):

```json
{
  "scripts": {
    "build": "tamagui build --target web ./src && next build"
  }
}
```

**Important:** Without the `--` wrapper, files are modified in-place and not restored. Only use this approach if you're building in a CI environment where the files are discarded anyway.

For more details, see the [Compiler Installation guide](/docs/intro/compiler-install#cli-based-in-place-compilation).

### check

Checks your dependencies for inconsistent versions across your project. This helps identify version mismatches that can cause issues, especially in monorepos.

```bash
npx tamagui check
```

**Flags:**

- `--debug` - Enable debug output
- `--verbose` - Enable verbose debug output

**Example output:**

The command will scan your project and report any packages with mismatched versions, helping you maintain consistency across your dependencies.

### generate

Builds your entire Tamagui configuration and outputs any CSS. This is useful for pre-generating your design system's CSS and validating your configuration.

```bash
npx tamagui generate
```

**Flags:**

- `--debug` - Enable debug output
- `--verbose` - Enable verbose debug output

**What it does:**

1. Loads and validates your Tamagui configuration
2. Generates CSS for all your tokens, themes, and components
3. Outputs to the `.tamagui` directory
4. Also generates an LLM-friendly prompt file at `.tamagui/prompt.md`

### generate-themes

Pre-builds your theme configuration for faster runtime performance. This generates optimized theme objects from your theme definitions.

```bash
npx tamagui generate-themes <input-path> <output-path>
```

**Example:**

```bash
npx tamagui generate-themes ./themes/input.ts ./themes/generated.ts
```

**Flags:**

- `--debug` - Enable debug output
- `--verbose` - Enable verbose debug output

**Use case:** If you have complex theme generation logic, this command pre-computes your themes at build time rather than runtime.

### add

<Notice theme="green">
  **Tamagui Pro** - This command is available exclusively to Tamagui Pro members.
</Notice>

Add pre-configured fonts and icons from Tamagui's curated collections. This includes Google Fonts and Iconify icon packs.

```bash
# Add a font
npx tamagui add font [packages-path]

# Add an icon pack
npx tamagui add icon [packages-path]
```

**Flags:**

- `--debug` - Enable debug output
- `--verbose` - Enable verbose debug output

**Interactive selection:**

The command will present an interactive menu where you can search and select from available fonts or icons:

- **Fonts**: Browse Google Fonts with weight, style, and subset information
- **Icons**: Browse Iconify collections with icon counts and license information

**Default path:** If you don't provide a path, packages are installed to `./packages` in your current directory.

**Requirements:** This feature requires [Tamagui Pro](https://tamagui.dev/takeout) membership for access to the font and icon repositories.

### generate-prompt

Generate an LLM-friendly markdown file from your Tamagui configuration. This creates comprehensive documentation of your design system that can be used with AI assistants.

```bash
npx tamagui generate-prompt
```

**Flags:**

- `--output <path>` - Custom output path (default: `.tamagui/prompt.md`)
- `--debug` - Enable debug output

**What it includes:**

- All your tokens (colors, sizes, space, etc.)
- Theme definitions and variants
- Component configurations
- Font families and configurations
- Media queries and breakpoints

**Use case:** Share this file with AI assistants like Claude or ChatGPT to get better suggestions that align with your design system.

## Global Flags

All commands support these flags:

- `--help` - Show help for the command
- `--version` - Show CLI version
- `--debug` - Enable debug output
- `--verbose` - Enable verbose debug output (more detailed than `--debug`)

## Examples

### Production Build Pipeline

```json
{
  "scripts": {
    "build": "tamagui check && tamagui build --target web ./src -- next build"
  }
}
```

### CI with Optimization Verification

```json
{
  "scripts": {
    "build": "tamagui build --target web --expect-optimizations 10 ./src -- next build"
  }
}
```

This fails the build if fewer than 10 components are optimized, helping catch configuration issues in CI.

### Cross-Platform Mobile App

```json
{
  "scripts": {
    "build:ios": "tamagui build --target native ./src -- eas build --platform ios",
    "build:android": "tamagui build --target native ./src -- eas build --platform android",
    "build:web": "tamagui build --target web ./src -- vite build"
  }
}
```

## Troubleshooting

### Build command fails with "cannot find module"

Make sure you have a `tamagui.build.ts` config file that correctly points to your configuration:

```ts
export default {
  config: './tamagui.config.ts', // Verify this path is correct
  components: ['tamagui'],
}
```

### Check command reports false positives

The check command is strict about version consistency. If you have a specific reason for version mismatches (like testing), you can document them in your README.

### Add command shows "Repository not found"

The `add` command requires Tamagui Takeout access. Visit [tamagui.dev/takeout](https://tamagui.dev/takeout) to learn more.


## guides/create-tamagui-app

---
title: Bootstrapping
description: Starter kits for React Native
---

Set up a starter repo to help learn about and/or bootstrap your tamagui app:

### Templates

There are four starter templates:

- Free - Expo, Expo Router, Solito and Next.js (with both `/pages` and `/app`
  dir).
- Learn - Client-only web app with Webpack or Vite. Useful to understand how to
  set up tamagui.config.ts.
- [Expo Router](/docs/guides/expo) - The Expo Router default starter with
  Tamagui added via `@tamagui/metro-plugin`.
- [Takeout](/takeout) - A more robust, paid and supported starter kit.

### Usage

```bash
npm create tamagui@latest
```

Check out
[the source of the templates](https://github.com/tamagui/starter-free).

A big shout out to [Fernando Rojo](https://twitter.com/fernandotherojo) for
creating [Solito](https://solito.dev), a great library for sharing all your
views between Expo and Next.js, and the bootstrap repo we borrowed from.

### Starting out

The starter-free app requires yarn to run.

To run the app:

```bash
cd myapp
yarn
yarn web # Web local dev
yarn native # Expo local dev (only for `starter-free`)
```

## 📦 Included packages (`starter-free`)

- `tamagui` for cross-platform views, themes and animations
- `solito` for cross-platform navigation
- `expo-router` for having same routing concepts between web and native
- Expo SDK 48
- Next.js 13

By integrating `Tamagui`, `expo-router`, `Solito`, and, `Next.js` you can enjoy
the power and simplicity of creating universal apps.

## 🗂 Folder layout

- `apps` entry points for each app

  - `expo`
  - `next`

- `packages` shared packages across apps
  - `app` you'll be importing most files from `app/`
    - `features` (don't use a `screens` folder. organize by feature.)
    - `provider` (all the providers that wrap the app, and some no-ops for Web.)
    - `navigation` only for starter-free template, contains the navigation code
      for RN

You can add other folders inside of `packages/` if you know what you're doing
and have a good reason to.

## 🏁 Start the app

- Install dependencies: `yarn`

- Next.js local dev: `yarn web`
  - Runs `yarn next`
- Expo local dev: `yarn native`
  - Runs `expo start`

## 🆕 Add new dependencies

### Pure JS dependencies

If you're installing a JavaScript-only dependency that will be used across
platforms, install it in `packages/app`:

```bash
cd packages/app
yarn add date-fns
cd ../..
yarn
```

### Native dependencies

If you're installing a library with any native code, you must install it in
`apps/expo`:

```bash
cd apps/expo
yarn add react-native-reanimated

cd ../..
yarn
```

You can also install the native library inside of `packages/app` if you want to
get autoimport for that package inside of the `app` folder. However, you need to
be careful and install the _exact_ same version in both packages. If the
versions mismatch at all, you'll potentially get terrible bugs. This is a
classic monorepo issue. I use `lerna-update-wizard` to help with this (you don't
need to use Lerna to use that lib).

```

```


## guides/design-systems

---
title: Design Systems
description: Put together your own design system.
---

Tamagui allows you to build out your own set of components that are optimized
with the compiler whenever they are used in your app.

By default, if you just use `styled()` in your app, Tamagui **won't** be able to
optimize those components. This is because the compiler needs to know about
those components at build-time.

Let's break down how to set this up in more detail.

## Step 1: Create a package

Your design system needs to live in it's own npm module, which can be private to
just your app. That way you can later direct the compiler to look for that
package.

Design systems can extend off each other. In fact `tamagui` extends off
`@tamagui/core`, which contains simple base level components.

So, for example, if you'd like to use the Tamagui `XStack`, `YStack`, `Button`
and `Paragraph` in your design system, you would add `tamagui` to your design
system's package.json.

If you want to build more from scratch, then use `@tamagui/core` and only import
either the `View` or `Text` components. For the purpose of this guide, we'll use
`@tamagui/core`.

Add package.json:

```json
{
  "name": "@ourapp/components",
  "types": "./types/index.d.ts",
  "main": "dist/cjs",
  "module": "dist/esm",
  "module:jsx": "dist/jsx",
  "files": ["types", "src", "dist"],
  "sideEffects": ["*.css"],
  "dependencies": {
    "@tamagui/core": "*"
  },
  "scripts": {
    "build": "tamagui-build",
    "watch": "tamagui-build --watch"
  },
  "devDependencies": {
    "@tamagui/build": "*"
  }
}
```

If using Typescript, a `tsconfig.json` of your choosing:

```
{
  "types": ["node", "react"],
  "lib": ["dom", "esnext"],
  "compilerOptions": {
    "baseUrl": "./", // resolve absolute module names from here
    "outDir": "dist"
  },
  "include": ["./**/*.ts"],
  "exclude": [
    "node_modules/**/*"
  ]
}
```

There are a few things to note here:

- We're using `@tamagui/build` to build this package, which is a small script
  built around `esbuild` and `typescript` that makes sure you output your
  components _with JSX preserved_.
- We then set `module:jsx`, which then needs to be added to your webpack
  `resolve.mainFields`.
  - The Next.js plugin handles this for you automatically.
- `sideEffects` field is important, otherwise webpack will remove the generated
  CSS in production.

## Step 2: Create your design system.

Check the [configuration](/docs/core/configuration) for more detail on this
step.

You'll be creating a `tamagui.config.ts` at the root of your app. It will
contain a full suite of tokens, themes, and fonts exported onto a single named
`config` export.

## Step 3: Define and export components

Now, create and export the components. You can re-export components from
`tamagui` or `@tamagui/core` as well. Let's create a Circle component

`Circle.tsx`:

```tsx
import { GetProps, YStack, styled } from 'tamagui' // or '@tamagui/core' if extending just that

export const Circle = styled(YStack, {
  alignItems: 'center',
  justifyContent: 'center',
  borderRadius: 100_000_000,
  overflow: 'hidden',

  variants: {
    size: {
      '...size': (size, { tokens }) => {
        return {
          width: tokens.size[size] ?? size,
          height: tokens.size[size] ?? size,
        }
      },
    },
  },
})

export type CircleProps = GetProps<typeof Circle>
```

And then export from `index.tsx`:

```tsx
export * from './Circle'
```

## Step 4: Set up your build

Now in your app, add `@ourapp/components` and `tamagui` (since we are extending
it) to your package.json, and update your tamagui build configuration.

### Webpack

In your `webpack.config.js`:

```js
{
  loader: 'tamagui-loader',
  options: {
    config: './tamagui.config.ts',
    components: ['@ourapp/components', 'tamagui'],
  },
}
```

### Next.js

In your `next.config.js`:

```js
export default withPlugins([
  withTamagui({
    config: './tamagui.config.ts',
    components: ['@ourapp/components', 'tamagui'],
  })
])
```

### React Native

In your `babel.config.js`:

```js
export default {
  plugins: [
    [
      '@tamagui/babel-plugin',
      {
        exclude: /node_modules/,
        config: './tamagui.config.ts',
        components: ['@ourapp/components', 'tamagui']
      },
    ],
  ],
}
```

### Configuration notes

You only need `tamagui` in your `components` array if you extend it, otherwise
no need. You can also extend your own base level module so long as they export
Tamagui `styled` components.

## Step 5: Test it out

In your app, you should now be able to import and use your Circle component.
Using the debug pragma, you can also verify the extraction is working. Make sure
the build settings `logTimings: true` and `disableExtraction: false` are set so
you can see the compiler at work:

Anywhere in your app:

```tsx
import { Circle } from '@ourapp/components'

export default () => <Circle size="$large" />
```

When it compiles you should see something like:

```bash
»                  app.tsx  16ms ׁ·    1 optimized · 1 flattened
```

To get more information on any extraction, use the `// debug` pragma:

```tsx
// debug
// ^ the above pragma will direct Tamagui to output a lot of information on the extraction
import { Circle } from '@ourapp/components'

export default () => <Circle size="$large" />
```

You should see much more log output with details on how it extracted, including
the final CSS and JS.


## guides/developing

---
title: Developing with Tamagui
description: Tips and tricks
---

Tamagui includes many helpful tools for debugging UI.

### Debugging

Tamagui has several ways of giving you more insight into what's happening at
compile-time.

#### Visualizer

You can setup a simple visualizer that shows a quick heads-up-display when you
hold "Option" (or any key you define) after a short period of time, revealing
the styled components names and even the file numbers and components they are in
(if you have the compiler plugin installed) as an overlay over app in
development mode.

```tsx
import { setupDev } from '@tamagui/core'

setupDev({
  // can just be true as well for defaulting to key: Alt + delay: 800
  visualizer: {
    key: 'Alt',
    delay: 800
  }
})
```

#### Debug pragma

To see what's being extracted, why, and every step along the optimization
pipeline, add `// debug` to the top of any file. Adding `// debug-verbose` will
show even more information, including more granular timings.

If you're developing in your design system package that is built with
@tamagui/build, esbuild will strip this banner. You can try using `//! debug`
(esbuild only preserves comments at the top that start with `//!`), but still
occasionally esbuild will insert a helper above the comment, breaking it, so be
sure to check the built file in `dist/jsx`.

#### Debug prop

Adding `debug` to any Tamagui component will output a lot of information on
what's going on at runtime. Use it like so:

```tsx
import { Button } from 'tamagui'

export default () => <Button debug>Hello world</Button>
```

And you'll see props, styles, and a variety of variables relevant to processing
them.

You can do `<Button debug="break" />` to have it break at the beginning of
rendering, or `<Button debug="verbose" />` to have it output more detailed debug
information.

#### DEBUG env

If you set `DEBUG=tamagui` before your build command, you will get the full
debug output of every file built. This is useful for seeing everything that's
happening across every file, and especially helpful for diagnosing production
issues.

### Runtime introspection

In development mode, Tamagui sets the variable `Tamagui` onto `globalThis` with
a lot of helpful internals, including your entire parsed config from
`tamagui.config.ts`.

Beyond your config, you have:

- **allSelectors**: All the selectors inserted by Tamagui (before runtime).

#### Inspecting Components

Any `styled()` component will have a `staticConfig` property attached to it:

```tsx
const Circle = styled(View, {
  borderRadius: 1000,
})

console.log(Circle.staticConfig) // lots of helpful information
```

- `componentName` is taken from the `name` key
- `variants` contains the merged variants including parents.
- `defaultProps` is the extracted props left to use as defaults on the
  component.

### Classes generated

Tamagui generates a few helpful classes. For components created with `styled()`
where a `name` is set like this:

```tsx
const MyButton = styled(View, {
  name: 'MyButton',
  backgroundColor: 'red',
})
```

Tamagui adds the classname `is_MyButton`. This is a useful escape hatch for
attaching CSS to any extra component. All the default Tamagui components have
their name set.

For components that extend a Text-based component, a further classname is set of
the format `font_[fontFamily]`. So if you do:

```tsx
<Paragraph fontFamily="$body" />
```

The classnames `is_Paragraph` and `font_body` will be output to DOM.


## guides/expo

---
title: Expo Guide
description: How to set up Tamagui with Expo
---

We've created a new template repo for starting an Expo Router based app based on
the Expo 3 starter repo.

<Notice theme="blue">
  This template requires Yarn 4.4.0 or greater. You can set yarn to the latest
  version by running `yarn set version stable`.
</Notice>

```bash
yarn create tamagui@latest --template expo-router
```

There are also [pre-made community Expo starters](/community).

## Install

Use this guide to set up Tamagui with Expo Native and Web.

<Notice theme="blue">
  To support dark mode, update your `app.json` to `app.config.ts` and set
  `userInterfaceStyle` to `"automatic"`.
</Notice>

### Native

Create a new [Expo](https://docs.expo.dev/get-started/create-a-project/#initialize-a-new-project) project:

```bash
yarn dlx create-expo-app -t expo-template-blank-typescript
```

<Notice theme="blue">This guide assumes Expo is configured with TypeScript support.</Notice>

- The following steps are optional but useful for many apps, they enable the optimizing compiler, reanimated, as well as using `process.env.XYZ` for environment variables.

Add `@tamagui/babel-plugin`:

```bash
yarn add @tamagui/babel-plugin
```

Update your `babel.config.js` to include the optional `@tamagui/babel-plugin`:

```js fileName="babel.config.js"
module.exports = function (api) {
  api.cache(true)
  return {
    presets: ['babel-preset-expo'],
    plugins: [
      [
        '@tamagui/babel-plugin',
        {
          components: ['tamagui'],
          config: './tamagui.config.ts',
          logTimings: true,
          disableExtraction: process.env.NODE_ENV === 'development',
        },
      ],

      // NOTE: this is only necessary if you are using reanimated for animations
      'react-native-reanimated/plugin',
    ],
  }
}

```

<Notice>
  If you're using a monorepo you probably want to use [this Metro
  configuration](/docs/guides/metro#native).
</Notice>

### Expo Router / Web

- First, follow the [Metro configuration guide](/docs/guides/metro#web-support) to enable web support.

Add `@tamagui/config` and `tamagui` to your package.json and install them. Then add a `tamagui.config.ts`:

```tsx fileName="tamagui.config.ts"
import { defaultConfig } from '@tamagui/config/v5'
import { createTamagui } from 'tamagui'

export const tamaguiConfig = createTamagui(defaultConfig)

export default tamaguiConfig

export type Conf = typeof tamaguiConfig

declare module 'tamagui' {
  interface TamaguiCustomConfig extends Conf {}
}
```

Then update `app/_layout.tsx`:

```tsx showMore fileName="app/_layout.tsx"
import '../tamagui-web.css'

import { DarkTheme, DefaultTheme, ThemeProvider } from '@react-navigation/native'
import { Stack } from 'expo-router'
import { useColorScheme } from 'react-native'
import { TamaguiProvider } from 'tamagui'

import { tamaguiConfig } from '../tamagui.config'

export default function RootLayout() {
  const colorScheme = useColorScheme()

  return (
    // add this
    <TamaguiProvider config={tamaguiConfig} defaultTheme={colorScheme!}>
      <ThemeProvider value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
        <Stack>
          <Stack.Screen name="(tabs)" options={{ headerShown: false }} />
          <Stack.Screen name="modal" options={{ presentation: 'modal' }} />
        </Stack>
      </ThemeProvider>
    </TamaguiProvider>
  )
}
```

### Setup Tamagui

From here on out you can follow the [Installation](/docs/intro/installation) and [Configuration](/docs/core/configuration) docs.

## Loading fonts

Install the `expo-font` package:

```bash
npx expo install expo-font
```

Load your fonts for React Native to recognize them. There are several ways to do this:

<Spacer size="$6" />

<InlineTabs id="font" defaultValue="tamagui">
<InlineTabs.List>
    <InlineTabs.Tab value="tamagui">Tamagui</InlineTabs.Tab>
    <InlineTabs.Tab value="expo">Expo Google Fonts</InlineTabs.Tab>
</InlineTabs.List>

<InlineTabs.Content value="tamagui">
Using the `@tamagui/font-inter` package which is a pre-configured version of the Inter font that works with Tamagui:

Import the `useFonts` hook and load the fonts:

```tsx fileName="App.tsx"
import { useFonts } from 'expo-font'

function App() {
  const [loaded] = useFonts({
    Inter: require('@tamagui/font-inter/otf/Inter-Medium.otf'),
    InterBold: require('@tamagui/font-inter/otf/Inter-Bold.otf'),
  })

  useEffect(() => {
    if (loaded) {
      // can hide splash screen here
    }
  }, [loaded])

  if (!loaded) {
    return null
  }

  return <MyApp />
}
```

</InlineTabs.Content>

<InlineTabs.Content value="expo">
Using the `@expo-google-fonts` package which is a collection of Google Fonts that work with Expo:

<Notice theme="green">
  You can find the [full list of
  fonts](https://github.com/expo/google-fonts/tree/master/font-packages) and
  usage instructions in the [Expo
  documentation](https://github.com/expo/google-fonts).
</Notice>

Install the font package:

```bash
npx expo install @expo-google-fonts/inter
```

Import the `useFonts` hook and load the fonts:

```tsx fileName="App.tsx"
import { useFonts, Inter_400Regular, Inter_900Black } from '@expo-google-fonts/inter'

function App() {
  const [loaded] = useFonts({
    Inter_400Regular,
    Inter_900Black,
  })

  useEffect(() => {
    if (loaded) {
      // can hide splash screen here
    }
  }, [loaded])

  if (!loaded) {
    return null
  }

  return <MyApp />
}
```

</InlineTabs.Content>
</InlineTabs>

<Notice>
  For more information on loading fonts in Expo, see the [Expo
  documentation](https://docs.expo.dev/develop/user-interface/fonts/).
</Notice>

## First time starting Expo

The first time running your project with Tamagui, be sure to clear the cache:

```bash
npx expo start -c
```

Your `package.json` scripts should look something like this:

```json fileName="package.json"
{
  "scripts": {
    "start-native": "expo start -c",
    "start-web": "expo start -c",
    "android": "yarn expo run:android",
    "ios": "yarn expo run:ios",
    "web": "expo start --web"
  }
}
```


## guides/how-to-build-a-button

---
title: How to Build a Button
description:
  Learn how to create a powerful yet simple API for a button with Tamagui and
  compound components
hero: HowToBuildAButton
demoName: BuildAButton
---

<HeroContainer noScroll>
  <BuildAButtonDemo />
</HeroContainer>

<IntroParagraph>
  Deceptively innocuous, designing a robust yet flexible API even for a Button
  can be filled with surprising challenges.
</IntroParagraph>

More than just text in a box, buttons need icons, themes, variants, sizes, often
loading indicators and several interactive states, and always a delicate balance
between sub-components' positions, spacing and style.

All of this ideally delivered in a simple API that still allows complete
customization.

Tamagui allows for building a button with "compound components", a concept
popularized by [Radix](https://www.radix-ui.com/). With the introduction of
`createStyledContext` in version 1.28 this pattern has become especially easy,
so we wrote this guide to show off building more advanced compound component
APIs. While this guide covers a button, this is applicable to many types of UI
components.

So what are compound components? Simply, it's when you have, say, a
`<Button />` that wants children `<Button.Text />` and `<Button.Icon />`.

And why do they exist? Well, we'll explain that and more in this guide.

Keep in mind that this is an advanced use case. Usually you can just grab
Tamagui components off the shelf and use them with inline styles. But sometimes
you need components that want multiple related children.

<Notice>
  This guide is more of a primer on designing compound components, teaching
  advanced concepts that aren't necessary if you just want to build your app. If
  you just want a simple, ready to use Button, you can use the [Tamagui
  Button](/docs/components/button) itself directly.
</Notice>

### The Final Result

To the point, here is the final code we'll end up with for our Button, ready to
copy and paste right into your app:

```tsx
import { getSize, getSpace } from '@tamagui/get-token'
import {
  GetProps,
  SizeTokens,
  View,
  Text,
  createStyledContext,
  styled,
  useTheme,
  withStaticProperties,
} from '@tamagui/web'
import { cloneElement, isValidElement, useContext } from 'react'

export const ButtonContext = createStyledContext({
  size: '$md' as SizeTokens,
})

export const ButtonFrame = styled(View, {
  name: 'Button',
  context: ButtonContext,
  backgroundColor: '$background',
  alignItems: 'center',
  flexDirection: 'row',

  hoverStyle: {
    backgroundColor: '$backgroundHover',
  },

  pressStyle: {
    backgroundColor: '$backgroundPress',
  },

  variants: {
    size: {
      '...size': (name, { tokens }) => {
        return {
          height: tokens.size[name],
          borderRadius: tokens.radius[name],
          // note the getSpace and getSize helpers will let you shift down/up token sizes
          // whereas with gap we just multiply by 0.2
          // this is a stylistic choice, and depends on your design system values
          gap: tokens.space[name].val * 0.2,
          paddingHorizontal: getSpace(name, {
            shift: -1,
          }),
        }
      },
    },
  } as const,

  defaultVariants: {
    size: '$md',
  },
})

type ButtonProps = GetProps<typeof ButtonFrame>

export const ButtonText = styled(Text, {
  name: 'ButtonText',
  context: ButtonContext,
  color: '$color',
  userSelect: 'none',

  variants: {
    size: {
      '...fontSize': (name, { font }) => ({
        fontSize: font?.size[name],
      }),
    },
  } as const,
})

const ButtonIcon = (props: { children: any }) => {
  const { size } = useContext(ButtonContext.context)
  const smaller = getSize(size, {
    shift: -2,
  })
  const theme = useTheme()
  return isValidElement(props.children) ? cloneElement(props.children, {
    size: smaller.val * 0.5,
    color: theme.color.get(),
  }) : null
}

export const Button = withStaticProperties(ButtonFrame, {
  Props: ButtonContext.Provider,
  Text: ButtonText,
  Icon: ButtonIcon,
})
```

Now you can use your button like so:

```tsx
export default () => (
  <Button>
    <Button.Icon>
      <Moon />
    </Button.Icon>
    <Button.Text>hi</Button.Text>
  </Button>
)

// multiple icons:
export default () => (
  <Button>
    <Button.Icon>
      <Moon />
    </Button.Icon>
    <Button.Text>hi</Button.Text>
    <Button.Icon>
      <Moon />
    </Button.Icon>
  </Button>
)
```

This may only be a ~hundred lines of code, but there's a lot to take in. And
behind the scenes, there's a lot going on to make this possible.

### From the Start

This guide will build up to this complete example from the ground up, which
should help explain both why many patterns exist, and also how they work.

We start with the assumption that we have a design system set up with a tokens
that use keys `sm`, `md`, and `lg` and a few themes:

```tsx
import { createTamagui, createTokens } from '@tamagui/core'

export default createTamagui({
  tokens: createTokens({
    size: {
      sm: 38,
      md: 46,
      lg: 60,
    },
    space: {
      sm: 15,
      md: 20,
      lg: 25,
    },
    radius: {
      sm: 4,
      md: 8,
      lg: 12,
    },
    // ... the rest of your tokens
  }),

  themes: {
    light: {
      background: '#fff',
      color: '#000',
    },

    // define a Button sub-theme, see the Themes docs for more
    light_Button: {
      background: '#ccc',
      backgroundPress: '#bbb', // darker background on press
      backgroundHover: '#ddd', // lighter background on hover
      color: '#222'
    },
  },

  // ... the rest of your tamagui.config.ts
})
```

If you'd like to see a more complete work-up of a Tamagui config, check out
[the Simple Web starter repo source code](https://github.com/tamagui/tamagui/blob/main/code/starters/simple-web/src/tamagui.config.ts)
or go ahead and create that starter using `npm create tamagui@latest`.

With that in mind, we can create the outer frame of the button fairly simply,
like so:

```tsx
import { View, styled } from '@tamagui/core'

const ButtonFrame = styled(View, {
  // the name indicates to use the sub-theme `Button`
  // since we defined light_Button, if our theme is light, this component
  // will always use the values from our `light_Button` theme
  name: 'Button',

  alignItems: 'center',
  flexDirection: 'row',

  // our $ prefixed values look for theme first, then fallback to tokens
  backgroundColor: '$background', // #ccc
  hoverStyle: {
    backgroundColor: '$backgroundHover', // #ddd
  },
  pressStyle: {
    backgroundColor: '$backgroundPress', // #bbb
  },

  // these all use tokens
  // note that size tokens are used only for these properties:
  //   width, height, minWidth, minHeight, maxWidth and maxHeight
  height: '$md', // 46

  // meanwhile our radius token is used here
  borderRadius: '$md', // 8

  // and space tokens are used for all others
  paddingHorizontal: '$sm', // 25
})

```

This gets us a simple rounded rectangle that uses the `md` tokens from your
design system and the `background` styles from your theme.

We set the `name` property to `"Button"`, which tells Tamagui to check for a
sub-theme `Button` that extends the current one. So since we have a theme
`light` and we have a sub-theme `light_Button`, Tamagui finds the theme
`light_Button` and apply it to this component, getting the `background` value
from that theme (assuming we have our base theme set to light).

Finally we set the `hoverStyle` and `pressStyle` so our button frame looks nice
when hovered or pressed.

Next we'll want a Text component to go inside:

```tsx
import { Text, styled } from '@tamagui/core'

export const ButtonText = styled(Text, {
  name: 'ButtonText',
  color: '$color',
  fontFamily: '$body',
  fontSize: '$md',
  lineHeight: '$md',
  userSelect: 'none',
})
```

This is pretty similar to the frame, just with its own name and with a `color`
set rather than a `background`. Since we didn't define a `light_ButtonText`
theme, this will fall back to the color defined on the light theme. Finally,
button text usually isn't selectable so we set `userSelect` to none.

We can use our simple button. It's nicely themed, but only renders at one size:

```tsx
export default () => (
  <Button>
    <ButtonText>
      Hello world
    </ButtonText>
  </Button>
)
```

So we'll make it sizeable. First, let's look at how we'd solve this without the
new `createStyledContext` helper, so we can understand why it exists.

We can add some variants:

```tsx
const ButtonFrame = styled(View, {
  name: 'Button',
  backgroundColor: '$background',
  alignItems: 'center',
  flexDirection: 'row',

  variants: {
    size: {
      sm: {
        height: '$sm',
        borderRadius: '$sm',
        gap: 4,
      },
      md: {
        height: '$md',
        borderRadius: '$md',
        gap: 6,
      },
      lg: {
        height: '$lg',
        borderRadius: '$lg',
        gap: 8,
      },
    },
  } as const,
})

```

Still, this is somewhat repetitive and prone to mistakes via typo. To allow us
to avoid duplication while also always working even if we add a new sizes to our
design system in the future, we can use the handy Tamagui
[Spread Variants](/docs/core/variants#spread-variants):

```tsx
import { View, styled } from '@tamagui/core'

const ButtonFrame = styled(View, {
  name: 'Button',
  backgroundColor: '$background',
  alignItems: 'center',
  flexDirection: 'row',

  variants: {
    size: {
      '...size': (name, { tokens }) => {
        return {
          height: tokens.size[name],
          borderRadius: tokens.radius[name],
          gap: tokens.space[name].val * 0.2,
        }
      },
    },
  } as const,
})

export const ButtonText = styled(Text, {
  name: 'ButtonText',
  color: '$color',
  userSelect: 'none',

  variants: {
    size: {
      '...fontSize': (name, { font }) => ({
        fontSize: font?.size[name],
      }),
    },
  } as const,
})
```

<Notice>
  The `...size` spread variant `name` defaults to implicit any. Update
  `tsconfig.json`'s `"noImplicitAny": false` to avoid TypeScript errors.
</Notice>

So now we can pass in our new `size` property:

```tsx
<ButtonFrame size="$md">
  <ButtonText size="$md" />
</ButtonFrame>
```

Lets just clean up these two components so they are more clearly meant to be
used together (and are easier to import for users):

```tsx
export const Button = ButtonFrame as typeof ButtonFrame & {
  Text: typeof ButtonText
}

Button.Text = Text
```

What is this whole `typeof ButtonFrame` thing? It's just TypeScript being
awkward. Since Tamagui uses this pattern internally for many components, we've
made a small helper:

```tsx
import { withStaticProperties } from '@tamagui/core'

export const Button = withStaticProperties(ButtonFrame, {
  Text: ButtonText,
})
```

Which is functionally the same as the above. So, now our users can do:

```tsx
import { Button } from './OurButton'

export default () => (
  <Button size="$md">
    <Button.Text size="$md">Hello world</Button.Text>
  </Button>
)
```

### Improving the API

Great. We've gotten our custom Button exported. But there's something quite
unfortunate about our API as it stands, and that is that we have to always pass
`size` to both components. This is brittle and ugly.

One typical way to solve this would be to abstract both of these components into
our own React component:

```tsx
const Button = ({
  size,
  children,
  textProps,
  ...props
}: ViewProps & {
  textProps?: TextProps
}) => (
  <ButtonFrame size={size} {...props}>
    <ButtonText size={size} {...textProps}>
      {children}
    </ButtonText>
  </ButtonFrame>
)
```

But suddenly we have issues. What if a user wants to show an icon? We need to
add `icon` and `iconProps`. And what if they want to change the order? You may
reach for something like `direction="reverse"`. But then what if they want a
loading indicator after, but an icon before. Or maybe they want to show big text
above, and smaller text below.

The problem with abstracting your Button into a single component is that the
internals are surprisingly flexible, and ideally they want to each be styled
independently.

Abstracting the tree structure that's output is our problem. The compound
component API gives us full control - we can customize every piece, even using
the existing `styled` function to re-style each piece, before re-exporting it
again:

```tsx
import { withStaticProperties } from '@tamagui/core'

import { Button } from './OurButton'

const CustomButtonFrame = styled(Button, {
  // override some styles
})

const CustomButtonText = styled(Button.Text, {
  // override some styles
})

export const CustomButton = withStaticProperties(CustomButtonFrame, {
  Text: CustomButtonText,
})

export default () => (
  <CustomButton>
    <CustomButton.Text>Hello world</CustomButton.Text>
    <CustomButton.Text size="$sm">(Smaller text)</CustomButton.Text>
  </CustomButton>
)
```

Not to mention by staying with the `styled` world, the Tamagui optimizing
compiler confidently flattens our Button components down to ridiculously fast
DOM output on the web, and pulls out all styles from the render loop on native.

So we want our compound component API both for ergonomics and for performance,
but we need some way to thread our `size` property down from the parent frame to
the inner sub-components.

### Solving size

In React this is typically what context is used for.

Here's how we'd solve for size using context in plain-old React, while still
keeping the compound API:

```tsx
import { createContext } from 'react'
import { SizeTokens, GetProps, withStaticProperties } from '@tamagui/core'
import { Button } from './OurButton'

const SizeContext = createContext<SizeTokens>('$md')

const ButtonFrame = Button.styleable(
  ({ size = '$md', ...props }: GetProps<typeof OGB.ButtonFrame>) => {
    return (
      <SizeContext.Provider value={size}>
        <Button size={size} {...props} />
      </SizeContext.Provider>
    )
  },
)

const ButtonText = Button.Text.styleable(
  (props: GetProps<typeof OGB.ButtonText>) => {
    const size = useContext(SizeContext)
    return <Button.Text size={size} {...props} />
  },
)

export const NewButton = withStaticProperties(ButtonFrame, {
  Text: ButtonText,
})
```

What the hell is `ButtonText.styleable` and `ButtonFrame.styleable`?

Well, while it's explained in [the styled docs](/docs/core/styled#styleable) but
the short of it is if you want a functional component that returns a styled
component _to then be able to be styled again_, well, you need `styleable`.

That's because merging things like themes, animations, variants, media queries,
and pseudo queries is quite complex, and if Tamagui doesn't know that there is a
functional component in between some styled components it can lead to unexpected
merging of styles.

More importantly, the above code is once again verbose, duplicative, and
brittle. For example sharing more than just `size` across these components would
require a pretty significant refactor of the context with a lot of logic
memoizing the context value properly. And, once again, by moving into functional
components, your base level views now won't be as optimized by the compiler.

### The solution, createStyledContext

Finally we get to use `createStyledContext`. We just change from `createContext`
to:

```tsx
import { createStyledContext } from '@tamagui/core'

export const ButtonContext = createStyledContext({
  size: '$md' as SizeTokens,
})
```

This in fact it returns a slightly modified `React.Context` just like
`createContext` does, so you can use it with `useContext` later on - though we
still export a `.context` object with the original type just in case there's any
issue.

And now, bringing back the full example, we add an extra property to each
`styled` component, `context`:

```tsx
import { View, styled, createStyledContext } from '@tamagui/core'

export const ButtonContext = createStyledContext({
  size: '$md' as SizeTokens,
})

const ButtonFrame = styled(View, {
  name: 'Button',
  context: ButtonContext,
  backgroundColor: '$background',
  alignItems: 'center',
  flexDirection: 'row',

  variants: {
    size: {
      '...size': (name, { tokens }) => {
        return {
          height: tokens.size[name],
          borderRadius: tokens.radius[name],
          gap: tokens.space[name].val * 0.2,
        }
      },
    },
  } as const,
})

const ButtonText = styled(Text, {
  name: 'ButtonText',
  context: ButtonContext,
  color: '$color',
  userSelect: 'none',

  variants: {
    size: {
      '...fontSize': (name, { font }) => ({
        fontSize: font?.size[name],
      }),
    },
  } as const,
})

export const Button = withStaticProperties(ButtonFrame, {
  Text: ButtonText,
  Props: ButtonContext.Provider,
})
```

...and we've finally arrived at our destination! We can now do the following:

```tsx
import { Button } from './OurButton'

export default () => (
  <Button size="$md">
    <Button.Text>
      Hello world
    </Button.Text>
  </Button>
)
```

This time since `Button` knows the `size` property is defined in the `context`
it will automatically pass size down from Button to Text, just like our
hand-rolled version did.

But - we don't have to write terribly verbose and brittle code, we get nice
types and memoization automatically, and the optimizing compiler is happy.

Notice we also exported a new property on Button, `Button.Props`.

Since `createStyledContext` returns a regular `React.Context` value, this works
the same as any other React provider. We can now control the variant from
anywhere above in the React tree:

```tsx
import { Button } from './OurButton'

export default () => (
  <Button.Props size="$md">
    <Button>
      <Button.Text>
        Hello world
      </Button.Text>
    </Button>
  </Button.Props>
)
```

The only difference from `createContext` is that the `createStyledContext`
Provider just takes props directly rather than inside a `value` object (and of
course the memoization comes for free).

### Adding an Icon

Are we done?

Not quite. One final common piece of a Button is having an icon that sizes and
colors properly as well. But an icon usually comes from some third-party
library, an SVG or perhaps an icon font.

Let's add a new component, `Button.Icon` and make this work.

Instead of going through `styled`, this component will be just a plain
functional component. It will still work nicely with the Tamagui themes and
sizes, showing how you can use them together with external React components.

```tsx
import { getTokens, useTheme } from '@tamagui/core'
import * as React from 'react'

const ButtonIcon = (props: { children: React.ReactNode }) => {
  const { size } = React.useContext(ButtonContext)
  const tokens = getTokens()
  const smallerSize = tokens.size[size].val * 0.5
  const theme = useTheme()
  return React.cloneElement(props.children, {
    width: smallerSize,
    height: smallerSize,
    color: theme.color.get(),
  })
}

// add it to your Button:

export const Button = withStaticProperties(ButtonFrame, {
  Text: ButtonText,
  Icon: ButtonIcon,
  Props: ButtonContext.Provider,
})
```

Now we can use it like this, assuming your Icon accepts `width`, `height`, and
`color`:

```tsx
import { MyIcon } from 'some-icon-library'

export default () => (
  <Button size="$lg">
    <Button.Icon>
      <MyIcon />
    </Button.Icon>
    <Button.Text>
      Hello world
    </Button.Text>
  </Button>
)
```

Of course you can make your `Button.Icon` work a bit differently if you'd like,
say changing out `children` + `cloneElement` for something like
`<Button.Icon icon={MyIcon} />`. It's up to you.

### Conclusion

We hope this has been helpful in explaining a variety of Tamagui features and
some of the benefits and ideas behind compound components.

There's certainly further you can go in building out your Button, but we think
that in under 150 lines of code you're getting a nearly ideal API, fully typed
sizing and themes, and a great balance of customization to performance.

If you are working on a smaller app, say one that shows only a few buttons at a
time or with a small set of button variations, then abstracting your Button into
a single functional Button component is totally fine.

But we'd encourage you to give the compound component API a try and see
how you like it.

With the new Tamagui APIs, we look forward to never having to carefully add a
forwardRef, re-jig the types, and then manually thread context just to share
some props between parent and child components, especially when even after all
of that we'd still somehow accidentally break memoization or scoped values!


## guides/metro

---
title: Metro Guide
description: How to set up Tamagui with Metro
---

## Basic Setup

The default Expo Metro configuration works out of the box:

```ts fileName="metro.config.js"
const { getDefaultConfig } = require('expo/metro-config')

module.exports = getDefaultConfig(__dirname)
```

## With Metro Plugin (Recommended)

For better dev experience, use `@tamagui/metro-plugin` which loads your Tamagui config and watches for changes:

```bash
yarn add @tamagui/metro-plugin
```

```tsx fileName="metro.config.js"
const { getDefaultConfig } = require('expo/metro-config')
const { withTamagui } = require('@tamagui/metro-plugin')

const config = getDefaultConfig(__dirname)

module.exports = withTamagui(config, {
  components: ['tamagui'],
  config: './tamagui.config.ts',
})
```

## CSS Generation

For web, Tamagui outputs CSS containing your themes and tokens. Create a `tamagui.build.ts` in your project root:

```ts fileName="tamagui.build.ts"
import type { TamaguiBuildOptions } from 'tamagui'

export default {
  components: ['tamagui'],
  config: './tamagui.config.ts',
  outputCSS: './tamagui-web.css',
} satisfies TamaguiBuildOptions
```

Then import the CSS in your app's layout:

```tsx fileName="app/_layout.tsx"
import '../tamagui-web.css'
// ... rest of your layout
```

In development, the CSS is generated automatically when Tamagui loads your config. For production builds, run:

```bash
tamagui generate
```

## Optimized Production Builds

For maximum optimization, use the `tamagui build` command which pre-compiles your components:

```bash
# Optimize and build, then restore source files
tamagui build --target web ./app -- expo export --platform web
```

The `--` syntax runs the command after optimization, then automatically restores your source files. Add this to your `package.json`:

```json
{
  "scripts": {
    "build:web": "expo export --platform web",
    "build:web:optimized": "tamagui build --target web ./app -- expo export --platform web"
  }
}
```

## Debugging

To debug the compiler output, add `// debug` at the top of a file:

```tsx
// debug
import { YStack } from 'tamagui'

export function MyComponent() {
  return <YStack flex={1} bg="$background" />
}
```

This will print detailed compiler output showing what optimizations are happening:

```
[✅] flattened YStack div
```

Use `// debug-verbose` for even more detailed output.


## guides/next-js

---
title: Next.js Guide
description: How to set up Tamagui with Next.js
---

<Notice theme="green">
  Running `npm create tamagui@latest` lets you choose the `starter-free`
  starter which is a very nicely configured Next.js app where you can take or
  leave whatever you want.
</Notice>

Create a new [Next.js](https://nextjs.org/docs/getting-started/installation)
project

```bash
npx create-next-app@latest
```

If you are using Turbopack, we have a section for optimization at the end of
this document. If you aren't, then you may want the optional
`@tamagui/next-plugin` which helps smooth out a few settings. We'll show how to
configure it for both `pages` and `app` router in this guide. See the
[compiler install docs](/docs/intro/compiler-install) for more options.

Add `@tamagui/next-plugin` to your project:

```bash
yarn add @tamagui/next-plugin
```

We recommend starting with our default config which gives you media queries and
other nice things:

```tsx fileName="tamagui.config.ts"
import { defaultConfig } from '@tamagui/config/v5'
import { createTamagui } from 'tamagui' // or '@tamagui/core'

const appConfig = createTamagui(defaultConfig)

export type AppConfig = typeof appConfig

declare module 'tamagui' {
  // or '@tamagui/core'
  // overrides TamaguiCustomConfig so your custom types
  // work everywhere you import `tamagui`
  interface TamaguiCustomConfig extends AppConfig {}
}

export default appConfig
```

From here, choose your Next.js routing option to continue:

<Spacer size="$4" />

<Grid gap="$4">
  <NextJSRouterCard
    title="Pages router"
    link="/docs/guides/next-js#pages-router"
    subtitle="Automatically generate routes based on the filenames."
    colorOffset={1}
  />
  <NextJSRouterCard
    title="App router"
    link="/docs/guides/next-js#app-router"
    subtitle="Allows more complex patterns and setups."
    colorOffset={0}
  />
  <NextJSRouterCard
    title="Turbopack"
    link="/docs/guides/next-js#turbopack"
    subtitle="Using Tamagui with Next.js Turbopack bundler."
    colorOffset={2}
  />
</Grid>

<Spacer size="$4" />

## Pages router

### next.config.js

Set up the optional Tamagui plugin to `next.config.js`:

```tsx showMore fileName="next.config.js"
const { withTamagui } = require('@tamagui/next-plugin')

module.exports = function (name, { defaultConfig }) {
  let config = {
    ...defaultConfig,
    // ...your configuration
  }
  const tamaguiPlugin = withTamagui({
    config: './tamagui.config.ts',
    components: ['tamagui'],
  })
  return {
    ...config,
    ...tamaguiPlugin(config),
  }
}
```

### pages/\_document.tsx

If you're using React Native Web components, you'll want to gather the
`react-native-web` styles in \_document:

```tsx fileName="_document.tsx"
import NextDocument, {
  DocumentContext,
  Head,
  Html,
  Main,
  NextScript,
} from 'next/document'
import { StyleSheet } from 'react-native'

export default class Document extends NextDocument {
  static async getInitialProps({ renderPage }: DocumentContext) {
    const page = await renderPage()

    // @ts-ignore RN doesn't have this type
    const rnwStyle = StyleSheet.getSheet()

    return {
      ...page,
      styles: (
        <style
          id={rnwStyle.id}
          dangerouslySetInnerHTML={{ __html: rnwStyle.textContent }}
        />
      ),
    }
  }
  render() {
    return (
      <Html lang="en">
        <Head>
          <meta id="theme-color" name="theme-color" />
          <meta name="color-scheme" content="light dark" />
        </Head>
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    )
  }
}
```

<Notice theme="blue">
  Tamagui automatically injects styles at runtime. You can optionally generate a
  static CSS file - see the [Static CSS Output](#static-css-output) section.
</Notice>

### pages/\_app.tsx

Add `TamaguiProvider`:

```tsx fileName="_app.tsx"
import { NextThemeProvider } from '@tamagui/next-theme'
import { AppProps } from 'next/app'
import Head from 'next/head'
import React, { useMemo } from 'react'
import { TamaguiProvider } from 'tamagui'
import tamaguiConfig from '../tamagui.config'

export default function App({ Component, pageProps }: AppProps) {
  // memo to avoid re-render on dark/light change
  const contents = useMemo(() => {
    return <Component {...pageProps} />
  }, [pageProps])

  return (
    <>
      <Head>
        <title>Your page title</title>
        <meta name="description" content="Your page description" />
        <link rel="icon" href="/favicon.ico" />
      </Head>
      <NextThemeProvider>
        <TamaguiProvider
            config={tamaguiConfig}
            disableInjectCSS
            disableRootThemeClass
          >
          {contents}
        </TamaguiProvider>
      </NextThemeProvider>
    </>
  )
}
```

<Notice theme="blue">
  Use `disableInjectCSS` for SSR apps to prevent duplicate style injection. Only omit it for client-only apps without server rendering.
</Notice>

### Themes

We've created a package that works with Tamagui to properly support SSR
light/dark themes that also respect user system preference, called
`@tamagui/next-theme`. It assumes your `light`/`dark` themes are named as such,
but you can override it. This is pre-configured in the create-tamagui starter.

```bash
yarn add @tamagui/next-theme
```

Here's how you'd set up your `_app.tsx`:

```tsx fileName="_app.tsx"
import { NextThemeProvider, useRootTheme } from '@tamagui/next-theme'
import { AppProps } from 'next/app'
import Head from 'next/head'
import React, { useMemo } from 'react'
import { TamaguiProvider, createTamagui } from 'tamagui'

// you usually export this from a tamagui.config.ts file:
import { defaultConfig } from '@tamagui/config/v5'
const tamaguiConfig = createTamagui(defaultConfig)

// make TypeScript type everything based on your config
type Conf = typeof tamaguiConfig
declare module '@tamagui/core' {
  interface TamaguiCustomConfig extends Conf {}
}

export default function App({ Component, pageProps }: AppProps) {
  const [theme, setTheme] = useRootTheme()

  // memo to avoid re-render on dark/light change
  const contents = useMemo(() => {
    return <Component {...pageProps} />
  }, [pageProps])

  return (
    <>
      <Head>
        <title>Your page title</title>
        <meta name="description" content="Your page description" />
        <link rel="icon" href="/favicon.ico" />
      </Head>
      <NextThemeProvider
        // change default theme (system) here:
        // defaultTheme="light"
        onChangeTheme={setTheme as any}
      >
        <TamaguiProvider
          config={tamaguiConfig}
          disableInjectCSS
          disableRootThemeClass
          defaultTheme={theme}
        >
          {contents}
        </TamaguiProvider>
      </NextThemeProvider>
    </>
  )
}
```

### Static CSS Output

You can generate a static CSS file for your themes and tokens. There are two
ways to do this:

#### Option 1: Using the CLI

The simplest approach is to use the Tamagui CLI to generate the CSS file:

```bash
npx tamagui generate
```

This outputs CSS to `.tamagui/tamagui.css`. Copy it to your public folder or
configure `outputCSS` in your `tamagui.build.ts`:

```ts fileName="tamagui.build.ts"
import type { TamaguiBuildOptions } from '@tamagui/core'

export default {
  components: ['tamagui'],
  config: './tamagui.config.ts',
  outputCSS: './public/tamagui.css',
} satisfies TamaguiBuildOptions
```

Then import it in your `_app.tsx`:

```tsx fileName="_app.tsx"
import '../public/tamagui.css'
```

#### Option 2: Using the Next.js Plugin

You can also have the plugin generate CSS during your Next.js build:

```tsx fileName="next.config.js"
const tamaguiPlugin = withTamagui({
  config: './tamagui.config.ts',
  components: ['tamagui'],
  outputCSS:
    process.env.NODE_ENV === 'production' ? './public/tamagui.css' : null,
  // faster dev mode, keeps debugging helpers:
  disableExtraction: process.env.NODE_ENV === 'development',
})
```

Then import it in your `_app.tsx`:

```tsx fileName="_app.tsx"
import '../public/tamagui.css'
```

<Notice theme="green">
  With `outputCSS`, you don't need `getCSS()` in your `_document.tsx` - all
  styles are handled by the static CSS file and runtime style injection.
</Notice>

### Font loading

To ensure font loads globally, add a global style to `styles` in
`_document.tsx`:

```tsx fileName="NextTamaguiProvider.tsx"
<style jsx global>{`
  html {
    font-family: 'Inter';
  }
`}</style>
```

## App router

Tamagui includes Server Components support for the Next.js app directory with
[`use client`](https://nextjs.org/docs/app/building-your-application/rendering/client-components#using-client-components-in-nextjs)
support.

Note that "use client" does render on the server, and since Tamagui extracts to
CSS statically and uses inline `<style />` tags for non-static styling, we get
excellent performance as-is.

### next.config.js

The Tamagui plugin is optional but helps with compatibility with the rest of the
React Native ecosystem. It requires CommonJS for now as the optimizing compiler
makes use of a variety of resolving features that haven't been ported to ESM
yet. Be sure to rename your `next.config.mjs` to `next.config.js` before adding
it:

```tsx fileName="next.config.js"
const { withTamagui } = require('@tamagui/next-plugin')

module.exports = function (name, { defaultConfig }) {
  let config = {
    ...defaultConfig,
    // ...your configuration
  }

  const tamaguiPlugin = withTamagui({
    config: './tamagui.config.ts',
    components: ['tamagui'],
    appDir: true,
  })

  return {
    ...config,
    ...tamaguiPlugin(config),
  }
}
```

<Notice theme="blue">
  You need to pass the `appDir` boolean to `@tamagui/next-plugin`.
</Notice>

### app/layout.tsx

Create a new component to add `TamaguiProvider`:

<Notice theme="blue">
  The internal usage of `next/head` is not supported in the app directory, so you need to add the `skipNextHead` prop to your `<NextThemeProvider>`.
</Notice>

```tsx fileName="NextTamaguiProvider.tsx"
'use client'

import { ReactNode } from 'react'
import { StyleSheet } from 'react-native'
import { useServerInsertedHTML } from 'next/navigation'
import { NextThemeProvider } from '@tamagui/next-theme'
import { TamaguiProvider } from 'tamagui'
import tamaguiConfig from '../tamagui.config'

export const NextTamaguiProvider = ({ children }: { children: ReactNode }) => {
  // only if using react-native-web components like ScrollView:
  useServerInsertedHTML(() => {
    // @ts-ignore
    const rnwStyle = StyleSheet.getSheet()
    return (
      <>
        <style
          dangerouslySetInnerHTML={{ __html: rnwStyle.textContent }}
          id={rnwStyle.id}
        />
      </>
    )
  })

  return (
    <NextThemeProvider skipNextHead>
      <TamaguiProvider config={tamaguiConfig} disableRootThemeClass>
        {children}
      </TamaguiProvider>
    </NextThemeProvider>
  )
}
```

<Notice theme="green">
  The `getNewCSS` helper in Tamagui will keep track of the last call and only
  return new styles generated since the last usage.
</Notice>

Then add it to your `app/layout.tsx`:

```tsx fileName="layout.tsx"
import { Metadata } from 'next'
import { NextTamaguiProvider } from './NextTamaguiProvider'

export const metadata: Metadata = {
  title: 'Your page title',
  description: 'Your page description',
  icons: '/favicon.ico',
}

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body>
        <NextTamaguiProvider>{children}</NextTamaguiProvider>
      </body>
    </html>
  )
}
```

<Notice theme="green">
  You can use `suppressHydrationWarning` to avoid the warning about mismatched
  content during hydration in dev mode.
</Notice>

### app/page.tsx

Now you're ready to start adding components to `app/page.tsx`:

```tsx fileName="page.tsx"
'use client'

import { Button } from 'tamagui'

export default function Home() {
  return <Button>Hello world!</Button>
}
```

### Themes

We've created a package that works with Tamagui to properly support SSR
light/dark themes that also respect user system preference, called
`@tamagui/next-theme`. It assumes your `light`/`dark` themes are named as such,
but you can override it. This is pre-configured in the create-tamagui starter.

```bash
yarn add @tamagui/next-theme
```

Here's how you'd set up your `NextTamaguiProvider.tsx`:

```tsx fileName="NextTamaguiProvider.tsx"
'use client'

import '@tamagui/font-inter/css/400.css'
import '@tamagui/font-inter/css/700.css'

import { ReactNode } from 'react'
import { StyleSheet } from 'react-native'
import { useServerInsertedHTML } from 'next/navigation'
import { NextThemeProvider, useRootTheme } from '@tamagui/next-theme'
import { TamaguiProvider } from 'tamagui'
import tamaguiConfig from '../tamagui.config'

export const NextTamaguiProvider = ({ children }: { children: ReactNode }) => {
  const [theme, setTheme] = useRootTheme()

  // only needed if using react-native-web components:
  useServerInsertedHTML(() => {
    // @ts-ignore
    const rnwStyle = StyleSheet.getSheet()
    return (
      <style
        dangerouslySetInnerHTML={{ __html: rnwStyle.textContent }}
        id={rnwStyle.id}
      />
    )
  })

  return (
    <NextThemeProvider
      skipNextHead
      // change default theme (system) here:
      // defaultTheme="light"
      onChangeTheme={(next) => {
        setTheme(next as any)
      }}
    >
      <TamaguiProvider
        config={tamaguiConfig}
        disableRootThemeClass
        defaultTheme={theme}
      >
        {children}
      </TamaguiProvider>
    </NextThemeProvider>
  )
}
```

### Static CSS Output

You can generate a static CSS file for your themes and tokens. Use either the
CLI or the Next.js plugin:

```tsx fileName="next.config.js"
const tamaguiPlugin = withTamagui({
  config: './tamagui.config.ts',
  components: ['tamagui'],
  outputCSS:
    process.env.NODE_ENV === 'production' ? './public/tamagui.css' : null,
  // faster dev mode, keeps debugging helpers:
  disableExtraction: process.env.NODE_ENV === 'development',
})
```

Then link the generated CSS file in your `app/layout.tsx`:

```tsx fileName="app/layout.tsx"
import '../public/tamagui.css'
```

<Notice theme="green">
  With React 19, Tamagui automatically injects runtime styles via style tags on
  the server. The `outputCSS` file handles themes and tokens generated at build
  time, so you don't need any `getCSS()` calls in your provider.
</Notice>

### Font loading

To ensure font loads globally, add a global style to `useServerInsertedHTML` in
`NextTamaguiProvider.tsx`:

```tsx fileName="NextTamaguiProvider.tsx"
<style jsx global>{`
  html {
    font-family: 'Inter';
  }
`}</style>
```

## NextThemeProvider

The `NextThemeProvider` is a provider that allows you to set the theme for your
app. It also provides a hook to access the current theme and a function to
change the theme.

<PropsTable
  data={[
    {
      name: 'skipNextHead',
      required: false,
      type: 'boolean',
      description: `Required in app router. The internal usage of next/head is not supported in the app directory, so you need to add it.`,
    },
    {
      name: 'enableSystem',
      required: false,
      type: 'boolean',
      description: `Whether to switch between dark and light themes based on prefers-color-scheme.`,
    },
    {
      name: 'defaultTheme',
      required: false,
      type: 'string',
      description:
        'If enableSystem is `false`, the default theme is light. Default theme name (for v0.0.12 and lower the default was light).',
    },
    {
      name: 'forcedTheme',
      required: false,
      type: 'string',
      description: 'Forced theme name for the current page.',
    },
    {
      name: 'onChangeTheme',
      required: false,
      type: '(name: string) => void',
      description:
        'Used to change the current theme. The function receives the theme name as a parameter.',
    },
    {
      name: 'systemTheme',
      required: false,
      type: 'string',
      description: 'System theme name for the current page.',
    },
    {
      name: 'enableColorScheme',
      required: false,
      type: 'boolean',
      description: `Whether to indicate to browsers which color scheme is used (dark or light) for built-in UI like inputs and buttons.`,
    },
    {
      name: 'disableTransitionOnChange',
      required: false,
      type: 'boolean',
      description: `Disable all CSS transitions when switching themes.`,
    },
    {
      name: 'storageKey',
      required: false,
      type: 'string',
      description: `Key used to store theme setting in localStorage.`,
    },
    {
      name: 'themes',
      required: false,
      type: 'string[]',
      description: `List of all available theme names.`,
    },
    {
      name: 'value',
      required: false,
      type: 'ValueObject',
      description: `Mapping of theme name to HTML attribute value. Object where key is the theme name and value is the attribute value.`,
    },
  ]}
/>

### Theme toggle

If you need to access the current theme, say for a toggle button, you will then
use the `useThemeSetting` hook. We'll release an update in the future that makes
this automatically work better with Tamagui's built-in `useThemeSetting`.

```tsx fileName="SwitchThemeButton.tsx"
import { useState } from 'react'
import { Button, useIsomorphicLayoutEffect } from 'tamagui'
import { useThemeSetting, useRootTheme } from '@tamagui/next-theme'

export const SwitchThemeButton = () => {
  const themeSetting = useThemeSetting()
  const [theme] = useRootTheme()

  const [clientTheme, setClientTheme] = useState<string | undefined>('light')

  useIsomorphicLayoutEffect(() => {
    setClientTheme(themeSetting.forcedTheme || themeSetting.current || theme)
  }, [themeSetting.current, themeSetting.resolvedTheme])

  return (
    <Button onPress={themeSetting.toggle}>Change theme: {clientTheme}</Button>
  )
}
```

## Turbopack

Turbopack doesn't support Webpack plugins, so you'll use the Tamagui CLI to
optimize your build. In dev mode, Tamagui works without any setup.

### Setup

Install the CLI:

```bash
yarn add -D @tamagui/cli
```

Create `tamagui.build.ts`:

```ts fileName="tamagui.build.ts"
import type { TamaguiBuildOptions } from '@tamagui/core'

export default {
  components: ['@tamagui/core'], // or ['tamagui']
  config: './tamagui.config.ts',
  outputCSS: './public/tamagui.css',
} satisfies TamaguiBuildOptions
```

Your `next.config.js` needs some configuration for Turbopack:

```js fileName="next.config.js"
module.exports = {
  // Some Tamagui packages may need transpiling
  transpilePackages: ['@tamagui/next-theme', '@tamagui/lucide-icons'],

  turbopack: {
    resolveAlias: {
      'react-native': 'react-native-web',
      'react-native-svg': '@tamagui/react-native-svg',
    },
  },
}
```

<Notice theme="blue">
  The `transpilePackages` array may need additional packages depending on which
  Tamagui packages you use. If you see module resolution errors, try adding the
  problematic package to this array.
</Notice>

### Build Scripts

The CLI can wrap your build command, optimizing files beforehand and restoring
them after:

```json fileName="package.json"
{
  "scripts": {
    "dev": "next dev --turbopack",
    "build": "tamagui build --target web ./src -- next build"
  }
}
```

The `--` separator tells the CLI to run `next build` after optimization, then
restore your source files automatically.

You can also target specific files or use `--include`/`--exclude` patterns:

```bash
# Target specific files
tamagui build --target web ./src/components/Button.tsx ./src/components/Card.tsx -- next build

# Use glob patterns
tamagui build --target web --include "src/components/**/*.tsx" --exclude "src/components/**/*.test.tsx" ./src -- next build
```

### CSS Setup

The CLI generates theme CSS to `outputCSS`. Commit this file to git and import
it in your layout:

```tsx fileName="app/layout.tsx"
import '../public/tamagui.css'
import { TamaguiProvider } from '@tamagui/core'
import config from '../tamagui.config'

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body>
        <TamaguiProvider config={config}>{children}</TamaguiProvider>
      </body>
    </html>
  )
}
```

<Notice theme="blue">
  With React 19, Tamagui automatically injects runtime styles via style tags.
  The `outputCSS` file handles themes and tokens that are generated at build
  time.
</Notice>

Run `npx tamagui build` once to generate the initial CSS file, then commit it.

### CI Verification

Use `--expect-optimizations` to fail builds if the compiler optimizes fewer than
the expected minimum number of components:

```json
{
  "build": "tamagui build --target web --expect-optimizations 5 ./src -- next build"
}
```

This will fail the build if fewer than 5 components are optimized, helping catch
configuration issues in CI.


## guides/one

---
title: One Guide
description: How to set up Tamagui with One
---

[One](https://onestack.dev) is a React framework for web, iOS, and Android, built on the power of Vite. It offers seamless cross-platform development capabilities and integrates well with Tamagui.

## Install

First, create a new One project:
```bash
npx one
```

Add Tamagui and its dependencies:

```bash
yarn add tamagui @tamagui/config
```

## Configuration

Create a `tamagui.config.ts` file in your project root:

```tsx fileName="tamagui.config.ts"
import { defaultConfig } from '@tamagui/config/v5'
import { createTamagui } from 'tamagui'

const tamaguiConfig = createTamagui(defaultConfig)

export default tamaguiConfig

// this is important!
export type Conf = typeof tamaguiConfig

declare module 'tamagui' {
  interface TamaguiCustomConfig extends Conf {}
}
```

Update your `vite.config.ts`:

```tsx fileName="vite.config.ts"
import { one } from 'one/vite'
import { tamaguiPlugin } from '@tamagui/vite-plugin'
import type { UserConfig } from 'vite'

export default {
  plugins: [
    one({
      web: {
        defaultRenderMode: 'ssg',
      },
    }),
    tamaguiPlugin({
      config: './tamagui.config.ts',
      components: ['tamagui'],
    }),
  ],

  // Vite 6 style configuration
  ssr: {
    noExternal: true,
  },

  optimizeDeps: {
    include: [
      '@tamagui/core',
      '@tamagui/config',
    ],
  },

  build: {
    cssTarget: 'safari15',
  },
} satisfies UserConfig
```

## Setup Tamagui Provider

Update your root layout file (typically `app/_layout.tsx`):

```tsx fileName="app/_layout.tsx"
import { TamaguiProvider } from 'tamagui'
import { Slot } from 'one'
import config from '../tamagui.config'

export default function Layout() {
  return (
    <TamaguiProvider config={config}>
      <Slot />
    </TamaguiProvider>
  )
}
```

## Usage

Now you can use Tamagui components in your One app:

```tsx fileName="app/index.tsx"
import { Button, Text, YStack } from 'tamagui'

export default function Home() {
  return (
    <YStack f={1} jc="center" ai="center" p="$4" gap="$4">
      <Text fontSize="$6">Welcome to Tamagui with One!</Text>
      <Button>Click me</Button>
    </YStack>
  )
}
```

## Themes

To support light and dark modes, you can use One's built-in color scheme support along with Tamagui's theming system. Update your root layout:

```tsx fileName="app/_layout.tsx"
import { TamaguiProvider, Theme } from 'tamagui'
import { Slot } from 'one'
import { useColorScheme } from 'react-native'
import config from '../tamagui.config'

export default function Layout() {
  const colorScheme = useColorScheme()

  return (
    <TamaguiProvider config={config} defaultTheme={colorScheme}>
      <Theme name={colorScheme}>
        <Slot />
      </Theme>
    </TamaguiProvider>
  )
}
```

## Performance

For better performance, you can use the `outputCSS` option in the Tamagui Vite plugin:

```tsx fileName="vite.config.ts"
tamaguiPlugin({
  config: './tamagui.config.ts',
  components: ['tamagui'],
  outputCSS: process.env.NODE_ENV === 'production' ? './public/tamagui.css' : null,
})
```

Then import the CSS in your root layout:

```tsx fileName="app/_layout.tsx"
import '../public/tamagui.css'
// ... rest of your imports and component
```

## Conclusion

You now have Tamagui set up with [One](https://onestack.dev)! You can start building your cross-platform app using Tamagui's powerful styling system and One's seamless development experience.


## guides/theme-builder

---
title: Creating Themes with Tamagui
description: Learn how to create a suite of themes for a Tamagui app
demoName: ThemeBuilder
---

<HeroContainer noScroll>
  <ThemeBuilderDemo />
</HeroContainer>

<IntroParagraph>
  Tamagui themes start simple, but can do some pretty powerful things. To make
  them easier to generate, we've built a few helpers. You can always just skip
  themes, or add a single basic theme if you prefer. This guide is for users
  wanting to generate a more complex suite of themes.
</IntroParagraph>

We have three ways to generate themes, from simplest to most powerful:

| Helper | Best for |
|--------|----------|
| **`createV5Theme`** | Quick start with sensible defaults. Minimal config. |
| **`createThemes`** | Custom palettes and structure while keeping conventions. |
| **`createThemeBuilder`** | Full control over every aspect of theme generation. |

We've also released [Theme](/theme), a free point-and-click way to create themes.

---

## createV5Theme

The simplest way to get a complete theme suite. Call it with no arguments for production-ready defaults, or pass options to customize.

```tsx
import { createV5Theme } from '@tamagui/themes/v5'

// zero-config - includes light, dark, accent, and color themes
export const themes = createV5Theme()
```

### Customizing

```tsx
import { createV5Theme, defaultChildrenThemes } from '@tamagui/themes/v5'
import { orange, orangeDark } from '@tamagui/colors'

export const themes = createV5Theme({
  // override base palettes
  lightPalette: ['#fff', '#f8f8f8', ...],
  darkPalette: ['#000', '#111', ...],

  // add/override color themes
  childrenThemes: {
    ...defaultChildrenThemes,
    orange: { light: orange, dark: orangeDark },
  },

  // disable component themes
  componentThemes: false,
})
```

### Options

- `lightPalette` / `darkPalette` - Override base 12-color palettes
- `childrenThemes` - Color themes (blue, red, etc). Accepts Radix color objects directly
- `grandChildrenThemes` - Third-level themes (defaults to `{ accent: { template: 'inverse' } }`)
- `componentThemes` - Component theme mappings, or `false` to disable

---

## createThemes

More control over structure while still getting automatic palette interpolation and component themes.

### Quick Start

```tsx
import { createThemes } from '@tamagui/theme-builder'

export const themes = createThemes({
  base: {
    palette: {
      light: ['#fff', '#000'],
      dark: ['#000', '#fff'],
    },
  },
})
```

### Full Example

```tsx
import { createThemes, defaultComponentThemes } from '@tamagui/theme-builder'
import * as Colors from '@tamagui/colors'

export const themes = createThemes({
  componentThemes: defaultComponentThemes,

  base: {
    palette: {
      light: ['#fff', '#f2f2f2', '#e0e0e0', '#999', '#666', '#333', '#000'],
      dark: ['#000', '#111', '#222', '#666', '#999', '#ccc', '#fff'],
    },
    extra: {
      light: { ...Colors.blue, shadowColor: 'rgba(0,0,0,0.1)' },
      dark: { ...Colors.blueDark, shadowColor: 'rgba(0,0,0,0.4)' },
    },
  },

  accent: {
    palette: {
      light: ['#000', '#333', '#666', '#999', '#ccc', '#eee', '#fff'],
      dark: ['#fff', '#eee', '#ccc', '#999', '#666', '#333', '#000'],
    },
  },

  childrenThemes: {
    blue: { palette: { light: Object.values(Colors.blue), dark: Object.values(Colors.blueDark) } },
    red: { palette: { light: Object.values(Colors.red), dark: Object.values(Colors.redDark) } },
  },

  grandChildrenThemes: {
    accent: { template: 'inverse' },
  },
})
```

### Structure

`createThemes` generates this hierarchy:

```
light / dark                    # base themes
├── light_accent / dark_accent  # accent themes
├── light_blue / dark_blue      # child themes
│   └── light_blue_accent       # grandchild themes
└── light_Button / dark_Button  # component themes
```

### Configuration

#### `base` (required)

```tsx
base: {
  palette: { light: string[], dark: string[] },
  // or single array (auto-reversed for dark):
  palette: string[],
  template: 'base' | 'surface1' | 'surface2' | 'surface3' | 'inverse',
  extra: { light: {...}, dark: {...} }, // non-inherited values
}
```

#### `accent`, `childrenThemes`, `grandChildrenThemes`

```tsx
accent: { palette: { light: [...], dark: [...] } }

childrenThemes: {
  blue: { palette: { light: [...], dark: [...] }, template: 'base' },
}

grandChildrenThemes: {
  accent: { template: 'inverse' }, // template-only inherits parent palette
}
```

#### `templates`

Override default templates. Maps property names to palette indices:

```tsx
templates: {
  base: { background: 6, color: -1, borderColor: 9 },
  surface1: { background: 7, color: -1, borderColor: 10 },
}
```

Defaults: `base`, `surface1`, `surface2`, `surface3`, `alt1`, `alt2`, `inverse`

#### `componentThemes`

```tsx
componentThemes: {
  Button: { template: 'surface3' },
  Card: { template: 'surface1' },
}
// or disable:
componentThemes: false
```

#### `getTheme`

Customize any generated theme:

```tsx
getTheme: ({ name, theme, scheme, level, palette }) => ({
  ...theme,
  shadowColor: scheme === 'dark' ? 'rgba(0,0,0,0.5)' : 'rgba(0,0,0,0.1)',
})
```

Parameters: `name`, `theme`, `scheme` ('light'|'dark'), `level` (1=base, 2=children, 3=grandchildren), `parentName`, `parentNames`, `palette`, `template`

### Generated Theme Shape

```tsx
{
  background, backgroundHover, backgroundPress, backgroundFocus,
  color, colorHover, colorPress, colorFocus,
  borderColor, borderColorHover, borderColorPress, borderColorFocus,
  placeholderColor, outlineColor,
  color1...color12,           // full palette scale
  background0...background08, // transparent variants
  accent1...accent12,         // if accent defined
}
```

---

## createThemeBuilder

The low-level API that powers `createThemes`. Use this when you need complete control over palette structure, template definitions, and theme hierarchy.

```tsx
import { createThemeBuilder } from '@tamagui/theme-builder'

const themesBuilder = createThemeBuilder()
  .addPalettes({
    dark: ['#000', '#111', '#222', '#999', '#ccc', '#eee', '#fff'],
    light: ['#fff', '#eee', '#ccc', '#999', '#222', '#111', '#000'],
  })
  .addTemplates({
    base: { background: 0, color: -0 },
    subtle: { background: 1, color: -1 },
  })
  .addThemes({
    light: { template: 'base', palette: 'light' },
    dark: { template: 'base', palette: 'dark' },
  })
  .addChildThemes({
    subtle: { template: 'subtle' },
  })

export const themes = themesBuilder.build()
```

### Build-time Generation

Optionally generate themes at build time to reduce bundle size:

```tsx
// next.config.js
withTamagui({
  themeBuilder: {
    input: './themes-input.tsx',
    output: './themes.tsx',
  },
})
```

Or use the CLI: `npx @tamagui/cli generate-themes ./src/themes-in.ts ./src/themes-out.ts`

---

## Concepts

### Palettes

A palette is a gradient of colors from background to foreground:

<Blog.ThemeBuilder.ExamplePalette showLabels />

```tsx
const dark_blue = [
  'hsl(212, 35.0%, 9.2%)',  // background
  'hsl(216, 50.0%, 11.8%)',
  // ...
  'hsl(206, 98.0%, 95.8%)', // foreground
]
```

### Templates

Templates map property names to palette indices:

```tsx
const template = { background: 0, color: 12 }
// Negative indices count from end: -1 = last, -2 = second-to-last
```

### Sub-themes

Underscore in theme names defines nesting: `dark_subtle` is a sub-theme of `dark`.

```tsx
<Theme name="dark">
  <Box />  {/* uses dark theme */}
  <Theme name="subtle">
    <Box />  {/* uses dark_subtle theme */}
  </Theme>
</Theme>
```

### Component Themes

Named components automatically pick up matching sub-themes:

```tsx
const Button = styled(View, { name: 'Button', ... })

// If dark_Button theme exists, <Button /> uses it automatically
```

### getTheme Callback

Both `createThemes` and `createThemeBuilder` support `getTheme` for customization:

```tsx
.getTheme(({ theme, scheme, level }) => ({
  ...theme,
  customBorder: scheme === 'dark' ? '#333' : '#ddd',
}))
```

### nonInheritedValues

Add values that don't cascade to child themes:

```tsx
.addThemes({
  light: {
    template: 'base',
    palette: 'light',
    nonInheritedValues: {
      blue1: '#e0f2fe',
      shadowColor: 'rgba(0,0,0,0.1)',
    },
  },
})
```


## guides/vite

---
title: Vite Guide
description: How to set up Tamagui with Vite
---

Tamagui now has two plugins for Vite: one that sets up everything you need to get going, and a second that adds CSS compilation. Both are included in the `@tamagui/vite-plugin` package.

## Install

<Notice theme="green">
  For a full-featured example, you can create a new app using `npm create tamagui@latest` and select the 'Simple Web' option which includes a Vite setup.
</Notice>

Create a new [Vite](https://vitejs.dev/guide/#scaffolding-your-first-vite-project) project:

```bash
npm create vite@latest
```

Add `@tamagui/vite-plugin`:

```bash
yarn add @tamagui/vite-plugin
```

### Configuration

Update your `vite.config.ts`:

```tsx fileName="vite.config.ts"
import react from '@vitejs/plugin-react-swc'
import { tamaguiPlugin } from '@tamagui/vite-plugin'

export default {
  plugins: [
    react(),
    tamaguiPlugin({
      // points to your tamagui config file
      config: 'src/tamagui.config.ts',
      // points to any linked packages or node_modules
      // that have tamagui components to optimize
      components: ['tamagui'],
      // turns on the optimizing compiler
      optimize: true,
    }),
  ].filter(Boolean),
}
```

Or a minimal manual setup for Vite that just adds some compatibility for react-native-web and react-native extensions:

```tsx showMore
config.define = {
  __DEV__: `${process.env.NODE_ENV === 'development' ? true : false}`,
  'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
}

config.resolve.alias['react-native'] = 'react-native-web'

// set up web extensions
config.optimizeDeps.esbuildOptions = {
  ...config.optimizeDeps.esbuildOptions,
  resolveExtensions: [
    '.web.js',
    '.web.jsx',
    '.web.ts',
    '.web.tsx',
    '.mjs',
    '.js',
    '.mts',
    '.ts',
    '.jsx',
    '.tsx',
    '.json',
  ],
  loader: {
    '.js': 'jsx',
  },
}
```


## guides/webpack

---
title: Webpack Guide
description: How to set up Tamagui with Webpack
---

First, install [webpack and webpack-cli](https://webpack.js.org/guides/installation/):

```bash
yarn add -D webpack webpack-cli
```

Then install the Tamagui plugin:

```bash
yarn add -D tamagui-loader
```

### Configuration

You can then use the plugin in `webpack.config.js`:

```tsx fileName="webpack.config.js"
const { TamaguiPlugin } = require('tamagui-loader')

config.plugins.push(
  new TamaguiPlugin({
    config: './src/tamagui.config.ts',
    components: ['tamagui'],
  }),
)
```

Or use a minimal manual setup:

```tsx showMore fileName="webpack.config.js"
// some stuff for react-native
config.plugins.push(
  new webpack.DefinePlugin({
    process: {
      env: {
        __DEV__: process.env.NODE_ENV === 'development' ? 'true' : 'false',
        NODE_ENV: JSON.stringify(process.env.NODE_ENV),
      },
    },
  })
)

config.resolve.alias['react-native$'] = 'react-native-web'

// set up web extensions
compiler.options.resolve.extensions = [
  '.web.tsx',
  '.web.ts',
  '.web.js',
  '.ts',
  '.tsx',
  '.js',
]
```

## Usage

To run server locally, install [webpack-dev-server](https://github.com/webpack/webpack-dev-server):

```bash
yarn add -D webpack-dev-server
```

You can then use the following command to start the server:

```bash
yarn run webpack serve
```


## intro/benchmarks

---
title: Benchmarks
description: Performance tests and comparisons
---

Tamagui compares well to the fastest libraries at runtime, and the compiler
further optimizes your styled components, flattening them down and hoisting
objects out of your rendering loop (and extracting CSS on the web).

With the compiler on Tamagui will output near optimal React code despite giving
many nice shorthand style props. It has a higher impact on web, but with initial
support for flattening more dynamic styled components now landed, Tamagui does
flatten a very high % of your code on all platforms. We look forward to updating
our benchmarks as we expect we've improved in a number of areas.

It's important to note that benchmarks never show a complete picture and that
these benchmarks are not being actively updated as of early 2023. Our plan is to
build out a better benchmarking setup to update this page.

## React Native

In [this benchmark](https://github.com/tamagui/benchmarks) Tamagui is within 10%
of the speed of vanilla React Native, even when using nearly all of Tamagui's
features. We render list of 28 items with a few sections, text and images.
Average of 5 runs on a Apple M2 Air:

<BenchmarkChartNative />

Running Tamagui without the compiler averages `125ms` or ~12% slower. Note that
the compiler is much more effective on the web as it turns many more props into
CSS and both bundle size and render performance are more important.

## Web

We forked and ran a few more
[benchmarks](https://github.com/tamagui/tamagui/tree/24ac758bbe5d6ff2f67f25071df4286e0594f578/starters/benchmark)
for the web.

**Legend:** RNW = react-native-web, SC = styled-components

### Simple component

Timing rendering a simple custom component.

<BenchmarkChart
  data={[
    { name: 'Tamagui', value: 0.018 },
    { name: 'RNW', value: 0.057 },
    { name: 'Dripsy', value: 0.042 },
    { name: 'Stitches', value: 0.023 },
    { name: 'Emotion', value: 0.041 },
  ]}
/>

### Updating variants

Changing variants is fast at runtime, and even faster when compiled:

<BenchmarkChart
  data={[
    { name: 'Tamagui', value: 0.02 },
    { name: 'RNW', value: 0.063 },
    { name: 'Dripsy', value: 0.108 },
    { name: 'Stitches', value: 0.037 },
    { name: 'Emotion', value: 0.069 },
    { name: 'SC', value: 0.081 },
  ]}
/>

> Since styled-components and Emotion don't offer a first-class variant API,
> this was done via prop interpolation.

### Updating inline styles

Tamagui has a big advantage for inline styles, it's the only library to compile
them and flatten the tree.

<BenchmarkChart
  data={[
    { name: 'Tamagui', value: 0.025 },
    { name: 'RNW', value: 0.06 },
    { name: 'Dripsy', value: 0.266 },
    { name: 'Stitches', value: 0.027 },
    { name: 'Emotion', value: 0.047 },
  ]}
/>

---

### Fully dynamic styles

These benchmarks don't benefit from the compiler. The React Native API surface
is much more feature-rich than DOM.

<BenchmarkChart
  data={[
    { name: 'Tamagui', value: 31.0 },
    { name: 'Dripsy', value: 57.5 },
    { name: 'Stitches', value: 14.5 },
    { name: 'Emotion', value: 49.01 },
  ]}
/>

> Note: This test was taken from the
> [styled-components benchmarks](https://github.com/styled-components/styled-components/tree/main/packages/benchmarks).

### Mounting deep tree

In this test, we mount a tree with many nested nodes.

<BenchmarkChart
  data={[
    { name: 'Tamagui', value: 18.61 },
    { name: 'Dripsy', value: 44.43 },
    { name: 'Stitches', value: 8.32 },
    { name: 'Emotion', value: 42.49 },
    { name: 'SC', value: 51.4 },
  ]}
/>

- SC - Styled Components

> Note: This test was taken from the
> [styled-components benchmarks](https://github.com/styled-components/styled-components/tree/main/packages/benchmarks).


## intro/colors

---
title: Colors
---

<Notice theme="blue">
  This is the Tamagui documentation site's [own color
  palette](https://github.com/tamagui/tamagui/blob/main/code/core/themes/src/tokens.tsx).
  You can customize your own using
  [@tamagui/create-theme](https://github.com/tamagui/tamagui/blob/738011d99e1dec6002a916edf424b75bb6cc0336/packages/themes/src/themes.tsx).
  See
  [@tamagui/colors](https://github.com/tamagui/tamagui/tree/main/code/core/colors/src)
  for Radix colors, or BYO color palette.
</Notice>

<ColorsDemo />


## intro/compiler-install

---
title: Tamagui Compiler
description: Adding the compiler to your apps
---

<IntroParagraph>
  The Tamagui Compiler significantly improves performance of both web and native
  applications through partial analysis and view flattening.
</IntroParagraph>

See the [Benchmarks](/docs/intro/benchmarks) or a more
[in-depth background](/docs/intro/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
statically analyze them and optimize them down to platform-native primitives.
The end result is less abstraction - like a `div` on web, or plain React Native
`View` on native:

<br />
<br />
<TamaguiExamplesCode />

<Notice theme="blue">
  The compiler generates built versions of your components and config into a
  `.tamagui` directory. You'll want to add that directory to your `.gitignore`.
</Notice>

## Install

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',
  // optional advanced optimization of styled() definitions within your app itself, not just ones in your components option
  // default is false
  enableDynamicEvaluation: false,
}

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:

```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.

Add `@tamagui/vite-plugin` and update your `vite.config.ts`:

```tsx
import { tamaguiPlugin } from '@tamagui/vite-plugin'

export default defineConfig({
  plugins: [
    tamaguiPlugin({
      // points to your tamagui config file
      config: 'src/tamagui.config.ts',
      // points to any linked packages or node_modules
      // that have tamagui components to optimize
      components: ['tamagui'],
      // turns on the optimizing compiler
      optimize: true,
    }),
  ],
})
```

### Next.js

See the ([guide](/docs/guides/next-js)) for more complete setup.

Add `@tamagui/next-plugin` and configure your `next.config.js`. Here we show a
fuller scope of the options

```js
// note next-compose-plugins somewhat unmaintained
// you can use a simple two-liner instead, see:
// https://github.com/cyrilwanner/next-compose-plugins/issues/59#issuecomment-1192523231
const withPlugins = require('next-compose-plugins')
const { withTamagui } = require('@tamagui/next-plugin')

export default withPlugins([
  withTamagui({
    config: './tamagui.config.ts',
    components: ['tamagui'],

    // rest are all optional:

    // disable static extraction, faster to iterate in dev mode (default false)
    disableExtraction: process.env.NODE_ENV === 'development',

    // Exclude react-native-web modules to lighten bundle
    excludeReactNativeWebExports: ['Switch', 'ProgressBar', 'Picker'],

    // By default, we configure webpack to pass anything inside your root or design system
    // to the Tamagui loader. If you are importing files from an external package, use this:
    shouldExtract: (path: string, projectRoot: string) => {
      if (path.includes('../packages/myapp')) {
        return true
      }
    },

    // Advanced:

    // Many packages give difficulty to the nextjs server-side (node) runtime when un-bundled.
    // for example, tamagui configures aliases like react-native => react-native-web.
    // if you're running into a module that has errors importing react-native, you'll want to
    // use a custom shouldExcludeFromServer function to include it (or override the default).
    // this is the exact same return type as webpack.externals.
    // returning undefined will let tamagui handle it, boolean or other values to override.
    shouldExcludeFromServer: ({ fullPath, request }) => {
      if (fullPath.includes('my-module')) {
        return 'commonjs ' + commonjs
      }
      if (request === 'some-hard-to-bundle-package') {
        return true
      }
    },
  }),
])
```

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.

### Babel / Metro

Note that the `@tamagui/babel-plugin` is completely optional, and on native
Tamagui doesn't optimize as much as on web, so leaving it out is actually
recommended to start. If later on you feel the need for a bit more speed, you
can try adding it.

```bash
yarn add @tamagui/babel-plugin
```

Add to your `babel.config.js`:

```js
module.exports = {
  plugins: [
    [
      '@tamagui/babel-plugin',
      {
        components: ['tamagui'],
        config: './tamagui.config.ts',
        importsWhitelist: ['constants.js', 'colors.js'],
        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's as simple as adding the babel plugin.

### 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/guides/cli).

#### Setup

1. Install:

```bash
yarn add -D @tamagui/cli
```

2. Create a `tamagui.build.ts` config file in your project root:

```ts
import type { TamaguiBuildOptions } from 'tamagui'

export default {
  config: './tamagui.config.ts',
  components: ['tamagui'],
  importsWhitelist: ['constants.js', 'colors.js'],
  outputCSS: './public/tamagui.css',
} satisfies TamaguiBuildOptions
```

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

# 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` or `.ios.tsx` extensions are optimized for web only
- Files with `.native.tsx` or `.android.tsx` extensions are optimized for native
  only
- Base files (`.tsx`) without platform-specific versions are optimized for all
  platforms
- If both `.web.tsx` and `.native.tsx` exist, the base `.tsx` file is skipped

#### 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** - just 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"
  }
}
```

The Tamagui CLI optimizes your components in-place, then your bundler processes
the already-optimized files.

**Learn more:** See the [CLI Guide](/docs/guides/cli) for documentation on all
CLI commands including `check`, `generate`, `add`, and more.

## Props

All compiler plugins accept the same options:

<PropsTable
  data={[
    {
      name: 'config',
      required: true,
      type: 'string',
      typeSimple: 'enum',
      description:
        'Relative path to your tamagui.config.ts file which should export default the result from createTamagui.',
    },
    {
      name: 'components',
      required: false,
      type: 'string[]',
      typeSimple: 'enum',
      default: "['tamagui']",
      description: `Array of npm modules containing Tamagui components which you'll be using in your app. For example:  if you are using the base Tamagui components. This directs the compiler to load and optimize.`,
    },
    {
      name: 'importsWhitelist',
      required: false,
      type: 'string[]',
      typeSimple: 'enum',
      description: `Array of whitelisted file paths (always end in .js) which the compiler may try and import and parse at build-time. It is normalized to ".js" ending for all file extensions (js, jsx, tsx, ts). This usually should be set to something like ['constants.js', 'colors.js'] for example, where you have a couple mostly static files of constants that are used as default values for styles.`,
    },
    {
      name: 'logTimings',
      required: false,
      type: 'boolean',
      typeSimple: 'enum',
      default: 'true',
      description:
        'Tamagui outputs information for each file it compiles on how long it took to run, how many components it optimized, and how many it flattened. Set to false to disable these logs.',
    },
    {
      name: 'disable',
      required: false,
      type: 'boolean',
      typeSimple: 'enum',
      default: 'false',
      description: 'Disable everything - debug and extraction.',
    },
    {
      name: 'disableExtraction',
      required: false,
      type: 'boolean',
      typeSimple: 'enum',
      default: 'false',
      description:
        'Disable extraction to CSS completely, instead fully relying on runtime. Setting this to true speed up development as generally your app will hot reload the Tamagui configuration itself.',
    },
    {
      name: 'disableDebugAttr',
      required: false,
      type: 'boolean',
      typeSimple: 'enum',
      default: 'false',
      description:
        'If enabled along with disableExtraction, all parsing will turn off. Normally turning off disableExtraction will keep the helpful debug attributes in DOM',
    },
    {
      name: 'disableFlattening',
      required: false,
      type: 'boolean',
      typeSimple: 'enum',
      default: 'false',
      description: 'Turns off tree-flattening.',
    },
    {
      name: 'enableDynamicEvaluation',
      required: false,
      type: 'boolean',
      typeSimple: 'enum',
      default: 'false',
      description:
        '(Experimental) Enables further extracting of any styled component, even if not in your components. See below for more information.',
    },
  ]}
/>

### Dynamic Evaluation

By default the Tamagui compiler only optimizes `styled` expressions found in the
modules defined by your `components` config. This means if you do an inline
`styled()` inside your actual app directory, it will default to runtime style
insertion.

This is typically Good Enough™️. As long as you define most of your common
components there, you'll get a very high hit rate of compiled styles being used
and runtime generation being skipped, as atomic styles with your design system
tokens will be mostly pre-generated.

Tamagui has _experimental_ support for loading any component, even if it occurs
somewhere outside your configured components modules. This is called "dynamic
loading", for now. You can enable it with the setting `enableDynamicEvaluation`
as seen above in the props table.

The way it works is, when the compiler detects a styled() expression outside one
of the defined component directories, it will run the following:

1. First, read the file and use a custom `babel` transform to _force_ all
   top-level variables to be exported.
2. Then, run `esbuild` and bundle the entire file to a temporary file in the
   same directory, something like `.tamagui-dynamic-eval-ComponentName.js`
3. Now, read the file in and load all new definitions found.
4. Finally, continue with optimization, using the newly optimized component.

You may see why this is experimental. It's very convenient as a developer, but
has a variety of edge cases that can be confusing or breaking, and we want to
avoid installation woes. Though it does continue on error and work generally, it
outputs warnings in Webpack currently due to our plugin not properly indicating
to Webpack about the new files (a fixable bug), which causes big yellow warning
output and a cache de-opt.

We're leaving this feature under the environment variable while it matures. Let
us know if you find it useful.

### 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 { View } from '@tamagui/core'

export default () => <View disableOptimization />
```

### Web-only apps

If you want autocompleted imports of `react-native` without having to install
all the weight of react-native, you can set `react-native` version to `0.0.0`,
and add `@types/react-native` at the latest version.


## intro/errors

---
title: Errors
---

### 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, we
include an upgrade script with the starter kits that you can call with "yarn
upgrade:tamagui" to help with this.

You may want to clear your node_modules as well 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.

### Warning 001

#### Ignorable modules

This error means the compiler can't bundle some dependencies into what it reads
at compile-time in order to optimize. It's not necessarily an error and usually
won't de-opt.

To see a full stack trace set:

```bash
DEBUG=tamagui
```

To disable the warning you can set:

```bash
TAMAGUI_IGNORE_BUNDLE_ERRORS=some_module_name,some_other_module_name
# Or to disable them all (not recommended):
TAMAGUI_IGNORE_BUNDLE_ERRORS=true
```

### Warning 002

You're rendering a Tamagui <Adapt /> component without nesting it inside a
parent that is able to adapt.


## intro/faq

---
title: FAQ
description: Common problems, questions and answers.
---

##


## intro/installation

---
title: Installation
description: Get Tamagui set up, step by step
---

<IntroParagraph>
  Setting up Tamagui can be easy, or take a bit of investment. We highly
  recommend starting with our starter templates and preset config.
</IntroParagraph>

Use `npm create tamagui@latest` to pick one of our starter templates.

## Requirements

- **React Native 0.76+** with New Architecture enabled (for native apps)
- React 18.2+ or React 19+
- TypeScript 5.0+ (recommended)

Tamagui 2 takes advantage of new style features in React Native 0.76+ including
`boxShadow`, `filter`, and more. On web, there are no version restrictions.

## Installation

To install from scratch:

```bash
yarn add @tamagui/core
```

Core is just the style library. If you plan to use our full UI kit, you can
avoid installing `@tamagui/core` and instead:

```bash
yarn add tamagui
```

The `tamagui` package is a superset of core, so anywhere the docs reference
`@tamagui/core`, you can replace it with `tamagui`.

Next, you'll want to set up your [configuration](/docs/core/configuration) and
provide it with `TamaguiProvider`:

```tsx fileName="App.tsx"
import { TamaguiProvider, View } from '@tamagui/core'
import config from './tamagui.config' // your configuration

export default function App() {
  return (
    <TamaguiProvider config={config}>
      <View width={200} height={200} backgroundColor="$background" />
    </TamaguiProvider>
  )
}
```

We have a recommended preset configuration called `@tamagui/config`:

```bash
yarn add @tamagui/config
```

You can use it like so:

```tsx fileName="App.tsx"
import { TamaguiProvider, createTamagui } from '@tamagui/core'
import { defaultConfig } from '@tamagui/config/v5'

// you usually export this from a tamagui.config.ts file
const config = createTamagui(defaultConfig)

type Conf = typeof config

// make imports typed
declare module '@tamagui/core' {
  interface TamaguiCustomConfig extends Conf {}
}

export default () => {
  return (
    <TamaguiProvider config={config}>{/* your app here */}</TamaguiProvider>
  )
}
```

And that's it!

<Preview>
  <DemoButton />
</Preview>

```tsx class=preview line=5
import { Button } from 'tamagui'

export default function Demo() {
  return <Button theme="blue">Hello world</Button>
}
```

From here, we recommend
[spending some time understanding configuration](/docs/core/configuration).

100% of Tamagui features work at both runtime and compile-time, which makes it
both easy to use and fast. Because it works fully at runtime, and because we've
invested a lot into building it such that it doesn't need any special bundler
configuration to work on either native or web, you can begin using Tamagui with
just the above setup.

Later on, you can [set up the compiler](/docs/intro/compiler-install) to gain
more performance and some nice development helpers.

---

## Next Steps

Once you have Tamagui installed, you'll want to explore:

- **[CLI](/docs/guides/cli)** - Command-line tools for building, optimizing, and managing your Tamagui projects
- **[Bundler Setup](/docs/guides/next-js)** - Integration guides for Next.js, Expo, Vite, Webpack, Metro, and more

Later on, you can [set up the compiler](/docs/intro/compiler-install) to gain
more performance and some nice development helpers.

---

## Bundler Guides

Tamagui generally doesn't require any special bundler setup as we've worked hard
to make it "just work" without configuration in a wide variety of environments.

That said, the broader React Native and React Native Web ecosystem is filled
with packages that do need configuration. Tamagui provides a variety of bundler
plugins that help with that:

<Spacer />

<Grid gap="$4">
  <LogoCard
    title="Next.js"
    img="/logos/next-js.svg"
    link="/docs/guides/next-js"
    subtitle="Full-featured React framework with great developer experience."
    colorOffset={2}
  />
  <LogoCard
    title="Expo"
    img="/logos/expo.svg"
    link="/docs/guides/expo"
    subtitle="Platform for creating universal native apps with JavaScript and React."
    colorOffset={6}
  />
  <LogoCard
    title="Vite"
    img="/logos/vite.svg"
    link="/docs/guides/vite"
    subtitle="Fast and modern development server and build tool."
    colorOffset={1}
  />
  <LogoCard
    title="Webpack"
    img="/logos/webpack.svg"
    link="/docs/guides/webpack"
    subtitle="Powerful module bundler for modern JavaScript applications."
    colorOffset={0}
  />
  <LogoCard
    title="Metro"
    img="/logos/metro.svg"
    link="/docs/guides/metro"
    subtitle="Fast, scalable, and serverless JavaScript bundler for React Native."
    colorOffset={3}
  />
  <LogoCard
    title="One"
    img="/logos/one.svg"
    link="/docs/guides/one"
    subtitle="Universal React framework with native support."
    colorOffset={4}
  />
</Grid>


## intro/introduction

---
title: Introduction
---

<DocsIntro />

<GetStarted />

#### Highlights

<Highlights
  disableLinks
  disableTitle
  large
  features={[
    `Core only has one dependency - React - but supports the full React Native View and Text API, a superset of the React Native Style API, styled(), powerful hooks, and the typed design system helpers in ~28Kb on web.`,
    `A smart, partial-evaluating compiler gives 0-runtime performance with the ergonomics of writing your code however you want - even inline, logic-filled code is optimized.`,
    `Every feature works at runtime and compile-time, so none of the usual limits of 0-runtime libraries, while optionally getting the same great performance.`,
    `useTheme and useMedia hooks with signal-like granularity and dirty tracking.`,
    `Unstyled and styled versions of all components.`,
  ]}
/>

---

## 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 of which we've adopted many APIs, and for the initial structure for this website.
- [Framer Motion](https://github.com/framer/motion) for the AnimatePresence
  implementation.


## intro/props

---
title: Tamagui Props
description: All the base props
---

Tamagui supports a superset of the React Native props. Start with:

- [View](https://reactnative.dev/docs/view-style-props), or
- [Text](https://reactnative.dev/docs/text-style-props)

From there, we add [style props](/docs/intro/styles) directly onto the same
object.

Finally, there are a few non-style props Tamagui adds:

## Event Props

All Tamagui View-based components have Pressable-like functionality built in. You don't need to wrap in `Pressable` or `TouchableOpacity` - add event handlers directly:

```tsx
<View
  onPress={() => console.log('pressed')}
  onHoverIn={() => console.log('hovered')}
  pressStyle={{ opacity: 0.8 }}
  hoverStyle={{ backgroundColor: '$backgroundHover' }}
/>
```

<PropsTable
  data={[
    {
      name: 'onPress',
      required: false,
      type: '(e: GestureResponderEvent) => void',
      description: (
        <span>
          Called when a press is released. Works on both web and native.
        </span>
      ),
    },
    {
      name: 'onPressIn',
      required: false,
      type: '(e: GestureResponderEvent) => void',
      description: (
        <span>
          Called immediately when a press starts.
        </span>
      ),
    },
    {
      name: 'onPressOut',
      required: false,
      type: '(e: GestureResponderEvent) => void',
      description: (
        <span>
          Called when a press is released or cancelled.
        </span>
      ),
    },
    {
      name: 'onLongPress',
      required: false,
      type: '(e: GestureResponderEvent) => void',
      description: (
        <span>
          Called when a press is held for longer than <code>delayLongPress</code> (default 500ms).
        </span>
      ),
    },
    {
      name: 'onHoverIn',
      required: false,
      type: '(e: MouseEvent) => void',
      description: (
        <span>
          <strong>Web Only</strong>: Called when the mouse enters the element. Maps to <code>onMouseEnter</code>.
        </span>
      ),
    },
    {
      name: 'onHoverOut',
      required: false,
      type: '(e: MouseEvent) => void',
      description: (
        <span>
          <strong>Web Only</strong>: Called when the mouse leaves the element. Maps to <code>onMouseLeave</code>.
        </span>
      ),
    },
    {
      name: 'onFocus',
      required: false,
      type: '(e: FocusEvent) => void',
      description: (
        <span>
          Called when the element receives focus.
        </span>
      ),
    },
    {
      name: 'onBlur',
      required: false,
      type: '(e: FocusEvent) => void',
      description: (
        <span>
          Called when the element loses focus.
        </span>
      ),
    },
    {
      name: 'delayPressIn',
      required: false,
      type: 'number',
      description: (
        <span>
          Duration in ms to wait before calling <code>onPressIn</code>.
        </span>
      ),
    },
    {
      name: 'delayPressOut',
      required: false,
      type: 'number',
      description: (
        <span>
          Duration in ms to wait before calling <code>onPressOut</code> after press is released.
        </span>
      ),
    },
    {
      name: 'delayLongPress',
      required: false,
      type: 'number',
      description: (
        <span>
          Duration in ms to wait before calling <code>onLongPress</code>. Default is 500ms.
        </span>
      ),
    },
    {
      name: 'minPressDuration',
      required: false,
      type: 'number',
      description: (
        <span>
          Minimum duration in ms that a press must be held before <code>onPressOut</code> is called.
        </span>
      ),
    },
    {
      name: 'cancelable',
      required: false,
      type: 'boolean',
      description: (
        <span>
          When true, allows the press to be cancelled by scrolling or other gestures.
        </span>
      ),
    },
    {
      name: 'disabled',
      required: false,
      type: 'boolean',
      description: (
        <span>
          Disables all press and hover interactions on the element.
        </span>
      ),
    },
    {
      name: 'focusable',
      required: false,
      type: 'boolean',
      description: (
        <span>
          Whether the element can receive focus. On web, controls <code>tabIndex</code>.
        </span>
      ),
    },
    {
      name: 'hitSlop',
      required: false,
      type: `number | Insets`,
      description: (
        <span>
          Extends the pressable area beyond the element bounds. Accepts a number (applies to all sides) or an object with <code>top</code>, <code>bottom</code>, <code>left</code>, <code>right</code>.
        </span>
      ),
    },
  ]}
/>

Use these events alongside style states like `pressStyle`, `hoverStyle`, and `focusStyle` to create interactive components. See the [Style API](/docs/intro/styles) for styling on interaction states.

## Other Props

<PropsTable
  data={[
    {
      name: 'transition',
      required: false,
      type: 'string',
      description: (
        <span>
          Apply a transition animation as defined in your createTamagui configuration. See the{' '}
          <a href="/docs/core/animations">Animations documentation</a> for more details on configuring animation drivers.
        </span>
      ),
    },

    {
      name: 'animateOnly',
      required: false,
      type: 'string[]',
      description: (
        <span>
          A list of properties to animate. Note that flat transforms can only be controlled via `transform`. Learn more in the{' '}
          <a href="/docs/core/animations#granular-animations">Granular animations</a> section.
        </span>
      ),
    },

    {
      name: 'theme',
      required: false,
      type: 'string',
      description: (
        <span>
          Apply a theme or sub-theme to this component and the components below it.
        </span>
      ),
    },

    {
      name: 'themeInverse',
      required: false,
      type: 'boolean',
      description: (
        <span>
          Invert light or dark theme for the sub-tree.
        </span>
      ),
    },

    {
      name: 'themeShallow',
      required: false,
      type: 'boolean',
      description: (
        <span>
          Used in combination with the theme prop, it prevents the theme from passing to children.
        </span>
      ),
    },

    {
      name: 'forceStyle',
      required: false,
      type: `'hover' | 'press' | 'focus' | 'focusVisible'`,
      description: (
        <span>
          Forces the pseudo style state to be on.
        </span>
      ),
    },

    {
      name: 'group',
      required: false,
      type: `boolean | string`,
      description: (
        <span>
          Marks this component as a group for use in styling children based on parents named group.
        </span>
      ),
    },

    {
      name: 'componentName',
      required: false,
      type: 'string',
      description: (
        <span>
          Equivalent to "name" property on styled() for automatically applying a theme.
        </span>
      ),
    },

    {
      name: 'className',
      required: false,
      type: 'string',
      description: (
        <span>
          <strong>Web Only</strong>: An escape hatch to set className. This works fully with the compiler, which will concat your defined className with its generated ones.
        </span>
      ),
    },

    {
      name: 'disableClassName',
      required: false,
      type: 'boolean ',
      description: (
        <span>
          <strong>Web Only</strong>: Disables className output of styles, instead using only inline styles.
        </span>
      ),
    },

    {
      name: 'tag',
      required: false,
      type: 'string',
      description: (
        <span>
          <strong>Web Only</strong>: Renders the final element as the given tag. Note: React Native Web doesn't support tag and as such if using any animation driver except CSS the tag prop will stop working. We'd recommend using "role" for most cases.
        </span>
      ),
    },

    {
      name: 'space',
      required: false,
      deprecated: true,
      type: "boolean | string | TamaguiConfig['space']",
      description: (
        <span>
          Use gap instead -
          Spacing is built into Tamagui and can accept a number or Token.space value. This will
          filter out any nullish child elements and insert a spacer components between the remaining
          elements.
        </span>
      ),
    },

    {
      name: 'spaceDirection',
      required: false,
      deprecated: true,
      default: 'both',
      type: `'horizontal' | 'vertical' | 'both'`,
      description: (
        <span>
          By default the space inserted is a square, but if you set it to horizontal it will be 0px tall, vertical will be 0px wide.
        </span>
      ),
    },

    {
      name: 'debug',
      required: false,
      type: `boolean | 'verbose' | 'break'`,
      description: (
        <span>
          When set Tamagui will output a variety of helpful information on how it parsed and
          applied styling. Verbose will output more information, and break will hit a debugger at
          the start of render in development mode.
        </span>
      ),
    },

    {
      name: 'untilMeasured',
      required: false,
      type: `'hide' | 'show'`,
      description: (
        <span>
          Works only alongside group, when children of the group are using container based sizing on native you can hide them until parent is measured.
        </span>
      ),
    },
    {

      name: 'disableOptimization',
      required: false,
      type: `boolean `,
      description: (
        <span>
          Disable all compiler optimization.
        </span>
      ),
    },

    {
      name: 'tabIndex',
      required: false,
      type: `string | number`,
      description: (
        <span>
          Used for controlling the order of focus with keyboard or assistive device enavigation.
        </span>
      ),
    },

    {
      name: 'role',
      required: false,
      type: 'Role',
      description: (
        <span>
          Indicates to accessibility services to treat UI component like a specific role.
        </span>
      ),
    },

    {
      name: 'asChild',
      required: false,
      type: `boolean | 'except-style' | 'except-style-web' | 'web'`,
      description: (
        <span>
          When true, Tamagui expects a single child element. Instead of rendering its own element, it will pass all props to that child, merging together any event handling props. When "except-style", the same behavior except Tamagui won't pass styles down from the parent, only non-style props.
        </span>
      ),
    },

    {
      name: 'passThrough',
      required: false,
      type: 'boolean',
      description: (
        <span>
          Advanced use only. React will "re-parent" components when it's internal tree structure changes.
          When making UI that adapts to another look, for better performance and avoiding losing user state (like focus, or uncontrolled text input), you can use passThrough rather than conditionally rendering a wrapping component.
        </span>
      ),
    },

]} />

## Event Handlers

Tamagui supports all React Native events on all platforms. On web, we also support all standard DOM event handlers:

### Cross-Platform Events
- `onPress`, `onLongPress`, `onPressIn`, `onPressOut` - Touch/press events that work on both native and web

### Web-Only Events
The following events are available on web and automatically ignored on native:

**Mouse Events**
- `onClick`, `onDoubleClick`, `onContextMenu`
- `onMouseEnter`, `onMouseLeave`, `onMouseMove`, `onMouseOver`, `onMouseOut`
- `onMouseDown`, `onMouseUp`
- `onWheel`

**Keyboard Events**
- `onKeyDown`, `onKeyUp`, `onKeyPress`

**Pointer Events** - Modern events that work across mouse, touch, and pen:
- `onPointerDown`, `onPointerUp`, `onPointerMove`
- `onPointerEnter`, `onPointerLeave`, `onPointerCancel`

**Drag and Drop**
- `onDrag`, `onDragStart`, `onDragEnd`
- `onDragEnter`, `onDragLeave`, `onDragOver`
- `onDrop`

**Input Events**
- `onChange`, `onInput`, `onBeforeInput`

**Scroll Events**
- `onScroll`

**Clipboard Events**
- `onCopy`, `onCut`, `onPaste`

**Focus Events**
- `onFocus`, `onBlur`

All event handlers receive the standard React synthetic event as their argument.


## intro/static

---
title: Static Optimization
description: How `@tamagui/static` works to speed up code.
---


## intro/styles

---
title: Style API
description: The Tamagui superset of React Native styles
---

Tamagui supports a superset of the React Native style properties - either to the
`styled()` function as the second argument, or directly as props on the View and
Text base components.

Here's how that looks in practice:

```tsx
import { View, styled } from '@tamagui/core'

const StyledView = styled(View, {
  padding: 10,
})

const MyView = () => (
  <StyledView
    backgroundColor="red"
    hoverStyle={{
      backgroundColor: 'green',
    }}
  />
)
```

The types for the full set of styles accepted by styled, View and Text are
exported as `ViewStyle` and `TextStyle`.

For the full base styles, see the React Native docs (version 2 targets React Native 0.82):

- [React Native View style props](https://reactnative.dev/docs/view-style-props)
- [React Native Text style props](https://reactnative.dev/docs/text-style-props)

The full Tamagui typed style props can be simplified to something like this,
except the values can also accept one of your design tokens:

```tsx
import { ViewStyle as RNViewStyle } from 'react-native'

type BaseViewStyle = RNViewStyle & FlatTransformStyles & CrossPlatformStyles & WebOnlyStyles

// Cross-platform styles:
type CrossPlatformStyles = {
  boxShadow?: BoxShadowValue  // string, object, or array
  filter?: FilterValue        // brightness, opacity cross-platform; blur, contrast, etc. Android 12+
  cursor?: Properties['cursor'] // web + iOS 17+ (trackpad/stylus/gaze)
  mixBlendMode?: 'normal' | 'multiply' | 'screen' | 'overlay' | ... // blend modes
  isolation?: 'auto' | 'isolate'
  boxSizing?: 'border-box' | 'content-box'
  display?: 'flex' | 'none' | 'contents' | ...
  position?: 'absolute' | 'relative' | 'fixed' | 'static' | 'sticky'  // 'fixed' converts to 'absolute' on native
  outlineColor?: ColorValue
  outlineOffset?: SpaceValue
  outlineStyle?: 'solid' | 'dotted' | 'dashed'
  outlineWidth?: SpaceValue
}

// these are accepted but only render on web:
type WebOnlyStyles =  {
  contain?: Properties['contain']
  touchAction?: Properties['touchAction']
  backdropFilter?: Properties['backdropFilter']
  backgroundImage?: Properties['backgroundImage']
  backgroundOrigin: Properties['backgroundOrigin'],
  backgroundPosition: Properties['backgroundPosition'],
  backgroundRepeat: Properties['backgroundRepeat'],
  backgroundSize: Properties['backgroundSize']
  backgroundColor: Properties['backgroundColor']
  backgroundClip: Properties['backgroundClip']
  backgroundBlendMode: Properties['backgroundBlendMode']
  backgroundAttachment: Properties['backgroundAttachment']
  background: Properties['background']
  clipPath: Properties['clipPath'],
  caretColor: Properties['caretColor']
  transformStyle: Properties['transformStyle'],
  mask: Properties['mask'],
  maskImage: Properties['maskImage'],
  textEmphasis: Properties['textEmphasis'],
  borderImage: Properties['borderImage'],
  float: Properties['float']
  content: Properties['content']
  overflowBlock: Properties['overflowBlock']
  overflowInline: Properties['overflowInline']
  maskBorder: Properties['maskBorder']
  maskBorderMode: Properties['maskBorderMode']
  maskBorderOutset: Properties['maskBorderOutset']
  maskBorderRepeat: Properties['maskBorderRepeat']
  maskBorderSlice: Properties['maskBorderSlice']
  maskBorderSource: Properties['maskBorderSource']
  maskBorderWidth: Properties['maskBorderWidth']
  maskClip: Properties['maskClip']
  maskComposite: Properties['maskComposite']
  maskMode: Properties['maskMode']
  maskOrigin: Properties['maskOrigin']
  maskPosition: Properties['maskPosition']
  maskRepeat: Properties['maskRepeat']
  maskSize: Properties['maskSize']
  maskType: Properties['maskType']
}

// these turn into the equivalent transform style props:
type FlatTransformStyles = {
  x?: number
  y?: number
  perspective?: number
  scale?: number
  scaleX?: number
  scaleY?: number
  skewX?: string
  skewY?: string
  matrix?: number[]
  rotate?: string
  rotateY?: string
  rotateX?: string
  rotateZ?: string
}

// add the pseudo and enter/exit style states
type WithStates = BaseViewStyle & {
  hoverStyle?: BaseViewStyle
  pressStyle?: BaseViewStyle
  focusStyle?: BaseViewStyle
  focusVisibleStyle?: BaseViewStyle
  disabledStyle?: BaseViewStyle
  enterStyle?: BaseViewStyle
  exitStyle?: BaseViewStyle
}

// final View style props
type ViewStyle = WithStates & {
  // add media queries
  $sm?: WithStates

  // add group queries
  $group-hover?: WithStates
  $group-focus?: WithStates
  $group-press?: WithStates

  // add group + container queries
  $group-sm-hover?: WithStates
  $group-sm-focus?: WithStates
  $group-sm-press?: WithStates

  // add named group queries
  $group-tabs?: WithStates
  $group-tabs-hover?: WithStates
  $group-tabs-focus?: WithStates
  $group-tabs-press?: WithStates

  // add named group + container queries
  $group-tabs-sm?: WithStates
  $group-tabs-sm-hover?: WithStates
  $group-tabs-sm-focus?: WithStates
  $group-tabs-sm-press?: WithStates

  // add theme queries
  $theme-light?: WithStates
  $theme-dark?: WithStates

  // add platform queries
  $platform-native?: WithStates
  $platform-ios?: WithStates
  $platform-android?: WithStates
  $platform-web?: WithStates
}

// Text style starts with this base but builds up the same:
type TextStyleBase = BaseViewStyle & {
  color?: string,
  fontFamily?: string,
  fontSize?: string,
  fontStyle?: string,
  fontWeight?: string,
  letterSpacing?: string,
  lineHeight?: string,
  textAlign?: string,
  textDecorationColor?: string,
  textDecorationLine?: string,
  textDecorationStyle?: string,
  textShadowColor?: string,
  textShadowOffset?: string,
  textShadowRadius?: string,
  textTransform?: string,
}
```

## CSS Shorthand with Variables

Tamagui supports using `$variables` directly inside CSS shorthand string values.
This is particularly useful for `boxShadow` where you want to reference theme
colors:

```tsx
// Single variable
<View boxShadow="0 0 10px $shadowColor" />

// Multiple variables
<View boxShadow="0 0 5px $shadowColor, 0 0 15px $color" />

// On web: resolves to CSS custom properties
// boxShadow: "0 0 10px var(--t-shadow-shadowColor)"

// On native: resolves to raw values
// boxShadow: "0 0 10px rgba(0,0,0,0.2)"
```

Supported shorthand properties with variable resolution:

- `boxShadow` - Works on both web and native
- `border`, `borderTop`, `borderRight`, `borderBottom`, `borderLeft` - Web only
- `background` - Web only

For native border styling, use individual props:

```tsx
// Web
<View border="1px solid $borderColor" />

// Native (or cross-platform)
<View borderWidth={1} borderStyle="solid" borderColor="$borderColor" />
```

## Parent based styling

Tamagui has a variety of ways to style a child based on the "parent", a parent
being one of: platform, screen size, theme, or group. All of these styles use
the same pattern, they use a `$` prefix for their styles, and they nest styles
as a sub-object.

For example you can target `$theme-light`, `$platform-ios`, or `$group-header`.
For screen size, which we call media queries, they have no prefix. Instead you
define media queries on `createTamagui`. For example, if you define a media
query named `large`, then `$large` is the prop name.

These parent style props accept all the Tamagui style props in their value
object.

#### Media query

Based on whatever media queries you define in `createTamagui`, you can now use
any of them to apply styling on native and web using the `$` prefix.

If you defined your media query like:

```tsx
createTamagui({
  media: {
    sm: { maxWidth: 800 },
  },
})
```

Then you can use it like:

```tsx
<Text color="red" $sm={{ color: 'blue' }} />
```

### Theme

Theme style props let you style a child based on a parent theme. At the moment,
they only can target your top level themes, so if you have `light`, and
`light_subtle` themes, then only `light` can be targeted.

Use them like so:

```tsx
<Text $theme-dark={{ color: 'white' }} />
```

### Platform

Platform style props let you style a child based on the platform the app is
running on. This can be one of `ios`, `android`, `web`, or `native` (iOS and
Android).

Use it like so:

```tsx
<Text $platform-ios={{ color: 'white' }} />
```

### Group

Groups are a new feature in beta that lets you define a named group, and then
style children based whether they are inside a parent that is given that group
name.

A short example:

```tsx
<View group="header">
  <Text $group-header={{ color: 'white' }} />
</View>
```

This will make the Text turn white, as it's inside a parent item with `group`
set to the matching `header` value.

Group styles also allow for targeting the parent pseudo state:

```tsx
<View group>
  <Text $group-hover={{ color: 'white' }} />
</View>
```

Now only when the parent View is hovered the Text will turn white. The allowed
pseudo modifiers are `hover` (web only), `press`, and `focus`.

For more advanced usecases you can use named groups

```tsx
<View group="card">
  <Text>Outer</Text>
  <View group>
    <Text $group-card-hover={{ color: 'blue' }}>Inner</Text>
    <Text $group-hover={{ color: 'green' }}>Sibling</Text>
  </View>
</View>
```

Now the `Inner` Text will turn blue when the `card` group is hovered, and the
`Sibling` Text will turn green when its parent is hovered.

To make this typed, you need to set `TypeOverride` alongside the same area you
set up your Tamagui types:

```tsx
declare module 'tamagui' {
  interface TamaguiCustomConfig extends AppConfig {}

  // if you want types for group styling props, define them like so:
  interface TypeOverride {
    groupNames(): 'a' | 'b' | 'c'
  }
}
```

#### Group Container

The final feature of group styles is the ability to style a child only when the
parent is of a certain size. On the web these are known as "container queries",
which is what Tamagui outputs as CSS under the hood. They look like this:

```tsx
<View group>
  <Text $group-sm={{ color: 'white' }} $group-sm-hover={{ color: 'green' }} />
</View>
```

Now the Text will be white, but only when the View matches the media query `sm`.
This uses the same media query breakpoints you defined in
`createTamagui({ media })`. You can see it also works with pseudo styles!

For more advanced use cases, you can use named groups with container queries:

```tsx
<View group="card">
  <View group>
    <Text
      $group-card-sm={{ color: 'white' }}
      $group-card-sm-hover={{ color: 'green' }}
    />
    <Text $group-sm={{ color: 'white' }} $group-sm-hover={{ color: 'green' }} />
  </View>
</View>
```

Now the first Text will be white when the `card` parent matches `sm`, and the
second Text will be white when no named parent matches `sm`.

_A note on group containers and native_

On Native, we don't have access to the layout of a React component as it first
renders. Only once we get the dimensions from the `onLayout` callback after the
first render are we able to apply group container styles.

Because of this, we've done two things.

First, there's a new property `untilMeasured`:

```tsx
<View group untilMeasured="hide">
  <Text
    $group-sm={{
      color: 'white',
    }}
  />
</View>
```

This takes two options, `show` or `hide`. If unset it defaults to `show`, which
means it will render before it measures. With `hide` set, the container will be
set to `opacity` 0 until it finishes measuring.

Alternatively, if you know the dimensions your container will be up-front, you
can set width and height on the container. When either of these are set, the
children will attempt to use these values on first render and if they satisfy
the media query, you'll avoid the need for a double render altogether.

## Style order is important

One thing that's very important to understand in Tamagui is that style props are
sensitive to their order - a feature that without which would leave us with
impossible styling challenges and awkward rules of inheritance we're trying to
get away from with CSS in JS.

For a detailed explanation of how prop order works and why it's necessary, see
the [Order is important](/docs/core/styled#order-is-important) section in the
styled() docs.


## 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. They are used either as the
first lookup for `$` prefixed style values, or with the `useTheme` hook
directly. Tamagui allows nesting themes - both the definition and at runtime. At
runtime Tamagui resolves theme values upwards, ultimately all the way back to
tokens.

If you learn faster through example, skip to [the Quick Start](#quick-start).

If you want to style `bento` or `tamagui` components: see
[Styling `tamagui` UI components](#styling-tamagui-components).

Once you've learned the basics here, be sure to
[check out the ThemeBuilder guide](/docs/guides/theme-builder) for generating
more interesting theme suites.

<Notice theme="green">
  If you want a copy-paste theme generation setup, [try this
  gist](https://gist.github.com/natew/3be503cc5990a1142d17ffadf86a134f) for a
  well-structured example.
</Notice>

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

One of the unique powers of Tamagui is theme nesting . Define a theme with a
name in the form of `parentName_subName` and Tamagui will let you nest themes,
with both `parentName` and `subName` being valid theme names.

You can do this as many times as you'd like. Here's an example of having three
levels:

- `dark_green_subtle`
- `light_green_subtle`

```tsx
<Theme name="dark">
  <Theme name="green">
    <Button theme="subtle">Hello world</Button>
  </Theme>
</Theme>
```

You can also access a specific sub-theme more specifically:

```tsx
<Theme name="dark">
  <Button theme="green_subtle">Hello world</Button>
</Theme>
```

In general you want your themes to all be the same shape - the same named keys
and typed values - but sub-themes can be sub-sets of parent themes. The
`useTheme` hook and style system will resolve missing keys upwards to parent
themes, and ultimately to tokens.

#### Component themes

Every Tamagui `styled()` component looks for its own specific theme if you pass
it the `name` property. For example:

```tsx
import { View, styled } from 'tamagui' // or '@tamagui/core'

const Circle = styled(View, {
  name: 'Circle',
  backgroundColor: '$background',
})
```

The `name` attribute will be removed from the defaultProps and used internally
by Tamagui to check for a sub-theme that ends with `_Circle`.

Now you can create the default theme for all Circle components at any level of
nesting:

```tsx
const dark_Circle = {
  background: 'darkred',
  color: 'white',
}

const light_Circle = {
  background: 'lightred',
  color: 'black',
}
```

<Notice theme="blue">
  Component themes must have the first letter capitalized.
</Notice>

- `dark_Circle`
- `dark_green_Circle`
- `dark_green_subtle_Circle`

This is an incredibly powerful and unique feature that allows authors of UI
components control over design, while still letting users customize them
completely.

---

### Styling Tamagui UI components

Tamagui comes in two parts: a core library and a full UI kit. The core library
(`@tamagui/core`) is flexible and doesn't have many rules. But the full UI kit
(`tamagui`) has some standard ways of doing things. This helps make everything
work well together.

In the `tamagui` UI kit, all components use these main theme keys:

- `background`: for background colors
- `color`: for text colors
- `borderColor`: for border colors
- `shadowColor`: for shadow colors
- `placeholderColor`: for placeholder text colors (doesn't change when you
  interact with it)

It also uses special versions of these for when you hover, press, or focus on
something. For example, `backgroundHover` or `colorPress`.

These keys help standardize how you style components, and make for easy
re-theming. They are also optional, if you find the theme system too complex for
your use case, you can always just use plain old style props

...plus all the pseudo variants for each, eg, `backgroundHover`,
`backgroundPress`, and `backgroundFocus`.

This means that you can easily re-theme `tamagui`'s UI kit and your own
components together in both light and dark mode.

A minimal theme might look like this:

```tsx
const dark = {
  // Standard keys for all components
  background: '#000',
  backgroundHover: '#111',
  backgroundPress: '#222',
  backgroundFocus: '#333',
  backgroundStrong: '#444',
  backgroundTransparent: 'rgba(0, 0, 0, 0.5)',
  color: '#fff',
  colorHover: '#eee',
  colorPress: '#ddd',
  colorFocus: '#ccc',
  colorTransparent: 'rgba(255, 255, 255, 0.5)',
  borderColor: '#555',
  borderColorHover: '#666',
  borderColorFocus: '#777',
  borderColorPress: '#888',
  placeholderColor: '#999',
  outlineColor: '#aaa',
  // Custom tokens like "brand"
  brandBackground: '#000', // You can add your own tokens like "brand"
  brandColor: '#fff', // and use them in your components
}

const light = {
  // Standard keys for all components
  background: '#fff',
  backgroundHover: '#f5f5f5',
  backgroundPress: '#e0e0e0',
  backgroundFocus: '#d5d5d5',
  backgroundStrong: '#ccc',
  backgroundTransparent: 'rgba(255, 255, 255, 0.5)',
  color: '#000',
  colorHover: '#111',
  colorPress: '#222',
  colorFocus: '#333',
  colorTransparent: 'rgba(0, 0, 0, 0.5)',
  borderColor: '#444',
  borderColorHover: '#555',
  borderColorFocus: '#666',
  borderColorPress: '#777',
  placeholderColor: '#888',
  outlineColor: '#999',
  // Custom tokens like "brand"
  brandBackground: '#000', // You can add your own tokens like "brand"
  brandColor: '#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-components, you can
follow this pattern:

```tsx
import { GetProps, View, Text, styled } from 'tamagui' // or '@tamagui/core'

const ButtonFrame = styled(View, {
  name: 'Button',
  backgroundColor: '$background',
})

const ButtonText = styled(Text, {
  name: 'ButtonText',
  color: '$color',
})

type ButtonProps = GetProps<typeof ButtonFrame>

// note: styleable will tell the tamagui compiler to optimize usages of this:
export const Button = ButtonFrame.styleable<ButtonProps>(
  ({ children, ...props }, ref) => {
    return (
      <ButtonFrame ref={ref} {...props}>
        <ButtonText>{children}</ButtonText>
      </ButtonFrame>
    )
  },
)
```

And now you can add two themes: `dark_Button` and `dark_ButtonText`, and
override their default styles.

## Quick start

To get started quickly, you can use the themes we've developed alongside this
site and with other apps, `@tamagui/themes`. It's even easier to see how it all
comes together by using
[create-tamagui to bootstrap](/docs/guides/create-tamagui-app).

To install, just add import it and add it to your `tamagui.config.ts`:

```tsx
import { color, radius, size, space, themes, zIndex } from '@tamagui/themes'
import { createTamagui, createTokens } from 'tamagui'

const tokens = createTokens({
  size,
  space,
  zIndex,
  color,
  radius,
})

const config = createTamagui({
  themes,
  tokens,
  // ... see Configuration
})

export type Conf = typeof config

declare module 'tamagui' {
  interface TamaguiCustomConfig extends Conf {}
}

export default config
```

<Notice theme="green">
  If you want to customize the starter themes, we recommend you just grab the
  `src` for `@tamagui/themes` and copy/paste it into your app, and customize
  from there.
</Notice>

## Full Example

Let's start with an example of inline styling with a subset of the
configuration:

```tsx
import { TamaguiProvider, createTokens, createTamagui, View, 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 = () => (
  <TamaguiProvider config={config} defaultTheme="light">
    <View backgroundColor="$red" />
    <Theme name="dark">
      <View backgroundColor="$red" />
    </Theme>
  </TamaguiProvider>
)
```

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
using `Tamagui.getCSS()`.

Finally, the compiler on web will extract your views roughly as so:

```tsx
export const App = () => (
  <Provider defaultTheme="light">
    <div className="baCo-2nesi3" />
    <Theme name="dark">
      <div className="baCo-2nesi3" />
    </Theme>
  </Provider>
)

// CSS output:
//  .color-2nesi3 { background-color: var(--red); }
```

## Ensuring valid types

Here's what we've landed on which helps ensure everything is typed properly.
Keep themes in a separate `themes.ts` file, and structure it like this:

```tsx
import { tokens } from './tokens'

const light = {
  background: '#fff',
  backgroundHover: tokens.color.gray3,
  backgroundPress: tokens.color.gray4,
  backgroundFocus: tokens.color.gray5,
  borderColor: tokens.color.gray4,
  borderColorHover: tokens.color.gray6,
  color: tokens.color.gray12,
  colorHover: tokens.color.gray11,
  colorPress: tokens.color.gray10,
  colorFocus: tokens.color.gray6,
  shadowColor: tokens.color.grayA5,
  shadowColorHover: tokens.color.grayA6,
}

// 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',
  backgroundHover: tokens.color.gray2Dark,
  backgroundPress: tokens.color.gray3Dark,
  backgroundFocus: tokens.color.gray4Dark,
  borderColor: tokens.color.gray3Dark,
  borderColorHover: tokens.color.gray4Dark,
  color: '#ddd',
  colorHover: tokens.color.gray11Dark,
  colorPress: tokens.color.gray10Dark,
  colorFocus: tokens.color.gray6Dark,
  shadowColor: tokens.color.grayA6,
  shadowColorHover: tokens.color.grayA7,
}

const dark_translucent: BaseTheme = {
  ...dark,
  background: 'rgba(0,0,0,0.7)',
  backgroundHover: 'rgba(0,0,0,0.5)',
  backgroundPress: 'rgba(0,0,0,0.25)',
  backgroundFocus: 'rgba(0,0,0,0.1)',
}

const light_translucent: BaseTheme = {
  ...light,
  background: 'rgba(255,255,255,0.85)',
  backgroundHover: 'rgba(250,250,250,0.85)',
  backgroundPress: 'rgba(240,240,240,0.85)',
  backgroundFocus: 'rgba(240,240,240,0.7)',
}

export const allThemes = {
  dark,
  light,
  dark_translucent,
  light_translucent,
} satisfies { [key: string]: BaseTheme }
// note: `satisfies` was introduced with TypeScript 4.9
```

## 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

<HeroContainer>
  <AddThemeDemo />
</HeroContainer>

```tsx hero template=AddTheme

```

### updateTheme

<HeroContainer>
  <UpdateThemeDemo />
</HeroContainer>

```tsx hero template=UpdateTheme

```

### replaceTheme

<HeroContainer>
  <ReplaceThemeDemo />
</HeroContainer>

```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

You can configure Tamagui to not send any themes JS to the client side, so long
as your are serving the resulting css file from the `getCSS` call on initial
load of your app (SSR).

To enable this you need to have your bundler tree shake away the themes object
you'd typically pass to `createTamagui` for the client bundle. Note this is a
somewhat advanced optimization and not necessary to do right away.

```tsx
import { themes as themesIn } from './your-themes-file'

// We leave this value empty for production client side bundles to save on bundle size.
// The `@tamagui/next-plugin` sets TAMAGUI_IS_SERVER automatically.
// If you pass an empty themes object Tamagui will try to hydrate by scanning CSS in browser environments.
// It typically takes low single-digit ms to scan and can save significantly on JS size.

const themes =
  process.env.TAMAGUI_IS_SERVER || process.env.NODE_ENV !== 'production'
    ? themesIn
    : ({} as typeof themesIn)

export const config = createTamagui({
  themes,
  // ...
})
```


## intro/thinking-in-tamagui

---
title: Thinking in Tamagui
description: TODO
---


## intro/tokens

---
title: Tokens
---

<Notice theme="blue">
  This only documents the default `@tamagui/config` tokens that power this site, you are free to create your own token system with more refined logical underpinning.
</Notice>

### Understanding the size scale

Most design systems use exponential scaling (2, 4, 8, 16, etc.). A lot of people ask why Tamagui doesn't.

Tamagui adopts a hybrid of more gradual increases below 10, before going exponential after 10. This hybrid approach is motivated by allowing designs to have a much more usable range of components.

#### Aligning all the keys

We align every token group and font group to use the same 1-16 keys so that a size 2 Button will look good with a size 2 spacing, a 2 Icon, and size 2 Text inside of it.

#### Quantum scale ($0.25, $0.5, $0.75)

The natural scale starts at 1, but Tamagui has three exponentially decreasing tokens below 1 for all token groups, with 0.25 often rounding to 1.

#### Natural scale ($1-$10)

For sizes 1 to 10, Tamagui increases more gradually. This yields a nice variety of sizes, with $2 being a great tiny UI, $4 is "average", $5 and $6 give you two very usable emphasized sizes, and 7 and above are great for more occasional CTA/hero type things.

#### Super-natural scale

Tamagui switches to an exponential scale from sizes 11 to 16 so you still get some nice chunky sizes.

<TokensDemo />


## intro/why-a-compiler

---
title: The Frontend Trilemma
---

<IntroParagraph>
  If you're developing a cross-platform app, you've committed to the **frontend
  development trilemma**, a choose-two-of-three best shown as a diagram:
</IntroParagraph>

<YStack w="100%" mih={500} als="center" $gtMd={{ my: '$4' }}>
  <Image
    title="The Frontend Trilemma: Choose two of: A native-feeling app, Deploying to native and web, and share code between the two apps."
    src="/trilemma.svg"
    size="hero"
    height={270 * 2}
    width={320 * 2}
    resizeMode="contain"
  />
</YStack>

React Native recommends writing things twice generally for the best UX as is
made clear in the `<title>` of the homepage:
"[learn once, write anywhere](https://reactnative.dev/)". This is opposed to the
sort of holy grail mantra of "write once, runs everywhere". Doing this results
in native-feeling and performing apps, while still saving quite a bit of
development time thanks to sharing everything besides your views: utils, state
and data management, hooks, etc.

The center of this above Venn diagram would be a sort of platonic ideal:
<strong>"write once, runs _well_ everywhere"</strong>. We're pretty far from it
as of now, but it's not _technically_ impossible. We can imagine a few ways to
get closer:

- A sort of Rails-for-React - unified routing, patterns, code gen, "all the
  batteries".
- A UI kit that adapts to each platform's primitives confidently with flexible
  APIs.
- A way to author styles that output to platform primitives without overhead:
  eg, CSS with media queries, pseudos, and variables on the web.

This document goes over how we can achieve the last one. The first one is doable
(and Expo is [working on as much](https://expo.github.io/router/docs/)), and the
second one [Tamagui UI](/docs/components/stacks) is working towards slowly.

**The idea is to make another "bump" towards properly native-experience apps
with shared code, much like how React Native Web made one:**

<YStack w="100%" mih={220} my="$4" als="center" $gtMd={{ scale: 1.5, my: 100 }}>
  <Image
    title="How possible it is to share more code: a lot more with React Native, and again with React Native Web"
    src="/code-sharing2.svg"
    size="hero"
    height={220}
    width={380}
    resizeMode="contain"
  />
</YStack>

This can be done especially on the web by reducing JS bundle size by a large %
and greatly increasing render performance with reduced tree depth, logic,
objects, and hooks. The Tamagui Compiler does this using partial evaluation,
static extraction and hoisting, code elimination, and tree-flattening.

You can
[skip to the technical details without the backstory](#how-tamagui-helps) from
here if you'd like.

<Table heading="Choose your journey">
  <TableCol>
    <TableCell head></TableCell>
    <TableCell>1</TableCell>
    <TableCell>2</TableCell>
    <TableCell>3</TableCell>
  </TableCol>
  <TableCol>
    <TableCell head>Strategy</TableCell>
    <TableCell>Universal</TableCell>
    <TableCell>Lean</TableCell>
    <TableCell>Big-Budget</TableCell>
  </TableCol>
  <TableCol>
    <TableCell head>Native + Web</TableCell>
    <TableCell>✅</TableCell>
    <TableCell>❌</TableCell>
    <TableCell>✅</TableCell>
  </TableCol>
  <TableCol>
    <TableCell head>Code Sharing</TableCell>
    <TableCell>\> 70%</TableCell>
    <TableCell>-</TableCell>
    <TableCell>\< 30%</TableCell>
  </TableCol>
  <TableCol>
    <TableCell head>Feels native</TableCell>
    <TableCell>❌</TableCell>
    <TableCell>✅</TableCell>
    <TableCell>✅</TableCell>
  </TableCol>
    <TableCol>
    <TableCell head>Ship Fast</TableCell>
    <TableCell>✅</TableCell>
    <TableCell>✅</TableCell>
    <TableCell>❌</TableCell>
  </TableCol>
</Table>

Universal apps (those you "write once" that "run everywhere") make sense today
for many cases: side-projecting, SEO-insensitive or enterprise-only apps, people
who want to ship fast, experiment more, are pre-product-market fit, or generally
have apps with simpler UI. Twitter and Tinder are two larger examples of this.

But today, at best, we use hooks for media queries and themes, which basically
touch every component. This causes whole-tree re-renders and more expensive
main-thread time in critical areas on the web. Combine that with the CSS-in-JS
approach of React Native Web greatly increasing bundle size, and even
medium-complexity pages will drop from 100% Lighthouse to half or worse (our
homepage, a good complex example due to showing off many features that are
well-optimized, goes from 95 or so down to 80ish with the compiler off).

With all your media queries, interactive styles, themes, animations, and dynamic
styles in JS, it's hard to make ambitious apps that don't feel janky.

### How Tamagui Helps

`@tamagui/static` is an optimizing compiler for React Native with four main
features:

<YStack py="$4">
  <Features
    size="$5"
    items={[
      `Extracts all types of styling syntax into atomic CSS.`,
      `Removes a high % of inline styles with partial evaluation and hoisting.`,
      `Reduces tree depth, flattening expensive styled components into div or View.`,
      `Evaluates useMedia and useTheme hooks, turning logical expressions into media queries and CSS variables.`,
    ]}
  />
</YStack>

The output is smaller bundles, better runtime performance, and many more native
primitives used on the web.

Here's what it does, in code:

<TamaguiExamplesCode />

See [more examples on the homepage](/).

Notice that the compiler turned the `Text` into a `p`, and the `YStack` into a
`div` (on native, this would be `Text` and `View`). This is known as
tree-flattening, and for both web and native it yields very nice improvements to
render performance.

This is a typical performance improvement, where much of the gains come from
flattening:

<YStack my="$2">
  <BenchmarkChartWeb />
</YStack>

Across a few apps, we've seen 30-50% of components typically flatten, with a
higher percent achievable just by being aware of how the flattening optimizes
(adding the `// debug` comment to the top of the file will show a fuller
output).

Meanwhile, on Native, because we can't optimize to anything beyond vanilla React
Native code, the gains are less. Still, the results are impressive given you now
have performance within 5% of hand-optimizing React Native code, except you get
a whole suite of features for free.

<YStack my="$2">
  <BenchmarkChartNative />
</YStack>

You can see [more Benchmarks with explanations here](/docs/intro/benchmarks).

The compiler itself deserves more detail, which we'll expand on in the blog. For
now, this serves as a decent introduction.

Compilers can dramatically improve code sharing without the typical sacrifice of
performance. They don't solve every problem of universal apps, but by making
responsive styling, themes and interactive styles all perform at native levels,
they unlock sharing a much larger percentage of the components located in the
middle to bottom of the render tree in apps.

It gives us a new choice, "Universal + Compiler" that lets us ship fast while
still feeling native:

<Table my="$6">
  <TableCol>
    <TableCell head></TableCell>
    <TableCell>4</TableCell>
  </TableCol>
  <TableCol>
    <TableCell head>Strategy</TableCell>
    <TableCell>Universal + Compiler</TableCell>
  </TableCol>
  <TableCol>
    <TableCell head>Native + Web</TableCell>
    <TableCell>✅</TableCell>
  </TableCol>
  <TableCol>
    <TableCell head>Code Sharing</TableCell>
    <TableCell>~60-90%</TableCell>
  </TableCol>
  <TableCol>
    <TableCell head>Feels native</TableCell>
    <TableCell>✅</TableCell>
  </TableCol>
  <TableCol>
    <TableCell head>Ship Fast</TableCell>
    <TableCell>✅</TableCell>
  </TableCol>
</Table>

Tamagui makes React faster, but mostly makes _making_ React faster.

Give Tamagui a try with `npm create tamagui@latest`.
