# Group

> Join related controls into one visually connected set.

Source: https://www.honestui.com/docs/components/group

```tsx
import { Ellipsis as EllipsisIcon, Files as FilesIcon, Film as FilmIcon } from "honestui/icons"

import { Button } from "@/components/honest-ui/ui/button"
import { Group, GroupItem, GroupSeparator } from "@/components/honest-ui/ui/group"

export function GroupDemo() {
  return (
    <Group>
      <GroupItem render={<Button variant="secondary" />}>
        <FilesIcon />
        Files
      </GroupItem>
      <GroupSeparator />
      <GroupItem render={<Button variant="secondary" />}>
        <FilmIcon />
        Media
      </GroupItem>
      <GroupSeparator />
      <GroupItem
        render={
          <Button variant="secondary" size="icon" aria-label="Menu" />
        }
      >
        <EllipsisIcon />
      </GroupItem>
    </Group>
  )
}

```

## Overview [#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](#dont-do-this)).

## Anatomy [#anatomy]

A group has three parts:

* **Group** is the container: an inline-flex box with a hairline border, shared background, and `overflow-hidden` that 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 `useRender` with a `render` prop, 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-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 [#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](#dont-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 [#installation]


  

  
    <CliBlock commands="[&#x22;group&#x22;]" />
  

  
    
      
        Install the following dependencies:
      

      ```bash
      npm install @base-ui-components/react
      ```

      
        Copy and paste the following code into your project.
      

      ### components/ui/group.tsx

```tsx
import * as React from "react"
import { mergeProps } from "@base-ui-components/react/merge-props"
import { useRender } from "@base-ui-components/react/use-render"

import { cn } from "@/lib/utils"
import { Separator } from "@/components/honest-ui/ui/separator"

function Group({
  className,
  children,
  ...props
}: React.ComponentProps<"div">) {
  return (
    <div
      data-slot="group"
      className={cn(
        "isolate inline-flex w-fit items-stretch overflow-hidden rounded-[var(--hui-radius-2)] border-[0.5px] border-[var(--hui-color-border-base-primary)] bg-[var(--hui-color-background-base-primary)] shadow-[var(--hui-shadow-feather)] has-[[data-slot=input-control]:focus-within]:border-[var(--hui-color-border-accent-emphasis)] *:pointer-coarse:after:min-w-auto",
        className
      )}
      role="group"
      {...props}
    >
      {children}
    </div>
  )
}

function GroupItem({
  className,
  render,
  ...props
}: useRender.ComponentProps<"div">) {
  const defaultProps = {
    className: cn(
      "relative min-w-0 self-stretch rounded-none! border-0! shadow-none! focus-visible:z-10 focus-visible:outline-offset-[var(--hui-focus-ring-offset-inset-border)] has-focus-visible:z-10 data-[slot=input-control]:bg-[var(--hui-color-background-base-primary)]",
      className
    ),
  }
  return useRender({
    defaultTagName: "div",
    render,
    props: mergeProps(defaultProps, props),
  })
}

function GroupSeparator({ className, ...props }: { className?: string }) {
  return (
    <Separator
      orientation="vertical"
      className={cn(
        "relative z-20 self-stretch bg-[var(--hui-color-border-base-primary)] data-[orientation=vertical]:h-auto!",
        className
      )}
      {...props}
    />
  )
}

export { Group, GroupItem, GroupSeparator }

```

      
        Update the import paths to match your project setup.
      
    
  


## Usage [#usage]

```tsx
import { Button } from "@/components/ui/button";
import { Group, GroupItem, GroupSeparator } from "@/components/ui/group";
```

```tsx
<Group>
  <GroupItem render={<Button variant="secondary" />}>Button</GroupItem>
  <GroupSeparator />
  <GroupItem render={<Button variant="secondary" />}>Button</GroupItem>
</Group>
```

## Don't do this [#dont-do-this]

### Grouping unrelated actions [#grouping-unrelated-actions]

```tsx
// 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>
```

```tsx
// Good
<div className="flex gap-2">
  <Button variant="destructive">Delete workspace</Button>
  <Button variant="ghost" render={[}>View docs](/docs)
</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 [#exclusive-filters-without-a-selected-state]

```tsx
// Bad
<Group>
  <GroupItem render={<Button variant="secondary" />}>Day</GroupItem>
  <GroupItem render={<Button variant="secondary" />}>Week</GroupItem>
  <GroupItem render={<Button variant="secondary" />}>Month</GroupItem>
</Group>
```

```tsx
// 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 [#naming-the-wrapper-instead-of-the-control]

```tsx
// Bad
<Group>
  <GroupItem aria-label="Copy URL">
    <Button size="icon">
      <CopyIcon />
    </Button>
  </GroupItem>
</Group>
```

```tsx
// 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 [#examples]

### With Input [#with-input]

An input joined to the action that consumes its value; focusing the input highlights the whole group's border.

```tsx
import { Copy as CopyIcon } from "honestui/icons"

import { Button } from "@/components/honest-ui/ui/button"
import { Group, GroupItem, GroupSeparator } from "@/components/honest-ui/ui/group"
import { Input } from "@/components/honest-ui/ui/input"

export function GroupWithInput() {
  return (
    <Group>
      <GroupItem
        render={<Input type="text" defaultValue="https://honestui.dev" />}
      />
      <GroupSeparator />
      <GroupItem
        render={
          <Button variant="secondary" size="icon" aria-label="Copy" />
        }
      >
        <CopyIcon />
      </GroupItem>
    </Group>
  )
}

```

### Segmented filters [#segmented-filters]

Three related views share one surface. Pair this pattern with ToggleGroup when selection must be announced — see [Don't do this](#exclusive-filters-without-a-selected-state).

```tsx
import { Button } from "@/components/honest-ui/ui/button"
import { Group, GroupItem } from "@/components/honest-ui/ui/group"

export function GroupSegmentedFilters() {
  return (
    <Group>
      <GroupItem render={<Button variant="secondary" />}>Day</GroupItem>
      <GroupItem render={<Button variant="secondary" />}>Week</GroupItem>
      <GroupItem render={<Button variant="secondary" />}>Month</GroupItem>
    </Group>
  )
}

```

### Filter actions [#filter-actions]

Status filters that trigger the same action with different arguments.

```tsx
import { Button } from "@/components/honest-ui/ui/button"
import { Group, GroupItem, GroupSeparator } from "@/components/honest-ui/ui/group"

export function GroupFilterActions() {
  return (
    <Group>
      <GroupItem render={<Button variant="secondary" />}>Open</GroupItem>
      <GroupSeparator />
      <GroupItem render={<Button variant="secondary" />}>Closed</GroupItem>
      <GroupSeparator />
      <GroupItem render={<Button variant="secondary" />}>Archived</GroupItem>
    </Group>
  )
}

```

### Toolbar actions [#toolbar-actions]

Icon-only items stay compact; every one carries an explicit accessible name.

```tsx
import { Download as DownloadIcon, Share as ShareIcon } from "honestui/icons"

import { Button } from "@/components/honest-ui/ui/button"
import { Group, GroupItem, GroupSeparator } from "@/components/honest-ui/ui/group"

export function GroupToolbarActions() {
  return (
    <Group>
      <GroupItem render={<Button variant="secondary" size="icon" />} aria-label="Share"><ShareIcon /></GroupItem>
      <GroupSeparator />
      <GroupItem render={<Button variant="secondary" size="icon" />} aria-label="Download"><DownloadIcon /></GroupItem>
    </Group>
  )
}

```

## API reference [#api-reference]

| Part             | Renders                                        | Props                                                                           |
| ---------------- | ---------------------------------------------- | ------------------------------------------------------------------------------- |
| `Group`          | `<div role="group">`                           | Native `div` props plus `className`; children stretch to equal height           |
| `GroupItem`      | configurable via `render`, defaults to `<div>` | Base UI `useRender` props including `render`; native element props pass through |
| `GroupSeparator` | vertical `<div>` (via Separator)               | `className` only                                                                |

Group applies no behavior to its children beyond layout: interactive children keep their own semantics, focus behavior, and disabled handling.
