ToggleGroup

Two-state buttons that can be toggled on or off

ToggleGroup arranges two-state buttons into a keyboard-navigable single or multiple selection control.

Features

  • Full keyboard navigation

  • Horizontal and vertical orientation

  • Single or multiple pressed buttons

  • Controlled or uncontrolled state

Installation

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

yarn add @tamagui/toggle-group

Usage

ToggleGroup handles the toggle state logic. For visual grouping with border radius, compose with XGroup or YGroup. Use activeStyle to customize the active state appearance:

// copy-paste: these skins import from files you own. copy each registry item
// below into your app, then adjust the relative import paths to fit.
// components/tamagui/ToggleGroup.tsx (registry item "togglegroup")
// npm dependencies: @tamagui/core @tamagui/toggle-group
import { XGroup, style } from 'tamagui';
import { ToggleGroup } from "../components/tamagui/ToggleGroup";
const activeItemStyle = style({
backgroundColor: 'color-5'
});
export default () => {
return <ToggleGroup type="single">
<XGroup>
<XGroup.Item>
<ToggleGroup.Item value="foo" borderRadius="4" activeStyle={activeItemStyle}>
Foo
</ToggleGroup.Item>
</XGroup.Item>
<XGroup.Item>
<ToggleGroup.Item value="bar" borderRadius="4" activeStyle={activeItemStyle}>
Bar
</ToggleGroup.Item>
</XGroup.Item>
</XGroup>
</ToggleGroup>;
};

For vertical layouts, use YGroup:

<ToggleGroup type="single" orientation="vertical">
<YGroup>
<YGroup.Item>
<ToggleGroup.Item value="top" borderRadius="4">
Top
</ToggleGroup.Item>
</YGroup.Item>
<YGroup.Item>
<ToggleGroup.Item value="bottom" borderRadius="4">
Bottom
</ToggleGroup.Item>
</YGroup.Item>
</YGroup>
</ToggleGroup>

API reference

ToggleGroup

ToggleGroup manages toggle state and keyboard navigation. It does not render any visible element by default. Use XGroup/YGroup for visual grouping.

Props

  • type

    "single" | "multiple"

    Determines whether a single or multiple items can be pressed at a time.

  • value

    string | string[]

    The controlled value of the pressed item(s). Use string for type="single", string[] for type="multiple".

  • defaultValue

    string | string[]

    The value of the item(s) to show as pressed when initially rendered.

  • orientation

    "horizontal" | "vertical"

    Default: 

    "horizontal"

    The orientation of the component, which determines how focus moves: horizontal for left/right arrows and vertical for up/down arrows.

  • disabled

    boolean

    Default: 

    false

    When true, prevents the user from interacting with the toggle group and all its items.

  • onValueChange

    (value: string | string[]) => void

    Event handler called when the pressed state of an item changes.

  • loop

    boolean

    Default: 

    true

    Whether or not to loop over after reaching the end or start of the items. Used for keyboard navigation.

  • disableDeactivation

    boolean

    Default: 

    false

    Won't let the user turn the active item off. Only applies to type="single".

  • rovingFocus

    boolean

    Default: 

    true

    Enable roving focus keyboard navigation between items.

  • ToggleGroup.Item

    ToggleGroup.Item extends View inheriting all the Tamagui standard props, plus:

    Props

  • value

    string

    A unique value for this toggle item.

  • disabled

    boolean

    Default: 

    false

    When true, prevents the user from interacting with this item.

  • activeStyle

    StylePiece

    Style piece applied when the item is in its active/pressed state. Accepts a style() piece.

  • activeTheme

    string | null

    Theme to apply when the item is active. Set to null for no theme change.

  • Styling active state

    Customize the active/pressed state using activeStyle:

    const greenActiveStyle = style({
    backgroundColor: 'green-500',
    })
    // Usage on ToggleGroup.Item
    <ToggleGroup.Item value="left" activeStyle={greenActiveStyle}>
    Left
    </ToggleGroup.Item>
    // Or via styled()
    const GreenItem = styled(ToggleGroup.Item, {
    activeStyle: greenActiveStyle,
    })

    You can also use activeTheme to apply a theme when active:

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

    import { useToggleGroupItem, ToggleGroup } from '@tamagui/toggle-group'
    function CustomLabel({ children }) {
    const { active, color } = useToggleGroupItem()
    return <html.span color={active ? 'green-10' : 'color'}>{children}</html.span>
    }
    // Usage
    ;<ToggleGroup.Item value="option1">
    <CustomLabel>Option 1</CustomLabel>
    </ToggleGroup.Item>

    Source

    v2-look ToggleGroup: theme palette, border, hover/press/focus color styling, and the default active (pressed-on) appearance on the unstyled @tamagui/ui ToggleGroup Item. The behavior keeps only structural layout + the size mechanism and emits aria-pressed/data-state. This is the exact source the registry ships — copy it into your app and the examples above import from your copy.

    components/tamagui/ToggleGroup.tsx
    // Styled ToggleGroup = the unstyled @tamagui/ui ToggleGroup behavior + the // default v2-look skin on its Item (theme palette, border, hover/press/focus // color styling, the default active appearance) and the size table: a square // hit target, the control height plus the skin's 1px border. // Single skin definition; the shadcn registry item is generated from this file. import { type ComponentSize, createRefComponent, type GetProps, styled, type TamaguiElement, withStaticProperties, } from '@tamagui/core' import { ToggleGroup as UiToggleGroup } from '@tamagui/toggle-group' import type * as React from 'react' export type ToggleGroupSize = ComponentSize | boolean const toggleGroupItemSize = { xs: { width: 26, height: 26, borderRadius: 'sm' }, sm: { width: 34, height: 34, borderRadius: 'md' }, md: { width: 38, height: 38, borderRadius: 'md' }, lg: { width: 42, height: 42, borderRadius: 'md' }, xl: { width: 50, height: 50, borderRadius: 'lg' }, } as const export const ToggleGroupItem = styled(UiToggleGroup.Item, { displayName: 'ToggleGroupItem', // "on" swaps the item onto the brand theme rather than recoloring its // background, so the fill, the border and the icon (icons read theme.color, // they do not inherit CSS color) move together. Checkbox and Switch do the same. activeTheme: 'brand', backgroundColor: 'background hover:background-hover press:background-press', borderColor: 'border-color hover:border-color-hover press:border-color-press', borderWidth: 1, outlineColor: 'focus-visible:outline-color', outlineWidth: 'focus-visible:2px', outlineStyle: 'focus-visible:solid', zIndex: 'focus-visible:10', variants: { size: { ...toggleGroupItemSize, true: toggleGroupItemSize.md, }, } as const, defaultVariants: { size: 'md', }, }) // see Dialog.tsx: withStaticProperties assigns in place, so composing onto // UiToggleGroup would rewrite @tamagui/ui's own ToggleGroup.Item for every consumer // of the unstyled package. const ToggleGroupRoot = createRefComponent< TamaguiElement, React.ComponentProps<typeof UiToggleGroup> >(function ToggleGroup(props, ref) { return <UiToggleGroup {...props} ref={ref} /> }) export const ToggleGroup = withStaticProperties(ToggleGroupRoot, { Item: ToggleGroupItem, }) export type ToggleGroupItemProps = GetProps<typeof ToggleGroupItem>

    Dependencies

    yarn add @tamagui/core @tamagui/toggle-group

    Expects theme tokens: background, background-hover, background-press, border-color, border-color-hover, border-color-press, outline-color.

    Need raw behavior without any skin? tamagui/unstyled re-exports the @tamagui/ui primitives (advanced).