Skip to content

Button Group

A versatile button group component that allows you to create sets of connected buttons with consistent styling. Perfect for toolbars, toggle switches, and action groups.

Component Code

import React, { useState } from "react";
const ButtonGroup = ({
buttons = [],
variant = "default",
size = "md",
orientation = "horizontal",
selectable = false,
multiple = false,
selectedValues = [],
onSelectionChange,
className = "",
}) => {
const [internalSelected, setInternalSelected] = useState(
new Set(
Array.isArray(selectedValues)
? selectedValues
: [selectedValues].filter(Boolean)
)
);
const handleButtonClick = (button, index) => {
if (selectable) {
const newSelected = new Set(internalSelected);
if (multiple) {
if (newSelected.has(button.value || index)) {
newSelected.delete(button.value || index);
} else {
newSelected.add(button.value || index);
}
} else {
if (newSelected.has(button.value || index)) {
newSelected.clear();
} else {
newSelected.clear();
newSelected.add(button.value || index);
}
}
setInternalSelected(newSelected);
if (onSelectionChange) {
onSelectionChange(Array.from(newSelected));
}
}
if (button.onClick) {
button.onClick(button, index);
}
};
const getVariantStyles = (isSelected = false) => {
const baseStyles =
"border focus:outline-none focus:ring-2 focus:ring-blue-500 focus:z-10 transition-all duration-200";
if (selectable && isSelected) {
switch (variant) {
case "primary":
return `${baseStyles} bg-blue-600 border-blue-600 text-white hover:bg-blue-700`;
case "secondary":
return `${baseStyles} bg-gray-600 border-gray-600 text-white hover:bg-gray-700`;
case "outline":
return `${baseStyles} bg-blue-50 border-blue-500 text-blue-700 hover:bg-blue-100`;
default:
return `${baseStyles} bg-gray-100 border-gray-400 text-gray-900 hover:bg-gray-200`;
}
}
switch (variant) {
case "primary":
return `${baseStyles} bg-blue-500 border-blue-500 text-white hover:bg-blue-600`;
case "secondary":
return `${baseStyles} bg-gray-500 border-gray-500 text-white hover:bg-gray-600`;
case "outline":
return `${baseStyles} bg-white border-gray-300 text-gray-700 hover:bg-gray-50`;
default:
return `${baseStyles} bg-white border-gray-300 text-gray-700 hover:bg-gray-50`;
}
};
const getSizeStyles = () => {
switch (size) {
case "sm":
return "px-3 py-1.5 text-sm";
case "lg":
return "px-6 py-3 text-base";
case "xl":
return "px-8 py-4 text-lg";
default:
return "px-4 py-2 text-sm";
}
};
const getPositionStyles = (index, total) => {
if (orientation === "vertical") {
if (index === 0) return "rounded-t-lg rounded-b-none border-b-0";
if (index === total - 1) return "rounded-b-lg rounded-t-none";
return "rounded-none border-b-0";
} else {
if (index === 0) return "rounded-l-lg rounded-r-none border-r-0";
if (index === total - 1) return "rounded-r-lg rounded-l-none";
return "rounded-none border-r-0";
}
};
const containerClasses =
orientation === "vertical"
? "inline-flex flex-col"
: "inline-flex flex-row";
return (
<div className={`${containerClasses} ${className}`} role="group">
{buttons.map((button, index) => {
const isSelected =
selectable && internalSelected.has(button.value || index);
const isDisabled = button.disabled;
return (
<button
key={button.value || index}
onClick={() => !isDisabled && handleButtonClick(button, index)}
disabled={isDisabled}
className={`
${getVariantStyles(isSelected)}
${getSizeStyles()}
${getPositionStyles(index, buttons.length)}
${isDisabled ? "opacity-50 cursor-not-allowed" : "cursor-pointer"}
relative
`}
aria-pressed={selectable ? isSelected : undefined}
>
{button.icon && <span className="mr-2">{button.icon}</span>}
{button.label}
</button>
);
})}
</div>
);
};
export default ButtonGroup;

Usage Example

import React, { useState } from "react";
import ButtonGroup from "./ButtonGroup";
import {
BoldIcon,
ItalicIcon,
UnderlineIcon,
AlignLeftIcon,
AlignCenterIcon,
AlignRightIcon,
} from "@heroicons/react/24/outline";
const App = () => {
const [alignment, setAlignment] = useState(["left"]);
const [formatting, setFormatting] = useState([]);
const alignmentButtons = [
{
label: "Left",
value: "left",
icon: <AlignLeftIcon className="w-4 h-4" />,
},
{
label: "Center",
value: "center",
icon: <AlignCenterIcon className="w-4 h-4" />,
},
{
label: "Right",
value: "right",
icon: <AlignRightIcon className="w-4 h-4" />,
},
];
const formatButtons = [
{ label: "Bold", value: "bold", icon: <BoldIcon className="w-4 h-4" /> },
{
label: "Italic",
value: "italic",
icon: <ItalicIcon className="w-4 h-4" />,
},
{
label: "Underline",
value: "underline",
icon: <UnderlineIcon className="w-4 h-4" />,
},
];
const actionButtons = [
{
label: "Save",
onClick: () => alert("Saved!"),
value: "save",
},
{
label: "Cancel",
onClick: () => alert("Cancelled!"),
value: "cancel",
},
{
label: "Delete",
onClick: () => alert("Deleted!"),
value: "delete",
disabled: true,
},
];
return (
<div className="max-w-4xl mx-auto p-6 space-y-8">
<h1 className="text-3xl font-bold text-gray-900">
Button Group Examples
</h1>
<div className="space-y-6">
<div>
<h3 className="text-lg font-semibold mb-3">
Text Alignment (Single Select)
</h3>
<ButtonGroup
buttons={alignmentButtons}
variant="outline"
selectable={true}
multiple={false}
selectedValues={alignment}
onSelectionChange={setAlignment}
/>
</div>
<div>
<h3 className="text-lg font-semibold mb-3">
Text Formatting (Multi Select)
</h3>
<ButtonGroup
buttons={formatButtons}
variant="default"
selectable={true}
multiple={true}
selectedValues={formatting}
onSelectionChange={setFormatting}
/>
</div>
<div>
<h3 className="text-lg font-semibold mb-3">Action Buttons</h3>
<ButtonGroup buttons={actionButtons} variant="primary" size="lg" />
</div>
<div>
<h3 className="text-lg font-semibold mb-3">Vertical Layout</h3>
<ButtonGroup
buttons={[
{ label: "Option 1", value: "1" },
{ label: "Option 2", value: "2" },
{ label: "Option 3", value: "3" },
]}
orientation="vertical"
variant="outline"
selectable={true}
/>
</div>
</div>
</div>
);
};
export default App;

Props

PropTypeDefaultDescription
buttonsArray<ButtonObject>[]Array of button configurations
variant'default' | 'primary' | 'secondary' | 'outline''default'Visual style variant
size'sm' | 'md' | 'lg' | 'xl''md'Button size
orientation'horizontal' | 'vertical''horizontal'Layout direction
selectablebooleanfalseWhether buttons can be selected/toggled
multiplebooleanfalseAllow multiple selections (only if selectable)
selectedValuesArray[]Initially selected button values
onSelectionChangefunctionundefinedCallback when selection changes
classNamestring''Additional CSS classes

Button Object Properties

PropertyTypeDescription
labelstringButton text content
valuestring | numberUnique identifier for the button
iconReactNodeOptional icon element
onClickfunctionClick handler for the button
disabledbooleanWhether the button is disabled

Features

  • Multiple Variants: Default, primary, secondary, and outline styles
  • Flexible Sizing: Small, medium, large, and extra-large options
  • Orientation Support: Horizontal and vertical layouts
  • Selection Modes: Single or multiple selection capability
  • Icon Support: Add icons to any button
  • Disabled State: Individual button disable functionality
  • Accessibility: Proper ARIA attributes and keyboard navigation

Common Use Cases

Toolbar Actions

const toolbarButtons = [
{ label: "New", value: "new", onClick: () => createNew() },
{ label: "Save", value: "save", onClick: () => save() },
{ label: "Export", value: "export", onClick: () => exportData() },
];
<ButtonGroup buttons={toolbarButtons} variant="outline" />;

Toggle Options

const viewOptions = [
{ label: "List", value: "list" },
{ label: "Grid", value: "grid" },
{ label: "Card", value: "card" },
];
<ButtonGroup
buttons={viewOptions}
selectable={true}
selectedValues={["list"]}
onSelectionChange={(selected) => setViewMode(selected[0])}
/>;

Filter Controls

const filterButtons = [
{ label: "All", value: "all" },
{ label: "Active", value: "active" },
{ label: "Completed", value: "completed" },
{ label: "Archived", value: "archived" },
];
<ButtonGroup
buttons={filterButtons}
variant="outline"
selectable={true}
multiple={true}
onSelectionChange={setActiveFilters}
/>;

Styling Variants

  • Default: Light gray background with subtle borders
  • Primary: Blue theme for primary actions
  • Secondary: Gray theme for secondary actions
  • Outline: Border-only style with transparent background

Best Practices

  1. Keep labels concise - Use short, clear button labels
  2. Group related actions - Only group logically related buttons
  3. Limit group size - Avoid too many buttons in one group (3-5 is ideal)
  4. Use consistent icons - If using icons, apply them consistently
  5. Consider mobile - Test on smaller screens to ensure usability
  6. Provide feedback - Use selection states to show active options