rapidUI

Toggle Group

Group of toggles with single or multiple selection.

Installation

pnpm dlx shadcn@latest add https://rdf.dev.360online.com/r/toggle-group.json

Installs to @/components/ui/toggle-group.

Manual installation

Install the dependencies:

pnpm add @base-ui/react@^1.8.0 class-variance-authority@^0.7.1

Add the components it builds on: toggle.

Copy the source into your project:

components/ui/toggle-group.tsx
'use client'

import { Toggle as TogglePrimitive } from '@base-ui/react/toggle'
import { ToggleGroup as ToggleGroupPrimitive } from '@base-ui/react/toggle-group'
import type { VariantProps } from 'class-variance-authority'
import * as React from 'react'
import { toggleVariants } from '@/components/ui/toggle'
import { cn } from '@/lib/utils'

const ToggleGroupContext = React.createContext<
	VariantProps<typeof toggleVariants> & {
		spacing?: number
		orientation?: 'horizontal' | 'vertical'
	}
>({
	size: 'default',
	variant: 'default',
	spacing: 2,
	orientation: 'horizontal',
})

function ToggleGroup({
	className,
	variant,
	size,
	spacing = 2,
	orientation = 'horizontal',
	children,
	...props
}: ToggleGroupPrimitive.Props &
	VariantProps<typeof toggleVariants> & {
		spacing?: number
		orientation?: 'horizontal' | 'vertical'
	}) {
	const contextValue = React.useMemo(
		() => ({ variant, size, spacing, orientation }),
		[variant, size, spacing, orientation],
	)

	return (
		<ToggleGroupPrimitive
			data-slot="toggle-group"
			data-variant={variant}
			data-size={size}
			data-spacing={spacing}
			data-orientation={orientation}
			orientation={orientation}
			style={{ '--gap': spacing } as React.CSSProperties}
			className={cn(
				'group/toggle-group flex w-fit flex-row items-center gap-[--spacing(var(--gap))] rounded-lg data-[size=sm]:rounded-[min(var(--radius-md),10px)] data-vertical:flex-col data-vertical:items-stretch',
				className,
			)}
			{...props}
		>
			<ToggleGroupContext.Provider value={contextValue}>{children}</ToggleGroupContext.Provider>
		</ToggleGroupPrimitive>
	)
}

function ToggleGroupItem({
	className,
	children,
	variant = 'default',
	size = 'default',
	...props
}: TogglePrimitive.Props & VariantProps<typeof toggleVariants>) {
	const context = React.useContext(ToggleGroupContext)

	return (
		<TogglePrimitive
			data-slot="toggle-group-item"
			data-variant={context.variant || variant}
			data-size={context.size || size}
			data-spacing={context.spacing}
			className={cn(
				'shrink-0 group-data-[spacing=0]/toggle-group:rounded-none group-data-[spacing=0]/toggle-group:px-2 focus:z-10 focus-visible:z-10 group-data-[spacing=0]/toggle-group:has-data-[icon=inline-end]:pr-1.5 group-data-[spacing=0]/toggle-group:has-data-[icon=inline-start]:pl-1.5 group-data-horizontal/toggle-group:data-[spacing=0]:first:rounded-l-lg group-data-vertical/toggle-group:data-[spacing=0]:first:rounded-t-lg group-data-horizontal/toggle-group:data-[spacing=0]:last:rounded-r-lg group-data-vertical/toggle-group:data-[spacing=0]:last:rounded-b-lg group-data-horizontal/toggle-group:data-[spacing=0]:data-[variant=outline]:border-l-0 group-data-vertical/toggle-group:data-[spacing=0]:data-[variant=outline]:border-t-0 group-data-horizontal/toggle-group:data-[spacing=0]:data-[variant=outline]:first:border-l group-data-vertical/toggle-group:data-[spacing=0]:data-[variant=outline]:first:border-t',
				toggleVariants({
					variant: context.variant || variant,
					size: context.size || size,
				}),
				className,
			)}
			{...props}
		>
			{children}
		</TogglePrimitive>
	)
}

export { ToggleGroup, ToggleGroupItem }

A set of two-state buttons that can be toggled on or off. Built on the Base UI ToggleGroup primitive with single or multiple selection modes.

Features

  • Single (default) or multiple selection
  • Built on the Base UI ToggleGroup primitive for accessibility
  • Shares styling with the Toggle component (toggleVariants)
  • variant and size set on the group are inherited by every item
  • spacing prop controls the gap between items; spacing={0} joins items into a segmented control
  • Horizontal and vertical orientation
  • Icon spacing via data-icon="inline-start" / data-icon="inline-end"
  • Controlled and uncontrolled modes
  • Individual item or group-wide disabled states

Usage

import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"
import { AlignCenter, AlignLeft, AlignRight, Bold, Italic } from "lucide-react"

// Single selection
<ToggleGroup defaultValue={["left"]}>
  <ToggleGroupItem value="left" aria-label="Align left">
    <AlignLeft />
  </ToggleGroupItem>
  <ToggleGroupItem value="center" aria-label="Align center">
    <AlignCenter />
  </ToggleGroupItem>
  <ToggleGroupItem value="right" aria-label="Align right">
    <AlignRight />
  </ToggleGroupItem>
</ToggleGroup>

// Multiple selection, segmented outline
<ToggleGroup multiple variant="outline" spacing={0}>
  <ToggleGroupItem value="bold">
    <Bold data-icon="inline-start" />
    Bold
  </ToggleGroupItem>
  <ToggleGroupItem value="italic">
    <Italic data-icon="inline-start" />
    Italic
  </ToggleGroupItem>
</ToggleGroup>

// Vertical
<ToggleGroup orientation="vertical">
  ...
</ToggleGroup>

Examples

Default

A joined (spacing={0}) outline group filtering cases by status. Give the group an aria-label so the set has a name.

Single Selection

By default one item can be pressed. The value is always an array, so a single selection is value={[language]}. Here it picks the language of a decision letter; ignoring the empty array in onValueChange keeps one option always selected.

<ToggleGroup value={[language]} onValueChange={(value) => value[0] && setLanguage(value[0])}>
  …
</ToggleGroup>

Multiple Selection

multiple lets any number of items be pressed – the weekdays for home help visits. Each short label has a full aria-label.

Variants

default and outline, set once on the group and inherited by every item.

Sizes

sm, default and lg.

Spacing

spacing={0} joins the items into a segmented control; any other value is the gap in spacing units.

Vertical

orientation="vertical" stacks the items – here as a filter for e-service areas, with counts.

With Icon and Text

Icons with data-icon="inline-start" next to text labels, for choosing how a decision is delivered.

View Modes

Icon-only items switching between list, grid and map views of municipal facilities.

Rating

A five-point satisfaction scale with icon-only items and a live text label for the current choice.

Disabled

Disable the whole group with disabled on ToggleGroup, or single items with disabled on ToggleGroupItem.

Props

ToggleGroup

  • multiple?: boolean - Whether several items can be pressed at once (default false)
  • value?: string[] - Controlled values of the pressed items (always an array, also for single selection)
  • defaultValue?: string[] - Default values for uncontrolled mode
  • onValueChange?: (value: string[], eventDetails) => void - Callback when selection changes
  • loopFocus?: boolean - Whether arrow keys wrap from the last item to the first (default true)
  • variant?: "default" | "outline" - Visual style applied to all items
  • size?: "sm" | "default" | "lg" - Size applied to all items
  • spacing?: number - Gap between items in spacing units (default 2; 0 joins items)
  • orientation?: "horizontal" | "vertical" - Layout direction (default "horizontal")
  • disabled?: boolean - Whether the entire group is disabled
  • Extends all Base UI ToggleGroup props

ToggleGroupItem

  • value: string - Unique identifier for the item (required)
  • variant?: "default" | "outline" - Used when the group does not set a variant
  • size?: "sm" | "default" | "lg" - Used when the group does not set a size
  • disabled?: boolean - Whether this specific item is disabled
  • Extends all Base UI Toggle props

Styling

Items use toggleVariants from @/components/ui/toggle. The group exposes data-variant, data-size, data-spacing and data-orientation attributes, and items expose data-slot="toggle-group-item", so you can target them with Tailwind data selectors:

<ToggleGroup className="rounded-lg bg-muted p-1">
  <ToggleGroupItem value="a" className="data-pressed:bg-background">A</ToggleGroupItem>
  <ToggleGroupItem value="b" className="data-pressed:bg-background">B</ToggleGroupItem>
</ToggleGroup>

Accessibility

  • Built on the Base UI ToggleGroup primitive with full keyboard support
  • Roving focus: Tab moves into and out of the group, arrow keys move between items
  • Space / Enter toggle the focused item
  • Home / End move to the first / last item
  • Provide an aria-label on icon-only items, and on the group when its purpose is not obvious

Best Practices

  1. Selection Modes

    • Use the default single selection for mutually exclusive options (radio-like behavior)
    • Use multiple for independent toggles that can be combined
  2. Labeling

    • Always provide meaningful aria-label attributes for icon-only items
    • Consider adding tooltips for icon-only items
  3. Visual Design

    • Use spacing={0} with variant="outline" for segmented controls
    • Keep the number of items manageable (typically 2-7 items)
  4. State Management

    • Use controlled mode when the value lives elsewhere
    • Provide sensible default values

Common Use Cases

  • Text Formatting: Bold, italic, underline in rich text editors
  • Layout Controls: Text alignment, view modes, display options
  • Filter Controls: Show/hide content categories or types
  • Media Controls: Playback options, audio settings
  • Segmented Controls: Day/week/month and similar period pickers

Integration Examples

With Form Libraries

import { useForm, Controller } from 'react-hook-form'

function PreferencesForm() {
  const { control, handleSubmit } = useForm()

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <Controller
        name="notifications"
        control={control}
        render={({ field }) => (
          <ToggleGroup
            multiple
            value={field.value || []}
            onValueChange={field.onChange}
          >
            <ToggleGroupItem value="email">Email</ToggleGroupItem>
            <ToggleGroupItem value="push">Push</ToggleGroupItem>
            <ToggleGroupItem value="sms">SMS</ToggleGroupItem>
          </ToggleGroup>
        )}
      />
    </form>
  )
}
  • Toggle
  • Button Group
  • Radio Group
  • Tabs