ButtonGroup
A connected row of Button elements sharing one container, size, and shape — usable as an "action" toolbar or a "tab" style segmented switch.
Overview
ButtonGroup renders a set of Buttons inside a single bordered container, applying one shared size and rounded shape to every button in the row. Pass the buttons declaratively via the buttons array — each entry accepts the same props as Button (minus size, which the group controls).
Two behaviour modes are supported:
action(default) — an independent group of buttons, each firing its ownonClick. Think toolbar or split-action controls.tab— a segmented switch. The button atactiveIndexgets adata-active="true"attribute (styled with a highlighted background); the rest render normally.
Import
import { ButtonGroup } from '@unflow.io/ui/components/ButtonGroup';Props
| Prop | Type | Default | Description |
|---|---|---|---|
buttons | Omit<IButton, 'size'>[] | — | Required. Buttons to render, in order. Each entry accepts any Button prop except size |
behaviour | 'action' | 'tab' | 'action' | Whether buttons act independently or as a segmented switch |
activeIndex | number | -1 | Index of the active button when behaviour="tab". Sets data-active="true" on that button |
size | 'xs' | 'sm' | 'md' | 'lg' | 'sm' | Size applied to the container and every button in the group |
rounded | boolean | — | Renders the container and buttons as fully rounded (pill shape) instead of the default corner radius |
All standard HTMLDivElement props are forwarded to the container. Passing children overrides the buttons-driven rendering entirely.
Action group
<ButtonGroup
buttons={[
{ label: 'Cut', onClick: () => alert('Cut') },
{ label: 'Copy', onClick: () => alert('Copy') },
{ label: 'Paste', onClick: () => alert('Paste') },
]}
/>Tab group
Set behaviour="tab" and drive activeIndex from state — each button's own onClick is responsible for updating it.
const [activeIndex, setActiveIndex] = useState(0);
<ButtonGroup
behaviour="tab"
activeIndex={activeIndex}
buttons={[
{ label: 'Day', onClick: () => setActiveIndex(0) },
{ label: 'Week', onClick: () => setActiveIndex(1) },
{ label: 'Month', onClick: () => setActiveIndex(2) },
]}
/>Sizes
<ButtonGroup size="xs" buttons={[{ label: 'XS 1' }, { label: 'XS 2' }]} />
<ButtonGroup size="sm" buttons={[{ label: 'SM 1' }, { label: 'SM 2' }]} />
<ButtonGroup size="md" buttons={[{ label: 'MD 1' }, { label: 'MD 2' }]} />
<ButtonGroup size="lg" buttons={[{ label: 'LG 1' }, { label: 'LG 2' }]} />Custom styling
Buttons keep their own variant, icons, and className — mix them freely, and combine with rounded for a pill-shaped group.
<ButtonGroup
rounded
buttons={[
{ startIcon: <ArrowLeft />, label: 'Previous', variant: 'secondary' },
{ startIcon: <Trash />, variant: 'destructiveSecondary' },
{ label: 'Next', variant: 'primary', endIcon: <ArrowRight /> },
]}
/>Accessibility
- Each button renders as a native
<button>via Button, so click and keyboard activation work out of the box. behaviour="tab"only applies a visualdata-activestate — it does not addrole="tablist"/role="tab"oraria-selected. If you're building an actual tabbed interface, add that ARIA wiring yourself (or reach for a dedicated tabs pattern).- Disabled buttons in the
buttonsarray behave like any disabledButton—disabledandaria-disabledare set, and they're skipped by pointer/keyboard interaction.
SplitButton
A compound action button combining a primary action (left) with a dropdown of secondary actions (right chevron). Useful when one action is clearly primary but alternatives must remain accessible.
Switch
A binary toggle input rendered as a native checkbox styled to look like a sliding switch.