Import
import { ButtonGroup } from "@timelycare/helix-ui"
Props
interface ButtonGroupProps extends React.HTMLAttributes<HTMLDivElement> {
className?: string
children: React.ReactNode
}
Styling
| Property | Value | Tailwind |
|---|---|---|
| Display | Inline flex | inline-flex |
| Gap (nested groups) | 8px | gap-2 |
| Border radius | 8px | rounded-md |
| Child border handling | Adjacent children share borders | First/last child get rounded corners, middle children get rounded-none |
Border Collapse Behavior
Adjacent buttons within a ButtonGroup share borders — inner borders are collapsed so you don't get a double-border effect. The first child gets rounded-l-md, the last child gets rounded-r-md, and middle children get rounded-none.
Variants
ButtonGroup is a layout-only container. Visual styling comes from the child Button components and their variants.
Common Patterns
Basic Group
<ButtonGroup>
<Button variant="outline">Cancel</Button>
<Button variant="outline">Save</Button>
</ButtonGroup>
Nested Groups with Spacing
Nest ButtonGroup components to create sections with spacing between groups:
<ButtonGroup>
<ButtonGroup>
<Button variant="outline">Archive</Button>
<Button variant="outline">Report</Button>
</ButtonGroup>
<ButtonGroup>
<Button variant="outline">Snooze</Button>
<Button variant="outline" size="icon" aria-label="More Options">
<MoreHorizontalIcon />
</Button>
</ButtonGroup>
</ButtonGroup>
Split Button (with Dropdown)
<ButtonGroup>
<Button variant="outline">Follow</Button>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" className="!pl-2">
<ChevronDownIcon />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem>Mute Conversation</DropdownMenuItem>
<DropdownMenuItem>Mark as Read</DropdownMenuItem>
<DropdownMenuItem variant="destructive">Delete</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</ButtonGroup>
With Input (Prefix/Suffix)
<ButtonGroup>
<Input placeholder="Search..." />
<Button>Search</Button>
</ButtonGroup>
With Select and Input
<ButtonGroup>
<ButtonGroup>
<Select value={currency} onValueChange={setCurrency}>
<SelectTrigger className="font-mono">{currency}</SelectTrigger>
<SelectContent>
<SelectItem value="$">$ US Dollar</SelectItem>
<SelectItem value="€">€ Euro</SelectItem>
</SelectContent>
</Select>
<Input placeholder="10.00" />
</ButtonGroup>
<ButtonGroup>
<Button aria-label="Send" size="icon" variant="outline">
<ArrowRightIcon />
</Button>
</ButtonGroup>
</ButtonGroup>
Accessibility
- ButtonGroup is a purely visual container — no ARIA role required
- Individual buttons retain their own accessibility attributes
- Use
aria-labelon icon-only buttons within the group - For split button patterns, the dropdown trigger should have
aria-label="More Options"or similar
Gotchas
| Problem | Solution |
|---|---|
| Double borders between buttons | ButtonGroup handles border collapse automatically; ensure buttons use variant="outline" |
| Corner radius on middle buttons | Middle children automatically get rounded-none; don't override |
| Split button dropdown misaligned | Use align="end" on DropdownMenuContent |
| Spacing between groups missing | Nest ButtonGroup inside another ButtonGroup for automatic gap |
| Input not flush with button | Both must be direct children of the same ButtonGroup |
See Also
- Related Components: Button (individual buttons), Dropdown Menu (split button pattern), Input Group (input with addons)
Last updated: February 19, 2026