Files
suaveplan/packages/ui/react/react-ui/src/components/toggle-group/toggle-group.md
2026-05-27 23:47:53 +01:00

7.0 KiB
Raw Blame History

ToggleGroup

(no submodules)

- toggle-group.variants — `toggleGroupVariants` and `…
  • toggle-group.variants — toggleGroupVariants and toggleGroupItemVariants are the cva (class-variance-authority) configurations powering `<To…

  • toggle-group.variants — toggleGroupVariants and `…

  • toggle-group.variants — toggleGroupVariants and toggleGroupItemVariants are the cva (class-variance-authority) configurations powering `<To…

  • toggle-group.variants — toggleGroupVariants and `…

  • toggle-group.variants — toggleGroupVariants and toggleGroupItemVariants are the cva (class-variance-authority) configurations powering `<To…

  • toggle-group.variants — toggleGroupVariants and `…

  • toggle-group.variants — toggleGroupVariants and toggleGroupItemVariants are the cva (class-variance-authority) configurations powering `<To…

  • toggle-group.variants — toggleGroupVariants and toggleGroupItemVariants are the cva (class-variance-authority) configurations powering `<To…

Purpose

<ToggleGroup> and <ToggleGroupItem> are the chassis option-set picker built on @radix-ui/react-toggle-group. Radix supplies the a11y semantics (role="group", roving tabindex across items, arrow-key navigation, native Space activation, controlled value propagation in both single and multiple modes); the chassis layers cva variant resolution and propagates variant + size to every <ToggleGroupItem> via context so consumers do not need to repeat the props on every item.

Per the shadcn rule, option sets of 2–7 choices use <ToggleGroup>; do not loop <Button> with manual aria-pressed state. The single mode exposes a string value, the multiple mode exposes a string array — both routes preserve the discriminated union in the type signature so consumers get autocomplete on the right shape.

Features

  • Two visual variants (default, outline) shared with <Toggle> for visual coherence.
  • Three sizes (sm, default, lg) for dense or roomy toolbars.
  • single and multiple selection modes with the discriminated value shape preserved.
  • Variant + size propagation from group to items via React context (per-item override wins over inherited).
  • Roving tabindex across items provided by Radix — only one item is in the tab order at any time.
  • Refs forwarded on both <ToggleGroup> and <ToggleGroupItem>.
  • motion-reduce:transition-none collapses transitions for prefers-reduced-motion: reduce.
  • Theme-driven variant overrides via useThemedVariants("ToggleGroup" | "ToggleGroupItem").

Usage: Basic Example

import { ToggleGroup, ToggleGroupItem } from "@suaveplan/react-ui";
import { AlignLeftIcon, AlignCenterIcon, AlignRightIcon } from "lucide-react";

export function AlignmentPicker() {
    return (
        <ToggleGroup type="single" defaultValue="left" aria-label="Text alignment">
            <ToggleGroupItem value="left" aria-label="Left align">
                <AlignLeftIcon />
            </ToggleGroupItem>
            <ToggleGroupItem value="center" aria-label="Center align">
                <AlignCenterIcon />
            </ToggleGroupItem>
            <ToggleGroupItem value="right" aria-label="Right align">
                <AlignRightIcon />
            </ToggleGroupItem>
        </ToggleGroup>
    );
}

Usage: Advanced Example

import { ToggleGroup, ToggleGroupItem, FieldSet, FieldLegend } from "@suaveplan/react-ui";
import { useState } from "react";

export function FilterPicker() {
    const [filters, setFilters] = useState<string[]>([]);

    return (
        <FieldSet>
            <FieldLegend>Filters</FieldLegend>
            <ToggleGroup
                type="multiple"
                value={filters}
                onValueChange={setFilters}
                variant="outline"
                size="sm"
            >
                <ToggleGroupItem value="open">Open</ToggleGroupItem>
                <ToggleGroupItem value="closed">Closed</ToggleGroupItem>
                <ToggleGroupItem value="merged">Merged</ToggleGroupItem>
                <ToggleGroupItem value="draft">Draft</ToggleGroupItem>
            </ToggleGroup>
        </FieldSet>
    );
}

API Reference

<ToggleGroup>

forwardRef<ComponentRef<typeof ToggleGroupPrimitive.Root>, ToggleGroupProps> — renders the Radix Root with the discriminated-union shape preserved.

ToggleGroupProps

Inherits the discriminated-union shape of Radix ToggleGroupPrimitive.Root ({ type: "single", value?: string } or { type: "multiple", value?: string[] }) minus asChild. Adds:

Prop Type Default Description
variant "default" | "outline" "default" Inherited by every item via context.
size "sm" | "default" | "lg" "default" Inherited by every item via context.

Common Radix props consumers will use:

Prop Type Default Description
type "single" | "multiple" required Selection mode.
value string | string[] uncontrolled Selected value(s).
defaultValue string | string[] none Initial value(s) for uncontrolled use.
onValueChange (value: string | string[]) => void none Fires on value change.
disabled boolean false Disables every item.

<ToggleGroupItem>

forwardRef<ComponentRef<typeof ToggleGroupPrimitive.Item>, ToggleGroupItemProps> — renders a Radix Item.

ToggleGroupItemProps

Inherits every prop from Radix ToggleGroupPrimitive.Item except asChild. Adds:

Prop Type Default Description
variant "default" | "outline" from group context Per-item override.
size "sm" | "default" | "lg" from group context Per-item override.
value string required Unique value within the surrounding group.

toggleGroupVariants / toggleGroupItemVariants

Both bare cva resolvers exported for external composition.

Implementation Notes

  • The variant + size propagation uses React context rather than React.cloneElement so deeply-nested item wrappers (<TooltipTrigger asChild><ToggleGroupItem>...</ToggleGroupItem></TooltipTrigger>) still receive the inherited values.
  • Per-item variant / size overrides win over the inherited context values because the cva call resolves variant ?? ctx.variant at render time.
  • The discriminated-union type is preserved at the public surface so consumers get autocomplete on value: string for type: "single" vs value: string[] for type: "multiple". Internally the chassis casts to a relaxed shape because TypeScript cannot narrow the spread back to the original union after the chassis adds its own props.
  • The chassis NEVER uses space-x-* for the row layout — flex gap-1 flows the same in RTL.
  • Refs target the Radix Root + Item — consumers can .focus() programmatically through the forwarded refs.