Actions
ButtonGroup
Related buttons joined into one control, such as Previous, a page number and Next, or a split button. Only the outer corners are round, borders are shared, and buttons without a border get a separator. Each child keeps its own variant, tone and size, and the group has a name for screen readers.
Install
Once per app, with Tailwind CSS v4 and an @/* path alias (Ouro HX is built on Base UI, so --base base; skip -t in an existing app):
npx shadcn@latest init --base base --preset nova --yes --no-monoreponpx shadcn@latest add ourostack/ouro-ui/ouro-base#v1.2.13Then the component. From v0.8.0 it installs only the helpers it imports and never touches your stylesheet (Installing):
npx shadcn@latest add ourostack/ouro-ui/button-group#v1.2.13Or, with the @ouro namespace set up:
npx shadcn@latest add @ouro/ouro-base @ouro/button-groupIts tests, stories and Playwright spec are optional. They need Vitest 4.1.11 or later, and from v0.8.0 never change a version your app has (Testing):
npx shadcn@latest add ourostack/ouro-ui/button-group#v1.2.13 ourostack/ouro-ui/button-group-tests#v1.2.13import { ButtonGroup } from "@/components/ouro/button-group/button-group";Paths use shadcn's default @/ alias; the CLI rewrites them to yours.
Example
<ButtonGroup label="Pagination">
<Button variant="outline" tone="secondary">Previous</Button>
<Button variant="outline" tone="secondary">Next</Button>
</ButtonGroup>Use instead when
- Use
ToggleGroupinstead when the buttons stay pressed and share one value, and a plain row (<div className="flex gap-2">) for buttons that are not one control.
Props
Type: ButtonGroupProps.
children(required)- Type:
ReactNode - The buttons, in order:
Buttons orToggles, each with its ownvariant,toneandsize. className- Type:
string - Layout only: margins, width, grid placement (
mt-4). Appearance classes warn in development. fullWidth- Type:
boolean - Stretches the group to the width of its container, with the buttons sharing it equally.
label(required)- Type:
string - Names the group for screen readers ("Pagination", "Text alignment"). It is not shown.
ref- Type:
Ref<HTMLDivElement> - The group element (
role="group").
Don't use
These are not props. Passing one fails the type check with the fix in the error, and in development a console message names the fix.
isAttached: Not a prop. A ButtonGroup always joins its buttons.orientation: Not a prop. A ButtonGroup is a row; stack buttons in a grid:<div className="grid gap-2">.size: Not a prop. Each Button keeps its own:<Button size="sm">.tone: Not a prop. Each Button keeps its own:<Button tone="secondary">.variant: Not a prop. Each Button keeps its own:<Button variant="outline">.
className is for layout only
Pass margins, width and grid placement (mt-4, w-full, col-span-2). Your classes always win over Ouro UI's: ButtonGroup merges className last with cn (tailwind-merge), so a class that conflicts with its own replaces it.layout-classes
Appearance comes from props, not classes. In development, a class that sets appearance logs one warning per component instance, naming the prop to use instead. A class sets appearance when, after its variants (hover:, sm:) and any ! are removed, it starts with bg-, text-, border-, rounded-, shadow-, font-, ring-, from-, via-, to-, fill- or stroke-, or is border, rounded, shadow or ring, or has a dark: variant. Text wrapping, overflow and table layout classes (text-wrap, text-ellipsis, border-collapse) are layout and do not warn.
Alignment classes (text-center) warn too: set alignment on a parent element.
Claim sources
- layout-classes: test:registry/ouro/components/ouro/button-group/button-group.test.tsx::ButtonGroup > forwards its ref and lets layout classes win
verify:testExists(registry/ouro/components/ouro/button-group/button-group.test.tsx::ButtonGroup > forwards its ref and lets layout classes win)