Button Group

Joins controls into one segmented unit — buttons, inputs, selects, or a mix. Inner corners are squared and adjacent borders overlap, so the row reads as a single control rather than a queue of separate ones.

src/components/ui/button-group.tsx

About

From the component's own source, where it was written.

Joins controls into one segmented unit — buttons, inputs, selects, or a mix.

Not to be confused with Group, which is a tray *behind* a set of cards. This one welds its children edge to edge.

Two things make that work. The inner corners are squared so only the outer two stay round, and each item after the first is pulled back a pixel so the two adjacent borders land on top of each other instead of stacking into a 2px seam.

The child rules reach one level deeper as well as at the top level: Select and Combobox render a positioning wrapper around their trigger, so the element that actually carries the radius is a button one level down.

Install

npx astralyx-ui add button-group

Usage

import { ButtonGroup } from '@/components/ui/button-group'

<ButtonGroup>
  <Button variant="secondary">Copy</Button>
  <Button variant="secondary">Paste</Button>
</ButtonGroup>

<ButtonGroup grow>
  <Input placeholder="Search" />
  <Button>Search</Button>
</ButtonGroup>

Composer

Change the props and the source below follows.

Props
tsx
<ButtonGroup>
  <Button variant="secondary">Copy</Button>
  <Button variant="secondary">Paste</Button>
  <Button variant="secondary">Cut</Button>
</ButtonGroup>

Examples

Buttons

Field and button

With a text segment

With a combobox

Vertical

API

Generated from the source, so it cannot fall behind the code. One table per exported component, then hooks, functions and types.

ButtonGroup

Joins controls into one segmented unit — buttons, inputs, selects, or a mix.

Extends ComponentProps<'div'>.

PropTypeDefaultDescription
orientation'horizontal' | 'vertical''horizontal'Which edges are joined. Vertical squares the top and bottom corners instead of the sides.
growbooleanfalseFill the width and let fields take the free space — the shape a search box plus a button wants.
responsiveResponsivefalseBreakpoint a horizontal group becomes a row at; below it the group stacks and welds its top and bottom edges instead of its sides. Defaults to false because a pair of icon buttons is fine at any width — set it for a field-and-button row, which is not. Ignored when orientation is vertical.
role"group"—Not "toolbar" — a toolbar implies arrow-key navigation between its controls. These are ordinary tab stops that happen to share edges.

ButtonGroupText

A non-interactive segment: a unit, a prefix, a counter. Takes the same size scale as the controls beside it.

Extends ComponentProps<'div'>.

PropTypeDefaultDescription
size'sm' | 'default' | 'lg''default'—
variantkeyof typeof textVariants'secondary'—

ButtonGroupSeparator

A rule between two segments that share a fill, where the collapsed borders leave no visible seam.

Extends ComponentProps<'div'>.

PropTypeDefaultDescription
orientation'horizontal' | 'vertical''horizontal'Which edges are joined. Vertical squares the top and bottom corners instead of the sides.

Functions

FunctionSignatureDescription
buttonGroupVariants(props?: { orientation?: 'horizontal' | 'vertical'; grow?: boolean }) => string—

Behaviour

TopicDetailDescription
nested triggershandledSelect and Combobox render a wrapper around their trigger, so the corner rules reach one level down as well as at the top level.