Button Group
Button Group represents a set of related actions.
"use client";
import * as React from "react";
import {
LuArchive,
LuArrowLeft,
LuCalendarPlus,
LuClock,
LuEllipsis,
LuFilter,
LuMailCheck,
LuTag,
LuTrash2,
} from "react-icons/lu";
import { Button } from "@/components/ui/button";
import { ButtonGroup } from "@/components/ui/button-group";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuSeparator,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
export default function ButtonGroupDemo() {
const [label, setLabel] = React.useState("personal");
return (
<ButtonGroup>
<ButtonGroup css={{ display: "none", sm: { display: "flex" } }}>
<Button variant="outline" size="icon" aria-label="Go Back">
<LuArrowLeft />
</Button>
</ButtonGroup>
<ButtonGroup>
<Button variant="outline">
<LuArchive /> Archive
</Button>
<Button variant="outline">
<LuEllipsis /> Report
</Button>
</ButtonGroup>
<ButtonGroup>
<Button variant="outline">
<LuClock /> Snooze
</Button>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="icon" aria-label="More Options">
<LuEllipsis />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" css={{ w: "52" }}>
<DropdownMenuGroup>
<DropdownMenuItem>
<LuMailCheck />
Mark as Read
</DropdownMenuItem>
<DropdownMenuItem>
<LuArchive />
Archive
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem>
<LuClock />
Snooze
</DropdownMenuItem>
<DropdownMenuItem>
<LuCalendarPlus />
Add to Calendar
</DropdownMenuItem>
<DropdownMenuItem>
<LuFilter />
Add to List
</DropdownMenuItem>
<DropdownMenuSub>
<DropdownMenuSubTrigger>
<LuTag />
Label As...
</DropdownMenuSubTrigger>
<DropdownMenuSubContent>
<DropdownMenuRadioGroup value={label} onValueChange={setLabel}>
<DropdownMenuRadioItem value="personal">Personal</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="work">Work</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="other">Other</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>
</DropdownMenuSubContent>
</DropdownMenuSub>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem variant="danger">
<LuTrash2 />
Trash
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
</ButtonGroup>
</ButtonGroup>
);
}
Installation
npx nore-ui-cli@latest add button-groupUsage
import { ButtonGroup, ButtonGroupSeparator, ButtonGroupText } from "@/components/ui/button-group";
<ButtonGroup>
<Button>Button 1</Button>
<Button>Button 2</Button>
</ButtonGroup>
Accessibility
- The
ButtonGroupcomponent has theroleattribute set togroup. - Use Tab to navigate between the buttons in the group.
- Use
aria-labeloraria-labelledbyto label the button group.
<ButtonGroup aria-label="Button group">
<Button>Button 1</Button>
<Button>Button 2</Button>
</ButtonGroup>
ButtonGroup vs ToggleGroup
- Use the
ButtonGroupcomponent when you want to group buttons that perform an action. - Use the
ToggleGroupcomponent when you want to group buttons that toggle a state.
Examples
Orientation
Set the orientation prop to change the button group layout.
import { LuMinus, LuPlus } from "react-icons/lu";
import { Button } from "@/components/ui/button";
import { ButtonGroup } from "@/components/ui/button-group";
export default function ButtonGroupOrientation() {
return (
<ButtonGroup orientation="vertical" aria-label="Media controls" css={{ h: "fit" }}>
<Button variant="outline" size="icon">
<LuPlus />
</Button>
<Button variant="outline" size="icon">
<LuMinus />
</Button>
</ButtonGroup>
);
}
Size
Control the size of buttons using the size prop on individual buttons.
import { LuPlus } from "react-icons/lu";
import { styled } from "styled-system/jsx";
import { Button } from "@/components/ui/button";
import { ButtonGroup } from "@/components/ui/button-group";
export default function ButtonGroupSize() {
return (
<styled.div css={{ display: "flex", flexDir: "column", alignItems: "flex-start", gap: "8" }}>
<ButtonGroup>
<Button variant="outline" size="sm">
Small
</Button>
<Button variant="outline" size="sm">
Button
</Button>
<Button variant="outline" size="sm">
Group
</Button>
<Button variant="outline" size="icon-sm">
<LuPlus />
</Button>
</ButtonGroup>
<ButtonGroup>
<Button variant="outline">Default</Button>
<Button variant="outline">Button</Button>
<Button variant="outline">Group</Button>
<Button variant="outline" size="icon">
<LuPlus />
</Button>
</ButtonGroup>
<ButtonGroup>
<Button variant="outline" size="lg">
Large
</Button>
<Button variant="outline" size="lg">
Button
</Button>
<Button variant="outline" size="lg">
Group
</Button>
<Button variant="outline" size="icon-lg">
<LuPlus />
</Button>
</ButtonGroup>
</styled.div>
);
}
Nested
Nest <ButtonGroup> components to create button groups with spacing.
"use client";
import { LuArrowLeft, LuArrowRight } from "react-icons/lu";
import { Button } from "@/components/ui/button";
import { ButtonGroup } from "@/components/ui/button-group";
export default function ButtonGroupNested() {
return (
<ButtonGroup>
<ButtonGroup>
<Button variant="outline" size="sm">
1
</Button>
<Button variant="outline" size="sm">
2
</Button>
<Button variant="outline" size="sm">
3
</Button>
<Button variant="outline" size="sm">
4
</Button>
<Button variant="outline" size="sm">
5
</Button>
</ButtonGroup>
<ButtonGroup>
<Button variant="outline" size="icon-sm" aria-label="Previous">
<LuArrowLeft />
</Button>
<Button variant="outline" size="icon-sm" aria-label="Next">
<LuArrowRight />
</Button>
</ButtonGroup>
</ButtonGroup>
);
}
Separator
The ButtonGroupSeparator component visually divides buttons within a group.
Buttons with variant outline do not need a separator since they have a border. For other variants, a separator is recommended to improve the visual hierarchy.
import { Button } from "@/components/ui/button";
import { ButtonGroup, ButtonGroupSeparator } from "@/components/ui/button-group";
export default function ButtonGroupSeparatorDemo() {
return (
<ButtonGroup>
<Button variant="secondary" size="sm">
Copy
</Button>
<ButtonGroupSeparator />
<Button variant="secondary" size="sm">
Paste
</Button>
</ButtonGroup>
);
}
Split
Create a split button group by adding two buttons separated by a ButtonGroupSeparator.
import { TbPlus } from "react-icons/tb";
import { Button } from "@/components/ui/button";
import { ButtonGroup, ButtonGroupSeparator } from "@/components/ui/button-group";
export default function ButtonGroupSplit() {
return (
<ButtonGroup>
<Button variant="secondary">Button</Button>
<ButtonGroupSeparator />
<Button size="icon" variant="secondary">
<TbPlus />
</Button>
</ButtonGroup>
);
}
Input
Wrap an Input component with buttons.
import { LuSearch } from "react-icons/lu";
import { Button } from "@/components/ui/button";
import { ButtonGroup } from "@/components/ui/button-group";
import { Input } from "@/components/ui/input";
export default function ButtonGroupInput() {
return (
<ButtonGroup>
<Input placeholder="Search..." />
<Button variant="outline" aria-label="Search">
<LuSearch />
</Button>
</ButtonGroup>
);
}
Input Group
Wrap an InputGroup component to create complex input layouts.
"use client";
import * as React from "react";
import { LuAudioLines, LuPlus } from "react-icons/lu";
import { Button } from "@/components/ui/button";
import { ButtonGroup } from "@/components/ui/button-group";
import {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupInput,
} from "@/components/ui/input-group";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
export default function ButtonGroupInputGroup() {
const [voiceEnabled, setVoiceEnabled] = React.useState(false);
return (
<ButtonGroup>
<ButtonGroup>
<Button variant="outline" size="icon" css={{ rounded: "full" }}>
<LuPlus />
</Button>
</ButtonGroup>
<ButtonGroup>
<InputGroup css={{ rounded: "full" }}>
<InputGroupInput
placeholder={voiceEnabled ? "Record and send audio..." : "Send a message..."}
disabled={voiceEnabled}
/>
<InputGroupAddon align="inline-end">
<Tooltip>
<TooltipTrigger asChild>
<InputGroupButton
onClick={() => setVoiceEnabled(!voiceEnabled)}
size="icon-xs"
data-active={voiceEnabled}
css={{
rounded: "full",
"&[data-active=true]": {
bg: "orange.100",
color: "orange.700",
_dark: {
bg: "orange.800",
color: "orange.100",
},
},
}}
aria-pressed={voiceEnabled}
>
<LuAudioLines />
</InputGroupButton>
</TooltipTrigger>
<TooltipContent>Voice Mode</TooltipContent>
</Tooltip>
</InputGroupAddon>
</InputGroup>
</ButtonGroup>
</ButtonGroup>
);
}
Dropdown Menu
Create a split button group with a DropdownMenu component.
"use client";
import {
LuCheck,
LuChevronDown,
LuCopy,
LuShare,
LuTrash,
LuTriangleAlert,
LuUserRoundX,
LuVolumeOff,
} from "react-icons/lu";
import { Button } from "@/components/ui/button";
import { ButtonGroup } from "@/components/ui/button-group";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
export default function ButtonGroupDropdown() {
return (
<ButtonGroup>
<Button variant="outline">Follow</Button>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" css={{ pl: "2!" }}>
<LuChevronDown />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuGroup>
<DropdownMenuItem>
<LuVolumeOff />
Mute Conversation
</DropdownMenuItem>
<DropdownMenuItem>
<LuCheck />
Mark as Read
</DropdownMenuItem>
<DropdownMenuItem>
<LuTriangleAlert />
Report Conversation
</DropdownMenuItem>
<DropdownMenuItem>
<LuUserRoundX />
Block User
</DropdownMenuItem>
<DropdownMenuItem>
<LuShare />
Share Conversation
</DropdownMenuItem>
<DropdownMenuItem>
<LuCopy />
Copy Conversation
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem variant="danger">
<LuTrash />
Delete Conversation
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
</ButtonGroup>
);
}
Select
Pair with a Select component.
"use client";
import * as React from "react";
import { LuArrowRight } from "react-icons/lu";
import { styled } from "styled-system/jsx";
import { Button } from "@/components/ui/button";
import { ButtonGroup } from "@/components/ui/button-group";
import { Input } from "@/components/ui/input";
import { Select, SelectContent, SelectItem, SelectTrigger } from "@/components/ui/select";
const CURRENCIES = [
{
value: "$",
label: "US Dollar",
},
{
value: "€",
label: "Euro",
},
{
value: "£",
label: "British Pound",
},
];
export default function ButtonGroupSelect() {
const [currency, setCurrency] = React.useState("$");
return (
<ButtonGroup>
<ButtonGroup>
<Select value={currency} onValueChange={setCurrency}>
<SelectTrigger css={{ fontFamily: "mono" }}>{currency}</SelectTrigger>
<SelectContent css={{ minW: "24" }}>
{CURRENCIES.map((currency) => (
<SelectItem key={currency.value} value={currency.value}>
{currency.value}{" "}
<styled.span css={{ color: "muted.fg" }}>{currency.label}</styled.span>
</SelectItem>
))}
</SelectContent>
</Select>
<Input placeholder="10.00" pattern="[0-9]*" />
</ButtonGroup>
<ButtonGroup>
<Button aria-label="Send" size="icon" variant="outline">
<LuArrowRight />
</Button>
</ButtonGroup>
</ButtonGroup>
);
}
Popover
Use with a Popover component.
import { LuBot, LuChevronDown } from "react-icons/lu";
import { styled } from "styled-system/jsx";
import { Button } from "@/components/ui/button";
import { ButtonGroup } from "@/components/ui/button-group";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import { Separator } from "@/components/ui/separator";
import { Textarea } from "@/components/ui/textarea";
export default function ButtonGroupPopover() {
return (
<ButtonGroup>
<Button variant="outline">
<LuBot /> Copilot
</Button>
<Popover>
<PopoverTrigger asChild>
<Button variant="outline" size="icon" aria-label="Open Popover">
<LuChevronDown />
</Button>
</PopoverTrigger>
<PopoverContent align="end" css={{ rounded: "xl", p: "0", textStyle: "sm" }}>
<styled.div css={{ px: "4", py: "3" }}>
<styled.div css={{ textStyle: "sm", fontWeight: "medium" }}>Agent Tasks</styled.div>
</styled.div>
<Separator />
<styled.div css={{ p: "4", textStyle: "sm", "& > p:not(:last-child)": { mb: "2" } }}>
<Textarea
placeholder="Describe your task in natural language."
css={{ mb: "4", resize: "none" }}
/>
<styled.p css={{ fontWeight: "medium" }}>Start a new task with Copilot</styled.p>
<styled.p css={{ color: "muted.fg" }}>
Describe your task in natural language. Copilot will work in the background and open a
pull request for your review.
</styled.p>
</styled.div>
</PopoverContent>
</Popover>
</ButtonGroup>
);
}
API Reference
ButtonGroup
The ButtonGroup component is a container that groups related buttons together with consistent styling.
| Prop | Type | Default |
|---|---|---|
orientation | "horizontal" | "vertical" | "horizontal" |
<ButtonGroup>
<Button>Button 1</Button>
<Button>Button 2</Button>
</ButtonGroup>
Nest multiple button groups to create complex layouts with spacing. See the nested example for more details.
<ButtonGroup>
<ButtonGroup />
<ButtonGroup />
</ButtonGroup>
ButtonGroupSeparator
The ButtonGroupSeparator component visually divides buttons within a group.
| Prop | Type | Default |
|---|---|---|
orientation | "horizontal" | "vertical" | "vertical" |
<ButtonGroup>
<Button>Button 1</Button>
<ButtonGroupSeparator />
<Button>Button 2</Button>
</ButtonGroup>
ButtonGroupText
Use this component to display text within a button group.
| Prop | Type | Default |
|---|---|---|
asChild | boolean | false |
<ButtonGroup>
<ButtonGroupText>Text</ButtonGroupText>
<Button>Button</Button>
</ButtonGroup>
Use the asChild prop to render a custom component as the text, for example a label.
import { ButtonGroupText } from "@/components/ui/button-group";
import { Label } from "@/components/ui/label";
export function ButtonGroupTextDemo() {
return (
<ButtonGroup>
<ButtonGroupText asChild>
<Label htmlFor="name">Text</Label>
</ButtonGroupText>
<Input placeholder="Type something here..." id="name" />
</ButtonGroup>
);
}