Skip to content

ButtonGroup ​

When two or more buttons belong to the same decision — Cancel and Save at the bottom of a modal, a small cluster of actions in a toolbar — wrap them in a ButtonGroup and it puts them in a row with the house gap between them. That is the whole job: no border, no shared background, and it does not fuse the buttons into one attached control.

ts
import { ButtonGroup } from '@scaler-tech/aurora/button-group'

Modal footer

Toolbar cluster

Use when ​

SituationUseWhy
Two or more actions that resolve one decision (Cancel / Save)ButtonGroupone wrapper owns the gap, so every footer in the app spaces alike
Pick one of a few options, selection persists and is visibleSegmentedControlit is an input with a v-model, not a set of actions
Buttons that happen to sit near each other but mean different thingsplain flex rowgrouping them implies a relationship that isn't there

API ​

No props and no emits. The buttons go in the default slot, and class falls through to the wrapper div.

Example ​

The full modal-footer shape, straight from CloseMeterModal. On desktop it is a plain row; on narrow screens both buttons go full-width and stack, and the order utilities float the primary to the top while leaving the secondary first in the DOM:

vue
<ModalFooter class="flex-col md:flex-row gap-2 md:gap-0">
    <div class="hidden md:block" />
    <ButtonGroup class="w-full md:w-auto flex-col md:flex-row">
        <Button variant="primary" size="md" class="w-full md:w-auto md:order-2" :loading="saving" @click="save">
            Close meter
        </Button>
        <Button variant="tertiary" size="md" class="w-full md:w-auto md:order-1" @click="close">
            Cancel
        </Button>
    </ButtonGroup>
</ModalFooter>

Incorrect / Correct ​

Incorrect (hand-rolled spacing — the gap drifts from every other footer in the app):

vue
<div class="flex items-center gap-3">
    <Button variant="tertiary" size="md" @click="close">Cancel</Button>
    <Button variant="primary" size="md" @click="save">Save</Button>
</div>

Correct (one wrapper, one gap, matches every other footer):

vue
<ButtonGroup>
    <Button variant="tertiary" size="md" @click="close">Cancel</Button>
    <Button variant="primary" size="md" @click="save">Save</Button>
</ButtonGroup>

Gotchas ​

  • Passing your own gap (class="gap-6") may silently do nothing: unlike Button, this wrapper skips tailwind-merge, so your utility lands next to the built-in gap-2 and the stylesheet decides. Needing a different gap usually means it isn't a button group.
  • Adding justify-end to the group doesn't move it — the group hugs its content, so alignment belongs on the parent (<ModalFooter class="justify-end">, or a spacer <div /> first).
  • Stacking inside a ModalFooter that keeps its own gap double-spaces the desktop row. Real footers zero the outer one — class="flex-col md:flex-row gap-2 md:gap-0".
  • Inside ConfirmModal you never write a ButtonGroup at all — it takes button descriptors as a prop and renders the groups itself, so markup you pass in is ignored.