ButtonGroup

Group related buttons together with consistent styling and spacing

Usage

import { Button, ButtonGroup } from '@hareui/vue'
<script setup lang="ts">import { Icon } from '@iconify/vue'import type { DropdownEntry } from '@hareui/vue'import { Button, ButtonGroup, Chip, Dropdown } from '@hareui/vue'const mergeOptions: DropdownEntry[] = [  { value: 'merge', label: 'Create a merge commit', description: 'All commits from this branch will be added to the base branch', class: 'flex flex-col items-start gap-1 [&>div]:items-start [&>div]:gap-1' },  { value: 'squash-and-merge', label: 'Squash and merge', description: 'The 14 commits from this branch will be combined into one commit in the base branch', class: 'flex flex-col items-start gap-1 [&>div]:items-start [&>div]:gap-1' },  { value: 'rebase-and-merge', label: 'Rebase and merge', description: 'The 14 commits from this branch will be rebased and added to the base branch', class: 'flex flex-col items-start gap-1 [&>div]:items-start [&>div]:gap-1' },]</script><template>  <div class="flex flex-col items-start gap-6">    <!-- Single button with dropdown -->    <div class="flex flex-col gap-2">      <ButtonGroup separator>        <Button>Merge pull request</Button>        <Dropdown :items="mergeOptions" class="max-w-[290px]" align="end">          <Button icon-only aria-label="More options">            <Icon icon="gravity-ui:chevron-down" />          </Button>        </Dropdown>      </ButtonGroup>    </div>    <!-- Individual buttons -->    <div class="flex flex-col gap-2">      <div class="flex flex-wrap gap-x-2 gap-y-4">        <ButtonGroup separator variant="tertiary">          <Button>            <Icon class="size-3.5" icon="gravity-ui:code-fork" />            Fork            <Chip color="accent" size="sm" variant="soft">              24            </Chip>          </Button>          <Button icon-only aria-label="More fork options">            <Icon icon="gravity-ui:chevron-down" />          </Button>        </ButtonGroup>        <ButtonGroup separator variant="tertiary">          <Button icon-only aria-label="Show QR code">            <Icon icon="gravity-ui:qr-code" />          </Button>          <Button>Scan to pay</Button>        </ButtonGroup>        <ButtonGroup separator variant="tertiary">          <Button>            <Icon icon="gravity-ui:thumbs-up" />            <span class="text-xs font-semibold">2.4K</span>          </Button>          <Button icon-only aria-label="Dislike">            <Icon icon="gravity-ui:thumbs-down" />          </Button>        </ButtonGroup>        <ButtonGroup separator variant="tertiary">          <Button>            <Icon class="size-3.5" icon="gravity-ui:star" />            Star          </Button>          <Button class="px-2">            <Chip color="accent" size="sm" variant="soft">              104            </Chip>          </Button>        </ButtonGroup>        <ButtonGroup separator variant="tertiary">          <Button>            <Icon icon="gravity-ui:pin" />            Pinned          </Button>          <Button icon-only aria-label="More pin options">            <Icon icon="gravity-ui:chevron-down" />          </Button>        </ButtonGroup>      </div>    </div>    <!-- Previous/Next Button Group -->    <div class="flex flex-col gap-2">      <ButtonGroup separator variant="tertiary">        <Button>          <Icon icon="gravity-ui:chevron-left" />          Previous        </Button>        <Button>          Next          <Icon icon="gravity-ui:chevron-right" />        </Button>      </ButtonGroup>    </div>    <!-- Content Selection Button Group -->    <div class="flex flex-col gap-2">      <ButtonGroup separator variant="tertiary">        <Button>          <Icon icon="gravity-ui:picture" />          Photos        </Button>        <Button>          <Icon icon="gravity-ui:video" />          Videos        </Button>        <Button icon-only aria-label="More options">          <Icon icon="gravity-ui:ellipsis" />        </Button>      </ButtonGroup>    </div>    <!-- Text Alignment Button Group -->    <div class="flex flex-col gap-2">      <ButtonGroup separator variant="tertiary">        <Button>Left</Button>        <Button>Center</Button>        <Button>Right</Button>      </ButtonGroup>    </div>    <!-- Icon-Only Alignment Button Group -->    <div class="flex flex-col gap-2">      <ButtonGroup separator variant="tertiary">        <Button icon-only aria-label="Align left">          <Icon icon="gravity-ui:text-align-left" />        </Button>        <Button icon-only aria-label="Align center">          <Icon icon="gravity-ui:text-align-center" />        </Button>        <Button icon-only aria-label="Align right">          <Icon icon="gravity-ui:text-align-right" />        </Button>        <Button icon-only aria-label="Justify">          <Icon icon="gravity-ui:text-align-justify" />        </Button>      </ButtonGroup>    </div>  </div></template>

Anatomy

<template>
  <ButtonGroup separator>
    <Button>First</Button>
    <Button>Second</Button>
    <Button>Third</Button>
  </ButtonGroup>
</template>

ButtonGroup wraps multiple Button components together, applying consistent styling, spacing, and automatic border radius handling. It passes size, variant, disabled and fullWidth to its Buttons through provide/inject.

HeroUI's ButtonGroup.Separator, placed inside each Button except the first, becomes the separator prop: every Button in the group renders the divider and the first Button hides it.

ui keys: base, separator, plus button and buttonOutline, which are classes added to each grouped Button (buttonOutline only to variant="outline" Buttons).

Examples

Variants

Primary

Secondary

Tertiary

Outline

Ghost

Danger

<script setup lang="ts">import { Button, ButtonGroup } from '@hareui/vue'</script><template>  <div class="flex flex-col gap-6">    <div class="flex flex-col gap-2">      <p class="text-sm text-muted">Primary</p>      <ButtonGroup separator variant="primary">        <Button>First</Button>        <Button>Second</Button>        <Button>Third</Button>      </ButtonGroup>    </div>    <div class="flex flex-col gap-2">      <p class="text-sm text-muted">Secondary</p>      <ButtonGroup separator variant="secondary">        <Button>First</Button>        <Button>Second</Button>        <Button>Third</Button>      </ButtonGroup>    </div>    <div class="flex flex-col gap-2">      <p class="text-sm text-muted">Tertiary</p>      <ButtonGroup separator variant="tertiary">        <Button>First</Button>        <Button>Second</Button>        <Button>Third</Button>      </ButtonGroup>    </div>    <div class="flex flex-col gap-2">      <p class="text-sm text-muted">Outline</p>      <ButtonGroup separator variant="outline">        <Button>First</Button>        <Button>Second</Button>        <Button>Third</Button>      </ButtonGroup>    </div>    <div class="flex flex-col gap-2">      <p class="text-sm text-muted">Ghost</p>      <ButtonGroup separator variant="ghost">        <Button>First</Button>        <Button>Second</Button>        <Button>Third</Button>      </ButtonGroup>    </div>    <div class="flex flex-col gap-2">      <p class="text-sm text-muted">Danger</p>      <ButtonGroup separator variant="danger">        <Button>First</Button>        <Button>Second</Button>        <Button>Third</Button>      </ButtonGroup>    </div>  </div></template>

Sizes

Small

Medium (default)

Large

<script setup lang="ts">import { Button, ButtonGroup } from '@hareui/vue'</script><template>  <div class="flex flex-col gap-4">    <div class="flex flex-col items-start gap-2">      <p class="text-sm text-muted">Small</p>      <ButtonGroup separator size="sm" variant="secondary">        <Button>First</Button>        <Button>Second</Button>        <Button>Third</Button>      </ButtonGroup>    </div>    <div class="flex flex-col items-start gap-2">      <p class="text-sm text-muted">Medium (default)</p>      <ButtonGroup separator size="md" variant="secondary">        <Button>First</Button>        <Button>Second</Button>        <Button>Third</Button>      </ButtonGroup>    </div>    <div class="flex flex-col items-start gap-2">      <p class="text-sm text-muted">Large</p>      <ButtonGroup separator size="lg" variant="secondary">        <Button>First</Button>        <Button>Second</Button>        <Button>Third</Button>      </ButtonGroup>    </div>  </div></template>

Orientation

Use the orientation prop to arrange buttons horizontally or vertically.

Horizontal
Vertical
<script setup lang="ts">import { Icon } from '@iconify/vue'import { Button, ButtonGroup } from '@hareui/vue'</script><template>  <div class="flex items-start gap-8">    <div class="flex flex-col gap-2">      <span class="text-sm text-muted">Horizontal</span>      <ButtonGroup orientation="horizontal" separator variant="tertiary">        <Button icon-only aria-label="Align left">          <Icon icon="gravity-ui:text-align-left" />        </Button>        <Button icon-only aria-label="Align center">          <Icon icon="gravity-ui:text-align-center" />        </Button>        <Button icon-only aria-label="Align right">          <Icon icon="gravity-ui:text-align-right" />        </Button>        <Button icon-only aria-label="Justify">          <Icon icon="gravity-ui:text-align-justify" />        </Button>      </ButtonGroup>    </div>    <div class="flex flex-col gap-2">      <span class="text-sm text-muted">Vertical</span>      <ButtonGroup orientation="vertical" separator variant="tertiary">        <Button icon-only aria-label="Align left">          <Icon icon="gravity-ui:text-align-left" />        </Button>        <Button icon-only aria-label="Align center">          <Icon icon="gravity-ui:text-align-center" />        </Button>        <Button icon-only aria-label="Align right">          <Icon icon="gravity-ui:text-align-right" />        </Button>        <Button icon-only aria-label="Justify">          <Icon icon="gravity-ui:text-align-justify" />        </Button>      </ButtonGroup>    </div>  </div></template>

With Icons

With icons

Icon only buttons

<script setup lang="ts">import { Icon } from '@iconify/vue'import { Button, ButtonGroup } from '@hareui/vue'</script><template>  <div class="flex flex-col gap-6">    <div class="flex flex-col items-start gap-2">      <p class="text-sm text-muted">With icons</p>      <ButtonGroup separator variant="secondary">        <Button>          <Icon icon="gravity-ui:globe" />          Search        </Button>        <Button>          <Icon icon="gravity-ui:plus" />          Add        </Button>        <Button>          <Icon icon="gravity-ui:trash-bin" />          Delete        </Button>      </ButtonGroup>    </div>    <div class="flex flex-col items-start gap-2">      <p class="text-sm text-muted">Icon only buttons</p>      <ButtonGroup separator variant="tertiary">        <Button icon-only aria-label="Search">          <Icon icon="gravity-ui:globe" />        </Button>        <Button icon-only aria-label="Add">          <Icon icon="gravity-ui:plus" />        </Button>        <Button icon-only aria-label="Delete">          <Icon icon="gravity-ui:trash-bin" />        </Button>      </ButtonGroup>    </div>  </div></template>

Full Width

<script setup lang="ts">import { Icon } from '@iconify/vue'import { Button, ButtonGroup } from '@hareui/vue'</script><template>  <div class="w-[400px] space-y-3">    <ButtonGroup full-width separator>      <Button>First</Button>      <Button>Second</Button>      <Button>Third</Button>    </ButtonGroup>    <ButtonGroup full-width separator>      <Button icon-only aria-label="Align left">        <Icon icon="gravity-ui:text-align-left" />      </Button>      <Button icon-only aria-label="Align center">        <Icon icon="gravity-ui:text-align-center" />      </Button>      <Button icon-only aria-label="Align right">        <Icon icon="gravity-ui:text-align-right" />      </Button>    </ButtonGroup>  </div></template>

Disabled State

All buttons disabled

Group disabled, but one button overrides

<script setup lang="ts">import { Button, ButtonGroup } from '@hareui/vue'</script><template>  <div class="flex flex-col gap-6">    <div class="flex flex-col items-start gap-2">      <p class="text-sm text-muted">All buttons disabled</p>      <ButtonGroup disabled separator>        <Button>First</Button>        <Button>Second</Button>        <Button>Third</Button>      </ButtonGroup>    </div>    <div class="flex flex-col items-start gap-2">      <p class="text-sm text-muted">Group disabled, but one button overrides</p>      <ButtonGroup disabled separator>        <Button>First</Button>        <Button>Second</Button>        <Button :disabled="false">          Third (enabled)        </Button>      </ButtonGroup>    </div>  </div></template>

Without Separator

Simply omit the separator prop.

<script setup lang="ts">import { Button, ButtonGroup } from '@hareui/vue'</script><template>  <ButtonGroup>    <Button>First</Button>    <Button>Second</Button>    <Button>Third</Button>  </ButtonGroup></template>

Customization

Tailwind CSS

<script setup lang="ts">import { Icon } from '@iconify/vue'import type { DropdownEntry } from '@hareui/vue'import { Button, ButtonGroup, Dropdown } from '@hareui/vue'const mergeOptions: DropdownEntry[] = [  { value: 'merge', label: 'Create a merge commit', description: 'All commits from this branch will be added to the base branch', class: 'flex flex-col items-start gap-1 rounded-none [&>div]:items-start [&>div]:gap-1' },  { value: 'squash-and-merge', label: 'Squash and merge', description: 'The 14 commits from this branch will be combined into one commit in the base branch', class: 'flex flex-col items-start gap-1 rounded-none [&>div]:items-start [&>div]:gap-1' },  { value: 'rebase-and-merge', label: 'Rebase and merge', description: 'The 14 commits from this branch will be rebased and added to the base branch', class: 'flex flex-col items-start gap-1 rounded-none [&>div]:items-start [&>div]:gap-1' },]</script><template>  <ButtonGroup separator>    <Button class="rounded-none bg-[#08872B] hover:bg-[#0FBF3E]">      Merge pull request    </Button>    <Dropdown :items="mergeOptions" class="max-w-[290px] rounded-none" align="end">      <Button icon-only aria-label="Merge options" class="rounded-none bg-[#08872B] hover:bg-[#0FBF3E]">        <Icon class="size-4" icon="gravity-ui:chevron-down" />      </Button>    </Dropdown>  </ButtonGroup></template>

Global theme override

Override the buttonGroup theme app-wide with createHareUI:

app.use(createHareUI({
  ui: {
    buttonGroup: {
      slots: { base: 'gap-2 rounded-lg', separator: 'opacity-25' },
    },
  },
}))

Styling Reference

Each rendered element carries the same data-slot string as HeroUI, so selectors translate 1:1.

ui keydata-slotDescription
basebutton-groupBase button group container
separatorbutton-group-separatorSeparator element between buttons
buttonbuttonClasses added to each grouped Button
buttonOutlinebuttonClasses added to each grouped outline Button

The ButtonGroup component automatically applies border radius to buttons:

  • First button gets rounded left/start edge
  • Last button gets rounded right/end edge
  • Middle buttons have no border radius
  • Single button gets full border radius on all edges

The edge radii are set through CSS variables, so a rounded-* class on a Button replaces them.

API Reference

Props

PropTypeDefaultDescription
asAsTag | Component"div"The element or component to render as.
orientation"horizontal" | "vertical"'horizontal'Layout direction of the buttons. Inside a Toolbar it defaults to the toolbar's orientation.
variant"danger" | "primary" | "secondary" | "tertiary" | "ghost" | "outline" | "danger-soft"-Variant applied to every Button in the group that doesn't set its own.
size"md" | "sm" | "lg"-Size applied to every Button in the group that doesn't set its own.
disabledbooleanundefinedDisables every Button in the group; a Button's own disabled wins (:disabled="false" re-enables it).
fullWidthbooleanundefinedWhether the group and its Buttons take the full width of the container.
separatorbooleanfalseDraws a divider at the start of every Button except the first (HeroUI's ButtonGroup.Separator).
uiComponentSlots<{ slots: { base: string; button: string; buttonOutline: string; separator: string; }; variants: { orientation: { horizontal: { base: string; button: string; buttonOutline: string; separator: string; }; vertical: { base: string; button: string; buttonOutline: string; separator: string; }; }; fullWidth: { true: { base: string; }; false: { base: string; }; }; }; defaultVariants: { orientation: string; fullWidth: boolean; }; }>-Per-slot class overrides. button/buttonOutline are added to each grouped Button, separator to each divider.

Slots

SlotPropsDescription
defaultanyThe Buttons. Buttons placed directly in the group read its props.

Note

  • ButtonGroup passes size, variant, disabled and fullWidth to its Button components
  • Only the group's own buttons receive the ButtonGroup props. A Button nested inside another Button does not inherit them. Unlike HeroUI, a Button rendered by a component in the group's slot (a Dropdown trigger, for example) does inherit them
  • Individual Button components can override the group's disabled prop by setting :disabled="false"
  • The component automatically handles border radius between buttons
  • Buttons in a group have their active/pressed scale transform removed for a more cohesive appearance

On this page

No Headings