Skip to main content

ButtonGroup

Join related buttons into one visual control set.

Use ButtonGroup when adjacent actions should read as one control set. Each child remains its own interactive control; the group supplies layout and shared presentation.

Basic usage#

import { Button, ButtonGroup } from 'moraine'

export function Example() {
  return (
    <ButtonGroup>
      <Button>Copy</Button>
      <Button variant="outline">Share</Button>
    </ButtonGroup>
  )
}

Playground#

Props
Slots

Anatomy#

ButtonGroup [component; slot=root]
├── ButtonGroup.Separator [part; slot=separator]
└── children [internal]

The group owns its separators; the caller supplies the child Buttons and their content.

Usage#

Composition#

Use ButtonGroup to align related actions without changing their semantics. Add ButtonGroup.Separator where a divider helps; it accepts direct class and style. Root classes and styles set defaults for the root and separator slots. Compose a menu or popover trigger as a child when an action needs an overlay.

import { Button, ButtonGroup } from 'moraine'

export function GroupComposition() {
  return (
    <div class="flex flex-col gap-4 items-start">
      <ButtonGroup>
        <Button variant="outline">Copy link</Button>
        <Button variant="outline">Duplicate</Button>
        <Button variant="outline">Archive</Button>
      </ButtonGroup>
    </div>
  )
}

Examples#

Toggle buttons#

Compose ToggleButton children for independently enabled actions. They inherit the group’s size and unpressed variant; activeVariant controls each button’s pressed appearance. ButtonGroup supplies layout and does not coordinate selection or add toolbar keyboard navigation.

import { ButtonGroup, ToggleButton } from 'moraine'

export function ToggleButtons() {
  return (
    <ButtonGroup size="sm" aria-label="Text formatting">
      <ToggleButton leading="i-lucide:bold" defaultPressed>
        Bold
      </ToggleButton>
      <ToggleButton leading="i-lucide:italic">Italic</ToggleButton>
      <ToggleButton leading="i-lucide:underline">Underline</ToggleButton>
    </ButtonGroup>
  )
}

Separator#

Insert ButtonGroup.Separator only where an explicit boundary is useful. Its orientation follows the group axis: vertical in a horizontal group and horizontal in a vertical group. An explicit orientation overrides this default.

import { Button, ButtonGroup } from 'moraine'

export function SeparatorExample() {
  return (
    <ButtonGroup aria-label="Clipboard actions">
      <Button variant="secondary">Copy</Button>
      <ButtonGroup.Separator />
      <Button variant="secondary">Paste</Button>
    </ButtonGroup>
  )
}
import { Button, ButtonGroup, DropdownMenu, Icon } from 'moraine'
import type { DropdownMenuT } from 'moraine'
import { createSignal } from 'solid-js'

export 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>
        <Button leading="i-lucide:download">Export report</Button>
        <ButtonGroup.Separator />
        <DropdownMenu placement="bottom" align="end">
          <DropdownMenu.Trigger as={Button} size="icon-md">
            <Icon name="i-lucide:chevron-down" />
          </DropdownMenu.Trigger>
          <DropdownMenu.Content items={exportItems} />
        </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>
  )
}

Popover composition#

import { Button, ButtonGroup, Icon, Popover } from 'moraine'

export function ButtonPopover() {
  return (
    <ButtonGroup aria-label="Document actions">
      <Button leading="i-lucide:save">Save document</Button>
      <Popover>
        <Popover.Trigger as={Button} size="icon-md" aria-label="Open save options">
          <Icon name="i-lucide:chevron-down" />
        </Popover.Trigger>
        <Popover.Content>
          <div class="p-3 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>
        </Popover.Content>
      </Popover>
    </ButtonGroup>
  )
}

Vertical layout#

import { Button, ButtonGroup } from 'moraine'

export 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" />
        <ButtonGroup.Separator />
        <Button size="icon-md" leading="i-lucide:minus" aria-label="Decrease quantity" />
      </ButtonGroup>
    </div>
  )
}

Attributes#

Attributes
data-orientationSlot: button-group-separatorDescription: Stores the rendered orientation (horizontal or vertical).

Props#

ButtonGroup#

Props for the ButtonGroup component. Renders a <div> element by default.

Prop

Separator#

Props for the ButtonGroup.Separator component. Renders a <div> element by default.

Prop