Button Group 按钮组
将相关按钮与控件组合为连续操作区。
"use client"
import * as React from "react"
import {
ArchiveIcon,
ArrowLeftIcon,
CalendarPlusIcon,
ClockIcon,
ListFilterIcon,
MailCheckIcon,
MoreHorizontalIcon,
TagIcon,
Trash2Icon,
} from "lucide-react"
import { Button } from "@scopify/ui/shadcn/components/button"
import { ButtonGroup } from "@scopify/ui/shadcn/components/button-group"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuSeparator,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuTrigger,
} from "@scopify/ui/shadcn/components/dropdown-menu"
export default function ButtonGroupDemo() {
const [label, setLabel] = React.useState("personal")
return (
<ButtonGroup>
<ButtonGroup className="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>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="icon" aria-label="More Options">
<MoreHorizontalIcon />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="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}
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="destructive">
<Trash2Icon />
Trash
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
</ButtonGroup>
</ButtonGroup>
)
}
Scopify 说明
Installation
npx shadcn@latest add button-groupUsage
import {
ButtonGroup,
ButtonGroupSeparator,
ButtonGroupText,
} from "@scopify/ui/shadcn/components/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 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.
Orientation
Set the orientation prop to change the button group layout.
import { MinusIcon, PlusIcon } from "lucide-react"
import { Button } from "@scopify/ui/shadcn/components/button"
import { ButtonGroup } from "@scopify/ui/shadcn/components/button-group"
export default function ButtonGroupOrientation() {
return (
<ButtonGroup
orientation="vertical"
aria-label="Media controls"
className="h-fit"
>
<Button variant="outline" size="icon">
<PlusIcon />
</Button>
<Button variant="outline" size="icon">
<MinusIcon />
</Button>
</ButtonGroup>
)
}
Size
Control the size of buttons using the size prop on individual buttons.
import { PlusIcon } from "lucide-react"
import { Button } from "@scopify/ui/shadcn/components/button"
import { ButtonGroup } from "@scopify/ui/shadcn/components/button-group"
export default function ButtonGroupSize() {
return (
<div className="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">
<PlusIcon />
</Button>
</ButtonGroup>
<ButtonGroup>
<Button variant="outline">Default</Button>
<Button variant="outline">Button</Button>
<Button variant="outline">Group</Button>
<Button variant="outline" size="icon">
<PlusIcon />
</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">
<PlusIcon />
</Button>
</ButtonGroup>
</div>
)
}
Nested
Nest <ButtonGroup> components to create button groups with spacing.
import { AudioLinesIcon, PlusIcon } from "lucide-react"
import { Button } from "@scopify/ui/shadcn/components/button"
import { ButtonGroup } from "@scopify/ui/shadcn/components/button-group"
import { Input } from "@scopify/ui/shadcn/components/input"
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
} from "@scopify/ui/shadcn/components/input-group"
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@scopify/ui/shadcn/components/tooltip"
export function ButtonGroupNested() {
return (
<ButtonGroup>
<ButtonGroup>
<Button variant="outline" size="icon">
<PlusIcon />
</Button>
</ButtonGroup>
<ButtonGroup>
<InputGroup>
<InputGroupInput placeholder="Send a message..." />
<Tooltip>
<TooltipTrigger asChild>
<InputGroupAddon align="inline-end">
<AudioLinesIcon />
</InputGroupAddon>
</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 { Button } from "@scopify/ui/shadcn/components/button"
import {
ButtonGroup,
ButtonGroupSeparator,
} from "@scopify/ui/shadcn/components/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 { IconPlus } from "lucide-react"
import { Button } from "@scopify/ui/shadcn/components/button"
import {
ButtonGroup,
ButtonGroupSeparator,
} from "@scopify/ui/shadcn/components/button-group"
export default 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 { SearchIcon } from "lucide-react"
import { Button } from "@scopify/ui/shadcn/components/button"
import { ButtonGroup } from "@scopify/ui/shadcn/components/button-group"
import { Input } from "@scopify/ui/shadcn/components/input"
export default function ButtonGroupInput() {
return (
<ButtonGroup>
<Input placeholder="Search..." />
<Button variant="outline" aria-label="Search">
<SearchIcon />
</Button>
</ButtonGroup>
)
}
Input Group
Wrap an InputGroup component to create complex input layouts.
"use client"
import * as React from "react"
import { AudioLinesIcon, PlusIcon } from "lucide-react"
import { Button } from "@scopify/ui/shadcn/components/button"
import { ButtonGroup } from "@scopify/ui/shadcn/components/button-group"
import {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupInput,
} from "@scopify/ui/shadcn/components/input-group"
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@scopify/ui/shadcn/components/tooltip"
export default function ButtonGroupInputGroup() {
const [voiceEnabled, setVoiceEnabled] = React.useState(false)
return (
<ButtonGroup className="[--radius:9999rem]">
<ButtonGroup>
<Button variant="outline" size="icon">
<PlusIcon />
</Button>
</ButtonGroup>
<ButtonGroup>
<InputGroup>
<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}
className="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}
>
<AudioLinesIcon />
</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 {
AlertTriangleIcon,
CheckIcon,
ChevronDownIcon,
CopyIcon,
ShareIcon,
TrashIcon,
UserRoundXIcon,
VolumeOffIcon,
} from "lucide-react"
import { Button } from "@scopify/ui/shadcn/components/button"
import { ButtonGroup } from "@scopify/ui/shadcn/components/button-group"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@scopify/ui/shadcn/components/dropdown-menu"
export default function ButtonGroupDropdown() {
return (
<ButtonGroup>
<Button variant="outline">Follow</Button>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" className="pl-2!">
<ChevronDownIcon />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-44">
<DropdownMenuGroup>
<DropdownMenuItem>
<VolumeOffIcon />
Mute Conversation
</DropdownMenuItem>
<DropdownMenuItem>
<CheckIcon />
Mark as Read
</DropdownMenuItem>
<DropdownMenuItem>
<AlertTriangleIcon />
Report Conversation
</DropdownMenuItem>
<DropdownMenuItem>
<UserRoundXIcon />
Block User
</DropdownMenuItem>
<DropdownMenuItem>
<ShareIcon />
Share Conversation
</DropdownMenuItem>
<DropdownMenuItem>
<CopyIcon />
Copy Conversation
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem variant="destructive">
<TrashIcon />
Delete Conversation
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
</ButtonGroup>
)
}
Select
Pair with a Select component.
"use client"
import * as React from "react"
import { ArrowRightIcon } from "lucide-react"
import { Button } from "@scopify/ui/shadcn/components/button"
import { ButtonGroup } from "@scopify/ui/shadcn/components/button-group"
import { Input } from "@scopify/ui/shadcn/components/input"
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectTrigger,
} from "@scopify/ui/shadcn/components/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 className="font-mono">{currency}</SelectTrigger>
<SelectContent className="min-w-24">
<SelectGroup>
{CURRENCIES.map((currency) => (
<SelectItem key={currency.value} value={currency.value}>
{currency.value}{" "}
<span className="text-muted-foreground">
{currency.label}
</span>
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
<Input placeholder="10.00" pattern="[0-9]*" />
</ButtonGroup>
<ButtonGroup>
<Button aria-label="Send" size="icon" variant="outline">
<ArrowRightIcon />
</Button>
</ButtonGroup>
</ButtonGroup>
)
}
Popover
Use with a Popover component.
import { BotIcon, ChevronDownIcon } from "lucide-react"
import { Button } from "@scopify/ui/shadcn/components/button"
import { ButtonGroup } from "@scopify/ui/shadcn/components/button-group"
import {
Field,
FieldDescription,
FieldLabel,
} from "@scopify/ui/shadcn/components/field"
import {
Popover,
PopoverContent,
PopoverDescription,
PopoverHeader,
PopoverTitle,
PopoverTrigger,
} from "@scopify/ui/shadcn/components/popover"
import { Textarea } from "@scopify/ui/shadcn/components/textarea"
export default function ButtonGroupPopover() {
return (
<ButtonGroup>
<Button variant="outline">
<BotIcon /> Copilot
</Button>
<Popover>
<PopoverTrigger asChild>
<Button variant="outline" size="icon" aria-label="Open Popover">
<ChevronDownIcon />
</Button>
</PopoverTrigger>
<PopoverContent align="end" className="rounded-xl text-sm">
<PopoverHeader>
<PopoverTitle>Start a new task with Copilot</PopoverTitle>
<PopoverDescription>
Describe your task in natural language.
</PopoverDescription>
</PopoverHeader>
<Field>
<FieldLabel htmlFor="task" className="sr-only">
Task Description
</FieldLabel>
<Textarea
id="task"
placeholder="I need to..."
className="resize-none"
/>
<FieldDescription>
Copilot will open a pull request for review.
</FieldDescription>
</Field>
</PopoverContent>
</Popover>
</ButtonGroup>
)
}
RTL
To enable RTL support in shadcn/ui, see the RTL configuration guide.
"use client"
import * as React from "react"
import {
ArchiveIcon,
ArrowLeftIcon,
CalendarPlusIcon,
ClockIcon,
ListFilterIcon,
MailCheckIcon,
MoreHorizontalIcon,
TagIcon,
Trash2Icon,
} from "lucide-react"
import {
useTranslation,
type Translations,
} from "@/components/language-selector"
import { Button } from "@/styles/radix-nova/ui-rtl/button"
import { ButtonGroup } from "@/styles/radix-nova/ui-rtl/button-group"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuSeparator,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuTrigger,
} from "@/styles/radix-nova/ui-rtl/dropdown-menu"
const translations: Translations = {
en: {
dir: "ltr",
values: {
archive: "Archive",
report: "Report",
snooze: "Snooze",
markAsRead: "Mark as Read",
addToCalendar: "Add to Calendar",
addToList: "Add to List",
labelAs: "Label As...",
personal: "Personal",
work: "Work",
other: "Other",
trash: "Trash",
},
},
ar: {
dir: "rtl",
values: {
archive: "أرشفة",
report: "تقرير",
snooze: "تأجيل",
markAsRead: "وضع علامة كمقروء",
addToCalendar: "إضافة إلى التقويم",
addToList: "إضافة إلى القائمة",
labelAs: "تصنيف كـ...",
personal: "شخصي",
work: "عمل",
other: "آخر",
trash: "سلة المهملات",
},
},
he: {
dir: "rtl",
values: {
archive: "ארכיון",
report: "דוח",
snooze: "דחה",
markAsRead: "סמן כנקרא",
addToCalendar: "הוסף ליומן",
addToList: "הוסף לרשימה",
labelAs: "תייג כ...",
personal: "אישי",
work: "עבודה",
other: "אחר",
trash: "פח",
},
},
}
export function ButtonGroupRtl() {
const { dir, t, language } = useTranslation(translations, "ar")
const [label, setLabel] = React.useState("personal")
return (
<div dir={dir}>
<ButtonGroup>
<ButtonGroup className="hidden sm:flex">
<Button variant="outline" size="icon" aria-label="Go Back">
<ArrowLeftIcon className="rtl:rotate-180" />
</Button>
</ButtonGroup>
<ButtonGroup>
<Button variant="outline">{t.archive}</Button>
<Button variant="outline">{t.report}</Button>
</ButtonGroup>
<ButtonGroup>
<Button variant="outline">{t.snooze}</Button>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="icon" aria-label="More Options">
<MoreHorizontalIcon />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent
align={dir === "rtl" ? "end" : "end"}
data-lang={dir === "rtl" ? language : undefined}
className="w-40"
>
<DropdownMenuGroup>
<DropdownMenuItem>
<MailCheckIcon />
{t.markAsRead}
</DropdownMenuItem>
<DropdownMenuItem>
<ArchiveIcon />
{t.archive}
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem>
<ClockIcon />
{t.snooze}
</DropdownMenuItem>
<DropdownMenuItem>
<CalendarPlusIcon />
{t.addToCalendar}
</DropdownMenuItem>
<DropdownMenuItem>
<ListFilterIcon />
{t.addToList}
</DropdownMenuItem>
<DropdownMenuSub>
<DropdownMenuSubTrigger>
<TagIcon />
{t.labelAs}
</DropdownMenuSubTrigger>
<DropdownMenuSubContent
data-lang={dir === "rtl" ? language : undefined}
>
<DropdownMenuRadioGroup
value={label}
onValueChange={setLabel}
>
<DropdownMenuRadioItem value="personal">
{t.personal}
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="work">
{t.work}
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="other">
{t.other}
</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>
</DropdownMenuSubContent>
</DropdownMenuSub>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem variant="destructive">
<Trash2Icon />
{t.trash}
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
</ButtonGroup>
</ButtonGroup>
</div>
)
}
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 "@scopify/ui/shadcn/components/button-group"
import { Label } from "@scopify/ui/shadcn/components/label"
export function ButtonGroupTextDemo() {
return (
<ButtonGroup>
<ButtonGroupText asChild>
<Label htmlFor="name">Text</Label>
</ButtonGroupText>
<Input placeholder="Type something here..." id="name" />
</ButtonGroup>
)
}