---
title: ButtonGroup
description: Cohesive group of related buttons with shared size, variant, and orientation.
sidebar:
  order: 1
search:
  tags: [button, actions, toolbar, group]
---

# ButtonGroup

> Cohesive group of related buttons with shared size, variant, and orientation.

## Import

```tsx
import { Button, ButtonGroup } from 'moraine'
```

## Usage

`ButtonGroup` joins the edges of direct button children and provides shared `size` and `variant`
defaults. A child `Button` can override either default when needed.

Add an accessible label when the surrounding content does not already name the group.

Set `separator` to render a decorative separator between adjacent controls. Customize its root
with the `separator` slot in `classes` or `styles`.

```tsx
<ButtonGroup separator classes={{ separator: 'text-primary' }}>
  <Button>Export</Button>
  <Button>Share</Button>
</ButtonGroup>
```

## Slot Structure

```text
root
├── Button (x n)
└── separator (between adjacent children, optional)
```

## Examples

### Basic

```tsx
function Basic() {
  return (
    <ButtonGroup variant="outline" aria-label="Document history">
      <Button leading="i-lucide:undo-2">Undo</Button>
      <Button leading="i-lucide:redo-2">Redo</Button>
    </ButtonGroup>
  )
}
```

### Variants

```tsx
function Variants() {
  const VARIANTS: ButtonGroupVariant[] = ['default', 'secondary', 'outline', 'ghost', 'destructive']

  return (
    <div class="flex flex-wrap gap-3">
      <For each={VARIANTS}>
        {(variant) => (
          <ButtonGroup variant={variant} aria-label={`${variant} actions`}>
            <Button>Previous</Button>
            <Button>Next</Button>
          </ButtonGroup>
        )}
      </For>
    </div>
  )
}
```

### Sizes

```tsx
function Sizes() {
  const SIZES: ButtonGroupSize[] = ['xs', 'sm', 'md', 'lg', 'xl']

  return (
    <div class="flex flex-wrap gap-3 items-end">
      <For each={SIZES}>
        {(size) => (
          <ButtonGroup size={size} variant="outline" aria-label={`${size} pagination`}>
            <Button>1</Button>
            <Button>2</Button>
            <Button>3</Button>
          </ButtonGroup>
        )}
      </For>
    </div>
  )
}
```

### Orientations

Horizontal and vertical groups using compact quantity controls.

```tsx
function Vertical() {
  return (
    <div class="flex flex-wrap gap-6 items-start">
      <ButtonGroup variant="outline" aria-label="Horizontal quantity controls">
        <Button size="icon-md" leading="i-lucide:minus" aria-label="Decrease quantity" />
        <Button size="icon-md" leading="i-lucide:plus" aria-label="Increase quantity" />
      </ButtonGroup>

      <ButtonGroup orientation="vertical" variant="outline" aria-label="Vertical quantity controls">
        <Button size="icon-md" leading="i-lucide:plus" aria-label="Increase quantity" />
        <Button size="icon-md" leading="i-lucide:minus" aria-label="Decrease quantity" />
      </ButtonGroup>
    </div>
  )
}
```

### Separators

Toggle separators between adjacent controls with a switch.

```tsx
function Separator() {
  const [showSeparator, setShowSeparator] = createSignal(true)

  return (
    <div class="flex flex-col gap-4 items-start">
      <Switch label="Show separators" checked={showSeparator()} onChange={setShowSeparator} />

      <ButtonGroup separator={showSeparator()} aria-label="Document actions">
        <Button leading="i-lucide:download">Export</Button>
        <Button leading="i-lucide:share-2">Share</Button>
        <Button leading="i-lucide:archive">Archive</Button>
      </ButtonGroup>
    </div>
  )
}
```

### Popover

Compose a primary button with a Popover trigger inside the same group.

```tsx
function ButtonPopover() {
  return (
    <ButtonGroup separator aria-label="Document actions">
      <Button leading="i-lucide:save">Save document</Button>
      <Popover
        content={
          <div class="space-y-1">
            <p class="text-sm font-medium">Save options</p>
            <p class="text-xs text-muted-foreground">Choose where to save this document.</p>
          </div>
        }
      >
        <Button size="icon-md" aria-label="Open save options">
          <Icon name="i-lucide:chevron-down" />
        </Button>
      </Popover>
    </ButtonGroup>
  )
}
```

### Dropdown

A report export action composed from a button trigger and a dropdown menu.

```tsx
function DropdownAction() {
  const [exportedFormat, setExportedFormat] = createSignal<string>()

  const exportItems: DropdownMenuT.Item[] = [
    {
      type: 'group',
      label: 'Export report',
      children: [
        {
          label: 'PDF document',
          description: 'Best for sharing and printing',
          icon: 'i-lucide:file-text',
          onSelect: () => setExportedFormat('PDF'),
        },
        {
          label: 'CSV spreadsheet',
          description: 'Best for analysis and imports',
          icon: 'i-lucide:table-2',
          onSelect: () => setExportedFormat('CSV'),
        },
        {
          label: 'JSON data',
          description: 'Best for integrations',
          icon: 'i-lucide:braces',
          onSelect: () => setExportedFormat('JSON'),
        },
      ],
    },
  ]

  return (
    <div class="flex flex-col gap-3 items-start">
      <ButtonGroup separator>
        <Button leading="i-lucide:download">Export report</Button>
        <DropdownMenu items={exportItems}>
          <Button size="icon-md">
            <Icon name="i-lucide:chevron-down" />
          </Button>
        </DropdownMenu>
      </ButtonGroup>

      <p class="text-sm text-muted-foreground min-h-5" role="status" aria-live="polite">
        {exportedFormat() ? `Report exported as ${exportedFormat()}.` : 'Choose an export format.'}
      </p>
    </div>
  )
}
```

## API Reference

### Attributes

#### `root`

Container that joins the edges of its direct button children.

##### Data Attributes

| Attribute | Type | Description |
| --- | --- | --- |
| data-orientation | string \| undefined | Stores the rendered orientation. |
| data-size | string | Stores the resolved size variant. |
| data-variant | string | Stores the resolved visual variant. |

##### ARIA Attributes

| Attribute | Type | Description |
| --- | --- | --- |
| role | string | Defines the semantic role exposed to assistive technology. |

#### `separator`

Separator element between buttons.

##### Data Attributes

| Attribute | Type | Description |
| --- | --- | --- |
| data-orientation | string \| undefined | Stores the rendered orientation. |

##### ARIA Attributes

| Attribute | Type | Description |
| --- | --- | --- |
| aria-hidden | boolean \| string \| undefined | Hides decorative content from assistive technology. |

### Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| children | JSX.Element | — | Buttons or compatible controls rendered as a cohesive group. |
| class | ClassValue | — | Class applied to the component root or trigger element. |
| classes | ButtonGroupT.Classes \| undefined | — | — |
| id | string \| undefined | — | Optional identifier for the group root. |
| orientation | "horizontal" \| "vertical" \| undefined | — | — |
| ref | JSX.HTMLElementTags["div"] extends { ref?: infer Ref; } ? Ref : never \| undefined | — | — |
| role | JSX.AriaAttributes["role"] \| undefined | — | ARIA role for the group root. |
| separator | boolean \| undefined | — | Whether to render a decorative separator between adjacent controls. |
| size | "xs" \| "sm" \| "md" \| "lg" \| "xl" \| "icon-xs" \| "icon-sm" \| "icon-md" \| "icon-lg" \| "icon-xl" \| undefined | — | — |
| style | JSX.CSSProperties \| undefined | — | — |
| styles | ButtonGroupT.Styles \| undefined | — | — |
| variant | "link" \| "default" \| "destructive" \| "outline" \| "secondary" \| "ghost" \| undefined | — | — |

### ARIA

Accessibility attributes and roles emitted by the component markup.

| Attribute | Type | Description |
| --- | --- | --- |
| aria-hidden | boolean \| string \| undefined | Hides decorative content from assistive technology. |
| role | string | Defines the semantic role exposed to assistive technology. |

### Data Attributes

State and slot attributes exposed for styling hooks and selectors.

| Attribute | Type | Description |
| --- | --- | --- |
| data-orientation | string \| undefined | Stores the rendered orientation. |
| data-size | string | Stores the resolved size variant. |
| data-slot | string | Identifies the rendered slot for styling hooks and selectors. |
| data-variant | string | Stores the resolved visual variant. |
