Button Group

Joins buttons, inputs and text into one control, across or down.

View as Markdown
<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-group

Usage

import {  ButtonGroup,  ButtonGroupSeparator,  ButtonGroupText,} from "@/components/primitives/button-group";
<ButtonGroup>  <Button>Button 1</Button>  <Button>Button 2</Button></ButtonGroup>

Composition

ButtonGroup├── Button├── ButtonGroupSeparator├── ButtonGroupText└── ButtonGroup

Accessibility

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.

.com
<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>

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.

PropValuesDefault
orientation"horizontal" | "vertical""horizontal"

ButtonGroupSeparator

A Separator that fits between the group's items.

PropValuesDefault
orientation"horizontal" | "vertical""vertical"

ButtonGroupText

Takes every div prop, plus sx, applied last.

PropValuesDefault
renderReact.ReactElement