- Alert
- Avatar
- Badge
- Breadcrumb
- Button
- Button Group
- Calendar
- Card
- Carousel
- Chart
- Checkbox
- Checkbox Group
- ComboBox
- Command
- Data Table
- Date Field
- Date Picker
- Date Range Picker
- Dialog
- Disclosure
- Disclosure Group
- Drawer
- Empty
- Field
- File Trigger
- Form
- Grid List
- Hover Card
- Input
- Input Group
- Input OTP
- Item
- Kbd
- Label
- ListBox
- Menu
- Number Field
- Pagination
- Popover
- Progress Bar
- Radio Group
- Range Calendar
- Resizable
- Search Field
- Select
- Separator
- Sheet
- Sidebar
- Skeleton
- Slider
- Sonner
- Spinner
- Switch
- Table
- Tabs
- Tag Group
- Text Field
- Textarea
- Time Field
- Toast
- Toggle Button
- Toggle Button Group
- Tooltip
- Tree
- Typography
"use client"
import * as React from "react"
import {
ArchiveIcon,
ArrowLeftIcon,
CalendarPlusIcon,
ClockIcon,
ListFilterPlusIcon,
MailCheckIcon,
MoreHorizontalIcon,
TagIcon,
Trash2Icon,
} from "lucide-react"
import { SubmenuTrigger } from "react-aria-components"
import { Button } from "@/components/ui/button"
import { ButtonGroup } from "@/components/ui/button-group"
import {
Menu,
MenuItem,
MenuSeparator,
MenuTrigger,
} from "@/components/ui/menu"
export function ButtonGroupDemo() {
return (
<ButtonGroup>
<ButtonGroup className="hidden sm:flex">
<Button variant="outline" size="icon" aria-label="Go Back">
<ArrowLeftIcon />
</Button>
</ButtonGroup>
<ButtonGroup>
<Button variant="outline">Archive</Button>
<Button variant="outline">Report</Button>
</ButtonGroup>
<ButtonGroup>
<Button variant="outline">Snooze</Button>
<MenuTrigger>
<Button variant="outline" size="icon" aria-label="More Options">
<MoreHorizontalIcon />
</Button>
<Menu placement="bottom end" className="w-52" selectionMode="single">
<MenuItem>
<MailCheckIcon />
Mark as Read
</MenuItem>
<MenuItem>
<ArchiveIcon />
Archive
</MenuItem>
<MenuSeparator />
<MenuItem>
<ClockIcon />
Snooze
</MenuItem>
<MenuItem>
<CalendarPlusIcon />
Add to Calendar
</MenuItem>
<MenuItem>
<ListFilterPlusIcon />
Add to List
</MenuItem>
<SubmenuTrigger>
<MenuItem>
<TagIcon />
Label As...
</MenuItem>
<Menu selectionMode="single" defaultSelectedKeys={["personal"]}>
<MenuItem id="personal">Personal</MenuItem>
<MenuItem id="work">Work</MenuItem>
<MenuItem id="other">Other</MenuItem>
</Menu>
</SubmenuTrigger>
<MenuSeparator />
<MenuItem className="text-destructive focus:bg-destructive/10 dark:focus:bg-destructive/20 focus:text-destructive">
<Trash2Icon />
Trash
</MenuItem>
</Menu>
</MenuTrigger>
</ButtonGroup>
</ButtonGroup>
)
}
Installation
pnpmnpmyarnbunpnpm dlx shadcn@latest add button-group
Usage
import {
ButtonGroup,
ButtonGroupSeparator,
ButtonGroupText,
} from "@/components/ui/button-group"<ButtonGroup>
<Button>Button 1</Button>
<Button>Button 2</Button>
</ButtonGroup>Accessibility
- The
ButtonGroupcomponent has theroleattribute set togroup. - Use Tab to 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.
import { MinusIcon, PlusIcon } from "lucide-react"
import { Button } from "@/components/ui/button"
import { ButtonGroup } from "@/components/ui/button-group"
export 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.
import { PlusIcon } from "lucide-react"
import { Button } from "@/components/ui/button"
import { ButtonGroup } from "@/components/ui/button-group"
export 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>
)
}
Nested
Nest <ButtonGroup> components to create button groups with spacing.
"use client"
import { ArrowLeftIcon, ArrowRightIcon } from "lucide-react"
import { Button } from "@/components/ui/button"
import { ButtonGroup } from "@/components/ui/button-group"
export function ButtonGroupNested() {
return (
<ButtonGroup>
<ButtonGroup>
<Button variant="outline" size="sm">
1
</Button>
<Button variant="outline" size="sm">
2
</Button>
<Button variant="outline" size="sm">
3
</Button>
<Button variant="outline" size="sm">
4
</Button>
<Button variant="outline" size="sm">
5
</Button>
</ButtonGroup>
<ButtonGroup>
<Button variant="outline" size="icon-sm" aria-label="Previous">
<ArrowLeftIcon />
</Button>
<Button variant="outline" size="icon-sm" aria-label="Next">
<ArrowRightIcon />
</Button>
</ButtonGroup>
</ButtonGroup>
)
}
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.
import { Button } from "@/components/ui/button"
import {
ButtonGroup,
ButtonGroupSeparator,
} from "@/components/ui/button-group"
export 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.
import { IconPlus } from "@tabler/icons-react"
import { Button } from "@/components/ui/button"
import {
ButtonGroup,
ButtonGroupSeparator,
} from "@/components/ui/button-group"
export function ButtonGroupSplit() {
return (
<ButtonGroup>
<Button variant="secondary">Button</Button>
<ButtonGroupSeparator />
<Button size="icon" variant="secondary">
<IconPlus />
</Button>
</ButtonGroup>
)
}
Input
Wrap an Input component with buttons.
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 function ButtonGroupInput() {
return (
<ButtonGroup>
<Input placeholder="Search..." />
<Button variant="outline" aria-label="Search">
<SearchIcon />
</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, TooltipTrigger } from "@/components/ui/tooltip"
export function ButtonGroupInputGroup() {
const [voiceEnabled, setVoiceEnabled] = React.useState(false)
return (
<ButtonGroup className="[--radius:9999rem]">
<ButtonGroup>
<Button variant="outline" size="icon">
<PlusIcon />
</Button>
</ButtonGroup>
<ButtonGroup>
<InputGroup>
<InputGroupInput
placeholder={
voiceEnabled ? "Record and send audio..." : "Send a message..."
}
disabled={voiceEnabled}
/>
<InputGroupAddon align="inline-end">
<TooltipTrigger>
<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>
<Tooltip>Voice Mode</Tooltip>
</TooltipTrigger>
</InputGroupAddon>
</InputGroup>
</ButtonGroup>
</ButtonGroup>
)
}
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 {
Menu,
MenuItem,
MenuSeparator,
MenuTrigger,
} from "@/components/ui/menu"
export function ButtonGroupDropdown() {
return (
<ButtonGroup>
<Button variant="outline">Follow</Button>
<MenuTrigger>
<Button variant="outline" className="!pl-2">
<ChevronDownIcon />
</Button>
<Menu placement="bottom end" className="[--radius:1rem]">
<MenuItem>
<VolumeOffIcon />
Mute Conversation
</MenuItem>
<MenuItem>
<CheckIcon />
Mark as Read
</MenuItem>
<MenuItem>
<AlertTriangleIcon />
Report Conversation
</MenuItem>
<MenuItem>
<UserRoundXIcon />
Block User
</MenuItem>
<MenuItem>
<ShareIcon />
Share Conversation
</MenuItem>
<MenuItem>
<CopyIcon />
Copy Conversation
</MenuItem>
<MenuSeparator />
<MenuItem className="text-destructive focus:bg-destructive/10 dark:focus:bg-destructive/20 focus:text-destructive">
<TrashIcon />
Delete Conversation
</MenuItem>
</Menu>
</MenuTrigger>
</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, SelectItem } from "@/components/ui/select"
const CURRENCIES = [
{
value: "$",
label: "US Dollar",
},
{
value: "€",
label: "Euro",
},
{
value: "£",
label: "British Pound",
},
]
export function ButtonGroupSelect() {
const [currency, setCurrency] = React.useState("$")
return (
<ButtonGroup>
<ButtonGroup>
<Select
selectedKey={currency}
onSelectionChange={(key) => setCurrency(key as string)}
className="min-w-24 font-mono"
>
{CURRENCIES.map((currency) => (
<SelectItem key={currency.value} id={currency.value}>
{currency.value}{" "}
<span className="text-muted-foreground">{currency.label}</span>
</SelectItem>
))}
</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.
import { BotIcon, ChevronDownIcon } from "lucide-react"
import { Button } from "@/components/ui/button"
import { ButtonGroup } from "@/components/ui/button-group"
import { DialogTrigger } from "@/components/ui/dialog"
import { Popover } from "@/components/ui/popover"
import { Separator } from "@/components/ui/separator"
import { Textarea } from "@/components/ui/textarea"
export function ButtonGroupPopover() {
return (
<ButtonGroup>
<Button variant="outline">
<BotIcon /> Copilot
</Button>
<DialogTrigger>
<Button variant="outline" size="icon" aria-label="Open Popover">
<ChevronDownIcon />
</Button>
<Popover className="rounded-xl p-0 text-sm">
<div className="px-4 py-3">
<div className="text-sm font-medium">Agent Tasks</div>
</div>
<Separator />
<div className="p-4 text-sm *:[p:not(:last-child)]:mb-2">
<Textarea
placeholder="Describe your task in natural language."
className="mb-4 resize-none"
/>
<p className="font-medium">Start a new task with Copilot</p>
<p className="text-muted-foreground">
Describe your task in natural language. Copilot will work in the
background and open a pull request for your review.
</p>
</div>
</Popover>
</DialogTrigger>
</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 |
|---|---|---|
asChild | boolean | false |
<ButtonGroup>
<ButtonGroupText>Text</ButtonGroupText>
<Button>Button</Button>
</ButtonGroup>Use the asChild 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 asChild>
<Label htmlFor="name">Text</Label>
</ButtonGroupText>
<Input placeholder="Type something here..." id="name" />
</ButtonGroup>
)
}