Button Group
A container that groups related buttons together with consistent styling.
Loading…
import { PlusIcon } from "lucide-react";import { Button } from "@/components/ui/button";import { ButtonGroup } from "@/components/ui/button-group";export default function ButtonGroupDemo() { return ( <ButtonGroup> <Button variant="outline">Archive</Button> <Button variant="outline">Report</Button> <Button variant="outline">Snooze</Button> <Button variant="outline" size="icon" aria-label="Add"> <PlusIcon /> </Button> </ButtonGroup> );}Installation
npx shadcn@latest add @cortexcn/button-groupCopy and paste the following code into your project.
import * as React from "react";import { cva, type VariantProps } from "class-variance-authority";import { Slot } from "radix-ui";import { cn } from "@/lib/utils";import { Separator } from "@/components/ui/separator";const buttonGroupVariants = cva( "flex w-fit items-stretch [&>*]:relative [&>*]:focus-visible:z-10 [&>[data-slot=select-trigger]:not([class*='w-'])]:w-fit [&>input]:flex-1 has-[>[data-slot=button-group]]:gap-2", { variants: { orientation: { horizontal: "[&>*:not(:first-child)]:rounded-l-none [&>*:not(:first-child)]:border-l-0 [&>*:not(:last-child)]:rounded-r-none", vertical: "flex-col [&>*:not(:first-child)]:rounded-t-none [&>*:not(:first-child)]:border-t-0 [&>*:not(:last-child)]:rounded-b-none", }, }, defaultVariants: { orientation: "horizontal", }, },);function ButtonGroup({ className, orientation = "horizontal", ...props}: React.ComponentProps<"div"> & VariantProps<typeof buttonGroupVariants>) { return ( <div data-slot="button-group" data-orientation={orientation} role="group" className={cn(buttonGroupVariants({ orientation }), className)} {...props} /> );}function ButtonGroupText({ className, asChild = false, ...props}: React.ComponentProps<"div"> & { asChild?: boolean }) { const Comp = asChild ? Slot.Root : "div"; return ( <Comp className={cn( "flex items-center gap-2 rounded-md border bg-muted px-4 text-sm font-medium shadow-xs [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4", className, )} {...props} /> );}function ButtonGroupSeparator({ className, orientation = "vertical", ...props}: React.ComponentProps<typeof Separator>) { return ( <Separator data-slot="button-group-separator" orientation={orientation} className={cn( "relative !m-0 self-stretch bg-input data-[orientation=vertical]:h-auto", className, )} {...props} /> );}export { ButtonGroup, ButtonGroupSeparator, ButtonGroupText, buttonGroupVariants,};Update the import paths to match your project setup.
Usage
import {
ButtonGroup,
ButtonGroupSeparator,
ButtonGroupText,
} from "@/components/ui/button-group";<ButtonGroup>
<Button>Button 1</Button>
<Button>Button 2</Button>
</ButtonGroup>Composition
Use the following composition to build a ButtonGroup:
ButtonGroup
├── Button or Input
├── ButtonGroupSeparator
└── ButtonGroupTextAccessibility
- The
ButtonGroupcomponent has theroleattribute set togroup. - Use Tab to navigate between the buttons in the group.
- Use
aria-labeloraria-labelledbyto label the button group.
Examples
Orientation
Set the orientation prop to change the button group layout.
Loading…
import { MinusIcon, PlusIcon } from "lucide-react";import { Button } from "@/components/ui/button";import { ButtonGroup } from "@/components/ui/button-group";export default function ButtonGroupOrientation() { return ( <ButtonGroup orientation="vertical" aria-label="Media controls" className="h-fit" > <Button variant="outline" size="icon"> <PlusIcon /> </Button> <Button variant="outline" size="icon"> <MinusIcon /> </Button> </ButtonGroup> );}Size
Control the size of buttons using the size prop on individual buttons.
Loading…
import { PlusIcon } from "lucide-react";import { Button } from "@/components/ui/button";import { ButtonGroup } from "@/components/ui/button-group";export default function ButtonGroupSize() { return ( <div className="flex flex-col items-start gap-8"> <ButtonGroup> <Button variant="outline" size="sm"> Small </Button> <Button variant="outline" size="sm"> Button </Button> <Button variant="outline" size="sm"> Group </Button> <Button variant="outline" size="icon-sm"> <PlusIcon /> </Button> </ButtonGroup> <ButtonGroup> <Button variant="outline">Default</Button> <Button variant="outline">Button</Button> <Button variant="outline">Group</Button> <Button variant="outline" size="icon"> <PlusIcon /> </Button> </ButtonGroup> <ButtonGroup> <Button variant="outline" size="lg"> Large </Button> <Button variant="outline" size="lg"> Button </Button> <Button variant="outline" size="lg"> Group </Button> <Button variant="outline" size="icon-lg"> <PlusIcon /> </Button> </ButtonGroup> </div> );}Separator
The ButtonGroupSeparator component visually divides buttons within a group.
Loading…
import { Button } from "@/components/ui/button";import { ButtonGroup, ButtonGroupSeparator,} from "@/components/ui/button-group";export default function ButtonGroupSeparatorDemo() { return ( <ButtonGroup> <Button variant="secondary" size="sm"> Copy </Button> <ButtonGroupSeparator /> <Button variant="secondary" size="sm"> Paste </Button> </ButtonGroup> );}Split
Create a split button group by adding two buttons separated by a ButtonGroupSeparator.
Loading…
import { PlusIcon } from "lucide-react";import { Button } from "@/components/ui/button";import { ButtonGroup, ButtonGroupSeparator,} from "@/components/ui/button-group";export default function ButtonGroupSplit() { return ( <ButtonGroup> <Button variant="secondary">Button</Button> <ButtonGroupSeparator /> <Button size="icon" variant="secondary"> <PlusIcon /> </Button> </ButtonGroup> );}Input
Wrap an Input component with buttons.
Loading…
import { SearchIcon } from "lucide-react";import { Button } from "@/components/ui/button";import { ButtonGroup } from "@/components/ui/button-group";import { Input } from "@/components/ui/input";export default function ButtonGroupInput() { return ( <ButtonGroup className="w-full max-w-sm"> <Input placeholder="Search..." /> <Button variant="outline" aria-label="Search"> <SearchIcon /> </Button> </ButtonGroup> );}Input Group
Nest an InputGroup to create complex input layouts.
Loading…
"use client";import * as React from "react";import { AudioLinesIcon, PlusIcon } from "lucide-react";import { Button } from "@/components/ui/button";import { ButtonGroup } from "@/components/ui/button-group";import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput,} from "@/components/ui/input-group";import { Tooltip, TooltipContent, TooltipTrigger,} from "@/components/ui/tooltip";export default function ButtonGroupInputGroup() { const [voiceEnabled, setVoiceEnabled] = React.useState(false); return ( <ButtonGroup className="w-full max-w-sm [--radius:9999rem]"> <ButtonGroup> <Button variant="outline" size="icon"> <PlusIcon /> </Button> </ButtonGroup> <ButtonGroup className="flex-1"> <InputGroup> <InputGroupInput placeholder={ voiceEnabled ? "Record and send audio..." : "Send a message..." } disabled={voiceEnabled} /> <InputGroupAddon align="inline-end"> <Tooltip> <TooltipTrigger asChild> <InputGroupButton onClick={() => setVoiceEnabled(!voiceEnabled)} size="icon-xs" data-active={voiceEnabled} className="data-[active=true]:bg-orange-100 data-[active=true]:text-orange-700 dark:data-[active=true]:bg-orange-800 dark:data-[active=true]:text-orange-100" aria-pressed={voiceEnabled} > <AudioLinesIcon /> </InputGroupButton> </TooltipTrigger> <TooltipContent>Voice Mode</TooltipContent> </Tooltip> </InputGroupAddon> </InputGroup> </ButtonGroup> </ButtonGroup> );}Dropdown Menu
Create a split button group with a DropdownMenu component.
Loading…
"use client";import { AlertTriangleIcon, CheckIcon, ChevronDownIcon, CopyIcon, ShareIcon, TrashIcon, UserRoundXIcon, VolumeOffIcon,} from "lucide-react";import { Button } from "@/components/ui/button";import { ButtonGroup } from "@/components/ui/button-group";import { DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger,} from "@/components/ui/dropdown-menu";export default function ButtonGroupDropdown() { return ( <ButtonGroup> <Button variant="outline">Follow</Button> <DropdownMenu> <DropdownMenuTrigger asChild> <Button variant="outline" className="pl-2!" aria-label="More options"> <ChevronDownIcon /> </Button> </DropdownMenuTrigger> <DropdownMenuContent align="end" className="w-44"> <DropdownMenuGroup> <DropdownMenuItem> <VolumeOffIcon /> Mute Conversation </DropdownMenuItem> <DropdownMenuItem> <CheckIcon /> Mark as Read </DropdownMenuItem> <DropdownMenuItem> <AlertTriangleIcon /> Report Conversation </DropdownMenuItem> <DropdownMenuItem> <UserRoundXIcon /> Block User </DropdownMenuItem> <DropdownMenuItem> <ShareIcon /> Share Conversation </DropdownMenuItem> <DropdownMenuItem> <CopyIcon /> Copy Conversation </DropdownMenuItem> </DropdownMenuGroup> <DropdownMenuSeparator /> <DropdownMenuGroup> <DropdownMenuItem variant="destructive"> <TrashIcon /> Delete Conversation </DropdownMenuItem> </DropdownMenuGroup> </DropdownMenuContent> </DropdownMenu> </ButtonGroup> );}Select
Pair with a Select component.
Loading…
"use client";import * as React from "react";import { ArrowRightIcon } from "lucide-react";import { Button } from "@/components/ui/button";import { ButtonGroup } from "@/components/ui/button-group";import { Input } from "@/components/ui/input";import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger,} from "@/components/ui/select";const CURRENCIES = [ { value: "$", label: "US Dollar" }, { value: "€", label: "Euro" }, { value: "£", label: "British Pound" },];export default function ButtonGroupSelect() { const [currency, setCurrency] = React.useState("$"); return ( <ButtonGroup> <ButtonGroup> <Select value={currency} onValueChange={setCurrency}> <SelectTrigger className="font-mono">{currency}</SelectTrigger> <SelectContent className="min-w-24"> <SelectGroup> {CURRENCIES.map((c) => ( <SelectItem key={c.value} value={c.value}> {c.value}{" "} <span className="text-muted-foreground">{c.label}</span> </SelectItem> ))} </SelectGroup> </SelectContent> </Select> <Input placeholder="10.00" pattern="[0-9]*" /> </ButtonGroup> <ButtonGroup> <Button aria-label="Send" size="icon" variant="outline"> <ArrowRightIcon /> </Button> </ButtonGroup> </ButtonGroup> );}Popover
Use with a Popover component.
Loading…
import { BotIcon, ChevronDownIcon } from "lucide-react";import { Button } from "@/components/ui/button";import { ButtonGroup } from "@/components/ui/button-group";import { Field, FieldDescription, FieldLabel } from "@/components/ui/field";import { Popover, PopoverContent, PopoverDescription, PopoverHeader, PopoverTitle, PopoverTrigger,} from "@/components/ui/popover";import { Textarea } from "@/components/ui/textarea";export default function ButtonGroupPopover() { return ( <ButtonGroup> <Button variant="outline"> <BotIcon /> Copilot </Button> <Popover> <PopoverTrigger asChild> <Button variant="outline" size="icon" aria-label="Open Popover"> <ChevronDownIcon /> </Button> </PopoverTrigger> <PopoverContent align="end" className="rounded-xl text-sm"> <PopoverHeader> <PopoverTitle>Start a new task with Copilot</PopoverTitle> <PopoverDescription> Describe your task in natural language. </PopoverDescription> </PopoverHeader> <Field> <FieldLabel htmlFor="task" className="sr-only"> Task Description </FieldLabel> <Textarea id="task" placeholder="I need to..." className="resize-none" /> <FieldDescription> Copilot will open a pull request for review. </FieldDescription> </Field> </PopoverContent> </Popover> </ButtonGroup> );}RTL
Button groups respect the document direction — pass dir="rtl" to flip the layout.
Loading…
"use client";import * as React from "react";import { ArrowLeftIcon, MoreHorizontalIcon } from "lucide-react";import { Button } from "@/components/ui/button";import { ButtonGroup } from "@/components/ui/button-group";import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger,} from "@/components/ui/dropdown-menu";const content = { ltr: { dir: "ltr" as const, archive: "Archive", report: "Report", snooze: "Snooze", markAsRead: "Mark as Read", }, rtl: { dir: "rtl" as const, archive: "أرشفة", report: "تقرير", snooze: "تأجيل", markAsRead: "وضع علامة كمقروء", },};export default function ButtonGroupRtl() { const [mode, setMode] = React.useState<"rtl" | "ltr">("rtl"); const t = content[mode]; return ( <div className="flex flex-col items-center gap-6"> <Button variant="ghost" size="sm" onClick={() => setMode((m) => (m === "rtl" ? "ltr" : "rtl"))} > Toggle dir ({mode}) </Button> <div dir={t.dir}> <ButtonGroup> <Button variant="outline" size="icon" aria-label="Go Back"> <ArrowLeftIcon className="rtl:rotate-180" /> </Button> <Button variant="outline">{t.archive}</Button> <Button variant="outline">{t.report}</Button> <Button variant="outline">{t.snooze}</Button> <DropdownMenu> <DropdownMenuTrigger asChild> <Button variant="outline" size="icon" aria-label="More Options"> <MoreHorizontalIcon /> </Button> </DropdownMenuTrigger> <DropdownMenuContent align="end" className="w-40"> <DropdownMenuItem>{t.markAsRead}</DropdownMenuItem> </DropdownMenuContent> </DropdownMenu> </ButtonGroup> </div> </div> );}API Reference
ButtonGroup
| Prop | Type | Default |
|---|---|---|
orientation | "horizontal" | "vertical" | "horizontal" |
ButtonGroupSeparator
| Prop | Type | Default |
|---|---|---|
orientation | "horizontal" | "vertical" | "vertical" |
ButtonGroupText
| Prop | Type | Default |
|---|---|---|
asChild | boolean | false |