Search for a command to run...
A container that groups related buttons together with consistent styling.
npx shadcn@latest add @zaidan/button-grouppnpx shadcn add @zaidan/button-groupyarn dlx shadcn@latest add @zaidan/button-groupbunx shadcn@latest add @zaidan/button-groupCopy and paste the following code into your project.
1import { cva, type VariantProps } from "class-variance-authority";2import { type ComponentProps, mergeProps, splitProps } from "solid-js";3
4import { cn } from "~/lib/utils";5import { Separator } from "~/components/ui/separator";6
7const buttonGroupVariants = cva(8 "z-button-group flex w-fit items-stretch *:focus-visible:relative *:focus-visible:z-10 [&>[data-slot=select-trigger]:not([class*='w-'])]:w-fit [&>input]:flex-1",9 {10 variants: {11 orientation: {12 horizontal:13 "z-button-group-orientation-horizontal *:data-slot:rounded-r-none [&>[data-slot]~[data-slot]]:rounded-l-none [&>[data-slot]~[data-slot]]:border-l-0",14 vertical:15 "z-button-group-orientation-vertical flex-col *:data-slot:rounded-b-none [&>[data-slot]~[data-slot]]:rounded-t-none [&>[data-slot]~[data-slot]]:border-t-0",16 },17 },18 defaultVariants: {19 orientation: "horizontal",20 },21 },22);23
24type ButtonGroupProps = ComponentProps<"div"> & VariantProps<typeof buttonGroupVariants>;25
26const ButtonGroup = (props: ButtonGroupProps) => {27 const [local, others] = splitProps(props, ["class", "orientation"]);28 return (29 // biome-ignore lint/a11y/useSemanticElements: <exception for button group>30 <div31 class={cn(buttonGroupVariants({ orientation: local.orientation }), local.class)}32 data-orientation={local.orientation}33 data-slot="button-group"34 role="group"35 {...others}36 />37 );38};39
40type ButtonGroupTextProps = ComponentProps<"div">;41
42const ButtonGroupText = (props: ButtonGroupTextProps) => {43 const [local, others] = splitProps(props, ["class"]);44 return (45 <div46 data-slot="button-group-text"47 class={cn("z-button-group-text flex items-center [&_svg]:pointer-events-none", local.class)}48 {...others}49 />50 );51};52
53type ButtonGroupSeparatorProps = ComponentProps<typeof Separator>;54
55const ButtonGroupSeparator = (props: ButtonGroupSeparatorProps) => {56 const mergedProps = mergeProps({ orientation: "vertical" } as const, props);57 const [local, others] = splitProps(mergedProps, ["class", "orientation"]);58 return (59 <Separator60 class={cn(61 "relative z-button-group-separator self-stretch data-[orientation=horizontal]:mx-px data-[orientation=vertical]:my-px data-[orientation=vertical]:h-auto data-[orientation=horizontal]:w-auto",62 local.class,63 )}64 data-slot="button-group-separator"65 orientation={local.orientation}66 {...others}67 />68 );69};70
71export { ButtonGroup, ButtonGroupText, ButtonGroupSeparator, buttonGroupVariants };ButtonGroup component has the role attribute set to group.Tab to navigate between the buttons in the group.aria-label or aria-labelledby to label the button group.1<ButtonGroup aria-label="Button group">2 <Button>Button 1</Button>3 <Button>Button 2</Button>4</ButtonGroup>ButtonGroup component when you want to group buttons that perform an action.ToggleGroup component when you want to group buttons that toggle a state.When using Select inside a ButtonGroup, you need to handle yourself the rounded border or not.
It is necessary because Kobalte wrap Select inside a div, and button group expect a button obviously
Here are the source code of all the examples from the preview page:
import { Button } from "~/components/ui/button";import { ButtonGroup } from "~/components/ui/button-group";function ButtonGroupBasic() { return ( <Example title="Basic"> <div class="flex flex-col gap-4"> <ButtonGroup> <Button variant="outline">Button</Button> <Button variant="outline">Another Button</Button> </ButtonGroup> </div> </Example> );}import { Button } from "~/components/ui/button";import { ButtonGroup } from "~/components/ui/button-group";import { Input } from "~/components/ui/input";function ButtonGroupWithInput() { return ( <Example title="With Input"> <div class="flex flex-col gap-4"> <ButtonGroup> <Button variant="outline">Button</Button> <Input placeholder="Type something here..." /> </ButtonGroup> <ButtonGroup> <Input placeholder="Type something here..." /> <Button variant="outline">Button</Button> </ButtonGroup> </div> </Example> );}import { Button } from "~/components/ui/button";import { ButtonGroup, ButtonGroupText } from "~/components/ui/button-group";import { Input } from "~/components/ui/input";import { Label } from "~/components/ui/label";function ButtonGroupWithText() { return ( <Example title="With Text"> <div class="flex flex-col gap-4"> <ButtonGroup> <ButtonGroupText>Text</ButtonGroupText> <Button variant="outline">Another Button</Button> </ButtonGroup> <ButtonGroup> <ButtonGroupText> <Label for="input-text">GPU Size</Label> </ButtonGroupText> <Input id="input-text" placeholder="Type something here..." /> </ButtonGroup> </div> </Example> );}import { AlertTriangle, Check, ChevronDown, Copy, Share, Trash, UserRoundX, VolumeX,} from "lucide-solid";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";function ButtonGroupWithDropdown() { return ( <Example title="With Dropdown"> <div class="flex flex-col gap-4"> <ButtonGroup> <Button variant="outline">Update</Button> <DropdownMenu> <DropdownMenuTrigger as={Button} variant="outline" size="icon" class=""> <ChevronDown /> </DropdownMenuTrigger> <DropdownMenuContent> <DropdownMenuItem>Disable</DropdownMenuItem> <DropdownMenuItem variant="destructive">Uninstall</DropdownMenuItem> </DropdownMenuContent> </DropdownMenu> </ButtonGroup> <ButtonGroup> <Button variant="outline">Follow</Button> <DropdownMenu> <DropdownMenuTrigger as={Button} variant="outline" size="icon" class=""> <ChevronDown /> </DropdownMenuTrigger> <DropdownMenuContent class="w-50"> <DropdownMenuGroup> <DropdownMenuItem> <VolumeX /> 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> </div> </Example> );}import { ArrowRight } from "lucide-solid";import { Button } from "~/components/ui/button";import { ButtonGroup } from "~/components/ui/button-group";import { Field } from "~/components/ui/field";import { Input } from "~/components/ui/input";import { Label } from "~/components/ui/label";import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue,} from "~/components/ui/select";function ButtonGroupWithSelect() { return ( <Example title="With Select"> <Field> <Label for="amount">Amount</Label> <ButtonGroup> <Select options={currencyItems} optionValue="value" optionTextValue="label" defaultValue={currencyItems[0]} itemComponent={(props) => ( <SelectItem item={props.item}>{props.item.rawValue.label}</SelectItem> )} > <SelectTrigger class="rounded-r-none"> <SelectValue<(typeof currencyItems)[number]>> {(state) => state.selectedOption()?.label} </SelectValue> </SelectTrigger> <SelectContent /> </Select> <Input placeholder="Enter amount to send" class="rounded-l-none border-l-0" /> <Button variant="outline"> <ArrowRight /> </Button> </ButtonGroup> </Field> </Example> );}import { FlipHorizontal, FlipVertical, RotateCw } from "lucide-solid";import { Button } from "~/components/ui/button";import { ButtonGroup } from "~/components/ui/button-group";function ButtonGroupWithIcons() { return ( <Example title="With Icons"> <div class="flex flex-col gap-4"> <ButtonGroup> <Button variant="outline"> <FlipHorizontal /> </Button> <Button variant="outline"> <FlipVertical /> </Button> <Button variant="outline"> <RotateCw /> </Button> </ButtonGroup> </div> </Example> );}import { Search } from "lucide-solid";import { InputGroup, InputGroupAddon, InputGroupInput,} from "~/components/ui/input-group";function ButtonGroupWithInputGroup() { return ( <Example title="With Input Group"> <div class="flex flex-col gap-4"> <InputGroup> <InputGroupInput placeholder="Type to search..." /> <InputGroupAddon align="inline-start" class="text-muted-foreground"> <Search /> </InputGroupAddon> </InputGroup> </div> </Example> );}import { Minus, Plus } from "lucide-solid";import { Button } from "~/components/ui/button";import { ButtonGroup } from "~/components/ui/button-group";import { Field, FieldGroup } from "~/components/ui/field";import { InputGroup, InputGroupAddon, InputGroupInput,} from "~/components/ui/input-group";import { Label } from "~/components/ui/label";function ButtonGroupWithFields() { return ( <Example title="With Fields"> <FieldGroup class="grid grid-cols-3 gap-4"> <Field class="col-span-2"> <Label for="width">Width</Label> <ButtonGroup> <InputGroup> <InputGroupInput id="width" /> <InputGroupAddon class="text-muted-foreground">W</InputGroupAddon> <InputGroupAddon align="inline-end" class="text-muted-foreground"> px </InputGroupAddon> </InputGroup> <Button variant="outline" size="icon"> <Minus /> </Button> <Button variant="outline" size="icon"> <Plus /> </Button> </ButtonGroup> </Field> </FieldGroup> </Example> );}import { Heart } from "lucide-solid";import { Button } from "~/components/ui/button";import { ButtonGroup } from "~/components/ui/button-group";function ButtonGroupWithLike() { return ( <Example title="With Like"> <ButtonGroup> <Button variant="outline"> <Heart data-icon="inline-start" /> Like </Button> <Button as="span" variant="outline" size="icon" class="w-12"> 1.2K </Button> </ButtonGroup> </Example> );}import { ButtonGroup } from "~/components/ui/button-group";import { Input } from "~/components/ui/input";import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue,} from "~/components/ui/select";function ButtonGroupWithSelectAndInput() { return ( <Example title="With Select and Input"> <ButtonGroup> <Select options={durationItems} optionValue="value" optionTextValue="label" defaultValue={durationItems[0]} itemComponent={(props) => ( <SelectItem item={props.item}>{props.item.rawValue.label}</SelectItem> )} > <SelectTrigger id="duration" class="rounded-r-none"> <SelectValue<(typeof durationItems)[number]>> {(state) => state.selectedOption()?.label} </SelectValue> </SelectTrigger> <SelectContent /> </Select> <Input class="rounded-l-none border-l-0" /> </ButtonGroup> </Example> );}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";function ButtonGroupNested() { return ( <Example title="Nested"> <ButtonGroup> <ButtonGroup> <Button variant="outline" size="icon"> <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> </Example> );}import { ArrowLeft, ArrowRight } from "lucide-solid";import { Button } from "~/components/ui/button";import { ButtonGroup } from "~/components/ui/button-group";function ButtonGroupPagination() { return ( <Example title="Pagination"> <ButtonGroup> <Button variant="outline" size="sm"> <ArrowLeft data-icon="inline-start" /> Previous </Button> <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> <Button variant="outline" size="sm"> Next <ArrowRight data-icon="inline-end" /> </Button> </ButtonGroup> </Example> );}import { ArrowLeft, ArrowRight } from "lucide-solid";import { Button } from "~/components/ui/button";import { ButtonGroup } from "~/components/ui/button-group";function ButtonGroupPaginationSplit() { return ( <Example title="Pagination Split"> <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-xs"> <ArrowLeft /> </Button> <Button variant="outline" size="icon-xs"> <ArrowRight /> </Button> </ButtonGroup> </ButtonGroup> </Example> );}import { ArrowLeft, ArrowRight } from "lucide-solid";import { Button } from "~/components/ui/button";import { ButtonGroup } from "~/components/ui/button-group";function ButtonGroupNavigation() { return ( <Example title="Navigation"> <ButtonGroup> <ButtonGroup> <Button variant="outline"> <ArrowLeft /> </Button> <Button variant="outline"> <ArrowRight /> </Button> </ButtonGroup> <ButtonGroup aria-label="Single navigation button"> <Button variant="outline" size="icon"> <ArrowLeft /> </Button> </ButtonGroup> </ButtonGroup> </Example> );}import { Button } from "~/components/ui/button";import { ButtonGroup } from "~/components/ui/button-group";import { Field } from "~/components/ui/field";import { Label } from "~/components/ui/label";function ButtonGroupTextAlignment() { return ( <Example title="Text Alignment"> <Field> <Label id="alignment-label">Text Alignment</Label> <ButtonGroup aria-labelledby="alignment-label"> <Button variant="outline" size="sm"> Left </Button> <Button variant="outline" size="sm"> Center </Button> <Button variant="outline" size="sm"> Right </Button> <Button variant="outline" size="sm"> Justify </Button> </ButtonGroup> </Field> </Example> );}import { Minus, Plus } from "lucide-solid";import { Button } from "~/components/ui/button";import { ButtonGroup } from "~/components/ui/button-group";function ButtonGroupVertical() { return ( <Example title="Vertical"> <div class="flex gap-6"> <ButtonGroup orientation="vertical" aria-label="Media controls" class="h-fit"> <Button variant="outline" size="icon"> <Plus /> </Button> <Button variant="outline" size="icon"> <Minus /> </Button> </ButtonGroup> </div> </Example> );}import { Copy, FlipHorizontal, FlipVertical, RotateCw, Search, Share, Trash,} from "lucide-solid";import { Button } from "~/components/ui/button";import { ButtonGroup } from "~/components/ui/button-group";function ButtonGroupVerticalNested() { return ( <Example title="Vertical Nested"> <ButtonGroup orientation="vertical" aria-label="Design tools palette"> <ButtonGroup orientation="vertical"> <Button variant="outline" size="icon"> <Search /> </Button> <Button variant="outline" size="icon"> <Copy /> </Button> <Button variant="outline" size="icon"> <Share /> </Button> </ButtonGroup> <ButtonGroup orientation="vertical"> <Button variant="outline" size="icon"> <FlipHorizontal /> </Button> <Button variant="outline" size="icon"> <FlipVertical /> </Button> <Button variant="outline" size="icon"> <RotateCw /> </Button> </ButtonGroup> <ButtonGroup> <Button variant="outline" size="icon"> <Trash /> </Button> </ButtonGroup> </ButtonGroup> </Example> );}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>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>Use this component to display text within a button group.
<ButtonGroup> <ButtonGroupText>Text</ButtonGroupText> <Button>Button</Button></ButtonGroup>Use a Label component inside ButtonGroupText to associate it with an input.
1import { ButtonGroup, ButtonGroupText } from "~/components/ui/button-group";2import { Input } from "~/components/ui/input";3import { Label } from "~/components/ui/label";4
5export function ButtonGroupTextDemo() {6 return (7 <ButtonGroup>8 <ButtonGroupText>9 <Label for="name">Text</Label>10 </ButtonGroupText>11 <Input placeholder="Type something here..." id="name" />12 </ButtonGroup>13 );14}