Button Group
A container that groups related buttons together with consistent styling.
"use client"
import * as React from "react"
import {
ArchiveIcon,
ArrowLeftIcon,
CalendarPlusIcon,
ClockIcon,
ListFilterIcon,
MailCheckIcon,
MoreHorizontalIcon,
TagIcon,
Trash2Icon,
} from "lucide-react"
import { Button } from "@/components/ui/button"
import { ButtonGroup } from "@/components/ui/button-group"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuSeparator,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
export default function ButtonGroupDemo() {
const [label, setLabel] = React.useState("personal")
return (
<ButtonGroup>
<ButtonGroup className="hidden sm:flex">
<Button
variant="outline"
size="icon-sm"
className="max-md:size-10"
aria-label="Go Back"
>
<ArrowLeftIcon data-icon="inline-start" />
</Button>
</ButtonGroup>
<ButtonGroup>
<Button variant="outline">Archive</Button>
<Button variant="outline">Report</Button>
</ButtonGroup>
<ButtonGroup>
<Button variant="outline">Snooze</Button>
<DropdownMenu>
<DropdownMenuTrigger
render={
<Button
variant="outline"
size="icon-sm"
className="max-md:size-10"
aria-label="More Options"
/>
}
>
<MoreHorizontalIcon data-icon="inline-start" />
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-40">
<DropdownMenuGroup>
<DropdownMenuItem>
<MailCheckIcon />
Mark as Read
</DropdownMenuItem>
<DropdownMenuItem>
<ArchiveIcon />
Archive
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem>
<ClockIcon />
Snooze
</DropdownMenuItem>
<DropdownMenuItem>
<CalendarPlusIcon />
Add to Calendar
</DropdownMenuItem>
<DropdownMenuItem>
<ListFilterIcon />
Add to List
</DropdownMenuItem>
<DropdownMenuSub>
<DropdownMenuSubTrigger>
<TagIcon />
Label As...
</DropdownMenuSubTrigger>
<DropdownMenuSubContent>
<DropdownMenuRadioGroup
value={label}
onValueChange={setLabel}
>
<DropdownMenuRadioItem value="personal">
Personal
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="work">
Work
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="other">
Other
</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>
</DropdownMenuSubContent>
</DropdownMenuSub>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem variant="destructive">
<Trash2Icon />
Trash
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
</ButtonGroup>
</ButtonGroup>
)
}Installation
pnpm dlx shadcn@latest add @kickresume/button-groupUsage
import { ButtonGroup, ButtonGroupSeparator, ButtonGroupText } from "@/components/ui/button-group"<ButtonGroup>
<ButtonGroup className="hidden sm:flex">
<Button
variant="outline"
size="icon-sm"
className="max-md:size-10"
aria-label="Go Back"
>
<ArrowLeftIcon data-icon="inline-start" />
</Button>
</ButtonGroup>
<ButtonGroup>
<Button variant="outline">Archive</Button>
<Button variant="outline">Report</Button>
</ButtonGroup>
<ButtonGroup>
<Button variant="outline">Snooze</Button>
<DropdownMenu>
<DropdownMenuTrigger
render={
<Button
variant="outline"
size="icon-sm"
className="max-md:size-10"
aria-label="More Options"
/>
}
>
<MoreHorizontalIcon data-icon="inline-start" />
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-40">
<DropdownMenuGroup>
<DropdownMenuItem>
<MailCheckIcon />
Mark as Read
</DropdownMenuItem>
<DropdownMenuItem>
<ArchiveIcon />
Archive
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem>
<ClockIcon />
Snooze
</DropdownMenuItem>
<DropdownMenuItem>
<CalendarPlusIcon />
Add to Calendar
</DropdownMenuItem>
<DropdownMenuItem>
<ListFilterIcon />
Add to List
</DropdownMenuItem>
<DropdownMenuSub>
<DropdownMenuSubTrigger>
<TagIcon />
Label As...
</DropdownMenuSubTrigger>
<DropdownMenuSubContent>
<DropdownMenuRadioGroup
value={label}
onValueChange={setLabel}
>
<DropdownMenuRadioItem value="personal">
Personal
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="work">
Work
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="other">
Other
</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>
</DropdownMenuSubContent>
</DropdownMenuSub>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem variant="destructive">
<Trash2Icon />
Trash
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
</ButtonGroup>
</ButtonGroup>Composition
Use the following composition to build a ButtonGroup:
ButtonGroup
├── Button or Input
├── ButtonGroupSeparator
└── ButtonGroupTextAccessibility
- The
ButtonGroupcomponent has theroleattribute set togroup. - Use
Tabto navigate between the buttons in the group. - Use
aria-labeloraria-labelledbyto label the button group.
<ButtonGroup aria-label="Button group">
<Button>Button 1</Button>
<Button>Button 2</Button>
</ButtonGroup>ButtonGroup vs ToggleGroup
- Use the
ButtonGroupcomponent when you want to group buttons that perform an action. - Use the
ToggleGroupcomponent when you want to group buttons that toggle a state.
Examples
Orientation
Set the orientation prop to change the button group layout.
"use client"
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" aria-label="Increase">
<PlusIcon data-icon="inline-start" />
</Button>
<Button variant="outline" size="icon" aria-label="Decrease">
<MinusIcon data-icon="inline-start" />
</Button>
</ButtonGroup>
)
}Size
Control the size of buttons using the size prop on individual buttons.
"use client"
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"
className="max-md:size-10"
aria-label="Add"
>
<PlusIcon data-icon="inline-start" />
</Button>
</ButtonGroup>
<ButtonGroup>
<Button variant="outline">Default</Button>
<Button variant="outline">Button</Button>
<Button variant="outline">Group</Button>
<Button
variant="outline"
size="icon-sm"
className="max-md:size-10"
aria-label="Add"
>
<PlusIcon data-icon="inline-start" />
</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"
className="size-11"
aria-label="Add"
>
<PlusIcon data-icon="inline-start" />
</Button>
</ButtonGroup>
</div>
)
}Nested
Nest <ButtonGroup> components to create button groups with spacing.
"use client"
import { AudioLinesIcon, PlusIcon } from "lucide-react"
import { Button } from "@/components/ui/button"
import { ButtonGroup } from "@/components/ui/button-group"
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
} from "@/components/ui/input-group"
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip"
export default function ButtonGroupNested() {
return (
<TooltipProvider>
<ButtonGroup>
<ButtonGroup>
<Button
variant="outline"
size="icon"
className="size-[50px]"
aria-label="Add attachment"
>
<PlusIcon data-icon="inline-start" />
</Button>
</ButtonGroup>
<ButtonGroup>
<InputGroup>
<InputGroupInput
placeholder="Send a message..."
aria-label="Message"
/>
<Tooltip>
<TooltipTrigger
render={<InputGroupAddon align="inline-end" />}
aria-label="Voice mode"
>
<AudioLinesIcon />
</TooltipTrigger>
<TooltipContent>Voice Mode</TooltipContent>
</Tooltip>
</InputGroup>
</ButtonGroup>
</ButtonGroup>
</TooltipProvider>
)
}Separator
The ButtonGroupSeparator component visually divides buttons within a group.
Buttons with variant outline do not need a separator since they have a border. For other variants, a separator is recommended to improve the visual hierarchy.
"use client"
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.
"use client"
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-sm"
variant="secondary"
className="max-md:size-10"
aria-label="Add"
>
<PlusIcon data-icon="inline-start" />
</Button>
</ButtonGroup>
)
}Input
Wrap an Input component with buttons.
"use client"
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>
<Input placeholder="Search..." aria-label="Search" />
<Button
variant="outline"
className="h-[50px] max-md:h-[50px]"
aria-label="Search"
>
<SearchIcon data-icon="inline-start" />
</Button>
</ButtonGroup>
)
}Input Group
Wrap an InputGroup component to create complex input layouts.
"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,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip"
export default function ButtonGroupInputGroup() {
const [voiceEnabled, setVoiceEnabled] = React.useState(false)
return (
<TooltipProvider>
<ButtonGroup>
<ButtonGroup>
<Button
variant="outline"
size="icon"
className="size-[50px]"
aria-label="Add attachment"
>
<PlusIcon data-icon="inline-start" />
</Button>
</ButtonGroup>
<ButtonGroup>
<InputGroup>
<InputGroupInput
aria-label="Message"
placeholder={
voiceEnabled ? "Record and send audio..." : "Send a message..."
}
disabled={voiceEnabled}
/>
<InputGroupAddon align="inline-end">
<Tooltip>
<TooltipTrigger
render={
<InputGroupButton
onClick={() => setVoiceEnabled(!voiceEnabled)}
size="icon-xs"
data-active={voiceEnabled}
variant={voiceEnabled ? "secondary" : "ghost"}
aria-pressed={voiceEnabled}
aria-label="Toggle voice mode"
/>
}
>
<AudioLinesIcon data-icon="inline-start" />
</TooltipTrigger>
<TooltipContent>Voice Mode</TooltipContent>
</Tooltip>
</InputGroupAddon>
</InputGroup>
</ButtonGroup>
</ButtonGroup>
</TooltipProvider>
)
}Dropdown Menu
Create a split button group with a DropdownMenu component.
"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
render={
<Button
variant="outline"
className="pl-2!"
aria-label="More follow options"
/>
}
>
<ChevronDownIcon data-icon="inline-start" />
</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.
"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 = [
{ label: "US Dollar", value: "$" },
{ label: "Euro", value: "€" },
{ label: "British Pound", value: "£" },
]
export default function ButtonGroupSelect() {
const [currency, setCurrency] = React.useState("$")
return (
<ButtonGroup>
<ButtonGroup>
<Select
items={CURRENCIES}
value={currency}
onValueChange={(value) => setCurrency(value as string)}
>
<SelectTrigger className="font-mono" aria-label="Currency">
{currency}
</SelectTrigger>
<SelectContent alignItemWithTrigger={false} align="start">
<SelectGroup>
{CURRENCIES.map((item) => (
<SelectItem key={item.value} value={item.value}>
{item.value}{" "}
<span className="text-muted-foreground">{item.label}</span>
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
<Input placeholder="10.00" pattern="[0-9]*" aria-label="Amount" />
</ButtonGroup>
<ButtonGroup>
<Button
aria-label="Send"
size="icon"
variant="outline"
className="size-[50px]"
>
<ArrowRightIcon data-icon="inline-start" />
</Button>
</ButtonGroup>
</ButtonGroup>
)
}Popover
Use with a Popover component.
"use client"
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 data-icon="inline-start" /> Copilot
</Button>
<Popover>
<PopoverTrigger
render={
<Button
variant="outline"
size="icon-sm"
className="max-md:size-10"
aria-label="Open Popover"
/>
}
>
<ChevronDownIcon data-icon="inline-start" />
</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>
)
}API Reference
ButtonGroup
The ButtonGroup component is a container that groups related buttons together with consistent styling.
| Prop | Type | Default |
|---|---|---|
orientation | "horizontal" | "vertical" | "horizontal" |
<ButtonGroup>
<Button>Button 1</Button>
<Button>Button 2</Button>
</ButtonGroup>Nest multiple button groups to create complex layouts with spacing. See the nested example for more details.
<ButtonGroup>
<ButtonGroup />
<ButtonGroup />
</ButtonGroup>ButtonGroupSeparator
The ButtonGroupSeparator component visually divides buttons within a group.
| Prop | Type | Default |
|---|---|---|
orientation | "horizontal" | "vertical" | "vertical" |
<ButtonGroup>
<Button>Button 1</Button>
<ButtonGroupSeparator />
<Button>Button 2</Button>
</ButtonGroup>ButtonGroupText
Use this component to display text within a button group.
| Prop | Type | Default |
|---|---|---|
render | ReactElement | — |
<ButtonGroup>
<ButtonGroupText>Text</ButtonGroupText>
<Button>Button</Button>
</ButtonGroup>Use the render prop to render a custom component as the text, for example a label.
import { ButtonGroupText } from "@/components/ui/button-group"
import { Label } from "@/components/ui/label"
export function ButtonGroupTextDemo() {
return (
<ButtonGroup>
<ButtonGroupText render={<Label htmlFor="name">Text</Label>} />
<Input placeholder="Type something here..." id="name" />
</ButtonGroup>
)
}