# Button Group (/ui/components/react/button-group)



<Playground component="button-group" />

## Usage [#usage]

```tsx
import { ButtonGroup } from '@appica/ui-react/button-group'
import { Button } from '@appica/ui-react/button'
```

```tsx
<ButtonGroup variant="outline">
  <Button>Years</Button>
  <Button>Months</Button>
  <Button>Days</Button>
</ButtonGroup>
```

`ButtonGroup` is a thin wrapper around your existing [`Button`](/ui/components/react/button)s. It collapses the inner corners and borders into one seamless control and shares `variant`, `size`, and `disabled` with every child through React context — so you set them once on the group instead of repeating them on each button.

## Examples [#examples]

### Variants [#variants]

Set `variant` on the group and every [`Button`](/ui/components/react/button) inherits it. Outlined variants (`outline`, `primary-outline`, `light`) overlap their shared borders so the seam stays a single hairline.

```tsx
import { ButtonGroup } from '@appica/ui-react/button-group'
import { Button } from '@appica/ui-react/button'

const variants = ['primary', 'primary-outline', 'secondary', 'soft', 'outline', 'destructive'] as const

export default function ButtonGroupVariants() {
  return (
    <div className="flex flex-col items-center gap-4">
      {variants.map((variant) => (
        <ButtonGroup key={variant} variant={variant}>
          <Button>Years</Button>
          <Button>Months</Button>
          <Button>Days</Button>
        </ButtonGroup>
      ))}
    </div>
  )
}
```

### Sizes [#sizes]

`size` cascades to the children too. Use it to scale the whole control at once.

```tsx
import { ButtonGroup } from '@appica/ui-react/button-group'
import { Button } from '@appica/ui-react/button'

const sizes = ['sm', 'md', 'lg'] as const

export default function ButtonGroupSizes() {
  return (
    <div className="flex flex-col items-center gap-4">
      {sizes.map((size) => (
        <ButtonGroup key={size} size={size} variant="outline">
          <Button>Left</Button>
          <Button>Center</Button>
          <Button>Right</Button>
        </ButtonGroup>
      ))}
    </div>
  )
}
```

### Vertical orientation [#vertical-orientation]

Set `orientation="vertical"` to stack the buttons; the group rounds the top and bottom of the stack instead of the left and right ends.

```tsx
import { ButtonGroup } from '@appica/ui-react/button-group'
import { Button } from '@appica/ui-react/button'

export default function ButtonGroupOrientation() {
  return (
    <ButtonGroup orientation="vertical" variant="outline">
      <Button>Profile</Button>
      <Button>Billing</Button>
      <Button>Settings</Button>
    </ButtonGroup>
  )
}
```

### Icon buttons [#icon-buttons]

Pair an `icon-*` size with icon-only buttons to build a compact toolbar. Give each button an `aria-label` since there's no visible text.

```tsx
import { ButtonGroup } from '@appica/ui-react/button-group'
import { Button } from '@appica/ui-react/button'
import { Bold, Italic, Underline } from '@appica/icons-react'

export default function ButtonGroupIconButtons() {
  return (
    <ButtonGroup variant="outline" size="icon-md">
      <Button aria-label="Bold">
        <Bold />
      </Button>
      <Button aria-label="Italic">
        <Italic />
      </Button>
      <Button aria-label="Underline">
        <Underline />
      </Button>
    </ButtonGroup>
  )
}
```

### Split button [#split-button]

A common pattern: a primary action joined to a [dropdown menu](/ui/components/react/dropdown-menu) of related actions. The menu trigger is rendered as a `Button`, so it still inherits the group's `variant` through context even though it's nested inside `DropdownMenu`.

```tsx
import { ButtonGroup } from '@appica/ui-react/button-group'
import { Button } from '@appica/ui-react/button'
import {
  DropdownMenu,
  DropdownMenuTrigger,
  DropdownMenuContent,
  DropdownMenuItem,
} from '@appica/ui-react/dropdown-menu'
import { ChevronDown } from '@appica/icons-react'

export default function ButtonGroupSplitButton() {
  return (
    <ButtonGroup variant="outline">
      <Button>Save</Button>
      <DropdownMenu>
        <DropdownMenuTrigger
          className="group/split"
          render={
            <Button size="icon-md" aria-label="More save options">
              <ChevronDown className="group-data-popup-open/split:rotate-180 motion-safe:transition-transform motion-safe:duration-200" />
            </Button>
          }
        />
        <DropdownMenuContent>
          <DropdownMenuItem>Save and continue</DropdownMenuItem>
          <DropdownMenuItem>Save as draft</DropdownMenuItem>
          <DropdownMenuItem>Save as template</DropdownMenuItem>
        </DropdownMenuContent>
      </DropdownMenu>
    </ButtonGroup>
  )
}
```

## RTL [#rtl]

Every Appica UI component supports right-to-left layouts out of the box. Set the `dir` attribute on a container (commonly your `<html>` element) so CSS logical properties resolve correctly, and wrap your tree in `DirectionProvider` so direction-aware behavior — roving focus, popup placement, and the like — follows the same direction.

```tsx
import { DirectionProvider } from '@appica/ui-react/providers/direction-provider'

export default function RootLayout({ children }) {
  return (
    <html lang="ar" dir="rtl">
      <body>
        <DirectionProvider dir="rtl">{children}</DirectionProvider>
      </body>
    </html>
  )
}
```

For setup details and caveats, see the [RTL guide](/ui/docs/react/rtl).

<RtlPreview component="button-group" />

## API reference [#api-reference]

| Prop          | <ColMinWidth width="200">Type</ColMinWidth>                                                                              | Default        | <ColMinWidth width="220">Description</ColMinWidth>                                              |
| ------------- | ------------------------------------------------------------------------------------------------------------------------ | -------------- | ----------------------------------------------------------------------------------------------- |
| `variant`     | <Code>'primary' \| 'primary-outline' \| 'secondary' \| 'soft' \| 'outline' \| 'ghost' \| 'destructive' \| 'light'</Code> | —              | Visual style applied to every child `Button`. Inherited unless a button sets its own `variant`. |
| `size`        | <Code>'sm' \| 'md' \| 'lg' \| 'icon-sm' \| 'icon-md' \| 'icon-lg'</Code>                                                 | —              | Height/padding applied to every child `Button`. Inherited unless a button sets its own `size`.  |
| `orientation` | <Code>'horizontal' \| 'vertical'</Code>                                                                                  | `'horizontal'` | Lay the buttons out in a row or a column; controls which corners are rounded.                   |
| `disabled`    | `boolean`                                                                                                                | `false`        | Disables every child `Button`. A child can't re-enable itself; the group's `disabled` wins.     |
| `className`   | `string`                                                                                                                 | —              | Extra classes, merged after the group classes via `tailwind-merge`.                             |

`ButtonGroup` renders a `<div role="group">` and forwards every other prop (`id`, `aria-*`, `ref`, …) to it. Each child reads `variant`, `size`, and `disabled` from group context — a value set directly on a `Button` always overrides the group, except `disabled`, which is additive (the group disabling can't be overridden by a child).

## Accessibility [#accessibility]

* Renders a `<div role="group">` so assistive tech announces the buttons as one related set. Add an `aria-label` or `aria-labelledby` to name the group when its purpose isn't clear from context.
* Each child remains a standalone [`Button`](/ui/components/react/button), so focus order, **Tab**, **Enter**, and **Space** behave exactly as they do for a lone button.
* Icon-only buttons have no text — give each one an `aria-label`.
* `disabled` on the group removes every child from the tab order and sets `data-disabled` for styling.
