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
| Situation | Use | Why |
|---|---|---|
| Two or more actions that resolve one decision (Cancel / Save) | ButtonGroup | one wrapper owns the gap, so every footer in the app spaces alike |
| Pick one of a few options, selection persists and is visible | SegmentedControl | it is an input with a v-model, not a set of actions |
| Buttons that happen to sit near each other but mean different things | plain flex row | grouping 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: unlikeButton, this wrapper skipstailwind-merge, so your utility lands next to the built-ingap-2and the stylesheet decides. Needing a different gap usually means it isn't a button group. - Adding
justify-endto 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
ModalFooterthat keeps its owngapdouble-spaces the desktop row. Real footers zero the outer one —class="flex-col md:flex-row gap-2 md:gap-0". - Inside
ConfirmModalyou never write aButtonGroupat all — it takes button descriptors as a prop and renders the groups itself, so markup you pass in is ignored.