ToggleButtonGroup
Groups multiple ToggleButtons into a unified control, allowing users to select one or multiple options.
Usage
import { ToggleButton, ToggleButtonGroup } from '@hareui/vue'<script setup lang="ts">import { Icon } from '@iconify/vue'import { ToggleButton, ToggleButtonGroup } from '@hareui/vue'</script><template> <ToggleButtonGroup selection-mode="multiple" separator> <ToggleButton icon-only aria-label="Bold" value="bold"> <Icon icon="gravity-ui:bold" /> </ToggleButton> <ToggleButton icon-only aria-label="Italic" value="italic"> <Icon icon="gravity-ui:italic" /> </ToggleButton> <ToggleButton icon-only aria-label="Underline" value="underline"> <Icon icon="gravity-ui:underline" /> </ToggleButton> <ToggleButton icon-only aria-label="Strikethrough" value="strikethrough"> <Icon icon="gravity-ui:strikethrough" /> </ToggleButton> </ToggleButtonGroup></template>Anatomy
<template>
<ToggleButtonGroup selection-mode="multiple" separator>
<ToggleButton value="first">First</ToggleButton>
<ToggleButton value="second">Second</ToggleButton>
<ToggleButton value="third">Third</ToggleButton>
</ToggleButtonGroup>
</template>HeroUI's ToggleButtonGroup.Separator, placed inside each ToggleButton except the first, becomes the separator prop: every ToggleButton renders the divider and the first one hides it. Each ToggleButton's value (HeroUI's id) is what the group's v-model holds.
ui keys: base, separator, plus button, which is added to each ToggleButton in the group.
Examples
Sizes
<script setup lang="ts">import { Icon } from '@iconify/vue'import { ToggleButton, ToggleButtonGroup } from '@hareui/vue'</script><template> <div class="flex flex-col gap-6"> <div class="flex flex-col gap-2"> <span class="text-sm text-muted">Small</span> <ToggleButtonGroup selection-mode="multiple" separator size="sm"> <ToggleButton icon-only aria-label="Bold" value="bold"> <Icon icon="gravity-ui:bold" /> </ToggleButton> <ToggleButton icon-only aria-label="Italic" value="italic"> <Icon icon="gravity-ui:italic" /> </ToggleButton> <ToggleButton icon-only aria-label="Underline" value="underline"> <Icon icon="gravity-ui:underline" /> </ToggleButton> <ToggleButton icon-only aria-label="Strikethrough" value="strikethrough"> <Icon icon="gravity-ui:strikethrough" /> </ToggleButton> </ToggleButtonGroup> </div> <div class="flex flex-col gap-2"> <span class="text-sm text-muted">Medium (default)</span> <ToggleButtonGroup selection-mode="multiple" separator size="md"> <ToggleButton icon-only aria-label="Bold" value="bold"> <Icon icon="gravity-ui:bold" /> </ToggleButton> <ToggleButton icon-only aria-label="Italic" value="italic"> <Icon icon="gravity-ui:italic" /> </ToggleButton> <ToggleButton icon-only aria-label="Underline" value="underline"> <Icon icon="gravity-ui:underline" /> </ToggleButton> <ToggleButton icon-only aria-label="Strikethrough" value="strikethrough"> <Icon icon="gravity-ui:strikethrough" /> </ToggleButton> </ToggleButtonGroup> </div> <div class="flex flex-col gap-2"> <span class="text-sm text-muted">Large</span> <ToggleButtonGroup selection-mode="multiple" separator size="lg"> <ToggleButton icon-only aria-label="Bold" value="bold"> <Icon icon="gravity-ui:bold" /> </ToggleButton> <ToggleButton icon-only aria-label="Italic" value="italic"> <Icon icon="gravity-ui:italic" /> </ToggleButton> <ToggleButton icon-only aria-label="Underline" value="underline"> <Icon icon="gravity-ui:underline" /> </ToggleButton> <ToggleButton icon-only aria-label="Strikethrough" value="strikethrough"> <Icon icon="gravity-ui:strikethrough" /> </ToggleButton> </ToggleButtonGroup> </div> </div></template>Orientation
<script setup lang="ts">import { Icon } from '@iconify/vue'import { ToggleButton, ToggleButtonGroup } 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> <ToggleButtonGroup orientation="horizontal" selection-mode="multiple" separator> <ToggleButton icon-only aria-label="Bold" value="bold"> <Icon icon="gravity-ui:bold" /> </ToggleButton> <ToggleButton icon-only aria-label="Italic" value="italic"> <Icon icon="gravity-ui:italic" /> </ToggleButton> <ToggleButton icon-only aria-label="Underline" value="underline"> <Icon icon="gravity-ui:underline" /> </ToggleButton> </ToggleButtonGroup> </div> <div class="flex flex-col gap-2"> <span class="text-sm text-muted">Vertical</span> <ToggleButtonGroup orientation="vertical" selection-mode="multiple" separator> <ToggleButton icon-only aria-label="Bold" value="bold"> <Icon icon="gravity-ui:bold" /> </ToggleButton> <ToggleButton icon-only aria-label="Italic" value="italic"> <Icon icon="gravity-ui:italic" /> </ToggleButton> <ToggleButton icon-only aria-label="Underline" value="underline"> <Icon icon="gravity-ui:underline" /> </ToggleButton> </ToggleButtonGroup> </div> </div></template>Full Width
<script setup lang="ts">import { Icon } from '@iconify/vue'import { ToggleButton, ToggleButtonGroup } from '@hareui/vue'</script><template> <div class="w-full max-w-md space-y-3"> <ToggleButtonGroup full-width selection-mode="multiple" separator> <ToggleButton icon-only aria-label="Bold" value="bold"> <Icon icon="gravity-ui:bold" /> </ToggleButton> <ToggleButton icon-only aria-label="Italic" value="italic"> <Icon icon="gravity-ui:italic" /> </ToggleButton> <ToggleButton icon-only aria-label="Underline" value="underline"> <Icon icon="gravity-ui:underline" /> </ToggleButton> <ToggleButton icon-only aria-label="Strikethrough" value="strikethrough"> <Icon icon="gravity-ui:strikethrough" /> </ToggleButton> </ToggleButtonGroup> <ToggleButtonGroup full-width selection-mode="single" separator> <ToggleButton value="left"> <Icon icon="gravity-ui:text-align-left" /> Left </ToggleButton> <ToggleButton value="center"> <Icon icon="gravity-ui:text-align-center" /> Center </ToggleButton> <ToggleButton value="right"> <Icon icon="gravity-ui:text-align-right" /> Right </ToggleButton> </ToggleButtonGroup> </div></template>Disabled
<script setup lang="ts">import { Icon } from '@iconify/vue'import { ToggleButton, ToggleButtonGroup } from '@hareui/vue'</script><template> <div class="flex flex-col gap-6"> <div class="flex flex-col gap-2"> <span class="text-sm text-muted">All buttons disabled</span> <ToggleButtonGroup disabled selection-mode="multiple" separator> <ToggleButton icon-only aria-label="Bold" value="bold"> <Icon icon="gravity-ui:bold" /> </ToggleButton> <ToggleButton icon-only aria-label="Italic" value="italic"> <Icon icon="gravity-ui:italic" /> </ToggleButton> <ToggleButton icon-only aria-label="Underline" value="underline"> <Icon icon="gravity-ui:underline" /> </ToggleButton> </ToggleButtonGroup> </div> <div class="flex flex-col gap-2"> <span class="text-sm text-muted">Individual button disabled</span> <ToggleButtonGroup selection-mode="multiple" separator> <ToggleButton icon-only aria-label="Bold" value="bold"> <Icon icon="gravity-ui:bold" /> </ToggleButton> <ToggleButton disabled icon-only aria-label="Italic" value="italic"> <Icon icon="gravity-ui:italic" /> </ToggleButton> <ToggleButton icon-only aria-label="Underline" value="underline"> <Icon icon="gravity-ui:underline" /> </ToggleButton> </ToggleButtonGroup> </div> </div></template>Without Separator
Omit the separator prop to render buttons without dividers.
<script setup lang="ts">import { Icon } from '@iconify/vue'import { ToggleButton, ToggleButtonGroup } from '@hareui/vue'</script><template> <ToggleButtonGroup selection-mode="multiple"> <ToggleButton icon-only aria-label="Bold" value="bold"> <Icon icon="gravity-ui:bold" /> </ToggleButton> <ToggleButton icon-only aria-label="Italic" value="italic"> <Icon icon="gravity-ui:italic" /> </ToggleButton> <ToggleButton icon-only aria-label="Underline" value="underline"> <Icon icon="gravity-ui:underline" /> </ToggleButton> <ToggleButton icon-only aria-label="Strikethrough" value="strikethrough"> <Icon icon="gravity-ui:strikethrough" /> </ToggleButton> </ToggleButtonGroup></template>Detached
Use detached to separate buttons with gaps instead of connecting them.
<script setup lang="ts">import { Icon } from '@iconify/vue'import { ToggleButton, ToggleButtonGroup } from '@hareui/vue'</script><template> <div class="flex flex-col gap-6"> <div class="flex flex-col gap-2"> <span class="text-sm text-muted">Attached (default)</span> <ToggleButtonGroup selection-mode="multiple" separator> <ToggleButton icon-only aria-label="Bold" value="bold"> <Icon icon="gravity-ui:bold" /> </ToggleButton> <ToggleButton icon-only aria-label="Italic" value="italic"> <Icon icon="gravity-ui:italic" /> </ToggleButton> <ToggleButton icon-only aria-label="Underline" value="underline"> <Icon icon="gravity-ui:underline" /> </ToggleButton> <ToggleButton icon-only aria-label="Strikethrough" value="strikethrough"> <Icon icon="gravity-ui:strikethrough" /> </ToggleButton> </ToggleButtonGroup> </div> <div class="flex flex-col gap-2"> <span class="text-sm text-muted">Detached</span> <ToggleButtonGroup detached selection-mode="multiple"> <ToggleButton icon-only aria-label="Bold" value="bold"> <Icon icon="gravity-ui:bold" /> </ToggleButton> <ToggleButton icon-only aria-label="Italic" value="italic"> <Icon icon="gravity-ui:italic" /> </ToggleButton> <ToggleButton icon-only aria-label="Underline" value="underline"> <Icon icon="gravity-ui:underline" /> </ToggleButton> <ToggleButton icon-only aria-label="Strikethrough" value="strikethrough"> <Icon icon="gravity-ui:strikethrough" /> </ToggleButton> </ToggleButtonGroup> </div> </div></template>Selection Mode
Use selection-mode="single" for mutually exclusive choices or selection-mode="multiple" for independent toggles.
<script setup lang="ts">import { Icon } from '@iconify/vue'import { ToggleButton, ToggleButtonGroup } from '@hareui/vue'</script><template> <div class="flex flex-col gap-6"> <div class="flex flex-col gap-2"> <span class="text-sm text-muted">Single selection</span> <ToggleButtonGroup :default-value="['center']" selection-mode="single" separator> <ToggleButton value="left"> <Icon icon="gravity-ui:text-align-left" /> Left </ToggleButton> <ToggleButton value="center"> <Icon icon="gravity-ui:text-align-center" /> Center </ToggleButton> <ToggleButton value="right"> <Icon icon="gravity-ui:text-align-right" /> Right </ToggleButton> </ToggleButtonGroup> </div> <div class="flex flex-col gap-2"> <span class="text-sm text-muted">Multiple selection</span> <ToggleButtonGroup :default-value="['bold', 'underline']" selection-mode="multiple" separator> <ToggleButton icon-only aria-label="Bold" value="bold"> <Icon icon="gravity-ui:bold" /> </ToggleButton> <ToggleButton icon-only aria-label="Italic" value="italic"> <Icon icon="gravity-ui:italic" /> </ToggleButton> <ToggleButton icon-only aria-label="Underline" value="underline"> <Icon icon="gravity-ui:underline" /> </ToggleButton> <ToggleButton icon-only aria-label="Strikethrough" value="strikethrough"> <Icon icon="gravity-ui:strikethrough" /> </ToggleButton> </ToggleButtonGroup> </div> </div></template>Controlled
Selected: bold
<script setup lang="ts">import { Icon } from '@iconify/vue'import { ToggleButton, ToggleButtonGroup } from '@hareui/vue'import { ref } from 'vue'const selected = ref(['bold'])</script><template> <div class="flex flex-col gap-4"> <ToggleButtonGroup v-model="selected" selection-mode="multiple" separator> <ToggleButton icon-only aria-label="Bold" value="bold"> <Icon icon="gravity-ui:bold" /> </ToggleButton> <ToggleButton icon-only aria-label="Italic" value="italic"> <Icon icon="gravity-ui:italic" /> </ToggleButton> <ToggleButton icon-only aria-label="Underline" value="underline"> <Icon icon="gravity-ui:underline" /> </ToggleButton> <ToggleButton icon-only aria-label="Strikethrough" value="strikethrough"> <Icon icon="gravity-ui:strikethrough" /> </ToggleButton> </ToggleButtonGroup> <p class="text-sm text-muted"> Selected: <span class="font-medium">{{ selected.length > 0 ? selected.join(', ') : 'None' }}</span> </p> </div></template>Customization
Tailwind CSS
<script setup lang="ts">import { Icon } from '@iconify/vue'import { ToggleButton, ToggleButtonGroup } from '@hareui/vue'</script><template> <ToggleButtonGroup aria-label="Text formatting" class="gap-1 rounded-xl border border-border/80 bg-surface p-1 shadow-sm" selection-mode="multiple" > <ToggleButton icon-only aria-label="Bold" class="rounded-lg text-muted data-[selected=true]:bg-accent-soft data-[selected=true]:text-accent-soft-foreground" value="bold"> <Icon icon="gravity-ui:bold" /> </ToggleButton> <ToggleButton icon-only aria-label="Italic" class="rounded-lg text-muted data-[selected=true]:bg-accent-soft data-[selected=true]:text-accent-soft-foreground" value="italic"> <Icon icon="gravity-ui:italic" /> </ToggleButton> <ToggleButton icon-only aria-label="Underline" class="rounded-lg text-muted data-[selected=true]:bg-accent-soft data-[selected=true]:text-accent-soft-foreground" value="underline"> <Icon icon="gravity-ui:underline" /> </ToggleButton> </ToggleButtonGroup></template>Global theme override
Override the toggleButtonGroup theme app-wide with createHareUI:
app.use(createHareUI({
ui: {
toggleButtonGroup: {
slots: { base: 'rounded-lg', separator: 'opacity-25' },
variants: { fullWidth: { true: { base: 'w-full' } } },
},
},
}))Styling Reference
Each rendered element carries the same data-slot string as HeroUI, so selectors translate 1:1.
ui key | data-slot | Description |
|---|---|---|
base | toggle-button-group | Group container |
separator | toggle-button-group-separator | Divider inside each ToggleButton |
button | toggle-button | Classes added to each grouped ToggleButton |
API Reference
Props
| Prop | Type | Default | Description |
|---|---|---|---|
as | AsTag | Component | "div" | The element or component to render as. |
modelValue | string[] | - | The values of the selected ToggleButtons (controlled, v-model). |
defaultValue | string[] | [] | The initially selected values when uncontrolled. |
selectionMode | "multiple" | "single" | "single" | Whether one or several buttons can be selected. Single selection makes the buttons radios. |
disallowEmptySelection | boolean | false | Whether the last selected button can't be deselected. |
orientation | "horizontal" | "vertical" | 'horizontal' | Layout direction, also the arrow keys that move focus. Inside a Toolbar it defaults to the toolbar's orientation. |
size | "md" | "sm" | "lg" | - | Size applied to every ToggleButton that doesn't set its own. |
disabled | boolean | false | Disables every ToggleButton in the group; a ToggleButton's own :disabled="false" re-enables it. |
fullWidth | boolean | false | Whether the group stretches to its container and the buttons share the width. |
detached | boolean | false | Separates the buttons with a gap and fully rounds each one instead of joining them. |
separator | boolean | false | Draws a divider at the start of every button except the first (HeroUI's ToggleButtonGroup.Separator).
Hidden when detached. |
ui | ComponentSlots<{ slots: { base: string; button: string; separator: string; }; variants: { orientation: { horizontal: { base: string; button: string; separator: string; }; vertical: { base: string; button: string; separator: string; }; }; fullWidth: { true: { base: string; button: string; }; false: { base: string; }; }; detached: { true: { base: string; button: string; separator: string; }; false: { base: string; }; }; }; defaultVariants: { orientation: string; fullWidth: boolean; detached: boolean; }; }> | - | Per-slot class overrides. button is added to each ToggleButton, separator to each divider. |
Slots
| Slot | Props | Description |
|---|---|---|
default | { selected: string[]; } | The ToggleButtons, each with a value. |
Emits
| Event | Payload | Description |
|---|---|---|
update:modelValue | [value: string[]] | - |
Note
- ToggleButtonGroup passes
sizeto all child ToggleButton components - Each ToggleButton needs a unique
valuethat corresponds to the values inv-model/default-value - The
disabledprop disables all child ToggleButtons. A button can't opt back in with:disabled="false": as in React Aria, the group'sdisabledwins - The component automatically handles border radius between buttons
- Use
disallow-empty-selectionwithselection-mode="single"to ensure one option is always selected



