<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> 설치
다음 코드를 프로젝트에 복사하여 붙여넣으세요.
사용법
<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>