Button Group
A container that groups related buttons together with consistent styling.
import { ArchiveIcon, ArrowLeftIcon, CalendarPlusIcon, ClockIcon, ListFilterIcon, MailCheckIcon, MoreHorizontalIcon, TagIcon, Trash2Icon,} from "lucide-solid";import { createSignal } from "solid-js";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] = createSignal("personal");
return ( <ButtonGroup> <ButtonGroup class="hidden sm:flex"> <Button variant="outline" size="icon" aria-label="Go back"> <ArrowLeftIcon /> </Button> </ButtonGroup> <ButtonGroup> <Button variant="outline">Archive</Button> <Button variant="outline">Report</Button> </ButtonGroup> <ButtonGroup> <Button variant="outline">Snooze</Button> <DropdownMenu placement="bottom-end"> <DropdownMenuTrigger as={Button} variant="outline" size="icon" class="w-fit" aria-label="More options" > <MoreHorizontalIcon /> </DropdownMenuTrigger> <DropdownMenuContent class="w-40"> <DropdownMenuGroup> <DropdownMenuItem> <MailCheckIcon /> Mark as Read </DropdownMenuItem> <DropdownMenuItem> <ArchiveIcon /> Archive </DropdownMenuItem> </DropdownMenuGroup> <DropdownMenuSeparator /> <DropdownMenuGroup> <DropdownMenuItem> <ClockIcon /> Snooze </DropdownMenuItem> <DropdownMenuItem> <CalendarPlusIcon /> Add to Calendar </DropdownMenuItem> <DropdownMenuItem> <ListFilterIcon /> Add to List </DropdownMenuItem> <DropdownMenuSub> <DropdownMenuSubTrigger> <TagIcon /> Label As... </DropdownMenuSubTrigger> <DropdownMenuSubContent> <DropdownMenuRadioGroup value={label()} onChange={setLabel}> <DropdownMenuRadioItem value="personal">Personal</DropdownMenuRadioItem> <DropdownMenuRadioItem value="work">Work</DropdownMenuRadioItem> <DropdownMenuRadioItem value="other">Other</DropdownMenuRadioItem> </DropdownMenuRadioGroup> </DropdownMenuSubContent> </DropdownMenuSub> </DropdownMenuGroup> <DropdownMenuSeparator /> <DropdownMenuGroup> <DropdownMenuItem variant="destructive"> <Trash2Icon /> Trash </DropdownMenuItem> </DropdownMenuGroup> </DropdownMenuContent> </DropdownMenu> </ButtonGroup> </ButtonGroup> );}Installation
Usage
import { ButtonGroup, ButtonGroupSeparator, ButtonGroupText,} from "~/components/ui/button-group";<ButtonGroup> <Button>Button 1</Button> <Button>Button 2</Button></ButtonGroup>Composition
Use the following composition to build a ButtonGroup:
ButtonGroup├── Button or Input├── ButtonGroupSeparator└── ButtonGroupTextAccessibility
- The
ButtonGroupcomponent has theroleattribute set togroup. - Use
Tabto 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.
Orientation
Set the orientation prop to change the button group layout.
import { Minus, Plus } from "lucide-solid";
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" class="h-fit"> <Button variant="outline" size="icon" aria-label="Increase"> <Plus /> </Button> <Button variant="outline" size="icon" aria-label="Decrease"> <Minus /> </Button> </ButtonGroup> );}Size
Control the size of buttons using the size prop on individual buttons.
import { Plus } from "lucide-solid";
import { Button } from "~/components/ui/button";import { ButtonGroup } from "~/components/ui/button-group";
export default function ButtonGroupSize() { return ( <div class="flex flex-col items-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" aria-label="Add small item"> <Plus /> </Button> </ButtonGroup> <ButtonGroup> <Button variant="outline">Default</Button> <Button variant="outline">Button</Button> <Button variant="outline">Group</Button> <Button variant="outline" size="icon" aria-label="Add item"> <Plus /> </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" aria-label="Add large item"> <Plus /> </Button> </ButtonGroup> </div> );}Nested
Nest <ButtonGroup> components to create button groups with spacing.
import { AudioLines, Plus } from "lucide-solid";
import { Button } from "~/components/ui/button";import { ButtonGroup } from "~/components/ui/button-group";import { InputGroup, InputGroupAddon, InputGroupInput } from "~/components/ui/input-group";import { Tooltip, TooltipContent, TooltipTrigger } from "~/components/ui/tooltip";
export default function ButtonGroupNested() { return ( <ButtonGroup> <ButtonGroup> <Button variant="outline" size="icon" aria-label="Add"> <Plus /> </Button> </ButtonGroup> <ButtonGroup> <InputGroup> <InputGroupInput placeholder="Send a message..." /> <Tooltip> <TooltipTrigger as={InputGroupAddon} align="inline-end"> <AudioLines /> </TooltipTrigger> <TooltipContent>Voice Mode</TooltipContent> </Tooltip> </InputGroup> </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 type { Component } from "solid-js";import { Button } from "~/components/ui/button";import { ButtonGroup, ButtonGroupSeparator } from "~/components/ui/button-group";
const GroupSeparator = ButtonGroupSeparator as unknown as Component;
export default function ButtonGroupSeparatorDemo() { return ( <ButtonGroup> <Button variant="secondary" size="sm"> Copy </Button> <GroupSeparator /> <Button variant="secondary" size="sm"> Paste </Button> </ButtonGroup> );}Split
Create a split button group by adding two buttons separated by a ButtonGroupSeparator.
import { Plus } from "lucide-solid";import type { Component } from "solid-js";
import { Button } from "~/components/ui/button";import { ButtonGroup, ButtonGroupSeparator } from "~/components/ui/button-group";
const GroupSeparator = ButtonGroupSeparator as unknown as Component;
export default function ButtonGroupSplit() { return ( <ButtonGroup> <Button variant="secondary">Button</Button> <GroupSeparator /> <Button size="icon" variant="secondary" aria-label="Add"> <Plus /> </Button> </ButtonGroup> );}Input
Wrap an Input component with buttons.
import { Search } from "lucide-solid";
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"> <Search /> </Button> </ButtonGroup> );}Input Group
Wrap an InputGroup component to create complex input layouts.
import { AudioLines, Plus } from "lucide-solid";import { createSignal } from "solid-js";
import { Button, buttonVariants } from "~/components/ui/button";import { ButtonGroup } from "~/components/ui/button-group";import { InputGroup, InputGroupAddon, InputGroupInput } from "~/components/ui/input-group";import { Tooltip, TooltipContent, TooltipTrigger } from "~/components/ui/tooltip";
export default function ButtonGroupInputGroup() { const [voiceEnabled, setVoiceEnabled] = createSignal(false);
return ( <ButtonGroup class="[--radius:9999rem]"> <ButtonGroup> <Button variant="outline" size="icon" aria-label="Add"> <Plus /> </Button> </ButtonGroup> <ButtonGroup> <InputGroup> <InputGroupInput placeholder={voiceEnabled() ? "Record and send audio..." : "Send a message..."} disabled={voiceEnabled()} /> <InputGroupAddon align="inline-end"> <Tooltip> <TooltipTrigger onClick={() => setVoiceEnabled((enabled) => !enabled)} data-active={voiceEnabled()} class={`${buttonVariants({ variant: "ghost", size: "icon-xs" })} data-[active=true]:bg-orange-100 data-[active=true]:text-orange-700 dark:data-[active=true]:bg-orange-800 dark:data-[active=true]:text-orange-100`} aria-pressed={voiceEnabled()} aria-label="Toggle voice mode" > <AudioLines /> </TooltipTrigger> <TooltipContent>Voice Mode</TooltipContent> </Tooltip> </InputGroupAddon> </InputGroup> </ButtonGroup> </ButtonGroup> );}Dropdown Menu
Create a split button group with a DropdownMenu component.
import { AlertTriangle, Check, ChevronDown, Copy, Share, Trash, UserRoundX, VolumeOff,} from "lucide-solid";
import { Button, buttonVariants } 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 placement="bottom-end"> <DropdownMenuTrigger class={`${buttonVariants({ variant: "outline" })} pl-2!`} aria-label="More options" > <ChevronDown /> </DropdownMenuTrigger> <DropdownMenuContent class="w-44"> <DropdownMenuGroup> <DropdownMenuItem> <VolumeOff /> Mute Conversation </DropdownMenuItem> <DropdownMenuItem> <Check /> Mark as Read </DropdownMenuItem> <DropdownMenuItem> <AlertTriangle /> Report Conversation </DropdownMenuItem> <DropdownMenuItem> <UserRoundX /> Block User </DropdownMenuItem> <DropdownMenuItem> <Share /> Share Conversation </DropdownMenuItem> <DropdownMenuItem> <Copy /> Copy Conversation </DropdownMenuItem> </DropdownMenuGroup> <DropdownMenuSeparator /> <DropdownMenuGroup> <DropdownMenuItem variant="destructive"> <Trash /> Delete Conversation </DropdownMenuItem> </DropdownMenuGroup> </DropdownMenuContent> </DropdownMenu> </ButtonGroup> );}Select
Pair with a Select component.
import { ArrowRight } from "lucide-solid";import { createSignal } from "solid-js";
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 = [ { label: "US Dollar", value: "$" }, { label: "Euro", value: "€" }, { label: "British Pound", value: "£" },];
export default function ButtonGroupSelect() { const [currency, setCurrency] = createSignal(currencies[0].value);
return ( <ButtonGroup> <ButtonGroup> <Select options={currencies} optionValue="value" optionTextValue="label" value={currencies.find((item) => item.value === currency())} onChange={(item) => setCurrency(item?.value ?? "$")} itemComponent={(props) => ( <SelectItem item={props.item}> {props.item.rawValue.value}{" "} <span class="text-muted-foreground">{props.item.rawValue.label}</span> </SelectItem> )} > <SelectTrigger class="font-mono">{currency()}</SelectTrigger> <SelectContent /> </Select> <Input placeholder="10.00" inputmode="numeric" /> </ButtonGroup> <ButtonGroup> <Button aria-label="Send" size="icon" variant="outline"> <ArrowRight /> </Button> </ButtonGroup> </ButtonGroup> );}Popover
Use with a Popover component.
import { Bot, ChevronDown } from "lucide-solid";
import { Button, buttonVariants } from "~/components/ui/button";import { ButtonGroup } from "~/components/ui/button-group";import { Field, FieldDescription, FieldLabel } from "~/components/ui/field";import { Popover, PopoverContent, PopoverDescription, PopoverHeader, PopoverTitle, PopoverTrigger,} from "~/components/ui/popover";import { Textarea } from "~/components/ui/textarea";
export default function ButtonGroupPopover() { return ( <ButtonGroup> <Button variant="outline"> <Bot /> Copilot </Button> <Popover placement="bottom-end"> <PopoverTrigger class={buttonVariants({ variant: "outline", size: "icon" })} aria-label="Open Popover" > <ChevronDown /> </PopoverTrigger> <PopoverContent class="rounded-xl text-sm"> <PopoverHeader> <PopoverTitle>Start a new task with Copilot</PopoverTitle> <PopoverDescription>Describe your task in natural language.</PopoverDescription> </PopoverHeader> <Field> <FieldLabel for="button-group-task" class="sr-only"> Task Description </FieldLabel> <Textarea id="button-group-task" placeholder="I need to..." class="resize-none" /> <FieldDescription>Copilot will open a pull request for review.</FieldDescription> </Field> </PopoverContent> </Popover> </ButtonGroup> );}API Reference
ButtonGroup
The ButtonGroup component is a container that groups related buttons together with consistent styling.
<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.
<ButtonGroup> <Button>Button 1</Button> <ButtonGroupSeparator /> <Button>Button 2</Button></ButtonGroup>ButtonGroupText
Use this component to display text within a button group.
<ButtonGroup> <ButtonGroupText>Text</ButtonGroupText> <Button>Button</Button></ButtonGroup>Use the as prop to render a custom component as the text, for example a label.
import { ButtonGroup, ButtonGroupText } from "~/components/ui/button-group";import { Input } from "~/components/ui/input";import { Label } from "~/components/ui/label";
export function ButtonGroupTextDemo() { return ( <ButtonGroup> <ButtonGroupText as={Label} for="name"> Text </ButtonGroupText> <Input placeholder="Type something here..." id="name" /> </ButtonGroup> );}