HextaUI

Toggle group

A row of toggles for one choice or many, with a fill that slides to the picked item, joined or spaced items and arrow-key focus.

import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"

export function ToggleGroupDemo() {
  return (
    <ToggleGroup aria-label="Date range" defaultValue={["week"]}>
      <ToggleGroupItem value="day">Day</ToggleGroupItem>
      <ToggleGroupItem value="week">Week</ToggleGroupItem>
      <ToggleGroupItem value="month">Month</ToggleGroupItem>
      <ToggleGroupItem value="year">Year</ToggleGroupItem>
    </ToggleGroup>
  )
}
pnpm dlx shadcn@latest add https://hextaui.com/r/toggle-group.json

Adds the component, the HextaUI theme tokens and any HextaUI components it depends on.

import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"
<ToggleGroup aria-label="Date range" defaultValue={["week"]}>
  <ToggleGroupItem value="day">Day</ToggleGroupItem>
  <ToggleGroupItem value="week">Week</ToggleGroupItem>
  <ToggleGroupItem value="month">Month</ToggleGroupItem>
</ToggleGroup>

When one item can be on at a time, a single fill slides to the picked item and takes its shape, so the group reads as one control. With multiple, each item fills on its own. The fill is placed before the first paint, so it never slides in on load.

ToggleGroup
└── ToggleGroupItem

Outline

variant="outline" joins the items into one bordered control.

import {
  IconAlignCenter,
  IconAlignJustified,
  IconAlignLeft,
  IconAlignRight,
} from "@tabler/icons-react"

import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"

export function ToggleGroupOutline() {
  return (
    <ToggleGroup
      variant="outline"
      aria-label="Text alignment"
      defaultValue={["left"]}
    >
      <ToggleGroupItem value="left" aria-label="Align left">
        <IconAlignLeft />
      </ToggleGroupItem>
      <ToggleGroupItem value="center" aria-label="Align center">
        <IconAlignCenter />
      </ToggleGroupItem>
      <ToggleGroupItem value="right" aria-label="Align right">
        <IconAlignRight />
      </ToggleGroupItem>
      <ToggleGroupItem value="justify" aria-label="Justify">
        <IconAlignJustified />
      </ToggleGroupItem>
    </ToggleGroup>
  )
}

Multiple

multiple lets any number of items be on, like text formatting.

import {
  IconBold,
  IconItalic,
  IconStrikethrough,
  IconUnderline,
} from "@tabler/icons-react"

import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"

export function ToggleGroupMultiple() {
  return (
    <ToggleGroup
      multiple
      variant="outline"
      aria-label="Text formatting"
      defaultValue={["bold", "italic"]}
    >
      <ToggleGroupItem value="bold" aria-label="Bold">
        <IconBold />
      </ToggleGroupItem>
      <ToggleGroupItem value="italic" aria-label="Italic">
        <IconItalic />
      </ToggleGroupItem>
      <ToggleGroupItem value="underline" aria-label="Underline">
        <IconUnderline />
      </ToggleGroupItem>
      <ToggleGroupItem value="strikethrough" aria-label="Strikethrough">
        <IconStrikethrough />
      </ToggleGroupItem>
    </ToggleGroup>
  )
}

Sizes

size on the group sets every item: sm, default or lg.

import { IconBold, IconItalic, IconUnderline } from "@tabler/icons-react"

import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"

export function ToggleGroupSizes() {
  return (
    <div className="flex flex-col items-center gap-4">
      {(["sm", "default", "lg"] as const).map((size) => (
        <ToggleGroup
          key={size}
          multiple
          size={size}
          variant="outline"
          aria-label={`Formatting, ${size}`}
          defaultValue={["bold"]}
        >
          <ToggleGroupItem value="bold" aria-label="Bold">
            <IconBold />
          </ToggleGroupItem>
          <ToggleGroupItem value="italic" aria-label="Italic">
            <IconItalic />
          </ToggleGroupItem>
          <ToggleGroupItem value="underline" aria-label="Underline">
            <IconUnderline />
          </ToggleGroupItem>
        </ToggleGroup>
      ))}
    </div>
  )
}

Spacing

spacing puts a gap between items on the spacing scale. Outline items with a gap keep their own borders and fills.

import { IconStar } from "@tabler/icons-react"

import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"

export function ToggleGroupSpacing() {
  return (
    <div className="flex flex-col items-center gap-4">
      <ToggleGroup spacing={1} aria-label="Rating filter" defaultValue={["4"]}>
        {["1", "2", "3", "4", "5"].map((stars) => (
          <ToggleGroupItem key={stars} value={stars}>
            {stars}
            <IconStar />
          </ToggleGroupItem>
        ))}
      </ToggleGroup>
      <ToggleGroup
        multiple
        spacing={2}
        variant="outline"
        size="sm"
        aria-label="Topics"
        defaultValue={["design"]}
      >
        <ToggleGroupItem value="design">Design</ToggleGroupItem>
        <ToggleGroupItem value="engineering">Engineering</ToggleGroupItem>
        <ToggleGroupItem value="research">Research</ToggleGroupItem>
      </ToggleGroup>
    </div>
  )
}

Icons and labels

Put an icon before the label. Icon-only items need an aria-label.

import { IconDeviceDesktop, IconMoon, IconSun } from "@tabler/icons-react"

import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"

export function ToggleGroupIcons() {
  return (
    <ToggleGroup aria-label="Theme" defaultValue={["system"]}>
      <ToggleGroupItem value="light">
        <IconSun />
        Light
      </ToggleGroupItem>
      <ToggleGroupItem value="dark">
        <IconMoon />
        Dark
      </ToggleGroupItem>
      <ToggleGroupItem value="system">
        <IconDeviceDesktop />
        System
      </ToggleGroupItem>
    </ToggleGroup>
  )
}

Vertical

orientation="vertical" stacks the items and moves focus with ↑ and ↓.

import { IconLayoutGrid, IconLayoutList, IconList } from "@tabler/icons-react"

import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"

export function ToggleGroupVertical() {
  return (
    <ToggleGroup
      orientation="vertical"
      variant="outline"
      aria-label="Layout"
      defaultValue={["grid"]}
    >
      <ToggleGroupItem value="grid" aria-label="Grid">
        <IconLayoutGrid />
      </ToggleGroupItem>
      <ToggleGroupItem value="cards" aria-label="Cards">
        <IconLayoutList />
      </ToggleGroupItem>
      <ToggleGroupItem value="list" aria-label="List">
        <IconList />
      </ToggleGroupItem>
    </ToggleGroup>
  )
}

Disabled

Disable one item, or the whole group with disabled. Disabled items are skipped by the keyboard.

import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"

export function ToggleGroupDisabled() {
  return (
    <div className="flex flex-col items-center gap-4">
      <ToggleGroup aria-label="Plan" defaultValue={["pro"]}>
        <ToggleGroupItem value="free">Free</ToggleGroupItem>
        <ToggleGroupItem value="pro">Pro</ToggleGroupItem>
        <ToggleGroupItem value="team" disabled>
          Team
        </ToggleGroupItem>
      </ToggleGroup>
      <ToggleGroup
        disabled
        variant="outline"
        aria-label="Billing"
        defaultValue={["monthly"]}
      >
        <ToggleGroupItem value="monthly">Monthly</ToggleGroupItem>
        <ToggleGroupItem value="yearly">Yearly</ToggleGroupItem>
      </ToggleGroup>
    </div>
  )
}

Controlled

Pass value and onValueChange. Ignoring the empty value keeps one item on, so pressing the picked item again does nothing.

Showing the Board view.

"use client"

import * as React from "react"

import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"

const views = ["Board", "List", "Calendar"]

export function ToggleGroupControlled() {
  const [view, setView] = React.useState("Board")

  return (
    <div className="flex flex-col items-center gap-3">
      <ToggleGroup
        variant="outline"
        aria-label="View"
        value={[view]}
        onValueChange={(value) => {
          if (value.length > 0) {
            setView(value[0])
          }
        }}
      >
        {views.map((item) => (
          <ToggleGroupItem key={item} value={item}>
            {item}
          </ToggleGroupItem>
        ))}
      </ToggleGroup>
      <p className="text-sm text-muted-foreground">Showing the {view} view.</p>
    </div>
  )
}

Right to left

Item order, joined corners, the sliding fill and arrow keys follow the reading direction.

import {
  IconAlignCenter,
  IconAlignLeft,
  IconAlignRight,
} from "@tabler/icons-react"

import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"

export function ToggleGroupRtl() {
  return (
    <div dir="rtl" className="flex flex-col items-center gap-4">
      <ToggleGroup aria-label="الفترة" defaultValue={["week"]}>
        <ToggleGroupItem value="day">يوم</ToggleGroupItem>
        <ToggleGroupItem value="week">أسبوع</ToggleGroupItem>
        <ToggleGroupItem value="month">شهر</ToggleGroupItem>
      </ToggleGroup>
      <ToggleGroup
        variant="outline"
        aria-label="محاذاة النص"
        defaultValue={["right"]}
      >
        <ToggleGroupItem value="right" aria-label="محاذاة لليمين">
          <IconAlignRight />
        </ToggleGroupItem>
        <ToggleGroupItem value="center" aria-label="توسيط">
          <IconAlignCenter />
        </ToggleGroupItem>
        <ToggleGroupItem value="left" aria-label="محاذاة لليسار">
          <IconAlignLeft />
        </ToggleGroupItem>
      </ToggleGroup>
    </div>
  )
}
KeyAction
TabMoves into the group on the last focused item, then out of it.
←→Moves between items, wrapping at the ends. ↑ and ↓ when vertical.
HomeEndMoves to the first or last item.
EnterSpaceTurns the focused item on or off.
  • The group has the group role; give it an aria-label. Each item is a button with aria-pressed.
  • Only one item is in the tab order, so the group is a single Tab stop and arrow keys move inside it.
  • Without multiple, pressing the picked item turns it off. Control the value to keep one item on.
  • With reduced motion, the fill fades instead of sliding.
PropTypeDefault
value
string[]–
defaultValue
string[]–
onValueChange
(value: string[], details) => void–
multiple
booleanfalse
variantSets every item's variant.
"default" | "outline""default"
sizeSets every item's size.
"default" | "sm" | "lg""default"
spacingGap between items on the spacing scale. 0 joins outline items.
0 | 0.5 | 1 | 1.5 | 2 | 2.5 | 3 | 4 | 5 | 6 | 80
orientation
"horizontal" | "vertical""horizontal"
loopFocus
booleantrue
disabled
booleanfalse
render
ReactElement | (props, state) => ReactElement<div>
AttributeDescription
data-slot="toggle-group"The root.
data-slot="toggle-group-indicator"The sliding fill, rendered without multiple. data-visible when an item is on.
data-variant / data-size / data-spacingThe group's variant, size and spacing.
data-orientation"horizontal" or "vertical".
data-multiplePresent when many items can be on.
data-disabledPresent when the group is disabled.
PropTypeDefault
valueIdentifies the item in the group's value.
string–
disabled
booleanfalse
variantUsed when the group sets none.
"default" | "outline"–
sizeUsed when the group sets none.
"default" | "sm" | "lg"–
nativeButton
booleantrue
render
ReactElement | (props, state) => ReactElement<button>
AttributeDescription
data-slot="toggle-group-item"Each item.
data-pressedThe item is on.
data-disabledThe item is disabled.
data-variant / data-sizeThe item's resolved variant and size.