Button Group

Related controls joined by shared edges.

Bring related actions together in one compact control. Outer corners stay rounded while shared edges remain clean and square.

tsx
import {  Button,  ButtonGroup,  buttonVariants,} from "@/components/ui/button"import {  DropdownMenu,  DropdownMenuContent,  DropdownMenuItem,  DropdownMenuSeparator,  DropdownMenuTrigger,} from "@/components/ui/dropdown-menu"export function Example() {  return (    <div className="flex flex-wrap items-center gap-2">      <Button variant="outline" size="icon" aria-label="Back">        <ArrowLeftIcon />      </Button>      <ButtonGroup>        <Button variant="outline">Archive</Button>        <Button variant="outline">Report</Button>      </ButtonGroup>      <ButtonGroup>        <Button variant="outline">Snooze</Button>        <DropdownMenu>          <DropdownMenuTrigger            aria-label="More actions"            className={buttonVariants({ variant: "outline", size: "icon" })}          >            <MoreHorizontalIcon />          </DropdownMenuTrigger>          <DropdownMenuContent align="end">            <DropdownMenuItem>              <CheckIcon />              Mark as done            </DropdownMenuItem>            <DropdownMenuItem>              <ArchiveIcon />              Move to archive            </DropdownMenuItem>            <DropdownMenuSeparator />            <DropdownMenuItem>              <ClockIcon />              Remind me later            </DropdownMenuItem>            <DropdownMenuItem>              <PinIcon />              Pin this scene            </DropdownMenuItem>            <DropdownMenuItem>              <ShareIcon />              Copy share link            </DropdownMenuItem>          </DropdownMenuContent>        </DropdownMenu>      </ButtonGroup>    </div>  )}

Installation

npx @arctis-sh/@arctis-sh/ui@latest add button-group

ButtonGroup ships with Button and is also exported from @arctis-sh/ui/button.

Usage

import {  Button,  ButtonGroup,  ButtonGroupSeparator,  ButtonGroupText,} from "@/components/ui/button"
<ButtonGroup>  <Button variant="outline">Archive</Button>  <Button variant="outline">Share</Button></ButtonGroup>

Composition

ButtonGroup
├── Button or Input
├── ButtonGroupSeparator
└── ButtonGroupText

Accessibility

ButtonGroup sets role="group". Tab moves between its buttons. Give the group a label with aria-label or aria-labelledby.

<ButtonGroup aria-label="Document actions">  <Button variant="outline">Archive</Button>  <Button variant="outline">Share</Button></ButtonGroup>

Orientation

Set orientation to stack the segments instead of placing them side by side.

tsx
import { Button, ButtonGroup } from "@/components/ui/button"export function Example() {  return (    <ButtonGroup orientation="vertical" aria-label="Volume">      <Button variant="outline" size="icon" aria-label="Increase">        <PlusIcon />      </Button>      <Button variant="outline" size="icon" aria-label="Decrease">        <MinusIcon />      </Button>    </ButtonGroup>  )}

Size

Set size on each button to control the group's density.

tsx
import { Button, ButtonGroup } from "@/components/ui/button"export function Example() {  return (    <div className="flex flex-col items-start gap-4">      <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" aria-label="Add">          <PlusIcon />        </Button>      </ButtonGroup>      <ButtonGroup>        <Button variant="outline">Default</Button>        <Button variant="outline">Button</Button>        <Button variant="outline">Group</Button>        <Button variant="outline" size="icon" aria-label="Add">          <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" aria-label="Add">          <PlusIcon />        </Button>      </ButtonGroup>    </div>  )}

Nested

Nest ButtonGroup components to arrange related clusters with space between them.

tsx
import { Button, ButtonGroup } from "@/components/ui/button"export function Example() {  return (    <ButtonGroup>      <ButtonGroup>        <Button variant="outline">1</Button>        <Button variant="outline">2</Button>        <Button variant="outline">3</Button>      </ButtonGroup>      <ButtonGroup>        <Button variant="outline" size="icon" aria-label="Add">          <PlusIcon />        </Button>        <Button variant="outline" size="icon" aria-label="Voice">          <AudioLinesIcon />        </Button>      </ButtonGroup>    </ButtonGroup>  )}

Separator

ButtonGroupSeparator adds a stronger division between children. Outline groups usually do not need one.

tsx
import {  Button,  ButtonGroup,  ButtonGroupSeparator,} from "@/components/ui/button"export function Example() {  return (    <ButtonGroup>      <Button variant="secondary">Copy</Button>      <ButtonGroupSeparator />      <Button variant="secondary">Paste</Button>    </ButtonGroup>  )}

Split

Pair a primary action with a separate menu trigger.

tsx
import {  Button,  ButtonGroup,  ButtonGroupSeparator,  buttonVariants,} from "@/components/ui/button"import {  DropdownMenu,  DropdownMenuContent,  DropdownMenuItem,  DropdownMenuTrigger,} from "@/components/ui/dropdown-menu"export function Example() {  return (    <ButtonGroup>      <Button variant="secondary">        <PlusIcon data-icon="inline-start" />        Button      </Button>      <ButtonGroupSeparator />      <DropdownMenu>        <DropdownMenuTrigger          aria-label="More options"          className={buttonVariants({ variant: "secondary", size: "icon" })}        >          <ChevronDownIcon />        </DropdownMenuTrigger>        <DropdownMenuContent align="end">          <DropdownMenuItem>Save</DropdownMenuItem>          <DropdownMenuItem>Save as draft</DropdownMenuItem>          <DropdownMenuItem>Export</DropdownMenuItem>        </DropdownMenuContent>      </DropdownMenu>    </ButtonGroup>  )}

Input

Place an input between supporting buttons.

tsx
import { Button, ButtonGroup } from "@/components/ui/button"export function Example() {  return (    <ButtonGroup className="w-full max-w-sm">      <input        placeholder="Search…"        className="h-9 min-w-0 flex-1 border border-border bg-transparent px-3 text-sm outline-none"      />      <Button variant="outline" size="icon" aria-label="Search">        <SearchIcon />      </Button>    </ButtonGroup>  )}

Input group

Keep the add control separate while joining the message field to its voice action.

tsx
import { Button, ButtonGroup } from "@/components/ui/button"export function Example() {  return (    <div className="flex w-full max-w-sm items-center gap-2">      <Button variant="outline" size="icon" aria-label="Add">        <PlusIcon />      </Button>      <ButtonGroup className="min-w-0 flex-1">        <input          placeholder="Send a message…"          className="h-9 min-w-0 flex-1 border border-border bg-transparent px-3 text-sm outline-none"        />        <Button variant="outline" size="icon" aria-label="Voice message">          <AudioLinesIcon />        </Button>      </ButtonGroup>    </div>  )}

Use DropdownMenu to create a split action.

tsx
import {  Button,  ButtonGroup,  buttonVariants,} from "@/components/ui/button"import {  DropdownMenu,  DropdownMenuContent,  DropdownMenuItem,  DropdownMenuTrigger,} from "@/components/ui/dropdown-menu"export function Example() {  return (    <ButtonGroup>      <Button variant="outline">Follow</Button>      <DropdownMenu>        <DropdownMenuTrigger          aria-label="Follow options"          className={buttonVariants({ variant: "outline", size: "icon" })}        >          <ChevronDownIcon />        </DropdownMenuTrigger>        <DropdownMenuContent align="end">          <DropdownMenuItem>Mute notifications</DropdownMenuItem>          <DropdownMenuItem>Unfollow</DropdownMenuItem>          <DropdownMenuItem>Block</DropdownMenuItem>        </DropdownMenuContent>      </DropdownMenu>    </ButtonGroup>  )}

Select

Pair a menu trigger with an input for a compact compound field.

tsx
import { useState } from "react"import {  Button,  ButtonGroup,  buttonVariants,} from "@/components/ui/button"import {  DropdownMenu,  DropdownMenuContent,  DropdownMenuItem,  DropdownMenuTrigger,} from "@/components/ui/dropdown-menu"export function Example() {  const [currency, setCurrency] = useState("$")  return (    <ButtonGroup>      <DropdownMenu>        <DropdownMenuTrigger          className={buttonVariants({ variant: "outline" })}        >          {currency}          <ChevronDownIcon data-icon="inline-end" />        </DropdownMenuTrigger>        <DropdownMenuContent align="start">          <DropdownMenuItem onClick={() => setCurrency("$")}>$</DropdownMenuItem>          <DropdownMenuItem onClick={() => setCurrency("")}></DropdownMenuItem>          <DropdownMenuItem onClick={() => setCurrency("£")}>£</DropdownMenuItem>        </DropdownMenuContent>      </DropdownMenu>      <input        placeholder="0.00"        className="h-9 w-24 border border-border bg-transparent px-3 text-sm outline-none"      />      <Button variant="outline">Pay</Button>    </ButtonGroup>  )}

Popover

Open richer content from a dedicated chevron segment.

tsx
import {  Button,  ButtonGroup,  buttonVariants,} from "@/components/ui/button"import {  DropdownMenu,  DropdownMenuContent,  DropdownMenuTrigger,} from "@/components/ui/dropdown-menu"export function Example() {  return (    <ButtonGroup>      <Button variant="outline">        <BotIcon data-icon="inline-start" />        Copilot      </Button>      <DropdownMenu>        <DropdownMenuTrigger          aria-label="Open Copilot"          className={buttonVariants({ variant: "outline", size: "icon" })}        >          <ChevronDownIcon />        </DropdownMenuTrigger>        <DropdownMenuContent align="end" className="w-64 p-3">          <p className="m-0 mb-2 text-[13px] text-muted-foreground">            Ask Copilot to draft, explain, or refactor.          </p>          <textarea            rows={3}            placeholder="Ask anything…"            className="m-0 block w-full resize-none rounded-md border border-border bg-transparent px-2.5 py-2 text-sm outline-none placeholder:text-muted-foreground"          />        </DropdownMenuContent>      </DropdownMenu>    </ButtonGroup>  )}

API Reference

ButtonGroup

PropTypeDefault
orientation"horizontal" | "vertical""horizontal"
classNamestring

Nest multiple groups when related clusters need a little separation. See Nested above.

<ButtonGroup>  <Button variant="outline">Left</Button>  <Button variant="outline">Right</Button></ButtonGroup>

ButtonGroupSeparator

PropTypeDefault
orientation"horizontal" | "vertical"follows group
classNamestring
<ButtonGroup>  <Button variant="outline">Save</Button>  <ButtonGroupSeparator />  <Button variant="outline">Cancel</Button></ButtonGroup>

ButtonGroupText

PropTypeDefault
asChildbooleanfalse
classNamestring
<ButtonGroup>  <ButtonGroupText>https://</ButtonGroupText>  <Button variant="outline">Open</Button></ButtonGroup>