cortexcn

Button Group

A container that groups related buttons together with consistent styling.

Loading…
import { PlusIcon } from "lucide-react";import { Button } from "@/components/ui/button";import { ButtonGroup } from "@/components/ui/button-group";export default function ButtonGroupDemo() {  return (    <ButtonGroup>      <Button variant="outline">Archive</Button>      <Button variant="outline">Report</Button>      <Button variant="outline">Snooze</Button>      <Button variant="outline" size="icon" aria-label="Add">        <PlusIcon />      </Button>    </ButtonGroup>  );}

Installation

npx shadcn@latest add @cortexcn/button-group
Copy and paste the following code into your project.
components/ui/button-group.tsx
import * as React from "react";import { cva, type VariantProps } from "class-variance-authority";import { Slot } from "radix-ui";import { cn } from "@/lib/utils";import { Separator } from "@/components/ui/separator";const buttonGroupVariants = cva(  "flex w-fit items-stretch [&>*]:relative [&>*]:focus-visible:z-10 [&>[data-slot=select-trigger]:not([class*='w-'])]:w-fit [&>input]:flex-1 has-[>[data-slot=button-group]]:gap-2",  {    variants: {      orientation: {        horizontal:          "[&>*:not(:first-child)]:rounded-l-none [&>*:not(:first-child)]:border-l-0 [&>*:not(:last-child)]:rounded-r-none",        vertical:          "flex-col [&>*:not(:first-child)]:rounded-t-none [&>*:not(:first-child)]:border-t-0 [&>*:not(:last-child)]:rounded-b-none",      },    },    defaultVariants: {      orientation: "horizontal",    },  },);function ButtonGroup({  className,  orientation = "horizontal",  ...props}: React.ComponentProps<"div"> & VariantProps<typeof buttonGroupVariants>) {  return (    <div      data-slot="button-group"      data-orientation={orientation}      role="group"      className={cn(buttonGroupVariants({ orientation }), className)}      {...props}    />  );}function ButtonGroupText({  className,  asChild = false,  ...props}: React.ComponentProps<"div"> & { asChild?: boolean }) {  const Comp = asChild ? Slot.Root : "div";  return (    <Comp      className={cn(        "flex items-center gap-2 rounded-md border bg-muted px-4 text-sm font-medium shadow-xs [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4",        className,      )}      {...props}    />  );}function ButtonGroupSeparator({  className,  orientation = "vertical",  ...props}: React.ComponentProps<typeof Separator>) {  return (    <Separator      data-slot="button-group-separator"      orientation={orientation}      className={cn(        "relative !m-0 self-stretch bg-input data-[orientation=vertical]:h-auto",        className,      )}      {...props}    />  );}export {  ButtonGroup,  ButtonGroupSeparator,  ButtonGroupText,  buttonGroupVariants,};
Update the import paths to match your project setup.

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
└── 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.

Examples

Orientation

Set the orientation prop to change the button group layout.

Loading…
import { MinusIcon, PlusIcon } from "lucide-react";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"      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.

Loading…
import { PlusIcon } from "lucide-react";import { Button } from "@/components/ui/button";import { ButtonGroup } from "@/components/ui/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>  );}

Separator

The ButtonGroupSeparator component visually divides buttons within a group.

Loading…
import { Button } from "@/components/ui/button";import {  ButtonGroup,  ButtonGroupSeparator,} from "@/components/ui/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.

Loading…
import { PlusIcon } from "lucide-react";import { Button } from "@/components/ui/button";import {  ButtonGroup,  ButtonGroupSeparator,} from "@/components/ui/button-group";export default function ButtonGroupSplit() {  return (    <ButtonGroup>      <Button variant="secondary">Button</Button>      <ButtonGroupSeparator />      <Button size="icon" variant="secondary">        <PlusIcon />      </Button>    </ButtonGroup>  );}

Input

Wrap an Input component with buttons.

Loading…
import { SearchIcon } from "lucide-react";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 className="w-full max-w-sm">      <Input placeholder="Search..." />      <Button variant="outline" aria-label="Search">        <SearchIcon />      </Button>    </ButtonGroup>  );}

Input Group

Nest an InputGroup to create complex input layouts.

Loading…
"use client";import * as React from "react";import { AudioLinesIcon, PlusIcon } from "lucide-react";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 default function ButtonGroupInputGroup() {  const [voiceEnabled, setVoiceEnabled] = React.useState(false);  return (    <ButtonGroup className="w-full max-w-sm [--radius:9999rem]">      <ButtonGroup>        <Button variant="outline" size="icon">          <PlusIcon />        </Button>      </ButtonGroup>      <ButtonGroup className="flex-1">        <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>  );}

Create a split button group with a DropdownMenu component.

Loading…
"use client";import {  AlertTriangleIcon,  CheckIcon,  ChevronDownIcon,  CopyIcon,  ShareIcon,  TrashIcon,  UserRoundXIcon,  VolumeOffIcon,} from "lucide-react";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 default function ButtonGroupDropdown() {  return (    <ButtonGroup>      <Button variant="outline">Follow</Button>      <DropdownMenu>        <DropdownMenuTrigger asChild>          <Button variant="outline" className="pl-2!" aria-label="More options">            <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.

Loading…
"use client";import * as React from "react";import { ArrowRightIcon } from "lucide-react";import { Button } from "@/components/ui/button";import { ButtonGroup } from "@/components/ui/button-group";import { Input } from "@/components/ui/input";import {  Select,  SelectContent,  SelectGroup,  SelectItem,  SelectTrigger,} from "@/components/ui/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((c) => (                <SelectItem key={c.value} value={c.value}>                  {c.value}{" "}                  <span className="text-muted-foreground">{c.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.

Loading…
import { BotIcon, ChevronDownIcon } from "lucide-react";import { Button } 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">        <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

Button groups respect the document direction — pass dir="rtl" to flip the layout.

Loading…
"use client";import * as React from "react";import { ArrowLeftIcon, MoreHorizontalIcon } from "lucide-react";import { Button } from "@/components/ui/button";import { ButtonGroup } from "@/components/ui/button-group";import {  DropdownMenu,  DropdownMenuContent,  DropdownMenuItem,  DropdownMenuTrigger,} from "@/components/ui/dropdown-menu";const content = {  ltr: {    dir: "ltr" as const,    archive: "Archive",    report: "Report",    snooze: "Snooze",    markAsRead: "Mark as Read",  },  rtl: {    dir: "rtl" as const,    archive: "أرشفة",    report: "تقرير",    snooze: "تأجيل",    markAsRead: "وضع علامة كمقروء",  },};export default function ButtonGroupRtl() {  const [mode, setMode] = React.useState<"rtl" | "ltr">("rtl");  const t = content[mode];  return (    <div className="flex flex-col items-center gap-6">      <Button        variant="ghost"        size="sm"        onClick={() => setMode((m) => (m === "rtl" ? "ltr" : "rtl"))}      >        Toggle dir ({mode})      </Button>      <div dir={t.dir}>        <ButtonGroup>          <Button variant="outline" size="icon" aria-label="Go Back">            <ArrowLeftIcon className="rtl:rotate-180" />          </Button>          <Button variant="outline">{t.archive}</Button>          <Button variant="outline">{t.report}</Button>          <Button variant="outline">{t.snooze}</Button>          <DropdownMenu>            <DropdownMenuTrigger asChild>              <Button variant="outline" size="icon" aria-label="More Options">                <MoreHorizontalIcon />              </Button>            </DropdownMenuTrigger>            <DropdownMenuContent align="end" className="w-40">              <DropdownMenuItem>{t.markAsRead}</DropdownMenuItem>            </DropdownMenuContent>          </DropdownMenu>        </ButtonGroup>      </div>    </div>  );}

API Reference

ButtonGroup

PropTypeDefault
orientation"horizontal" | "vertical""horizontal"

ButtonGroupSeparator

PropTypeDefault
orientation"horizontal" | "vertical""vertical"

ButtonGroupText

PropTypeDefault
asChildbooleanfalse

On this page