Button Group
Joins buttons, inputs and text into one control, across or down.
<ButtonGroup aria-label="Message actions"> <ButtonGroup> <Button aria-label="Go back" size="icon" variant="outline"> <ArrowLeftIcon data-rtl-flip /> </Button> </ButtonGroup> <ButtonGroup> <Button variant="outline">Archive</Button> <Button variant="outline">Report</Button> </ButtonGroup> <ButtonGroup> <Button variant="outline">Snooze</Button> <DropdownMenu> <DropdownMenuTrigger render={ <Button aria-label="More options" size="icon" variant="outline" /> } > <MoreHorizontalIcon /> </DropdownMenuTrigger> <DropdownMenuContent align="end"> <DropdownMenuItem>Mark as Read</DropdownMenuItem> <DropdownMenuItem>Archive</DropdownMenuItem> </DropdownMenuContent> </DropdownMenu> </ButtonGroup></ButtonGroup>Installation
$ npx shadcn@latest add @constella/button-groupUsage
import { ButtonGroup, ButtonGroupSeparator, ButtonGroupText,} from "@/components/primitives/button-group";<ButtonGroup> <Button>Button 1</Button> <Button>Button 2</Button></ButtonGroup>Composition
ButtonGroup├── Button├── ButtonGroupSeparator├── ButtonGroupText└── ButtonGroupAccessibility
ButtonGroup has role="group". Label it with aria-label or aria-labelledby; <kbd>Tab</kbd> moves between its buttons.
Use ButtonGroup for buttons that act. For buttons that toggle a state, use a toggle group.
Orientation
Use orientation="vertical" to stack the group.
<ButtonGroup aria-label="Media controls" orientation="vertical"> <Button aria-label="Volume up" size="icon" variant="outline"> <PlusIcon /> </Button> <Button aria-label="Volume down" size="icon" variant="outline"> <MinusIcon /> </Button></ButtonGroup>Size
Set size on each button; the group takes their height.
<ButtonGroup> <Button size="sm" variant="outline"> Small </Button> <Button size="sm" variant="outline"> Button </Button> <Button aria-label="Add" size="icon-sm" variant="outline"> <PlusIcon /> </Button></ButtonGroup>Nested
Nest groups to space them apart.
<ButtonGroup aria-label="Pagination"> <ButtonGroup> <Button size="sm" variant="outline"> 1 </Button> <Button size="sm" variant="outline"> 2 </Button> </ButtonGroup> <ButtonGroup> <Button aria-label="Previous" size="icon-sm" variant="outline"> <ArrowLeftIcon data-rtl-flip /> </Button> <Button aria-label="Next" size="icon-sm" variant="outline"> <ArrowRightIcon data-rtl-flip /> </Button> </ButtonGroup></ButtonGroup>Separator
Add ButtonGroupSeparator between buttons without an edge. outline buttons have one, so they don't need it.
<ButtonGroup> <Button size="sm">Copy</Button> <ButtonGroupSeparator /> <Button size="sm">Paste</Button></ButtonGroup>Split
Pair an action with an icon button, split by a separator.
<ButtonGroup> <Button>Button</Button> <ButtonGroupSeparator /> <Button aria-label="Add" size="icon"> <PlusIcon /> </Button></ButtonGroup>Text
Use ButtonGroupText for a fixed prefix or suffix. Pass render to make it a label.
<ButtonGroup> <ButtonGroupText render={<label htmlFor="url" />}>https://</ButtonGroupText> <InputGroup> <InputGroupInput id="url" /> <InputGroupAddon align="inline-end"> <LinkIcon /> </InputGroupAddon> </InputGroup> <ButtonGroupText>.com</ButtonGroupText></ButtonGroup>Input
Put an Input in the group to join it to a button.
<ButtonGroup> <Input aria-label="Search" placeholder="Search..." /> <Button aria-label="Search" size="icon" variant="outline"> <SearchIcon /> </Button></ButtonGroup>Input Group
Put an InputGroup in the group for an input with its own addons.
<ButtonGroup> <ButtonGroup> <Button aria-label="Add" size="icon" variant="outline"> <PlusIcon /> </Button> </ButtonGroup> <ButtonGroup> <InputGroup> <InputGroupInput aria-label="Message" placeholder="Send a message..." /> <InputGroupAddon align="inline-end"> <Tooltip> <TooltipTrigger render={ <InputGroupButton aria-label="Voice mode" aria-pressed={voice} onClick={() => setVoice(!voice)} size="icon-xs" /> } > <AudioLinesIcon /> </TooltipTrigger> <TooltipContent>Voice mode</TooltipContent> </Tooltip> </InputGroupAddon> </InputGroup> </ButtonGroup></ButtonGroup>Dropdown Menu
Render the last button as a Dropdown Menu trigger for a split button with more actions.
<ButtonGroup> <Button variant="outline">Follow</Button> <DropdownMenu> <DropdownMenuTrigger render={ <Button aria-label="More actions" size="icon" variant="outline" /> } > <ArrowDown01Icon /> </DropdownMenuTrigger> <DropdownMenuContent align="end"> <DropdownMenuItem>Mute Conversation</DropdownMenuItem> <DropdownMenuItem color="danger">Delete Conversation</DropdownMenuItem> </DropdownMenuContent> </DropdownMenu></ButtonGroup>Select
Put a Select beside an input, like a currency before an amount.
<ButtonGroup> <Select items={currencies} onValueChange={setCurrency} value={currency}> <SelectTrigger>{currency}</SelectTrigger> <SelectContent align="start" alignItemWithTrigger={false}> {currencies.map((item) => ( <SelectItem key={item.value} value={item.value}> {item.value} {item.label} </SelectItem> ))} </SelectContent> </Select> <Input placeholder="10.00" /></ButtonGroup>Popover
Render the last button as a Popover trigger for options that need more room.
<ButtonGroup> <Button variant="outline">Copilot</Button> <Popover> <PopoverTrigger render={ <Button aria-label="Open Popover" size="icon" variant="outline" /> } > <ArrowDown01Icon /> </PopoverTrigger> <PopoverContent align="end"> <PopoverHeader> <PopoverTitle>Start a new task with Copilot</PopoverTitle> <PopoverDescription> Describe your task in natural language. </PopoverDescription> </PopoverHeader> <Textarea placeholder="I need to..." /> </PopoverContent> </Popover></ButtonGroup>API Reference
ButtonGroup
A div with role="group". Takes every div prop, plus sx, applied last.
| Prop | Values | Default |
|---|---|---|
orientation | "horizontal" | "vertical" | "horizontal" |
ButtonGroupSeparator
A Separator that fits between the group's items.
| Prop | Values | Default |
|---|---|---|
orientation | "horizontal" | "vertical" | "vertical" |
ButtonGroupText
Takes every div prop, plus sx, applied last.
| Prop | Values | Default |
|---|---|---|
render | React.ReactElement |