Button

A pressable button with sizing, icons, and themes

Button combines accessible press behavior with shared sizing, icons, themes, and full HTML form semantics.

Features

  • One size prop for all styles

  • Icons before or after content

  • Explicit icon sizing with iconSize

  • Full HTML button form semantics

Installation

Button is already installed in tamagui:

import { Button } from 'tamagui'

@tamagui/button holds the unstyled half: useButton and the parts, with no styled Button of its own. Import from it when you are building your own button rather than using ours; see Build your own.

Usage

import { Button } from 'tamagui'
export default () => <Button>Lorem ipsum</Button>

Sizing

Sizing a button means adjusting many properties at once, not just on the outer frame but on the text inside it. Tamagui adjusts padding, border radius, font size, and icon size together with the size prop.

import { Button } from 'tamagui'
export default () => <Button size="lg">Lorem ipsum</Button>

Each name is a rung in the shared Sizing ladder (xs through xl, default md), so the frame, its text, and its icon agree by construction.

Variants

The variant prop selects a visual style. Tamagui ships outlined (transparent background with a visible border) and quiet (no background, no border).

import { Button, XStack } from 'tamagui'
export default () => (
<XStack gap="2">
<Button>Default</Button>
<Button variant="outlined">Outlined</Button>
<Button variant="quiet">Quiet</Button>
</XStack>
)

Icon theming

You can pass icons as either elements or components. If passing components, Tamagui themes them, passing size and color. The icon size comes from the Button’s size prop by default.

Set the icon size explicitly with iconSize (pixels, e.g. {18}). scaleIcon adjusts it further, relative to whichever size was resolved.

Spacing between an icon and the text comes from gap on the button frame, derived from the size table so it stays consistent with the button’s dimensions.

import { Button } from 'tamagui'
import { Star } from './components/icons' // your generated icons, see Lucide Icons
export default () => (
<>
<Button icon={Star} size="lg">
Icon sized by Button
</Button>
<Button icon={Star} iconSize={18} size="lg">
Icon sized explicitly
</Button>
</>
)

Web form props

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

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

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

Text styling

Button’s frame is a View, so text style props passed to it are ignored. The one exception is fontFamily, which forwards to the wrapped text, since a font family is a base value and needs no conditional resolution.

Everything else goes on Button.Text:

import { Button } from 'tamagui'
export default () => (
<Button>
<Button.Text color="blue" fontSize="5" fontWeight="bold">
Styled text
</Button.Text>
</Button>
)

Interactive text styles

Combine Button.Text with the group prop on Button to coordinate text styles across hover, press, and focus states:

import { Button } from 'tamagui'
export default () => (
<Button group="btn" backgroundColor="pink">
<Button.Text color="blue group-hover/btn:red">Hello world</Button.Text>
</Button>
)

The group prop lets any child reference all of Tamagui’s interactive states like group-hover/btn, group-press/btn, and group-focus/btn for precise styling per interaction state.

Build your own

No two buttons are alike, so @tamagui/button ships the behavior and the parts rather than a finished component. useButton themes the icon props, wraps bare children in a text, and handles the html nesting rules; the skin is yours. This is how tamagui’s own Button is built:

import {
ButtonFrame as ButtonBehaviorFrame,
ButtonText as ButtonBehaviorText,
createStyledContext,
createStyledHOC,
styled,
useButton,
withStaticProperties,
} from '@tamagui/ui'
const ButtonContext = createStyledContext<{ size?: 'sm' | 'md' | 'lg' }>({
size: 'md',
})
const ButtonFrame = styled(ButtonBehaviorFrame, {
context: ButtonContext,
backgroundColor: 'background hover:background-hover press:background-press',
borderColor: 'border-color hover:border-color-hover',
borderWidth: 1,
cursor: 'web:pointer',
})
const ButtonText = styled(ButtonBehaviorText, {
context: ButtonContext,
color: 'color',
fontWeight: '600',
})
const ButtonComponent = createStyledHOC(ButtonFrame, function Button(props, ref) {
const { props: buttonProps } = useButton(props, { Text: ButtonText })
return <ButtonFrame ref={ref} {...buttonProps} />
})
export const Button = withStaticProperties(ButtonComponent, {
Frame: ButtonFrame,
Text: ButtonText,
})

Because the frame declares ButtonContext, passing size to it is what publishes it to the text and icons below. Each component owns its context; there is no shared size context.

useButton also reads ButtonNestingContext, which is how a Button inside another Button (a Dialog.Trigger inside a toolbar button, say) renders as a span instead of nesting an invalid <button> inside a <button>.

For a longer walkthrough see How to Build a Button.

API reference

Button extends View, inheriting all the Tamagui standard props. Sizes come from the shared Sizing ladder.

Props

  • size

    'xs' | 'sm' | 'md' | 'lg' | 'xl'

    A named size ("xs" to "xl", default "md") is a rung in the sizing ladder: padding, border radius, font size, and icon size, never a height. Published to Button.Text and Button.Icon through the Button context.

  • variant

    "outlined" | "quiet"

    "outlined" is transparent with a border, "quiet" is transparent with no border.

  • circular

    boolean

    Forces a square frame with a fully rounded border radius.

  • disabled

    boolean

    Dims the button and sets `pointerEvents: "none"`. On web it also renders a disabled `<button>` and sets `aria-disabled`.

  • theme

    string

    Apply a theme just to the button and its children.

  • icon

    React.ReactNode | React.ComponentType<{ color?: string, size?: number }>

    Any React element or a component. Appears before the text. A component receives 'color' and 'size'.

  • iconAfter

    React.ReactNode | React.ComponentType<{ color?: string, size?: number }>

    Any React element or a component. Appears after the text. A component receives 'color' and 'size'.

  • iconSize

    number

    Explicitly set the icon size in pixels, overriding the one derived from 'size'.

  • scaleIcon

    number

    Scale the icon by this factor. Default is 1, applied after 'iconSize' or the default size calculation.

  • fontFamily

    string

    The one text style prop the frame forwards, since a font family does not vary per breakpoint. Every other text style belongs on `Button.Text`.

  • noTextWrap

    boolean

    Render children as given instead of wrapping bare text in a `Button.Text`.

  • // Web Form Props

    ---

    The following props are web-only and provide full HTML button form semantics. They are ignored on native.

  • type

    "button" | "submit" | "reset"

    Web-only. The button type. Defaults to "button" to prevent unintended form submissions.

  • form

    string

    Web-only. The ID of the form element the button is associated with.

  • formAction

    string

    Web-only. URL for form submission, overrides the form's action.

  • formMethod

    "get" | "post"

    Web-only. HTTP method for form submission.

  • formEncType

    string

    Web-only. Encoding type for form data.

  • formNoValidate

    boolean

    Web-only. Bypass form validation when submitting.

  • formTarget

    string

    Web-only. Where to display form response (_self, _blank, etc).

  • name

    string

    Web-only. Name submitted with form data.

  • value

    string

    Web-only. Value submitted with form data.

  • // Text Styling

    ---

    Apart from `fontFamily`, Button does not forward text style props. Use `Button.Text`, with the `group` prop for interactive states. See the Text Styling section above.

  • Button.Frame

    The button’s View. Renders as <button type="button"> on web with role="button".

    Button.Text

    The text the button wraps bare children in, and what you use to style the label. Extends SizableText.

    Button.Icon

    Sizes and colors an icon from the surrounding size context. Used for the icon and iconAfter props, and available for your own compositions.

    useButton

    Themes the icon and iconAfter props, wraps bare children in a text, and applies the html nesting rules. Returns the props to spread onto a frame, with the ones it consumed removed and everything else untouched.

    const { isNested, props } = useButton(props, { Text: MyButtonText })