A compact roving-focus selection group for one or several pressed options, with clear states and mobile-friendly sizing.
Project view
Pick your preferred layout
"use client";
import * as React from "react";
import { AlignJustify, Grid2X2, Rows3 } from "lucide-react";
import {
ToggleGroup,
ToggleGroupItem,
} from "@/components/ballmac/toggle-group";
export default function ToggleGroupDemo() {
const [view, setView] = React.useState("cards");
return (
<div className="w-full max-w-sm rounded-xl border bg-card p-4 shadow-sm">
<div className="mb-4 flex flex-col items-start justify-between gap-3 sm:flex-row sm:items-center">
<div>
<p className="text-sm font-semibold">Project view</p>
<p className="text-xs text-muted-foreground">
Pick your preferred layout
</p>
</div>
<ToggleGroup
type="single"
value={view}
onValueChange={(value) => value && setView(value)}
aria-label="Project view"
size="sm"
>
<ToggleGroupItem value="cards" aria-label="Cards">
<Grid2X2 aria-hidden="true" />
</ToggleGroupItem>
<ToggleGroupItem value="rows" aria-label="Rows">
<Rows3 aria-hidden="true" />
</ToggleGroupItem>
<ToggleGroupItem value="compact" aria-label="Compact">
<AlignJustify aria-hidden="true" />
</ToggleGroupItem>
</ToggleGroup>
</div>
<div className="rounded-lg border bg-background p-3 text-sm text-muted-foreground">
{view === "cards"
? "Projects shown as cards"
: view === "rows"
? "Projects shown as rows"
: "Compact list enabled"}
</div>
</div>
);
}Installation
$ pnpm dlx shadcn@latest add @ballmac/toggle-groupInstall the dependencies.
$ pnpm add radix-uiAdd the Ballmac items it builds on.
$ pnpm dlx shadcn@latest add @ballmac/directionCopy the source into your project.
components/ballmac/toggle-group.tsx// Ballmac UI: Toggle Group. https://ui.ballmac.com/components/toggle-group "use client"; import * as React from "react"; import { ToggleGroup as Primitive } from "radix-ui"; import { useDirection } from "@/lib/ballmac/direction"; import { cn } from "@/lib/utils"; type ToggleGroupProps = React.ComponentProps<typeof Primitive.Root> & { /** Surface treatment shared by the items. */ variant?: "default" | "outline"; /** Item height. */ size?: "sm" | "default" | "lg"; }; function ToggleGroup({ className, variant = "default", size = "default", ...props }: ToggleGroupProps) { const dir = useDirection(props.dir); return ( <Primitive.Root dir={dir} data-slot="toggle-group" data-variant={variant} data-size={size} className={cn( "inline-flex max-w-full items-center gap-1 rounded-lg bg-muted p-1", variant === "outline" && "border bg-background", className, )} {...props} /> ); } type ToggleGroupItemProps = React.ComponentProps<typeof Primitive.Item>; function ToggleGroupItem({ className, ...props }: ToggleGroupItemProps) { return ( <Primitive.Item data-slot="toggle-group-item" className={cn( "inline-flex min-w-0 items-center justify-center gap-2 rounded-md px-3 text-sm font-medium text-muted-foreground outline-none transition-[color,background-color,box-shadow] duration-150 hover:text-foreground focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-background data-[state=on]:text-foreground data-[state=on]:shadow-xs [[data-size=sm]_&]:h-8 [[data-size=default]_&]:h-9 [[data-size=lg]_&]:h-11 [[data-variant=outline]_&]:data-[state=on]:bg-primary/10 [[data-variant=outline]_&]:data-[state=on]:text-primary motion-reduce:transition-none [&_svg]:size-4", className, )} {...props} /> ); } export { ToggleGroup, ToggleGroupItem, type ToggleGroupProps, type ToggleGroupItemProps, };Update the import paths to match your project setup.
Usage
import { ToggleGroup, ToggleGroupItem } from "@/components/ballmac/toggle-group"The full example is in the Code tab above.
Examples
Multiple filters
Text formatting
import { Bold, Italic, Underline } from "lucide-react";
import {
ToggleGroup,
ToggleGroupItem,
} from "@/components/ballmac/toggle-group";
export default function ToggleGroupStates() {
return (
<div className="w-full max-w-xs">
<p className="mb-2 text-sm font-medium">Text formatting</p>
<ToggleGroup
type="multiple"
defaultValue={["bold"]}
variant="outline"
aria-label="Text formatting"
>
<ToggleGroupItem value="bold" aria-label="Bold">
<Bold aria-hidden="true" />
Bold
</ToggleGroupItem>
<ToggleGroupItem value="italic" aria-label="Italic">
<Italic aria-hidden="true" />
</ToggleGroupItem>
<ToggleGroupItem value="underline" aria-label="Underline">
<Underline aria-hidden="true" />
</ToggleGroupItem>
</ToggleGroup>
</div>
);
}API reference
| Prop | Type | Default |
|---|---|---|
variantSurface treatment shared by the items. | "default" | "outline" | "default" |
sizeItem height. | "sm" | "default" | "lg" | "default" |
Also accepts the standard attributes of its root element.
Accessibility
| Key | Action |
|---|---|
| Tab | Enters or exits the group |
| Arrow keys | Moves focus between items |
| Enter / Space | Selects the focused item |
Use with AI
Lets users choose one or multiple options from a short row while Radix handles roving focus. With the shadcn MCP server set up (guide), ask your agent:
Add the Ballmac UI Toggle Group (@ballmac/toggle-group) to this project with the shadcn MCP, then use it where it fits.
Use it for
- View mode or density selector
- Formatting options that can coexist
Not for
- Unrelated actions; use button-group
- A long list of options; use select
Registry JSON: https://ui.ballmac.com/r/toggle-group.json
Credits
Free to use in personal and commercial projects.
- npm
- radix-ui
- Registry
- @ballmac/directionshadcn/utils
Pairs well with
Button Group
Join adjacent actions into one compact control surface while retaining each button's visible keyboard focus and accessible name.
Accordion
Vertically stacked disclosure sections on Radix Accordion, single or multiple open, with hairline dividers, a rotating plus and height animation.
Avatar
A Radix avatar with image and initials fallback, three sizes, an optional presence dot, and AvatarGroup for overlapping stacks with a +N overflow counter.
Badge
A small pill label in four variants with an optional status dot and success, warning and error tones. Style links with badgeVariants().