import { createSignal } from "solid-js"
import {
IconArchive,
IconArrowLeft,
IconCalendarPlus,
IconClock,
IconDots,
IconFilterPlus,
IconMailCheck,
IconTag,
IconTrash
} from "~/components/icons"
import { Button } from "~/components/ui/button"
import { ButtonGroup } from "~/components/ui/button-group"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuPortal,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuSeparator,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuTrigger
} from "~/components/ui/dropdown-menu"
export function ButttonGroupDemo() {
const [label, setLabel] = createSignal("personal")
return (
<ButtonGroup>
<ButtonGroup class="hidden sm:flex">
<Button aria-label="Go Back" size="icon" variant="outline">
<IconArrowLeft />
</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
aria-label="More Options"
as={Button<"button">}
size="icon"
variant="outline"
>
<IconDots />
</DropdownMenuTrigger>
<DropdownMenuContent class="w-52">
<DropdownMenuGroup>
<DropdownMenuItem>
<IconMailCheck />
Mark as Read
</DropdownMenuItem>
<DropdownMenuItem>
<IconArchive />
Archive
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem>
<IconClock />
Snooze
</DropdownMenuItem>
<DropdownMenuItem>
<IconCalendarPlus />
Add to Calendar
</DropdownMenuItem>
<DropdownMenuItem>
<IconFilterPlus />
Add to List
</DropdownMenuItem>
<DropdownMenuSub overlap>
<DropdownMenuSubTrigger>
<IconTag />
Label As...
</DropdownMenuSubTrigger>
<DropdownMenuPortal>
<DropdownMenuSubContent>
<DropdownMenuRadioGroup onChange={setLabel} value={label()}>
<DropdownMenuRadioItem value="personal">Personal</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="work">Work</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="other">Other</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>
</DropdownMenuSubContent>
</DropdownMenuPortal>
</DropdownMenuSub>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem variant="destructive">
<IconTrash />
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>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 { IconMinus, IconPlus } from "~/components/icons"
import { Button } from "~/components/ui/button"
import { ButtonGroup } from "~/components/ui/button-group"
export function ButtonGroupOrientation() {
return (
<ButtonGroup aria-label="Media controls" class="h-fit" orientation="vertical">
<Button size="icon" variant="outline">
<IconPlus />
</Button>
<Button size="icon" variant="outline">
<IconMinus />
</Button>
</ButtonGroup>
)
}
Size
Control the size of buttons using the size prop on individual buttons.
import { IconPlus } from "~/components/icons"
import { Button } from "~/components/ui/button"
import { ButtonGroup } from "~/components/ui/button-group"
export function ButtonGroupSize() {
return (
<div class="flex flex-col items-start gap-8">
<ButtonGroup>
<Button size="sm" variant="outline">
Small
</Button>
<Button size="sm" variant="outline">
Button
</Button>
<Button size="sm" variant="outline">
Group
</Button>
<Button size="icon-sm" variant="outline">
<IconPlus />
</Button>
</ButtonGroup>
<ButtonGroup>
<Button variant="outline">Default</Button>
<Button variant="outline">Button</Button>
<Button variant="outline">Group</Button>
<Button size="icon" variant="outline">
<IconPlus />
</Button>
</ButtonGroup>
<ButtonGroup>
<Button size="lg" variant="outline">
Large
</Button>
<Button size="lg" variant="outline">
Button
</Button>
<Button size="lg" variant="outline">
Group
</Button>
<Button size="icon-lg" variant="outline">
<IconPlus />
</Button>
</ButtonGroup>
</div>
)
}
Nested
Nest <ButtonGroup> components to create button groups with spacing.
import { IconArrowLeft, IconArrowRight } from "~/components/icons"
import { Button } from "~/components/ui/button"
import { ButtonGroup } from "~/components/ui/button-group"
export function ButtonGroupNested() {
return (
<ButtonGroup>
<ButtonGroup>
<Button size="sm" variant="outline">
1
</Button>
<Button size="sm" variant="outline">
2
</Button>
<Button size="sm" variant="outline">
3
</Button>
<Button size="sm" variant="outline">
4
</Button>
<Button size="sm" variant="outline">
5
</Button>
</ButtonGroup>
<ButtonGroup>
<Button aria-label="Previous" size="icon-sm" variant="outline">
<IconArrowLeft />
</Button>
<Button aria-label="Next" size="icon-sm" variant="outline">
<IconArrowRight />
</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 function ButtonGroupSeparatorDemo() {
return (
<ButtonGroup>
<Button size="sm" variant="secondary">
Copy
</Button>
<ButtonGroupSeparator />
<Button size="sm" variant="secondary">
Paste
</Button>
</ButtonGroup>
)
}
Split
Create a split button group by adding two buttons separated by a ButtonGroupSeparator.
import { IconPlus } from "~/components/icons"
import { Button } from "~/components/ui/button"
import { ButtonGroup, ButtonGroupSeparator } from "~/components/ui/button-group"
export function ButtonGroupSplit() {
return (
<ButtonGroup>
<Button variant="secondary">Button</Button>
<ButtonGroupSeparator />
<Button size="icon" variant="secondary">
<IconPlus />
</Button>
</ButtonGroup>
)
}
Input
Wrap an Input component with buttons.
import { IconSearch } from "~/components/icons"
import { Button } from "~/components/ui/button"
import { ButtonGroup } from "~/components/ui/button-group"
import { Input } from "~/components/ui/input"
export function ButtonGroupInput() {
return (
<ButtonGroup>
<Input placeholder="Search..." />
<Button aria-label="Search" variant="outline">
<IconSearch />
</Button>
</ButtonGroup>
)
}
Input Group
Wrap an InputGroup component to create complex input layouts.
import { createSignal } from "solid-js"
import { IconAudioLines, IconPlus } from "~/components/icons"
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 function ButtonGroupInputGroup() {
const [voiceEnabled, setVoiceEnabled] = createSignal(false)
return (
<ButtonGroup class="[--radius:9999rem]">
<ButtonGroup>
<Button size="icon" variant="outline">
<IconPlus />
</Button>
</ButtonGroup>
<ButtonGroup>
<InputGroup>
<InputGroupInput
disabled={voiceEnabled()}
placeholder={voiceEnabled() ? "Record and send audio..." : "Send a message..."}
/>
<InputGroupAddon align="inline-end">
<Tooltip>
<InputGroupButton
aria-pressed={voiceEnabled}
as={TooltipTrigger}
class="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"
data-active={voiceEnabled}
onClick={() => setVoiceEnabled(!voiceEnabled)}
size="icon-xs"
>
<IconAudioLines />
</InputGroupButton>
<TooltipContent>Voice Mode</TooltipContent>
</Tooltip>
</InputGroupAddon>
</InputGroup>
</ButtonGroup>
</ButtonGroup>
)
}
Dropdown Menu
Create a split button group with a DropdownMenu component.
import {
IconAlertTriangle,
IconCheck,
IconChevronDown,
IconCopy,
IconShare,
IconTrash,
IconUserRoundX,
IconVolumeOff
} from "~/components/icons"
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 function ButtonGroupDropdown() {
return (
<ButtonGroup>
<Button variant="outline">Follow</Button>
<DropdownMenu placement="bottom-end">
<Button as={DropdownMenuTrigger} class="!pl-2" variant="outline">
<IconChevronDown />
</Button>
<DropdownMenuContent class="[--radius:1rem]">
<DropdownMenuGroup>
<DropdownMenuItem>
<IconVolumeOff />
Mute Conversation
</DropdownMenuItem>
<DropdownMenuItem>
<IconCheck />
Mark as Read
</DropdownMenuItem>
<DropdownMenuItem>
<IconAlertTriangle />
Report Conversation
</DropdownMenuItem>
<DropdownMenuItem>
<IconUserRoundX />
Block User
</DropdownMenuItem>
<DropdownMenuItem>
<IconShare />
Share Conversation
</DropdownMenuItem>
<DropdownMenuItem>
<IconCopy />
Copy Conversation
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem variant="destructive">
<IconTrash />
Delete Conversation
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
</ButtonGroup>
)
}
Select
Pair with a Select component.
import { createSignal } from "solid-js"
import { IconArrowRight } from "~/components/icons"
import { Button } from "~/components/ui/button"
import { ButtonGroup } from "~/components/ui/button-group"
import { Input } from "~/components/ui/input"
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue
} from "~/components/ui/select"
type Currency = {
value: string
label: string
}
const CURRENCIES: Currency[] = [
{
value: "$",
label: "US Dollar"
},
{
value: "€",
label: "Euro"
},
{
value: "£",
label: "British Pound"
}
]
export function ButtonGroupSelect() {
const [currency, setCurrency] = createSignal<Currency>(CURRENCIES[0])
return (
<ButtonGroup>
<ButtonGroup>
<Select
itemComponent={(props) => (
<SelectItem item={props.item}>
{props.item.rawValue.value}{" "}
<span class="text-muted-foreground">{props.item.rawValue.label}</span>
</SelectItem>
)}
onChange={setCurrency}
options={CURRENCIES}
optionTextValue="label"
optionValue="value"
value={currency()}
>
<SelectTrigger class="font-mono">
<SelectValue<Currency>>{(state) => state.selectedOption().value}</SelectValue>
</SelectTrigger>
<SelectContent class="min-w-24" />
</Select>
<Input pattern="[0-9]*" placeholder="10.00" />
</ButtonGroup>
<ButtonGroup>
<Button aria-label="Send" size="icon" variant="outline">
<IconArrowRight />
</Button>
</ButtonGroup>
</ButtonGroup>
)
}
Popover
Use with a Popover component.
import { IconBot, IconChevronDown } from "~/components/icons"
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 function ButtonGroupPopover() {
return (
<ButtonGroup>
<Button variant="outline">
<IconBot /> Copilot
</Button>
<Popover placement="bottom-end">
<Button aria-label="Open Popover" as={PopoverTrigger} size="icon" variant="outline">
<IconChevronDown />
</Button>
<PopoverContent class="rounded-xl p-0 text-sm">
<div class="px-4 py-3">
<div class="font-medium text-sm">Agent Tasks</div>
</div>
<Separator />
<div class="p-4 text-sm *:[p:not(:last-child)]:mb-2">
<Textarea
class="mb-4 resize-none"
placeholder="Describe your task in natural language."
/>
<p class="font-medium">Start a new task with Copilot</p>
<p class="text-muted-foreground">
Describe your task in natural language. Copilot will work in the background and open a
pull request for your review.
</p>
</div>
</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 { ButtonGroupText } from "~/components/ui/button-group"import { Label } from "~/components/ui/label"
export function ButtonGroupTextDemo() { return ( <ButtonGroup> <Label as={ButtonGroupText} htmlFor="name">Text</Label> <Input placeholder="Type something here..." id="name" /> </ButtonGroup> )}