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,disabledandfullWidthto 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.
<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 key | data-slot | Description |
|---|---|---|
base | button-group | Base button group container |
separator | button-group-separator | Separator element between buttons |
button | button | Classes added to each grouped Button |
buttonOutline | button | Classes 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
| Prop | Type | Default | Description |
|---|---|---|---|
as | AsTag | 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. |
disabled | boolean | undefined | Disables every Button in the group; a Button's own disabled wins (:disabled="false" re-enables it). |
fullWidth | boolean | undefined | Whether the group and its Buttons take the full width of the container. |
separator | boolean | false | Draws a divider at the start of every Button except the first (HeroUI's ButtonGroup.Separator). |
ui | ComponentSlots<{ 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
| Slot | Props | Description |
|---|---|---|
default | any | The Buttons. Buttons placed directly in the group read its props. |
Note
- ButtonGroup passes
size,variant,disabledandfullWidthto 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
disabledprop 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



