ZaidanHomeDocsComponentsBlocksChartsTypesetCreate

Command Palette

Search for a command to run...

GitHub
New
Sections
  • Introduction
  • Components
  • Blocks
  • Installation
  • Customization
  • Dark Mode
  • Typeset
  • Zaidan Skills
  • FAQ
  • Roadmap
  • Changelog
Installation
  • Astro
  • Manual
  • Solid Start
  • TanStack Router
  • TanStack Start
  • Vite
Blocks
  • Data Grid
  • Event Calendar
  • Filters
  • Gantt
  • Image Crop
  • Kanban
  • Message Scroller
  • Questionnaire
  • Sortable
Components
  • Accordion
  • Alert
  • Alert Dialog
  • Aspect Ratio
  • Attachment
  • Avatar
  • Badge
  • Breadcrumb
  • Bubble
  • Button
  • Button Group
  • Calendar
  • Card
  • Carousel
  • Chart
  • Checkbox
  • Collapsible
  • Combobox
  • Command
  • Context Menu
  • Dialog
  • Drawer
  • Dropdown Menu
  • Empty
  • Field
  • Hover Card
  • Input
  • Input Group
  • Input OTP
  • Item
  • Kbd
  • Label
  • Marker
  • Menubar
  • Message
  • Native Select
  • Navigation Menu
  • Pagination
  • Popover
  • Progress
  • Radio Group
  • Resizable
  • Scroll Area
  • Select
  • Separator
  • Sheet
  • Sidebar
  • Skeleton
  • Slider
  • Spinner
  • Switch
  • Table
  • Tabs
  • Textarea
  • Toast
  • Toggle
  • Toggle Group
  • Tooltip

Button Group

A container that groups related buttons together with consistent styling.

1
import {
2
ArchiveIcon,
3
ArrowLeftIcon,
4
CalendarPlusIcon,
5
ClockIcon,
6
ListFilterIcon,
7
MailCheckIcon,
8
MoreHorizontalIcon,
9
TagIcon,
10
Trash2Icon,
11
} from "lucide-solid";
12
import { createSignal } from "solid-js";
13
import { Button } from "~/components/ui/button";
14
import { ButtonGroup } from "~/components/ui/button-group";
15
import {
16
DropdownMenu,
17
DropdownMenuContent,
18
DropdownMenuGroup,
19
DropdownMenuItem,
20
DropdownMenuRadioGroup,
21
DropdownMenuRadioItem,
22
DropdownMenuSeparator,
23
DropdownMenuSub,
24
DropdownMenuSubContent,
25
DropdownMenuSubTrigger,
26
DropdownMenuTrigger,
27
} from "~/components/ui/dropdown-menu";
28
29
export default function ButtonGroupDemo() {
30
const [label, setLabel] = createSignal("personal");
31
32
return (
33
<ButtonGroup>
34
<ButtonGroup class="hidden sm:flex">
35
<Button variant="outline" size="icon" aria-label="Go back">
36
<ArrowLeftIcon />
37
</Button>
38
</ButtonGroup>
39
<ButtonGroup>
40
<Button variant="outline">Archive</Button>
41
<Button variant="outline">Report</Button>
42
</ButtonGroup>
43
<ButtonGroup>
44
<Button variant="outline">Snooze</Button>
45
<DropdownMenu placement="bottom-end">
46
<DropdownMenuTrigger
47
as={Button}
48
variant="outline"
49
size="icon"
50
class="w-fit"
51
aria-label="More options"
52
>
53
<MoreHorizontalIcon />
54
</DropdownMenuTrigger>
55
<DropdownMenuContent class="w-40">
56
<DropdownMenuGroup>
57
<DropdownMenuItem>
58
<MailCheckIcon />
59
Mark as Read
60
</DropdownMenuItem>
61
<DropdownMenuItem>
62
<ArchiveIcon />
63
Archive
64
</DropdownMenuItem>
65
</DropdownMenuGroup>
66
<DropdownMenuSeparator />
67
<DropdownMenuGroup>
68
<DropdownMenuItem>
69
<ClockIcon />
70
Snooze
71
</DropdownMenuItem>
72
<DropdownMenuItem>
73
<CalendarPlusIcon />
74
Add to Calendar
75
</DropdownMenuItem>
76
<DropdownMenuItem>
77
<ListFilterIcon />
78
Add to List
79
</DropdownMenuItem>
80
<DropdownMenuSub>
81
<DropdownMenuSubTrigger>
82
<TagIcon />
83
Label As...
84
</DropdownMenuSubTrigger>
85
<DropdownMenuSubContent>
86
<DropdownMenuRadioGroup value={label()} onChange={setLabel}>
87
<DropdownMenuRadioItem value="personal">Personal</DropdownMenuRadioItem>
88
<DropdownMenuRadioItem value="work">Work</DropdownMenuRadioItem>
89
<DropdownMenuRadioItem value="other">Other</DropdownMenuRadioItem>
90
</DropdownMenuRadioGroup>
91
</DropdownMenuSubContent>
92
</DropdownMenuSub>
93
</DropdownMenuGroup>
94
<DropdownMenuSeparator />
95
<DropdownMenuGroup>
96
<DropdownMenuItem variant="destructive">
97
<Trash2Icon />
98
Trash
99
</DropdownMenuItem>
100
</DropdownMenuGroup>
101
</DropdownMenuContent>
102
</DropdownMenu>
103
</ButtonGroup>
104
</ButtonGroup>
105
);
106
}

Installation

pnpm dlx shadcn@latest add @zaidan/button-group
npx shadcn@latest add @zaidan/button-group
yarn dlx shadcn@latest add @zaidan/button-group
bunx --bun shadcn@latest add @zaidan/button-group

Usage

1
import {
2
ButtonGroup,
3
ButtonGroupSeparator,
4
ButtonGroupText,
5
} from "~/components/ui/button-group";
1
<ButtonGroup>
2
<Button>Button 1</Button>
3
<Button>Button 2</Button>
4
</ButtonGroup>

Composition

Use the following composition to build a ButtonGroup:

ButtonGroup
├── Button or Input
├── ButtonGroupSeparator
└── ButtonGroupText

Accessibility

  • The ButtonGroup component has the role attribute set to group.
  • Use Tab to navigate between the buttons in the group.
  • Use 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 vs ToggleGroup

  • Use the ButtonGroup component when you want to group buttons that perform an action.
  • Use the ToggleGroup component when you want to group buttons that toggle a state.

Orientation

Set the orientation prop to change the button group layout.

1
import { Minus, Plus } from "lucide-solid";
2
3
import { Button } from "~/components/ui/button";
4
import { ButtonGroup } from "~/components/ui/button-group";
5
6
export default function ButtonGroupOrientation() {
7
return (
8
<ButtonGroup orientation="vertical" aria-label="Media controls" class="h-fit">
9
<Button variant="outline" size="icon" aria-label="Increase">
10
<Plus />
11
</Button>
12
<Button variant="outline" size="icon" aria-label="Decrease">
13
<Minus />
14
</Button>
15
</ButtonGroup>
16
);
17
}

Size

Control the size of buttons using the size prop on individual buttons.

1
import { Plus } from "lucide-solid";
2
3
import { Button } from "~/components/ui/button";
4
import { ButtonGroup } from "~/components/ui/button-group";
5
6
export default function ButtonGroupSize() {
7
return (
8
<div class="flex flex-col items-start gap-8">
9
<ButtonGroup>
10
<Button variant="outline" size="sm">
11
Small
12
</Button>
13
<Button variant="outline" size="sm">
14
Button
15
</Button>
16
<Button variant="outline" size="sm">
17
Group
18
</Button>
19
<Button variant="outline" size="icon-sm" aria-label="Add small item">
20
<Plus />
21
</Button>
22
</ButtonGroup>
23
<ButtonGroup>
24
<Button variant="outline">Default</Button>
25
<Button variant="outline">Button</Button>
26
<Button variant="outline">Group</Button>
27
<Button variant="outline" size="icon" aria-label="Add item">
28
<Plus />
29
</Button>
30
</ButtonGroup>
31
<ButtonGroup>
32
<Button variant="outline" size="lg">
33
Large
34
</Button>
35
<Button variant="outline" size="lg">
36
Button
37
</Button>
38
<Button variant="outline" size="lg">
39
Group
40
</Button>
41
<Button variant="outline" size="icon-lg" aria-label="Add large item">
42
<Plus />
43
</Button>
44
</ButtonGroup>
45
</div>
46
);
47
}

Nested

Nest <ButtonGroup> components to create button groups with spacing.

1
import { AudioLines, Plus } from "lucide-solid";
2
3
import { Button } from "~/components/ui/button";
4
import { ButtonGroup } from "~/components/ui/button-group";
5
import { InputGroup, InputGroupAddon, InputGroupInput } from "~/components/ui/input-group";
6
import { Tooltip, TooltipContent, TooltipTrigger } from "~/components/ui/tooltip";
7
8
export default function ButtonGroupNested() {
9
return (
10
<ButtonGroup>
11
<ButtonGroup>
12
<Button variant="outline" size="icon" aria-label="Add">
13
<Plus />
14
</Button>
15
</ButtonGroup>
16
<ButtonGroup>
17
<InputGroup>
18
<InputGroupInput placeholder="Send a message..." />
19
<Tooltip>
20
<TooltipTrigger as={InputGroupAddon} align="inline-end">
21
<AudioLines />
22
</TooltipTrigger>
23
<TooltipContent>Voice Mode</TooltipContent>
24
</Tooltip>
25
</InputGroup>
26
</ButtonGroup>
27
</ButtonGroup>
28
);
29
}

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.

1
import type { Component } from "solid-js";
2
import { Button } from "~/components/ui/button";
3
import { ButtonGroup, ButtonGroupSeparator } from "~/components/ui/button-group";
4
5
const GroupSeparator = ButtonGroupSeparator as unknown as Component;
6
7
export default function ButtonGroupSeparatorDemo() {
8
return (
9
<ButtonGroup>
10
<Button variant="secondary" size="sm">
11
Copy
12
</Button>
13
<GroupSeparator />
14
<Button variant="secondary" size="sm">
15
Paste
16
</Button>
17
</ButtonGroup>
18
);
19
}

Split

Create a split button group by adding two buttons separated by a ButtonGroupSeparator.

1
import { Plus } from "lucide-solid";
2
import type { Component } from "solid-js";
3
4
import { Button } from "~/components/ui/button";
5
import { ButtonGroup, ButtonGroupSeparator } from "~/components/ui/button-group";
6
7
const GroupSeparator = ButtonGroupSeparator as unknown as Component;
8
9
export default function ButtonGroupSplit() {
10
return (
11
<ButtonGroup>
12
<Button variant="secondary">Button</Button>
13
<GroupSeparator />
14
<Button size="icon" variant="secondary" aria-label="Add">
15
<Plus />
16
</Button>
17
</ButtonGroup>
18
);
19
}

Input

Wrap an Input component with buttons.

1
import { Search } from "lucide-solid";
2
3
import { Button } from "~/components/ui/button";
4
import { ButtonGroup } from "~/components/ui/button-group";
5
import { Input } from "~/components/ui/input";
6
7
export default function ButtonGroupInput() {
8
return (
9
<ButtonGroup>
10
<Input placeholder="Search..." />
11
<Button variant="outline" aria-label="Search">
12
<Search />
13
</Button>
14
</ButtonGroup>
15
);
16
}

Input Group

Wrap an InputGroup component to create complex input layouts.

1
import { AudioLines, Plus } from "lucide-solid";
2
import { createSignal } from "solid-js";
3
4
import { Button, buttonVariants } from "~/components/ui/button";
5
import { ButtonGroup } from "~/components/ui/button-group";
6
import { InputGroup, InputGroupAddon, InputGroupInput } from "~/components/ui/input-group";
7
import { Tooltip, TooltipContent, TooltipTrigger } from "~/components/ui/tooltip";
8
9
export default function ButtonGroupInputGroup() {
10
const [voiceEnabled, setVoiceEnabled] = createSignal(false);
11
12
return (
13
<ButtonGroup class="[--radius:9999rem]">
14
<ButtonGroup>
15
<Button variant="outline" size="icon" aria-label="Add">
16
<Plus />
17
</Button>
18
</ButtonGroup>
19
<ButtonGroup>
20
<InputGroup>
21
<InputGroupInput
22
placeholder={voiceEnabled() ? "Record and send audio..." : "Send a message..."}
23
disabled={voiceEnabled()}
24
/>
25
<InputGroupAddon align="inline-end">
26
<Tooltip>
27
<TooltipTrigger
28
onClick={() => setVoiceEnabled((enabled) => !enabled)}
29
data-active={voiceEnabled()}
30
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`}
31
aria-pressed={voiceEnabled()}
32
aria-label="Toggle voice mode"
33
>
34
<AudioLines />
35
</TooltipTrigger>
36
<TooltipContent>Voice Mode</TooltipContent>
37
</Tooltip>
38
</InputGroupAddon>
39
</InputGroup>
40
</ButtonGroup>
41
</ButtonGroup>
42
);
43
}

Dropdown Menu

Create a split button group with a DropdownMenu component.

1
import {
2
AlertTriangle,
3
Check,
4
ChevronDown,
5
Copy,
6
Share,
7
Trash,
8
UserRoundX,
9
VolumeOff,
10
} from "lucide-solid";
11
12
import { Button, buttonVariants } from "~/components/ui/button";
13
import { ButtonGroup } from "~/components/ui/button-group";
14
import {
15
DropdownMenu,
16
DropdownMenuContent,
17
DropdownMenuGroup,
18
DropdownMenuItem,
19
DropdownMenuSeparator,
20
DropdownMenuTrigger,
21
} from "~/components/ui/dropdown-menu";
22
23
export default function ButtonGroupDropdown() {
24
return (
25
<ButtonGroup>
26
<Button variant="outline">Follow</Button>
27
<DropdownMenu placement="bottom-end">
28
<DropdownMenuTrigger
29
class={`${buttonVariants({ variant: "outline" })} pl-2!`}
30
aria-label="More options"
31
>
32
<ChevronDown />
33
</DropdownMenuTrigger>
34
<DropdownMenuContent class="w-44">
35
<DropdownMenuGroup>
36
<DropdownMenuItem>
37
<VolumeOff />
38
Mute Conversation
39
</DropdownMenuItem>
40
<DropdownMenuItem>
41
<Check />
42
Mark as Read
43
</DropdownMenuItem>
44
<DropdownMenuItem>
45
<AlertTriangle />
46
Report Conversation
47
</DropdownMenuItem>
48
<DropdownMenuItem>
49
<UserRoundX />
50
Block User
51
</DropdownMenuItem>
52
<DropdownMenuItem>
53
<Share />
54
Share Conversation
55
</DropdownMenuItem>
56
<DropdownMenuItem>
57
<Copy />
58
Copy Conversation
59
</DropdownMenuItem>
60
</DropdownMenuGroup>
61
<DropdownMenuSeparator />
62
<DropdownMenuGroup>
63
<DropdownMenuItem variant="destructive">
64
<Trash />
65
Delete Conversation
66
</DropdownMenuItem>
67
</DropdownMenuGroup>
68
</DropdownMenuContent>
69
</DropdownMenu>
70
</ButtonGroup>
71
);
72
}

Select

Pair with a Select component.

1
import { ArrowRight } from "lucide-solid";
2
import { createSignal } from "solid-js";
3
4
import { Button } from "~/components/ui/button";
5
import { ButtonGroup } from "~/components/ui/button-group";
6
import { Input } from "~/components/ui/input";
7
import { Select, SelectContent, SelectItem, SelectTrigger } from "~/components/ui/select";
8
9
const currencies = [
10
{ label: "US Dollar", value: "$" },
11
{ label: "Euro", value: "€" },
12
{ label: "British Pound", value: "£" },
13
];
14
15
export default function ButtonGroupSelect() {
16
const [currency, setCurrency] = createSignal(currencies[0].value);
17
18
return (
19
<ButtonGroup>
20
<ButtonGroup>
21
<Select
22
options={currencies}
23
optionValue="value"
24
optionTextValue="label"
25
value={currencies.find((item) => item.value === currency())}
26
onChange={(item) => setCurrency(item?.value ?? "$")}
27
itemComponent={(props) => (
28
<SelectItem item={props.item}>
29
{props.item.rawValue.value}{" "}
30
<span class="text-muted-foreground">{props.item.rawValue.label}</span>
31
</SelectItem>
32
)}
33
>
34
<SelectTrigger class="font-mono">{currency()}</SelectTrigger>
35
<SelectContent />
36
</Select>
37
<Input placeholder="10.00" inputmode="numeric" />
38
</ButtonGroup>
39
<ButtonGroup>
40
<Button aria-label="Send" size="icon" variant="outline">
41
<ArrowRight />
42
</Button>
43
</ButtonGroup>
44
</ButtonGroup>
45
);
46
}

Popover

Use with a Popover component.

1
import { Bot, ChevronDown } from "lucide-solid";
2
3
import { Button, buttonVariants } from "~/components/ui/button";
4
import { ButtonGroup } from "~/components/ui/button-group";
5
import { Field, FieldDescription, FieldLabel } from "~/components/ui/field";
6
import {
7
Popover,
8
PopoverContent,
9
PopoverDescription,
10
PopoverHeader,
11
PopoverTitle,
12
PopoverTrigger,
13
} from "~/components/ui/popover";
14
import { Textarea } from "~/components/ui/textarea";
15
16
export default function ButtonGroupPopover() {
17
return (
18
<ButtonGroup>
19
<Button variant="outline">
20
<Bot />
21
Copilot
22
</Button>
23
<Popover placement="bottom-end">
24
<PopoverTrigger
25
class={buttonVariants({ variant: "outline", size: "icon" })}
26
aria-label="Open Popover"
27
>
28
<ChevronDown />
29
</PopoverTrigger>
30
<PopoverContent class="rounded-xl text-sm">
31
<PopoverHeader>
32
<PopoverTitle>Start a new task with Copilot</PopoverTitle>
33
<PopoverDescription>Describe your task in natural language.</PopoverDescription>
34
</PopoverHeader>
35
<Field>
36
<FieldLabel for="button-group-task" class="sr-only">
37
Task Description
38
</FieldLabel>
39
<Textarea id="button-group-task" placeholder="I need to..." class="resize-none" />
40
<FieldDescription>Copilot will open a pull request for review.</FieldDescription>
41
</Field>
42
</PopoverContent>
43
</Popover>
44
</ButtonGroup>
45
);
46
}

API Reference

ButtonGroup

The ButtonGroup component is a container that groups related buttons together with consistent styling.

PropTypeDefault
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.

PropTypeDefault
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.

PropTypeDefault
asValidComponent"div"
<ButtonGroup>
<ButtonGroupText>Text</ButtonGroupText>
<Button>Button</Button>
</ButtonGroup>

Use the as prop to render a custom component as the text, for example a label.

1
import { ButtonGroup, ButtonGroupText } from "~/components/ui/button-group";
2
import { Input } from "~/components/ui/input";
3
import { Label } from "~/components/ui/label";
4
5
export function ButtonGroupTextDemo() {
6
return (
7
<ButtonGroup>
8
<ButtonGroupText as={Label} for="name">
9
Text
10
</ButtonGroupText>
11
<Input placeholder="Type something here..." id="name" />
12
</ButtonGroup>
13
);
14
}

On This Page

  • Installation
  • Usage
  • Composition
  • Accessibility
  • ButtonGroup vs ToggleGroup
  • Orientation
  • Size
  • Nested
  • Separator
  • Split
  • Input
  • Input Group
  • Dropdown Menu
  • Select
  • Popover
  • API Reference
    • ButtonGroup
    • ButtonGroupSeparator
    • ButtonGroupText
Built by Kevin Abatan. The source code is available on GitHub.