- Accordion
- Agent Thinking
- Alert
- Alert Dialog
- Announcement
- Aspect Ratio
- Attachment
- Avatar
- Badge
- Breadcrumb
- Bubble
- Button
- Button Group
- Calendar
- Card
- Carousel
- Checkbox
- Checkbox Card
- Code Block
- Collapsible
- Color Picker
- Combobox
- Command
- Composer
- Composer Loader
- Context Menu
- Data Grid
- Data Table
- Date Picker
- Dialog
- Direction
- Drawer
- Dropdown Menu
- Empty
- Field
- File Upload
- Hover Card
- Input
- Input Group
- Input Otp
- Item
- Kbd
- Label
- Marker
- Menubar
- Message
- Message Scroller
- Meter
- Native Select
- Navigation Menu
- Number Field
- Pagination
- Period Switcher
- Popover
- Progress
- Questionnaire
- Radio Card
- Radio Group
- Resizable
- Reveal
- Scroll Area
- Search Field
- Select
- Separator
- Sheet
- Sidebar
- Skeleton
- Slider
- Social Button
- Sonner
- Spinner
- Status Dot
- Switch
- Switch Card
- Table
- Tabs
- Tag Group
- Textarea
- Theme Toggle
- Time Field
- Toggle
- Toggle Group
- Tooltip
- Trace
- Tree View
- Agent Creator 01
- Agent Limits Card 01
- Agent Progress 01
- AI Chat 01
- AI Image Generation 01
- AI Profile 01
- Alerts Card 01
- Auth Card 01
- Calendar 01
- Composer 01
- Composer Attachments 01
- Composer Panel 01
- Finance Dashboard 01
- Home Dashboard 01
- Marketing Dashboard 01
- Medical Profile 01
- Meeting Scheduler 01
- Multi Agent Chat 01
- Notification Center 01
- Patient Info Card 01
- Project Board 01
- Questionnaire 01
- Settings Modal 01
- Sidebar 01
- Stat Cards 01
- Task List 01
- Web Search 01
- Activity Rings Card 01
- Area Chart Card 01
- Bar List Card 01
- Combo Chart Card 01
- Contributions Card 01
- Earnings Chart Card 01
- Funnel Chart Card 01
- Heatmap Chart Card 01
- Line Chart Card 01
- Most Active Days Card 01
- Orders Chart Card 01
- Radar Chart Card 01
- Radial Chart Card 01
- Revenue Chart Card 01
- Sankey Chart Card 01
- Scatter Chart Card 01
- Sleep Score Card 01
- Stage Bars Card 01
- Steps Card 01
Installation
$ pnpm dlx shadcn@latest add @cobalt/toggle-group
Options
variant="segmented"puts the items on a track with a sliding thumb on the selected one.variant="ghost"has no track; the pressed item gets a filled chip.
Source
"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 { cn } from "cn";
import * as React from "react";
import { toggleVariants } from "@/components/ui/toggle";
const ToggleGroupContext = React.createContext<
Omit<VariantProps<typeof toggleVariants>, "variant"> & {
variant?: VariantProps<typeof toggleVariants>["variant"] | "segmented";
spacing?: number;
orientation?: "horizontal" | "vertical";
}
>({
orientation: "horizontal",
size: "default",
spacing: 2,
variant: "default",
});
function ToggleGroup({
className,
variant,
size,
spacing = 2,
orientation = "horizontal",
children,
ref,
...props
}: ToggleGroupPrimitive.Props &
Omit<VariantProps<typeof toggleVariants>, "variant"> & {
variant?: VariantProps<typeof toggleVariants>["variant"] | "segmented";
spacing?: number;
orientation?: "horizontal" | "vertical";
}) {
const innerRef = React.useRef<HTMLDivElement>(null);
const [thumb, setThumb] = React.useState<{
left: number;
top: number;
width: number;
height: number;
} | null>(null);
const segmented = variant === "segmented" || variant === "ghost";
React.useLayoutEffect(() => {
const el = innerRef.current;
if (!el || variant !== "segmented" || props.multiple) {
return;
}
const measure = () => {
const selected = el.querySelector<HTMLElement>(
'[data-slot="toggle-group-item"][data-pressed]'
);
setThumb(
selected
? {
left: selected.offsetLeft,
top: selected.offsetTop,
width: selected.offsetWidth,
height: selected.offsetHeight,
}
: null
);
};
const ro = new ResizeObserver(measure);
const observe = () => {
for (const item of el.querySelectorAll("button")) {
ro.observe(item);
}
measure();
};
ro.observe(el);
const mo = new MutationObserver(observe);
mo.observe(el, {
attributes: true,
childList: true,
characterData: true,
subtree: true,
attributeFilter: ["data-pressed"],
});
observe();
return () => {
ro.disconnect();
mo.disconnect();
};
}, [variant, props.multiple]);
return (
<ToggleGroupPrimitive
ref={(node) => {
innerRef.current = node;
if (typeof ref === "function") {
ref(node);
} else if (ref) {
ref.current = node;
}
}}
data-slot="toggle-group"
data-variant={variant}
data-size={size}
data-multiple={props.multiple ? "" : undefined}
data-spacing={spacing}
data-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-[6px] data-[spacing=0]:data-[variant=outline]:shadow-xs data-vertical:flex-col data-vertical:items-stretch",
segmented && "relative items-start gap-0.5 rounded-[10px]",
variant === "segmented" &&
"bg-[var(--toggle-group-track,var(--muted))] p-1",
className
)}
{...props}
onValueChange={(value, details) => {
// Segmented single-select always keeps one item selected.
if (segmented && !props.multiple && value.length === 0) {
details.cancel();
return;
}
props.onValueChange?.(value, details);
}}
>
{variant === "segmented" && !props.multiple && thumb && (
<span
aria-hidden
className="pointer-events-none absolute top-0 left-0 rounded-[6px] bg-[var(--surface-raised)] shadow-[shadow:var(--toggle-group-shadow,0_1px_rgb(0_0_0/0.05))] transition-[transform,width,height] duration-200 ease-[ease] motion-reduce:transition-none"
style={{
transform: `translate(${thumb.left}px, ${thumb.top}px)`,
width: thumb.width,
height: thumb.height,
}}
/>
)}
<ToggleGroupContext.Provider
value={{ orientation, size, spacing, variant }}
>
{children}
</ToggleGroupContext.Provider>
</ToggleGroupPrimitive>
);
}
function ToggleGroupItem({
className,
children,
variant = "default",
size = "default",
...props
}: TogglePrimitive.Props & VariantProps<typeof toggleVariants>) {
const context = React.useContext(ToggleGroupContext);
const segmented =
context.variant === "segmented" || context.variant === "ghost";
return (
<TogglePrimitive
data-slot="toggle-group-item"
data-variant={context.variant || variant}
data-size={context.size || size}
data-spacing={context.spacing}
className={cn(
!segmented &&
"shrink-0 group-data-[spacing=0]/toggle-group:rounded-none group-data-[spacing=0]/toggle-group:px-2 group-data-[spacing=0]/toggle-group:shadow-none 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-[6px] group-data-vertical/toggle-group:data-[spacing=0]:first:rounded-t-[6px] group-data-horizontal/toggle-group:data-[spacing=0]:last:rounded-r-[6px] group-data-vertical/toggle-group:data-[spacing=0]:last:rounded-b-[6px] data-[state=on]:bg-muted 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",
!segmented &&
toggleVariants({
size: context.size || size,
variant:
context.variant === "outline" ||
context.variant === "choice" ||
context.variant === "swatch"
? context.variant
: variant,
}),
segmented &&
"relative z-10 inline-flex shrink-0 cursor-pointer items-center justify-center rounded-[6px] px-2.5 py-1 text-center text-sm leading-5 font-normal whitespace-nowrap text-[color:var(--foreground-secondary,var(--muted-foreground))] outline-none hover:text-foreground hover:transition-colors hover:duration-200 hover:ease-[ease] focus-visible:ring-2 focus-visible:ring-ring data-pressed:font-medium data-pressed:text-foreground data-pressed:transition-none data-disabled:cursor-not-allowed data-disabled:opacity-50 motion-reduce:transition-none",
segmented &&
context.variant === "segmented" &&
"isolate before:absolute before:inset-0 before:-z-10 before:rounded-[inherit] group-data-[multiple]/toggle-group:data-pressed:before:bg-[var(--surface-raised)]",
className
)}
{...props}
>
{children}
</TogglePrimitive>
);
}
export { ToggleGroup, ToggleGroupItem }; On this page