이 사이트는 shadcn-svelte 공식 문서의 한국어 번역입니다.
6.9k

Button Group

Previous Next

일관된 스타일로 관련 버튼들을 그룹화하는 컨테이너입니다.

<script lang="ts">
  import Archive from "@lucide/svelte/icons/archive";
  import ArrowLeft from "@lucide/svelte/icons/arrow-left";
  import CalendarPlus from "@lucide/svelte/icons/calendar-plus";
  import Clock from "@lucide/svelte/icons/clock";
  import ListFilter from "@lucide/svelte/icons/list-filter";
  import MailCheck from "@lucide/svelte/icons/mail-check";
  import MoreHorizontal from "@lucide/svelte/icons/more-horizontal";
  import Tag from "@lucide/svelte/icons/tag";
  import Trash2 from "@lucide/svelte/icons/trash-2";
  import { Button } from "$lib/components/ui/button/index.js";
  import * as ButtonGroup from "$lib/components/ui/button-group/index.js";
  import * as DropdownMenu from "$lib/components/ui/dropdown-menu/index.js";
 
  let label = $state("personal");
</script>
 
<ButtonGroup.Root>
  <ButtonGroup.Root class="hidden sm:flex">
    <Button variant="outline" size="icon-sm" aria-label="뒤로 가기">
      <ArrowLeft />
    </Button>
  </ButtonGroup.Root>
  <ButtonGroup.Root>
    <Button size="sm" variant="outline">보관</Button>
    <Button size="sm" variant="outline">신고</Button>
  </ButtonGroup.Root>
  <ButtonGroup.Root>
    <Button size="sm" variant="outline">일시중지</Button>
    <DropdownMenu.Root>
      <DropdownMenu.Trigger>
        {#snippet child({ props })}
          <Button
            {...props}
            variant="outline"
            size="icon-sm"
            aria-label="더 많은 옵션"
          >
            <MoreHorizontal />
          </Button>
        {/snippet}
      </DropdownMenu.Trigger>
      <DropdownMenu.Content align="end" class="w-52">
        <DropdownMenu.Group>
          <DropdownMenu.Item>
            <MailCheck />
            읽음으로 표시
          </DropdownMenu.Item>
          <DropdownMenu.Item>
            <Archive />
            보관
          </DropdownMenu.Item>
        </DropdownMenu.Group>
        <DropdownMenu.Separator />
        <DropdownMenu.Group>
          <DropdownMenu.Item>
            <Clock />
            일시중지
          </DropdownMenu.Item>
          <DropdownMenu.Item>
            <CalendarPlus />
            캘린더에 추가
          </DropdownMenu.Item>
          <DropdownMenu.Item>
            <ListFilter />
            목록에 추가
          </DropdownMenu.Item>
          <DropdownMenu.Sub>
            <DropdownMenu.SubTrigger>
              <Tag />
              라벨 지정...
            </DropdownMenu.SubTrigger>
            <DropdownMenu.SubContent>
              <DropdownMenu.RadioGroup bind:value={label}>
                <DropdownMenu.RadioItem value="personal">
                  개인
                </DropdownMenu.RadioItem>
                <DropdownMenu.RadioItem value="work"
                  >업무</DropdownMenu.RadioItem
                >
                <DropdownMenu.RadioItem value="other"
                  >기타</DropdownMenu.RadioItem
                >
              </DropdownMenu.RadioGroup>
            </DropdownMenu.SubContent>
          </DropdownMenu.Sub>
        </DropdownMenu.Group>
        <DropdownMenu.Separator />
        <DropdownMenu.Group>
          <DropdownMenu.Item class="text-destructive focus:text-destructive">
            <Trash2 />
            휴지통
          </DropdownMenu.Item>
        </DropdownMenu.Group>
      </DropdownMenu.Content>
    </DropdownMenu.Root>
  </ButtonGroup.Root>
</ButtonGroup.Root>

설치

pnpm dlx shadcn-svelte@latest add button-group

사용법

<script lang="ts">
  import * as ButtonGroup from "$lib/components/ui/button-group/index.js";
</script>
<ButtonGroup.Root>
  <Button>버튼 1</Button>
  <Button>버튼 2</Button>
</ButtonGroup.Root>

접근성

  • ButtonGroup 컴포넌트는 role 속성이 group으로 설정되어 있습니다.
  • tabindex를 사용하여 그룹 내 버튼 간 탐색할 수 있습니다.
  • aria-label 또는 aria-labelledby를 사용하여 버튼 그룹에 레이블을 지정하세요.
<ButtonGroup aria-label="버튼 그룹">
  <Button>버튼 1</Button>
  <Button>버튼 2</Button>
</ButtonGroup>

ButtonGroup vs ToggleGroup

  • 동작을 수행하는 버튼을 그룹화할 때는 ButtonGroup 컴포넌트를 사용하세요.
  • 상태를 토글하는 버튼을 그룹화할 때는 ToggleGroup 컴포넌트를 사용하세요.

예제

방향

orientation prop을 설정하여 버튼 그룹 레이아웃을 변경할 수 있습니다.

<script lang="ts">
  import Minus from "@lucide/svelte/icons/minus";
  import Plus from "@lucide/svelte/icons/plus";
  import { Button } from "$lib/components/ui/button/index.js";
  import * as ButtonGroup from "$lib/components/ui/button-group/index.js";
</script>
 
<ButtonGroup.Root
  orientation="vertical"
  aria-label="미디어 컨트롤"
  class="h-fit"
>
  <Button variant="outline" size="icon">
    <Plus />
  </Button>
  <Button variant="outline" size="icon">
    <Minus />
  </Button>
</ButtonGroup.Root>

크기

개별 버튼의 size prop을 사용하여 버튼 크기를 제어할 수 있습니다.

<script lang="ts">
  import Plus from "@lucide/svelte/icons/plus";
  import { Button } from "$lib/components/ui/button/index.js";
  import * as ButtonGroup from "$lib/components/ui/button-group/index.js";
</script>
 
<div class="flex flex-col items-start gap-8">
  <ButtonGroup.Root>
    <Button variant="outline" size="sm">작은</Button>
    <Button variant="outline" size="sm">버튼</Button>
    <Button variant="outline" size="sm">그룹</Button>
    <Button variant="outline" size="icon-sm">
      <Plus />
    </Button>
  </ButtonGroup.Root>
  <ButtonGroup.Root>
    <Button variant="outline">기본</Button>
    <Button variant="outline">버튼</Button>
    <Button variant="outline">그룹</Button>
    <Button variant="outline" size="icon">
      <Plus />
    </Button>
  </ButtonGroup.Root>
  <ButtonGroup.Root>
    <Button variant="outline" size="lg">큰</Button>
    <Button variant="outline" size="lg">버튼</Button>
    <Button variant="outline" size="lg">그룹</Button>
    <Button variant="outline" size="icon-lg">
      <Plus />
    </Button>
  </ButtonGroup.Root>
</div>

중첩

ButtonGroup 컴포넌트를 중첩하여 간격이 있는 버튼 그룹을 만들 수 있습니다.

<script lang="ts">
  import ArrowLeft from "@lucide/svelte/icons/arrow-left";
  import ArrowRight from "@lucide/svelte/icons/arrow-right";
  import { Button } from "$lib/components/ui/button/index.js";
  import * as ButtonGroup from "$lib/components/ui/button-group/index.js";
</script>
 
<ButtonGroup.Root>
  <ButtonGroup.Root>
    <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.Root>
  <ButtonGroup.Root>
    <Button variant="outline" size="icon-sm" aria-label="이전">
      <ArrowLeft />
    </Button>
    <Button variant="outline" size="icon-sm" aria-label="다음">
      <ArrowRight />
    </Button>
  </ButtonGroup.Root>
</ButtonGroup.Root>

구분선

ButtonGroupSeparator 컴포넌트는 그룹 내 버튼을 시각적으로 구분합니다.

outline variant를 가진 버튼은 테두리가 있어 구분선이 필요하지 않습니다. 다른 variant의 경우 시각적 계층을 개선하기 위해 구분선을 사용하는 것이 좋습니다.

<script lang="ts">
  import { Button } from "$lib/components/ui/button/index.js";
  import * as ButtonGroup from "$lib/components/ui/button-group/index.js";
</script>
 
<ButtonGroup.Root>
  <Button variant="secondary" size="sm">복사</Button>
  <ButtonGroup.Separator />
  <Button variant="secondary" size="sm">붙여넣기</Button>
</ButtonGroup.Root>

분할

ButtonGroupSeparator로 구분된 두 개의 버튼을 추가하여 분할 버튼 그룹을 만들 수 있습니다.

<script lang="ts">
  import Plus from "@lucide/svelte/icons/plus";
  import { Button } from "$lib/components/ui/button/index.js";
  import * as ButtonGroup from "$lib/components/ui/button-group/index.js";
</script>
 
<ButtonGroup.Root>
  <Button variant="secondary">버튼</Button>
  <ButtonGroup.Separator />
  <Button variant="secondary" size="icon">
    <Plus />
  </Button>
</ButtonGroup.Root>

입력

Input 컴포넌트를 버튼으로 감쌀 수 있습니다.

<script lang="ts">
  import Search from "@lucide/svelte/icons/search";
  import { Button } from "$lib/components/ui/button/index.js";
  import * as ButtonGroup from "$lib/components/ui/button-group/index.js";
  import { Input } from "$lib/components/ui/input/index.js";
</script>
 
<ButtonGroup.Root>
  <Input placeholder="검색..." />
  <Button variant="outline" size="icon" aria-label="검색">
    <Search />
  </Button>
</ButtonGroup.Root>

입력 그룹

InputGroup 컴포넌트를 감싸서 복잡한 입력 레이아웃을 만들 수 있습니다.

<script lang="ts">
  import AudioLines from "@lucide/svelte/icons/audio-lines";
  import Plus from "@lucide/svelte/icons/plus";
  import { Button } from "$lib/components/ui/button/index.js";
  import * as ButtonGroup from "$lib/components/ui/button-group/index.js";
  import * as InputGroup from "$lib/components/ui/input-group/index.js";
  import * as Tooltip from "$lib/components/ui/tooltip/index.js";
 
  let voiceEnabled = $state(false);
</script>
 
<ButtonGroup.Root class="[--radius:9999rem]">
  <ButtonGroup.Root>
    <Button variant="outline" size="icon">
      <Plus />
    </Button>
  </ButtonGroup.Root>
  <ButtonGroup.Root>
    <InputGroup.Root>
      <InputGroup.Input
        placeholder={voiceEnabled
          ? "오디오 녹음 및 전송..."
          : "메시지 보내기..."}
        disabled={voiceEnabled}
      />
      <InputGroup.Addon align="inline-end">
        <Tooltip.Root>
          <Tooltip.Trigger>
            {#snippet child({ props })}
              <InputGroup.Button
                {...props}
                onclick={() => (voiceEnabled = !voiceEnabled)}
                size="icon-xs"
                data-active={voiceEnabled}
                class="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}
              >
                <AudioLines />
              </InputGroup.Button>
            {/snippet}
          </Tooltip.Trigger>
          <Tooltip.Content>음성 모드</Tooltip.Content>
        </Tooltip.Root>
      </InputGroup.Addon>
    </InputGroup.Root>
  </ButtonGroup.Root>
</ButtonGroup.Root>

드롭다운 메뉴

DropdownMenu 컴포넌트와 함께 분할 버튼 그룹을 만들 수 있습니다.

<script lang="ts">
  import AlertTriangle from "@lucide/svelte/icons/alert-triangle";
  import ChevronDown from "@lucide/svelte/icons/chevron-down";
  import CopyIcon from "@tabler/icons-svelte/icons/copy";
  import CheckIcon from "@tabler/icons-svelte/icons/check";
  import Share from "@lucide/svelte/icons/share";
  import Trash from "@lucide/svelte/icons/trash";
  import UserRoundX from "@lucide/svelte/icons/user-round-x";
  import VolumeOff from "@lucide/svelte/icons/volume-off";
  import { Button } from "$lib/components/ui/button/index.js";
  import * as ButtonGroup from "$lib/components/ui/button-group/index.js";
  import * as DropdownMenu from "$lib/components/ui/dropdown-menu/index.js";
</script>
 
<ButtonGroup.Root>
  <Button variant="outline">팔로우</Button>
  <DropdownMenu.Root>
    <DropdownMenu.Trigger>
      {#snippet child({ props })}
        <Button {...props} variant="outline" class="!ps-2">
          <ChevronDown />
        </Button>
      {/snippet}
    </DropdownMenu.Trigger>
    <DropdownMenu.Content align="end" class="[--radius:1rem]">
      <DropdownMenu.Group>
        <DropdownMenu.Item>
          <VolumeOff />
          대화 음소거
        </DropdownMenu.Item>
        <DropdownMenu.Item>
          <CheckIcon />
          읽음으로 표시
        </DropdownMenu.Item>
        <DropdownMenu.Item>
          <AlertTriangle />
          대화 신고
        </DropdownMenu.Item>
        <DropdownMenu.Item>
          <UserRoundX />
          사용자 차단
        </DropdownMenu.Item>
        <DropdownMenu.Item>
          <Share />
          대화 공유
        </DropdownMenu.Item>
        <DropdownMenu.Item>
          <CopyIcon />
          대화 복사
        </DropdownMenu.Item>
      </DropdownMenu.Group>
      <DropdownMenu.Separator />
      <DropdownMenu.Group>
        <DropdownMenu.Item variant="destructive">
          <Trash />
          대화 삭제
        </DropdownMenu.Item>
      </DropdownMenu.Group>
    </DropdownMenu.Content>
  </DropdownMenu.Root>
</ButtonGroup.Root>

셀렉트

Select 컴포넌트와 함께 사용할 수 있습니다.

<script lang="ts">
  import ArrowRight from "@lucide/svelte/icons/arrow-right";
  import { Button } from "$lib/components/ui/button/index.js";
  import * as ButtonGroup from "$lib/components/ui/button-group/index.js";
  import { Input } from "$lib/components/ui/input/index.js";
  import * as Select from "$lib/components/ui/select/index.js";
 
  const CURRENCIES = [
    {
      value: "$",
      label: "미국 달러"
    },
    {
      value: "€",
      label: "유로"
    },
    {
      value: "£",
      label: "영국 파운드"
    }
  ];
 
  let currency = $state("$");
</script>
 
<ButtonGroup.Root>
  <ButtonGroup.Root>
    <Select.Root type="single" bind:value={currency}>
      <Select.Trigger class="font-mono">
        {currency}
      </Select.Trigger>
      <Select.Content class="min-w-24">
        {#each CURRENCIES as currencyOption (currencyOption.value)}
          <Select.Item value={currencyOption.value}>
            {currencyOption.value}
            <span class="text-muted-foreground">{currencyOption.label}</span>
          </Select.Item>
        {/each}
      </Select.Content>
    </Select.Root>
    <Input placeholder="10.00" pattern="[0-9]*" />
  </ButtonGroup.Root>
  <ButtonGroup.Root>
    <Button aria-label="보내기" size="icon" variant="outline">
      <ArrowRight />
    </Button>
  </ButtonGroup.Root>
</ButtonGroup.Root>

팝오버

Popover 컴포넌트와 함께 사용할 수 있습니다.

<script lang="ts">
  import Bot from "@lucide/svelte/icons/bot";
  import ChevronDown from "@lucide/svelte/icons/chevron-down";
  import { Button } from "$lib/components/ui/button/index.js";
  import * as ButtonGroup from "$lib/components/ui/button-group/index.js";
  import * as Popover from "$lib/components/ui/popover/index.js";
  import { Separator } from "$lib/components/ui/separator/index.js";
  import { Textarea } from "$lib/components/ui/textarea/index.js";
</script>
 
<ButtonGroup.Root>
  <Button variant="outline" size="sm">
    <Bot />
    Copilot
  </Button>
  <Popover.Root>
    <Popover.Trigger>
      {#snippet child({ props })}
        <Button
          {...props}
          variant="outline"
          size="icon-sm"
          aria-label="팝오버 열기"
        >
          <ChevronDown />
        </Button>
      {/snippet}
    </Popover.Trigger>
    <Popover.Content align="end" class="rounded-xl p-0 text-sm">
      <div class="px-4 py-3">
        <div class="text-sm font-medium">에이전트 작업</div>
      </div>
      <Separator />
      <div class="p-4 text-sm *:[p:not(:last-child)]:mb-2">
        <Textarea
          placeholder="작업을 자연어로 설명하세요."
          class="mb-4 resize-none"
        />
        <p class="font-medium">Copilot으로 새 작업 시작</p>
        <p class="text-muted-foreground">
          작업을 자연어로 설명하세요. Copilot이 백그라운드에서 작업하고 검토를
          위한 풀 리퀘스트를 열어줍니다.
        </p>
      </div>
    </Popover.Content>
  </Popover.Root>
</ButtonGroup.Root>