Group
Join related controls into one visually connected set.
Overview
Use Group to visually connect controls that work together on a single object or decision: an input with its copy button, a set of view actions, segmented filter choices. The container draws one shared border, background, and shadow so the children read as one instrument rather than scattered buttons.
The connection is semantic as well as visual. The container renders role="group", so assistive technology announces the children as one related unit — which is exactly why unrelated controls should never be grouped to save space (see Don't do this).
Anatomy
A group has three parts:
- Group is the container: an inline-flex box with a hairline border, shared background, and
overflow-hiddenthat clips its children into one rounded shape. When an input inside receives focus, the whole group's border switches to the accent color. - GroupItem wraps each child. It uses Base UI's
useRenderwith arenderprop, so you render a real control (<Button />,<Input />) through it. It strips the child's own radius, border, and shadow so edges meet seamlessly. - GroupSeparator is a vertical separator that stretches the full height of the group, dividing children that should stay visually distinct.
Because the container clips overflow, a child's focus ring would be cut off at the edge; GroupItem raises focused children above their neighbors (z-index: 10) so the ring stays fully visible.
Group versus other components
Group only joins visuals. It provides no selection model, no labels, and no keyboard behavior beyond normal tab order — use it when the children already have all of that. Use Toolbar for a command set with roving focus, Fieldset for grouping one form question with its legend, InputGroup when context like a prefix or suffix belongs inside a control's own border, and ToggleGroup for pressed-state choices. As a layout primitive, Group itself has no loading, disabled, or destructive states; those belong to the controls inside.
Accessibility
Each control inside keeps its own name and semantics — Group adds nothing except the role="group" wrapper. Give icon-only children an accessible name on the rendered element, not on the wrapper (see Don't do this). DOM order is visual order, so tab order follows what people see.
On touch devices, Buttons normally expand their hit area invisibly; inside a Group that expansion is neutralized so adjacent items stay flush. Leave extra spacing around groups that are primary touch targets. Colors come from theme tokens, so groups adapt to dark mode automatically, and flexbox spacing mirrors in right-to-left locales.
Installation
Usage
import { Button } from "@/components/ui/button";
import { Group, GroupItem, GroupSeparator } from "@/components/ui/group";<Group>
<GroupItem render={<Button variant="secondary" />}>Button</GroupItem>
<GroupSeparator />
<GroupItem render={<Button variant="secondary" />}>Button</GroupItem>
</Group>Don't do this
Grouping unrelated actions
// Bad
<Group>
<GroupItem render={<Button variant="secondary" />}>Delete workspace</GroupItem>
<GroupItem render={<Button variant="secondary" />}>View docs</GroupItem>
<GroupItem render={<Button variant="secondary" />}>Sign out</GroupItem>
</Group>// Good
<div className="flex gap-2">
<Button variant="destructive">Delete workspace</Button>
<Button variant="ghost" render={<Link href="/docs" />}>View docs</Link>
</div>Visually attached controls imply they operate on the same object in the same way, and role="group" makes screen readers announce them as one unit. A destructive action sitting flush against navigation teaches people to stop reading group contents carefully. Group only controls that genuinely act together.
Exclusive filters without a selected state
// Bad
<Group>
<GroupItem render={<Button variant="secondary" />}>Day</GroupItem>
<GroupItem render={<Button variant="secondary" />}>Week</GroupItem>
<GroupItem render={<Button variant="secondary" />}>Month</GroupItem>
</Group>// Good
<ToggleGroup defaultValue={["week"]} aria-label="Range">
<Toggle value="day">Day</Toggle>
<Toggle value="week">Week</Toggle>
<Toggle value="month">Month</Toggle>
</ToggleGroup>Plain buttons cannot express "this one is active", so sighted users see no state change and screen-reader users hear nothing announced at all. An exclusive choice among options is exactly what ToggleGroup's single-selection mode emits aria-pressed for. Use Group for actions, not selections.
Naming the wrapper instead of the control
// Bad
<Group>
<GroupItem aria-label="Copy URL">
<Button size="icon">
<CopyIcon />
</Button>
</GroupItem>
</Group>// Good
<Group>
<GroupItem render={<Button size="icon" aria-label="Copy URL" />}>
<CopyIcon />
</GroupItem>
</Group>An accessible name belongs to the focusable control, because that is what screen readers and voice-control users interact with. Passing aria-label to the wrong layer leaves the button unnamed while adding a stray labelled <div>. Render the real control through render and label it there.
Examples
With Input
An input joined to the action that consumes its value; focusing the input highlights the whole group's border.
Segmented filters
Three related views share one surface. Pair this pattern with ToggleGroup when selection must be announced — see Don't do this.
Filter actions
Status filters that trigger the same action with different arguments.
Toolbar actions
Icon-only items stay compact; every one carries an explicit accessible name.
API reference
Group applies no behavior to its children beyond layout: interactive children keep their own semantics, focus behavior, and disabled handling.