Toggle Group
A shadcn-style toggle group component built with Ark UI primitives.
import {
AlignCenterIcon,
AlignJustifyIcon,
AlignLeftIcon,
AlignRightIcon,
} from "lucide-react";
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
const ToggleGroupBasicDemo = () => (
<ToggleGroup defaultValue={["left"]}>
<ToggleGroupItem aria-label="Align left" value="left">
<AlignLeftIcon />
</ToggleGroupItem>
<ToggleGroupItem aria-label="Align center" value="center">
<AlignCenterIcon />
</ToggleGroupItem>
<ToggleGroupItem aria-label="Align right" value="right">
<AlignRightIcon />
</ToggleGroupItem>
<ToggleGroupItem aria-label="Justify" value="justify">
<AlignJustifyIcon />
</ToggleGroupItem>
</ToggleGroup>
);
export default ToggleGroupBasicDemo;
Installation
npx shadcn@latest add @ark-cn/toggle-groupInstall the dependency required by this primitive:
npm install @ark-ui/react class-variance-authorityCopy the component source into your app:
TSXcomponents/ui/toggle-group.tsx
"use client";
import { ToggleGroup as ToggleGroupPrimitive } from "@ark-ui/react/toggle-group";
import { cva, type VariantProps } from "class-variance-authority";
import { createContext, useContext } from "react";
import { Separator } from "@/components/ui/separator";
import { cn } from "@/lib/utils";
const toggleGroupRootVariants = cva(
"inline-flex w-fit gap-px rounded-lg border border-input bg-muted/40 p-0.5 data-[orientation=vertical]:flex-col",
);
const toggleGroupItemVariants = cva(
"inline-flex shrink-0 cursor-pointer items-center justify-center gap-2 whitespace-nowrap rounded-md border font-medium text-sm outline-none transition-colors disabled:pointer-events-none disabled:opacity-50 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
{
defaultVariants: {
size: "lg",
variant: "default",
},
variants: {
size: {
default: "h-9 min-w-9 px-3 sm:h-8 sm:min-w-8",
lg: "h-10 min-w-10 px-3.5 sm:h-9 sm:min-w-9",
sm: "h-8 min-w-8 px-2.5 sm:h-7 sm:min-w-7",
},
variant: {
default:
"border-transparent bg-transparent text-foreground hover:bg-accent data-[state=on]:bg-background data-[state=on]:text-foreground data-[state=on]:shadow-xs/5",
outline:
"border-input/70 bg-background text-foreground hover:bg-accent/60 data-[state=on]:border-primary/30 data-[state=on]:bg-primary/12 data-[state=on]:text-primary",
},
},
},
);
type ToggleGroupStyleContextValue = {
size?: VariantProps<typeof toggleGroupItemVariants>["size"];
variant?: VariantProps<typeof toggleGroupItemVariants>["variant"];
};
const ToggleGroupStyleContext = createContext<ToggleGroupStyleContextValue>({
size: "lg",
variant: "default",
});
export type ToggleGroupProps = ToggleGroupPrimitive.RootProps &
VariantProps<typeof toggleGroupItemVariants>;
export const ToggleGroup = ({
className,
size,
variant,
...props
}: ToggleGroupProps) => (
<ToggleGroupStyleContext.Provider value={{ size, variant }}>
<ToggleGroupPrimitive.Root
data-slot="toggle-group"
className={cn(toggleGroupRootVariants(), className)}
{...props}
/>
</ToggleGroupStyleContext.Provider>
);
export type ToggleGroupItemProps = ToggleGroupPrimitive.ItemProps &
VariantProps<typeof toggleGroupItemVariants>;
export const ToggleGroupItem = ({
className,
size,
variant,
...props
}: ToggleGroupItemProps) => {
const group = useContext(ToggleGroupStyleContext);
return (
<ToggleGroupPrimitive.Item
data-slot="toggle-group-item"
className={cn(
toggleGroupItemVariants({
size: size ?? group.size,
variant: variant ?? group.variant,
}),
className,
)}
{...props}
/>
);
};
export type ToggleGroupSeparatorProps = {
className?: string;
orientation?: "horizontal" | "vertical";
};
export const ToggleGroupSeparator = ({
className,
orientation = "vertical",
}: ToggleGroupSeparatorProps) => (
<Separator
className={cn("bg-input/70", className)}
data-slot="toggle-group-separator"
orientation={orientation}
/>
);
export const ToggleGroupContext = ToggleGroupPrimitive.Context;
export const ToggleGroupRootProvider = ToggleGroupPrimitive.RootProvider;
export type {
UseToggleGroupProps,
UseToggleGroupReturn,
} from "@ark-ui/react/toggle-group";
export {
useToggleGroup,
useToggleGroupContext,
} from "@ark-ui/react/toggle-group";
Update import aliases to match your project setup.
Usage
import * as ToggleGroup from "@/components/ui/toggle-group"Read exported parts in src/components/ui/toggle-group.tsx and compose the primitive according to the Ark UI pattern for this component.
Examples
Basic
import {
AlignCenterIcon,
AlignJustifyIcon,
AlignLeftIcon,
AlignRightIcon,
} from "lucide-react";
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
const ToggleGroupBasicDemo = () => (
<ToggleGroup defaultValue={["left"]}>
<ToggleGroupItem aria-label="Align left" value="left">
<AlignLeftIcon />
</ToggleGroupItem>
<ToggleGroupItem aria-label="Align center" value="center">
<AlignCenterIcon />
</ToggleGroupItem>
<ToggleGroupItem aria-label="Align right" value="right">
<AlignRightIcon />
</ToggleGroupItem>
<ToggleGroupItem aria-label="Justify" value="justify">
<AlignJustifyIcon />
</ToggleGroupItem>
</ToggleGroup>
);
export default ToggleGroupBasicDemo;
Controlled
Value: left
import {
AlignCenterIcon,
AlignJustifyIcon,
AlignLeftIcon,
AlignRightIcon,
} from "lucide-react";
import { useState } from "react";
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
const ToggleGroupControlledDemo = () => {
const [value, setValue] = useState(["left"]);
return (
<div className="space-y-2">
<ToggleGroup
onValueChange={(event) => setValue(event.value)}
value={value}
variant="outline"
>
<ToggleGroupItem aria-label="Align left" value="left">
<AlignLeftIcon />
</ToggleGroupItem>
<ToggleGroupItem aria-label="Align center" value="center">
<AlignCenterIcon />
</ToggleGroupItem>
<ToggleGroupItem aria-label="Align right" value="right">
<AlignRightIcon />
</ToggleGroupItem>
<ToggleGroupItem aria-label="Justify" value="justify">
<AlignJustifyIcon />
</ToggleGroupItem>
</ToggleGroup>
<p className="text-muted-foreground text-xs">
Value:{" "}
<span className="text-foreground">{value.join(", ") || "none"}</span>
</p>
</div>
);
};
export default ToggleGroupControlledDemo;
Small
import { BoldIcon, ItalicIcon, UnderlineIcon } from "lucide-react";
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
const ToggleGroupSmallDemo = () => (
<ToggleGroup defaultValue={["bold"]} size="sm">
<ToggleGroupItem aria-label="Toggle bold" value="bold">
<BoldIcon />
</ToggleGroupItem>
<ToggleGroupItem aria-label="Toggle italic" value="italic">
<ItalicIcon />
</ToggleGroupItem>
<ToggleGroupItem aria-label="Toggle underline" value="underline">
<UnderlineIcon />
</ToggleGroupItem>
</ToggleGroup>
);
export default ToggleGroupSmallDemo;
Large
import { BoldIcon, ItalicIcon, UnderlineIcon } from "lucide-react";
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
const ToggleGroupLargeDemo = () => (
<ToggleGroup defaultValue={["bold"]} size="lg">
<ToggleGroupItem aria-label="Toggle bold" value="bold">
<BoldIcon />
</ToggleGroupItem>
<ToggleGroupItem aria-label="Toggle italic" value="italic">
<ItalicIcon />
</ToggleGroupItem>
<ToggleGroupItem aria-label="Toggle underline" value="underline">
<UnderlineIcon />
</ToggleGroupItem>
</ToggleGroup>
);
export default ToggleGroupLargeDemo;
Outline
import { BoldIcon, ItalicIcon, UnderlineIcon } from "lucide-react";
import {
ToggleGroup,
ToggleGroupItem,
ToggleGroupSeparator,
} from "@/components/ui/toggle-group";
const ToggleGroupOutlineDemo = () => (
<ToggleGroup defaultValue={["bold"]} variant="outline">
<ToggleGroupItem aria-label="Toggle bold" value="bold">
<BoldIcon />
</ToggleGroupItem>
<ToggleGroupSeparator />
<ToggleGroupItem aria-label="Toggle italic" value="italic">
<ItalicIcon />
</ToggleGroupItem>
<ToggleGroupSeparator />
<ToggleGroupItem aria-label="Toggle underline" value="underline">
<UnderlineIcon />
</ToggleGroupItem>
</ToggleGroup>
);
export default ToggleGroupOutlineDemo;
Vertical
import { BoldIcon, ItalicIcon, UnderlineIcon } from "lucide-react";
import {
ToggleGroup,
ToggleGroupItem,
ToggleGroupSeparator,
} from "@/components/ui/toggle-group";
const ToggleGroupVerticalDemo = () => (
<ToggleGroup defaultValue={["bold"]} orientation="vertical" variant="outline">
<ToggleGroupItem aria-label="Toggle bold" value="bold">
<BoldIcon />
</ToggleGroupItem>
<ToggleGroupSeparator orientation="horizontal" />
<ToggleGroupItem aria-label="Toggle italic" value="italic">
<ItalicIcon />
</ToggleGroupItem>
<ToggleGroupSeparator orientation="horizontal" />
<ToggleGroupItem aria-label="Toggle underline" value="underline">
<UnderlineIcon />
</ToggleGroupItem>
</ToggleGroup>
);
export default ToggleGroupVerticalDemo;
Disabled
import { BoldIcon, ItalicIcon, UnderlineIcon } from "lucide-react";
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
const ToggleGroupDisabledDemo = () => (
<ToggleGroup defaultValue={["bold"]} disabled>
<ToggleGroupItem aria-label="Toggle bold" value="bold">
<BoldIcon />
</ToggleGroupItem>
<ToggleGroupItem aria-label="Toggle italic" value="italic">
<ItalicIcon />
</ToggleGroupItem>
<ToggleGroupItem aria-label="Toggle underline" value="underline">
<UnderlineIcon />
</ToggleGroupItem>
</ToggleGroup>
);
export default ToggleGroupDisabledDemo;
With Disabled Item
import { BoldIcon, ItalicIcon, UnderlineIcon } from "lucide-react";
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
const ToggleGroupWithDisabledItemDemo = () => (
<ToggleGroup defaultValue={["bold"]}>
<ToggleGroupItem aria-label="Toggle bold" value="bold">
<BoldIcon />
</ToggleGroupItem>
<ToggleGroupItem aria-label="Toggle italic" disabled value="italic">
<ItalicIcon />
</ToggleGroupItem>
<ToggleGroupItem aria-label="Toggle underline" value="underline">
<UnderlineIcon />
</ToggleGroupItem>
</ToggleGroup>
);
export default ToggleGroupWithDisabledItemDemo;
Multiple
import { BoldIcon, ItalicIcon, UnderlineIcon } from "lucide-react";
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
const ToggleGroupMultipleDemo = () => (
<ToggleGroup defaultValue={["bold"]} multiple>
<ToggleGroupItem aria-label="Toggle bold" value="bold">
<BoldIcon />
</ToggleGroupItem>
<ToggleGroupItem aria-label="Toggle italic" value="italic">
<ItalicIcon />
</ToggleGroupItem>
<ToggleGroupItem aria-label="Toggle underline" value="underline">
<UnderlineIcon />
</ToggleGroupItem>
</ToggleGroup>
);
export default ToggleGroupMultipleDemo;
Root Provider
Current: left
import {
AlignCenterIcon,
AlignJustifyIcon,
AlignLeftIcon,
AlignRightIcon,
} from "lucide-react";
import {
ToggleGroupItem,
ToggleGroupRootProvider,
useToggleGroup,
} from "@/components/ui/toggle-group";
const ToggleGroupRootProviderDemo = () => {
const toggleGroup = useToggleGroup({ defaultValue: ["left"] });
return (
<div className="space-y-2">
<p className="text-muted-foreground text-xs">
Current:{" "}
<span className="text-foreground">
{toggleGroup.value.join(", ") || "none"}
</span>
</p>
<ToggleGroupRootProvider value={toggleGroup}>
<ToggleGroupItem aria-label="Align left" value="left">
<AlignLeftIcon />
</ToggleGroupItem>
<ToggleGroupItem aria-label="Align center" value="center">
<AlignCenterIcon />
</ToggleGroupItem>
<ToggleGroupItem aria-label="Align right" value="right">
<AlignRightIcon />
</ToggleGroupItem>
<ToggleGroupItem aria-label="Justify" value="justify">
<AlignJustifyIcon />
</ToggleGroupItem>
</ToggleGroupRootProvider>
</div>
);
};
export default ToggleGroupRootProviderDemo;
API reference
This component mirrors the upstream Ark UI primitive. All props and DOM behavior are defined by Ark unless you see an ark-cn-only row below.
ToggleGroup
| Prop | Type | Description |
|---|---|---|
| size? | "default" | "sm" | "lg" | Default size for items via context. |
| variant? | "default" | "outline" | Default item chrome variant. |
ToggleGroupItem
| Prop | Type | Description |
|---|---|---|
| size? | "default" | "sm" | "lg" | Per-item size override. |
| variant? | "default" | "outline" | Per-item variant override. |
ToggleGroupSeparator
| Prop | Type | Description |
|---|---|---|
| orientation? | "horizontal" | "vertical" | Separator direction. |
See the ARK UI documentation for the full API.
Accessibility
See the Ark UI documentation for clarification.