Button Group React component for Tailwind CSS v4
v1.0.0Added in 1.0.0
A React button group: buttons joined into one control or spaced in a row, plus a split button that pairs a main action with a menu.
Each button presses on its own spring inside the shared shape, and a focused one rises above its neighbours so the ring is never cut off. Tab moves from button to button. The chevron half of a split button opens its menu with a click, Enter, Space or ArrowDown, and turns while open; on a phone the menu arrives as an action sheet. Size, variant, tone, radius and color set on the group flow into every button that did not set its own.
Button Group is a Pro React component in the React button components category, written in TypeScript for React 19 and Tailwind CSS v4. It installs as source with npx wingo-ui@latest add button-group, together with Button, Dropdown Menu, Tones and UIProvider and the npm package lucide-react.
Add to a project
Requirements: React 19 and Tailwind CSS v4, in Next.js (App Router or Pages), Vite or React Router. Installation guide · Next.js setup · Vite setup
npx wingo-ui@latest add button-groupButton Group is part of Pro, with 298 more Pro items: $8 a month or $150 once. 14-day refund before you download any Pro item. See plans
Usage
Import ButtonGroup from @/components/ui/button-group.
import { Eye, Share2 } from "lucide-react"
import { Button } from "@/components/ui/button"
import { ButtonGroup, SplitButton } from "@/components/ui/button-group"
export function PublishBar({ onPublish }: { onPublish: () => void }) {
return (
<div className="flex flex-wrap items-center gap-2">
<ButtonGroup aria-label="Preview and share">
<Button leftIcon={<Eye />}>Preview</Button>
<Button leftIcon={<Share2 />}>Share</Button>
</ButtonGroup>
<SplitButton
tone="brand"
onClick={onPublish}
menuLabel="More publish options"
menuProps={{ title: "Publish listing" }}
items={[
{ label: "Save as draft" },
{ label: "Schedule publishing…" },
{ label: "Publish and duplicate" },
]}
>
Publish listing
</SplitButton>
</div>
)
}Props
childrenReactNodeButtons, SplitButtons, ButtonGroupText cells and ButtonGroupSeparators. Other elements (a DropdownMenu with a Button trigger) still attach, but do not inherit the group's props: set them on the trigger.
orientationdefault"horizontal""horizontal" | "vertical"A row, or a stack of full-width buttons (stacked actions on a phone).
attacheddefaulttruebooleanJoined edges: inner corners squared, borders collapsed, dividers between solid, soft and ghost buttons, one shadow for the whole group. Off, the buttons sit in a spaced row (gap-2).
sizedefault"md""sm" | "md" | "lg"Passed to every child that did not set one. Icon buttons stay square and follow along (sm becomes icon-sm, lg icon-lg). md grows to 44px on touch.
variantdefault"outline""solid" | "soft" | "outline" | "ghost"Passed down. outline is the classic joined group; solid gets 1px dividers in the text color.
tonedefault"neutral""neutral" | "brand" | "success" | "warning" | "danger" | "info"Passed down to every button.
radiusdefault"md""none" | "sm" | "md" | "lg" | "full"Passed down; it is the outer shape of the whole group (full makes a pill).
colordefault""stringAny CSS color, passed down in place of the tone. Leave empty to use the tone.
disableddefaultfalsebooleanDisables every child that did not set disabled itself.
fullWidthdefaultfalsebooleanThe group fills its container and the buttons share the width. Made for phone footers.
aria-labeldefault""stringThe group's accessible name (role="group"). The playground falls back to a sample name when empty.
classNamestringExtra classes for the group, merged last. Other props go to the root div.
Parts
SplitButton
componentA main action joined to a chevron that opens a dropdown menu. Takes the Button props for its main half (onClick, leftIcon, loading, loadingText, disabled) plus items: MenuEntry[], menuLabel (default "More options"), menuIcon (default ChevronDown), align ("end") and side ("bottom"), menuProps for the rest of the menu (title of the phone sheet, responsive, shortcut) and fullWidth. The phone sheet is named after a string label unless menuProps sets one. Inside a full-width or vertical group it stretches its main half. variant / tone / size / radius / color default to solid / neutral / md / md and style both halves; loading disables the menu too. Props: SplitButtonProps.
ButtonGroupText
componentA non-interactive cell with a button's height, padding and type, for units and counters ("USD", "3 / 12"). Inherits from the group like a button. Props: ComponentProps<"div"> & { size, variant, tone, radius, color, disabled }.
ButtonGroupSeparator
componentAn explicit extra break (a 1px line). Pass the group's orientation. Not needed between buttons: the attached group draws its own dividers. Props: ComponentProps<"div"> & { orientation?: "horizontal" | "vertical" }.
Examples
Attached and spaced
Joined into one shape or a spaced row, horizontal or stacked.
Pagination
Icon buttons around a ButtonGroupText counter.
Quantity and units
A pill stepper and a price with its currency cell.
Solid with dividers
One shadow for the group and a 1px divider in the text color between buttons.
Split buttons
Three sizes, an outline one, and loading, which blocks the menu too. On a phone the menu is a sheet.
Phone footer
fullWidth shares the width between the buttons; a vertical group stacks actions, split buttons included.
Custom color
Any CSS color flows into every button of the group.
Dependencies
Files
- components/ui/button-group.tsx
FAQ
Is the React Button Group component free?
The preview, props and docs are free; copying the source needs a Pro plan. See plans.
How do I install Button Group?
Run npx wingo-ui@latest add button-group. It also adds the Wingo UI items it builds on (Button, Dropdown Menu, Tones and UIProvider) and the npm package lucide-react. With shadcn, add the @wingo-ui registry with your API key, then run npx shadcn@latest add @wingo-ui/button-group.
How does Button Group behave on phones?
The chevron half of a split button opens its menu with a click, Enter, Space or ArrowDown, and turns while open; on a phone the menu arrives as an action sheet.