Meet Frontfriend.Your brand. Your rules. Your agents, connected.Explore what’s new
DocumentationButtongroup

Buttongroup

A container that groups related buttons together with consistent styling.

Interactive preview

Rendered from the shared Frontfriend stories with React Base, the same preview Studio shows. Styling comes from your Design System; the code below is native to Reka UI.

Installation

Complete project setup first. Your configuration must select vue/reka and include your Design System ID.

pnpm exec frontfriend download button-group -f vue --primitive reka

Review dependencies reported by the downloader and keep your source changes in version control.

Usage

<script setup lang="ts">
import { ButtonGroup } from "@/components/ui/button-group";
import { Button } from "@/components/ui/button";
import * as UI from "@/components/ui/button-group";

const Component = UI.ButtonGroup;

const args = {
  "orientation": "horizontal",
} satisfies Partial<
  InstanceType<typeof Component> extends { $props: infer Props } ? Props : {}
> &
  Record<string, unknown> & { orientation?: "horizontal" | "vertical" };
</script>

<template>
  <ButtonGroup aria-label="Button group" v-bind="args"
    ><Button variant="outline">Button 1</Button
    ><Button variant="outline">Button 2</Button></ButtonGroup
  >
</template>

Examples

Default

<script setup lang="ts">
import { ButtonGroup } from "@/components/ui/button-group";
import { Button } from "@/components/ui/button";
import * as UI from "@/components/ui/button-group";

const Component = UI.ButtonGroup;

const args = {

} satisfies Partial<
  InstanceType<typeof Component> extends { $props: infer Props } ? Props : {}
> &
  Record<string, unknown>;
</script>

<template>
  <ButtonGroup v-bind="args" aria-label="Button group"
    ><Button variant="outline">Button 1</Button
    ><Button variant="outline">Button 2</Button></ButtonGroup
  >
</template>

Orientation

<script setup lang="ts">
import { ButtonGroup } from "@/components/ui/button-group";
import { Button } from "@/components/ui/button";
import { PlusIcon } from "lucide-vue-next";
import { MinusIcon } from "lucide-vue-next";
import * as UI from "@/components/ui/button-group";

const Component = UI.ButtonGroup;

const args = {

} satisfies Partial<
  InstanceType<typeof Component> extends { $props: infer Props } ? Props : {}
> &
  Record<string, unknown>;
</script>

<template>
  <ButtonGroup orientation="vertical" aria-label="Media controls" class="h-fit"
    ><Button variant="outline" size="icon" aria-label="Increase"
      ><PlusIcon /></Button
    ><Button variant="outline" size="icon" aria-label="Decrease"
      ><MinusIcon /></Button
  ></ButtonGroup>
</template>

Size

<script setup lang="ts">
import { ButtonGroup } from "@/components/ui/button-group";
import { Button } from "@/components/ui/button";
import * as UI from "@/components/ui/button-group";

const Component = UI.ButtonGroup;

const args = {

} satisfies Partial<
  InstanceType<typeof Component> extends { $props: infer Props } ? Props : {}
> &
  Record<string, unknown>;
</script>

<template>
  <div class="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></ButtonGroup
    ><ButtonGroup
      ><Button variant="outline">Default</Button
      ><Button variant="outline">Button</Button
      ><Button variant="outline">Group</Button></ButtonGroup
    ><ButtonGroup
      ><Button variant="outline" size="lg">Large</Button
      ><Button variant="outline" size="lg">Button</Button
      ><Button variant="outline" size="lg">Group</Button></ButtonGroup
    >
  </div>
</template>

Nested

<script setup lang="ts">
import { ButtonGroup } from "@/components/ui/button-group";
import { Button } from "@/components/ui/button";
import { ArrowLeftIcon } from "lucide-vue-next";
import { ArrowRightIcon } from "lucide-vue-next";
import * as UI from "@/components/ui/button-group";

const Component = UI.ButtonGroup;

const args = {

} satisfies Partial<
  InstanceType<typeof Component> extends { $props: infer Props } ? Props : {}
> &
  Record<string, unknown>;
</script>

<template>
  <ButtonGroup
    ><ButtonGroup
      ><Button
        v-for="value in [1, 2, 3, 4, 5]"
        :key="value"
        variant="outline"
        size="sm"
        >{{ value }}</Button
      ></ButtonGroup
    ><ButtonGroup
      ><Button variant="outline" size="icon" aria-label="Previous"
        ><ArrowLeftIcon /></Button
      ><Button variant="outline" size="icon" aria-label="Next"
        ><ArrowRightIcon /></Button></ButtonGroup
  ></ButtonGroup>
</template>

Separator

<script setup lang="ts">
import { ButtonGroup } from "@/components/ui/button-group";
import { Button } from "@/components/ui/button";
import { ButtonGroupSeparator } from "@/components/ui/button-group";
import * as UI from "@/components/ui/button-group";

const Component = UI.ButtonGroup;

const args = {

} satisfies Partial<
  InstanceType<typeof Component> extends { $props: infer Props } ? Props : {}
> &
  Record<string, unknown>;
</script>

<template>
  <ButtonGroup
    ><Button variant="secondary" size="sm">Copy</Button
    ><ButtonGroupSeparator /><Button variant="secondary" size="sm"
      >Paste</Button
    ></ButtonGroup
  >
</template>

Split

<script setup lang="ts">
import { ButtonGroup } from "@/components/ui/button-group";
import { Button } from "@/components/ui/button";
import { ButtonGroupSeparator } from "@/components/ui/button-group";
import { PlusIcon } from "lucide-vue-next";
import * as UI from "@/components/ui/button-group";

const Component = UI.ButtonGroup;

const args = {

} satisfies Partial<
  InstanceType<typeof Component> extends { $props: infer Props } ? Props : {}
> &
  Record<string, unknown>;
</script>

<template>
  <ButtonGroup
    ><Button variant="secondary">Button</Button><ButtonGroupSeparator /><Button
      size="icon"
      variant="secondary"
      aria-label="Add"
      ><PlusIcon /></Button
  ></ButtonGroup>
</template>

With Input

<script setup lang="ts">
import { ButtonGroup } from "@/components/ui/button-group";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { SearchIcon } from "lucide-vue-next";
import * as UI from "@/components/ui/button-group";

const Component = UI.ButtonGroup;

const args = {

} satisfies Partial<
  InstanceType<typeof Component> extends { $props: infer Props } ? Props : {}
> &
  Record<string, unknown>;
</script>

<template>
  <ButtonGroup
    ><Input placeholder="Search..." /><Button
      variant="outline"
      aria-label="Search"
      ><SearchIcon /></Button
  ></ButtonGroup>
</template>

With Input Group

<script setup lang="ts">
import { ButtonGroup } from "@/components/ui/button-group";
import { Button } from "@/components/ui/button";
import { PlusIcon } from "lucide-vue-next";
import { InputGroup } from "@/components/ui/input-group";
import { InputGroupInput } from "@/components/ui/input-group";
import { InputGroupAddon } from "@/components/ui/input-group";
import { InputGroupButton } from "@/components/ui/input-group";
import { ShareIcon } from "lucide-vue-next";
import * as UI from "@/components/ui/button-group";

const Component = UI.ButtonGroup;

const args = {

} satisfies Partial<
  InstanceType<typeof Component> extends { $props: infer Props } ? Props : {}
> &
  Record<string, unknown>;
</script>

<template>
  <ButtonGroup class="[--radius:9999rem]"
    ><ButtonGroup
      ><Button variant="outline" size="icon" aria-label="Add"
        ><PlusIcon /></Button></ButtonGroup
    ><ButtonGroup
      ><InputGroup
        ><InputGroupInput placeholder="Send a message..." /><InputGroupAddon
          align="inline-end"
          ><InputGroupButton size="icon-xs" aria-label="Share"
            ><ShareIcon /></InputGroupButton></InputGroupAddon></InputGroup></ButtonGroup
  ></ButtonGroup>
</template>

Dropdown

<script setup lang="ts">
import { ButtonGroup } from "@/components/ui/button-group";
import { Button } from "@/components/ui/button";
import { DropdownMenu } from "@/components/ui/dropdown-menu";
import { DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
import { ChevronDownIcon } from "lucide-vue-next";
import { DropdownMenuContent } from "@/components/ui/dropdown-menu";
import { DropdownMenuGroup } from "@/components/ui/dropdown-menu";
import { DropdownMenuItem } from "@/components/ui/dropdown-menu";
import { VolumeOffIcon } from "lucide-vue-next";
import { CheckIcon } from "lucide-vue-next";
import { ShareIcon } from "lucide-vue-next";
import { CopyIcon } from "lucide-vue-next";
import { DropdownMenuSeparator } from "@/components/ui/dropdown-menu";
import { TrashIcon } from "lucide-vue-next";
import * as UI from "@/components/ui/button-group";

const Component = UI.ButtonGroup;

const args = {

} satisfies Partial<
  InstanceType<typeof Component> extends { $props: infer Props } ? Props : {}
> &
  Record<string, unknown>;
</script>

<template>
  <ButtonGroup
    ><Button variant="outline">Follow</Button
    ><DropdownMenu
      ><DropdownMenuTrigger as-child
        ><Button variant="outline" class="pl-2" aria-label="More"
          ><ChevronDownIcon /></Button></DropdownMenuTrigger
      ><DropdownMenuContent align="end" class="[--radius:1rem]"
        ><DropdownMenuGroup
          ><DropdownMenuItem
            ><VolumeOffIcon />Mute Conversation</DropdownMenuItem
          ><DropdownMenuItem><CheckIcon />Mark as Read</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
  >
</template>

Select Pairing

<script setup lang="ts">
import { ref } from "vue";
import { ButtonGroup } from "@/components/ui/button-group";
import { Select } from "@/components/ui/select";
import { SelectTrigger } from "@/components/ui/select";
import { SelectContent } from "@/components/ui/select";
import { SelectItem } from "@/components/ui/select";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { ArrowRightIcon } from "lucide-vue-next";
import * as UI from "@/components/ui/button-group";

const Component = UI.ButtonGroup;

const args = {

} satisfies Partial<
  InstanceType<typeof Component> extends { $props: infer Props } ? Props : {}
> &
  Record<string, unknown>;
const currency = ref("$");
</script>

<template>
  <ButtonGroup
    ><ButtonGroup
      ><Select v-model="currency"
        ><SelectTrigger class="font-mono">{{ currency }}</SelectTrigger
        ><SelectContent class="min-w-24"
          ><SelectItem value="$"
            >$ <span class="text-neutral-mid">US Dollar</span></SelectItem
          ><SelectItem value="€"
            >€ <span class="text-neutral-mid">Euro</span></SelectItem
          ><SelectItem value="£"
            >£ <span class="text-neutral-mid">British Pound</span></SelectItem
          ></SelectContent
        ></Select
      ><Input placeholder="10.00" pattern="[0-9]*" /></ButtonGroup
    ><ButtonGroup
      ><Button aria-label="Send" size="icon" variant="outline"
        ><ArrowRightIcon /></Button></ButtonGroup
  ></ButtonGroup>
</template>

Popover Pairing

<script setup lang="ts">
import { ButtonGroup } from "@/components/ui/button-group";
import { Button } from "@/components/ui/button";
import { BotIcon } from "lucide-vue-next";
import { Popover } from "@/components/ui/popover";
import { PopoverTrigger } from "@/components/ui/popover";
import { ChevronDownIcon } from "lucide-vue-next";
import { PopoverContent } from "@/components/ui/popover";
import { Textarea } from "@/components/ui/textarea";
import * as UI from "@/components/ui/button-group";

const Component = UI.ButtonGroup;

const args = {

} satisfies Partial<
  InstanceType<typeof Component> extends { $props: infer Props } ? Props : {}
> &
  Record<string, unknown>;
</script>

<template>
  <ButtonGroup
    ><Button variant="outline"><BotIcon /> Copilot</Button
    ><Popover
      ><PopoverTrigger as-child
        ><Button variant="outline" size="icon" aria-label="Open Popover"
          ><ChevronDownIcon /></Button></PopoverTrigger
      ><PopoverContent align="end" class="rounded-xl p-0 text-sm"
        ><div class="px-4 py-3">
          <div class="text-sm font-medium">Agent Tasks</div>
        </div>
        <div class="border-t border-neutral-subtle p-4 text-sm">
          <Textarea
            placeholder="Describe your task in natural language."
            class="mb-4 resize-none"
          />
          <p class="font-medium">Start a new task with Copilot</p>
          <p class="text-neutral-mid">
            Describe your task in natural language.
          </p>
        </div></PopoverContent
      ></Popover
    ></ButtonGroup
  >
</template>

Text As Child

<script setup lang="ts">
import { ButtonGroup } from "@/components/ui/button-group";
import { ButtonGroupText } from "@/components/ui/button-group";
import { Label } from "@/components/ui/label";
import { Input } from "@/components/ui/input";
import * as UI from "@/components/ui/button-group";

const Component = UI.ButtonGroup;

const args = {

} satisfies Partial<
  InstanceType<typeof Component> extends { $props: infer Props } ? Props : {}
> &
  Record<string, unknown>;
</script>

<template>
  <ButtonGroup
    ><ButtonGroupText as-child
      ><Label for="url">https://</Label></ButtonGroupText
    ><Input id="url" placeholder="frontfriend" /><ButtonGroupText
      >.dev</ButtonGroupText
    ></ButtonGroup
  >
</template>

API reference

Frontfriend-owned props. Native HTML and inherited primitive props remain documented by Reka UI.

ButtonGroup

Prop Type Default Description
class HTMLAttributes["class"] —
orientation ButtonGroupVariants["orientation"] —

ButtonGroupSeparator

Prop Type Default Description
class HTMLAttributes["class"] —

ButtonGroupText

Prop Type Default Description
as keyof HTMLElementTagNameMap —
asChild boolean —
class HTMLAttributes["class"] —
orientation "horizontal" | "vertical" —

Configuration determines the actual classes and available styling hooks. See Theming & component configuration before introducing a repeated local override.