<script lang="ts">
import { Button } from "$lib/components/ui/button/index.js";
import { Checkbox } from "$lib/components/ui/checkbox/index.js";
import * as Field from "$lib/components/ui/field/index.js";
import { Input } from "$lib/components/ui/input/index.js";
import * as Select from "$lib/components/ui/select/index.js";
import { Textarea } from "$lib/components/ui/textarea/index.js";
let month = $state<string>();
let year = $state<string>();
</script>
<div class="w-full max-w-md">
<form>
<Field.Group>
<Field.Set>
<Field.Legend>결제 수단</Field.Legend>
<Field.Description>모든 거래는 안전하게 암호화됩니다</Field.Description>
<Field.Group>
<Field.Field>
<Field.Label for="checkout-7j9-card-name-43j"
>카드 소유자 이름</Field.Label
>
<Input
id="checkout-7j9-card-name-43j"
placeholder="홍길동"
required
/>
</Field.Field>
<div class="grid grid-cols-3 gap-4">
<Field.Field class="col-span-2">
<Field.Label for="checkout-7j9-card-number-uw1"
>카드 번호</Field.Label
>
<Input
id="checkout-7j9-card-number-uw1"
placeholder="1234 5678 9012 3456"
required
/>
<Field.Description>16자리 번호를 입력하세요.</Field.Description>
</Field.Field>
<Field.Field class="col-span-1">
<Field.Label for="checkout-7j9-cvv">CVV</Field.Label>
<Input id="checkout-7j9-cvv" placeholder="123" required />
</Field.Field>
</div>
<div class="grid grid-cols-2 gap-4">
<Field.Field>
<Field.Label for="checkout-7j9-exp-month-ts6">월</Field.Label>
<Select.Root type="single" bind:value={month}>
<Select.Trigger id="checkout-7j9-exp-month-ts6">
<span>
{month || "MM"}
</span>
</Select.Trigger>
<Select.Content>
<Select.Item value="01">01</Select.Item>
<Select.Item value="02">02</Select.Item>
<Select.Item value="03">03</Select.Item>
<Select.Item value="04">04</Select.Item>
<Select.Item value="05">05</Select.Item>
<Select.Item value="06">06</Select.Item>
<Select.Item value="07">07</Select.Item>
<Select.Item value="08">08</Select.Item>
<Select.Item value="09">09</Select.Item>
<Select.Item value="10">10</Select.Item>
<Select.Item value="11">11</Select.Item>
<Select.Item value="12">12</Select.Item>
</Select.Content>
</Select.Root>
</Field.Field>
<Field.Field>
<Field.Label for="checkout-7j9-exp-year-f59">연도</Field.Label>
<Select.Root type="single" bind:value={year}>
<Select.Trigger id="checkout-7j9-exp-year-f59">
<span>
{year || "YYYY"}
</span>
</Select.Trigger>
<Select.Content>
<Select.Item value="2024">2024</Select.Item>
<Select.Item value="2025">2025</Select.Item>
<Select.Item value="2026">2026</Select.Item>
<Select.Item value="2027">2027</Select.Item>
<Select.Item value="2028">2028</Select.Item>
<Select.Item value="2029">2029</Select.Item>
</Select.Content>
</Select.Root>
</Field.Field>
</div>
</Field.Group>
</Field.Set>
<Field.Separator />
<Field.Set>
<Field.Legend>청구 주소</Field.Legend>
<Field.Description>결제 수단과 연결된 청구 주소</Field.Description>
<Field.Group>
<Field.Field orientation="horizontal">
<Checkbox id="checkout-7j9-same-as-shipping-wgm" checked={true} />
<Field.Label
for="checkout-7j9-same-as-shipping-wgm"
class="font-normal"
>
배송 주소와 동일
</Field.Label>
</Field.Field>
</Field.Group>
</Field.Set>
<Field.Separator />
<Field.Set>
<Field.Group>
<Field.Field>
<Field.Label for="checkout-7j9-optional-comments"
>코멘트</Field.Label
>
<Textarea
id="checkout-7j9-optional-comments"
placeholder="추가 코멘트를 입력하세요"
class="resize-none"
/>
</Field.Field>
</Field.Group>
</Field.Set>
<Field.Field orientation="horizontal">
<Button type="submit">제출</Button>
<Button variant="outline" type="button">취소</Button>
</Field.Field>
</Field.Group>
</form>
</div> 설치
다음 코드를 프로젝트에 복사하여 붙여넣으세요.
사용법
<script lang="ts">
import * as Field from "$lib/components/ui/field/index.js";
</script> <Field.Set>
<Field.Legend>프로필</Field.Legend>
<Field.Description>인보이스 및 이메일에 표시됩니다.</Field.Description>
<Field.Group>
<Field.Field>
<Field.Label for="name">전체 이름</Field.Label>
<Input id="name" autoComplete="off" placeholder="홍길동" />
<Field.Description>인보이스 및 이메일에 표시됩니다.</Field.Description>
</Field.Field>
<Field.Field>
<Field.Label for="username">사용자 이름</Field.Label>
<Input id="username" autoComplete="off" aria-invalid />
<Field.Error>다른 사용자 이름을 선택하세요.</Field.Error>
</Field.Field>
<Field.Field orientation="horizontal">
<Switch id="newsletter" />
<Field.Label for="newsletter">뉴스레터 구독</Field.Label>
</Field.Field>
</Field.Group>
</Field.Set> 구조
Field 패밀리는 접근 가능한 폼을 구성하기 위해 설계되었습니다. 일반적인 필드 구조는 다음과 같습니다:
<Field.Field>
<Field.Label for="input-id">라벨</Field.Label>
<!-- Input, Select, Switch, etc. -->
<Field.Description>선택적 도움말 텍스트.</Field.Description>
<Field.Error>유효성 검사 메시지.</Field.Error>
</Field.Field> Field.Field는 단일 필드의 핵심 래퍼입니다.Field.Content는 라벨과 설명을 그룹화하는 플렉스 컬럼입니다. 설명이 없으면 필수가 아닙니다.- 관련 필드는
Field.Group으로 감싸고, 의미론적 그룹화를 위해Field.Set과Field.Legend를 사용하세요.
예제
Input
<script lang="ts">
import * as Field from "$lib/components/ui/field/index.js";
import { Input } from "$lib/components/ui/input/index.js";
</script>
<div class="w-full max-w-md">
<Field.Set>
<Field.Group>
<Field.Field>
<Field.Label for="username">사용자명</Field.Label>
<Input id="username" type="text" placeholder="홍길동" />
<Field.Description
>계정의 고유한 사용자명을 선택하세요.</Field.Description
>
</Field.Field>
<Field.Field>
<Field.Label for="password">비밀번호</Field.Label>
<Field.Description>최소 8자 이상이어야 합니다.</Field.Description>
<Input id="password" type="password" placeholder="••••••••" />
</Field.Field>
</Field.Group>
</Field.Set>
</div> Textarea
<script lang="ts">
import * as Field from "$lib/components/ui/field/index.js";
import { Textarea } from "$lib/components/ui/textarea/index.js";
</script>
<div class="w-full max-w-md">
<Field.Set>
<Field.Group>
<Field.Field>
<Field.Label for="feedback">피드백</Field.Label>
<Textarea
id="feedback"
placeholder="여러분의 피드백은 개선에 도움이 됩니다..."
rows={4}
/>
<Field.Description
>서비스에 대한 의견을 공유해 주세요.</Field.Description
>
</Field.Field>
</Field.Group>
</Field.Set>
</div> Select
부서 또는 업무 영역을 선택하세요.
<script lang="ts">
import * as Field from "$lib/components/ui/field/index.js";
import * as Select from "$lib/components/ui/select/index.js";
let department = $state<string>();
const departments = [
{ value: "engineering", label: "Engineering" },
{ value: "design", label: "Design" },
{ value: "marketing", label: "Marketing" },
{ value: "sales", label: "Sales" },
{ value: "support", label: "Customer Support" },
{ value: "hr", label: "Human Resources" },
{ value: "finance", label: "Finance" },
{ value: "operations", label: "Operations" }
];
const departmentLabel = $derived(
departments.find((d) => d.value === department)?.label ?? "부서 선택"
);
</script>
<div class="w-full max-w-md">
<Field.Field>
<Field.Label for="department">부서</Field.Label>
<Select.Root type="single" bind:value={department}>
<Select.Trigger id="department">
{departmentLabel}
</Select.Trigger>
<Select.Content>
{#each departments as department (department.value)}
<Select.Item {...department} />
{/each}
</Select.Content>
</Select.Root>
<Field.Description>부서 또는 업무 영역을 선택하세요.</Field.Description>
</Field.Field>
</div> Slider
예산 범위를 설정하세요 ($200 - 800).
<script lang="ts">
import * as Field from "$lib/components/ui/field/index.js";
import { Slider } from "$lib/components/ui/slider/index.js";
let value = $state([200, 800]);
</script>
<div class="w-full max-w-md">
<Field.Field>
<Field.Label>가격 범위</Field.Label>
<Field.Description>
예산 범위를 설정하세요 ($<span class="font-medium tabular-nums"
>{value[0]}</span
>
-
<span class="font-medium tabular-nums">{value[1]}</span>).
</Field.Description>
<Slider
type="multiple"
bind:value
max={1000}
min={0}
step={10}
class="mt-2 w-full"
aria-label="Price Range"
/>
</Field.Field>
</div> Fieldset
<script lang="ts">
import * as Field from "$lib/components/ui/field/index.js";
import { Input } from "$lib/components/ui/input/index.js";
</script>
<div class="w-full max-w-md space-y-6">
<Field.Set>
<Field.Legend>주소 정보</Field.Legend>
<Field.Description
>주문을 배송하기 위해 주소가 필요합니다.</Field.Description
>
<Field.Group>
<Field.Field>
<Field.Label for="street">도로명 주소</Field.Label>
<Input
id="street"
type="text"
placeholder="서울시 강남구 테헤란로 123"
/>
</Field.Field>
<div class="grid grid-cols-2 gap-4">
<Field.Field>
<Field.Label for="city">도시</Field.Label>
<Input id="city" type="text" placeholder="서울" />
</Field.Field>
<Field.Field>
<Field.Label for="zip">우편번호</Field.Label>
<Input id="zip" type="text" placeholder="06234" />
</Field.Field>
</div>
</Field.Group>
</Field.Set>
</div> Checkbox
데스크톱 및 문서 폴더가 iCloud Drive와 동기화됩니다. 다른 기기에서 액세스할 수 있습니다.
<script lang="ts">
import * as Field from "$lib/components/ui/field/index.js";
import { Checkbox } from "$lib/components/ui/checkbox/index.js";
</script>
<div class="w-full max-w-md">
<Field.Group>
<Field.Set>
<Field.Legend variant="label">데스크톱에 표시할 항목</Field.Legend>
<Field.Description>데스크톱에 표시할 항목을 선택하세요.</Field.Description
>
<Field.Group class="gap-3">
<Field.Field orientation="horizontal">
<Checkbox id="finder-pref-9k2-hard-disks-ljj" checked />
<Field.Label for="finder-pref-9k2-hard-disks-ljj" class="font-normal">
하드 디스크
</Field.Label>
</Field.Field>
<Field.Field orientation="horizontal">
<Checkbox id="finder-pref-9k2-external-disks-1yg" />
<Field.Label
for="finder-pref-9k2-external-disks-1yg"
class="font-normal"
>
외장 디스크
</Field.Label>
</Field.Field>
<Field.Field orientation="horizontal">
<Checkbox id="finder-pref-9k2-cds-dvds-fzt" />
<Field.Label for="finder-pref-9k2-cds-dvds-fzt" class="font-normal">
CD, DVD 및 iPod
</Field.Label>
</Field.Field>
<Field.Field orientation="horizontal">
<Checkbox id="finder-pref-9k2-connected-servers-6l2" />
<Field.Label
for="finder-pref-9k2-connected-servers-6l2"
class="font-normal"
>
연결된 서버
</Field.Label>
</Field.Field>
</Field.Group>
</Field.Set>
<Field.Separator />
<Field.Field orientation="horizontal">
<Checkbox id="finder-pref-9k2-sync-folders-nep" checked />
<Field.Content>
<Field.Label for="finder-pref-9k2-sync-folders-nep">
데스크톱 및 문서 폴더 동기화
</Field.Label>
<Field.Description>
데스크톱 및 문서 폴더가 iCloud Drive와 동기화됩니다. 다른 기기에서
액세스할 수 있습니다.
</Field.Description>
</Field.Content>
</Field.Field>
</Field.Group>
</div> Radio
<script lang="ts">
import * as Field from "$lib/components/ui/field/index.js";
import * as RadioGroup from "$lib/components/ui/radio-group/index.js";
let plan = $state("monthly");
</script>
<div class="w-full max-w-md">
<Field.Set>
<Field.Label>구독 플랜</Field.Label>
<Field.Description
>연간 및 평생 플랜은 상당한 할인을 제공합니다.</Field.Description
>
<RadioGroup.Root bind:value={plan}>
<Field.Field orientation="horizontal">
<RadioGroup.Item value="monthly" id="plan-monthly" />
<Field.Label for="plan-monthly" class="font-normal"
>월간 ($9.99/월)</Field.Label
>
</Field.Field>
<Field.Field orientation="horizontal">
<RadioGroup.Item value="yearly" id="plan-yearly" />
<Field.Label for="plan-yearly" class="font-normal"
>연간 ($99.99/년)</Field.Label
>
</Field.Field>
<Field.Field orientation="horizontal">
<RadioGroup.Item value="lifetime" id="plan-lifetime" />
<Field.Label for="plan-lifetime" class="font-normal"
>평생 ($299.99)</Field.Label
>
</Field.Field>
</RadioGroup.Root>
</Field.Set>
</div> Switch
다중 인증을 활성화합니다. 2단계 인증 기기가 없는 경우 이메일로 전송된 일회용 코드를 사용할 수 있습니다.
<script lang="ts">
import * as Field from "$lib/components/ui/field/index.js";
import { Switch } from "$lib/components/ui/switch/index.js";
</script>
<div class="w-full max-w-md">
<Field.Field orientation="horizontal">
<Field.Content>
<Field.Label for="2fa">다중 인증</Field.Label>
<Field.Description>
다중 인증을 활성화합니다. 2단계 인증 기기가 없는 경우 이메일로 전송된
일회용 코드를 사용할 수 있습니다.
</Field.Description>
</Field.Content>
<Switch id="2fa" />
</Field.Field>
</div> Choice Card
선택 가능한 필드 그룹을 만들려면 Field 컴포넌트를 FieldLabel 안에 감싸세요. 이는 RadioItem, Checkbox, Switch 컴포넌트와 함께 작동합니다.
<script lang="ts">
import * as Field from "$lib/components/ui/field/index.js";
import * as RadioGroup from "$lib/components/ui/radio-group/index.js";
let computeEnvironment = $state("kubernetes");
</script>
<div class="w-full max-w-md">
<Field.Group>
<Field.Set>
<Field.Label for="compute-environment-p8w">컴퓨팅 환경</Field.Label>
<Field.Description>클러스터의 컴퓨팅 환경을 선택하세요.</Field.Description
>
<RadioGroup.Root bind:value={computeEnvironment}>
<Field.Label for="kubernetes-r2h">
<Field.Field orientation="horizontal">
<Field.Content>
<Field.Title>Kubernetes</Field.Title>
<Field.Description>
K8s로 구성된 클러스터에서 GPU 워크로드를 실행합니다.
</Field.Description>
</Field.Content>
<RadioGroup.Item value="kubernetes" id="kubernetes-r2h" />
</Field.Field>
</Field.Label>
<Field.Label for="vm-z4k">
<Field.Field orientation="horizontal">
<Field.Content>
<Field.Title>가상 머신</Field.Title>
<Field.Description>
VM으로 구성된 클러스터에 액세스하여 GPU 워크로드를 실행합니다.
</Field.Description>
</Field.Content>
<RadioGroup.Item value="vm" id="vm-z4k" />
</Field.Field>
</Field.Label>
</RadioGroup.Root>
</Field.Set>
</Field.Group>
</div> Field Group
Field.Group으로 Field 컴포넌트를 쌓으세요. 분할하려면 Field.Separator를 추가하세요.
<script lang="ts">
import * as Field from "$lib/components/ui/field/index.js";
import { Checkbox } from "$lib/components/ui/checkbox/index.js";
</script>
<div class="w-full max-w-md">
<Field.Group>
<Field.Set>
<Field.Label>응답</Field.Label>
<Field.Description>
연구나 이미지 생성과 같이 시간이 걸리는 요청에 ChatGPT가 응답하면 알림을
받습니다.
</Field.Description>
<Field.Group data-slot="checkbox-group">
<Field.Field orientation="horizontal">
<Checkbox id="push" checked disabled />
<Field.Label for="push" class="font-normal">푸시 알림</Field.Label>
</Field.Field>
</Field.Group>
</Field.Set>
<Field.Separator />
<Field.Set>
<Field.Label>작업</Field.Label>
<Field.Description>
생성한 작업에 업데이트가 있을 때 알림을 받습니다.
<a href="#/">작업 관리</a>
</Field.Description>
<Field.Group data-slot="checkbox-group">
<Field.Field orientation="horizontal">
<Checkbox id="push-tasks" />
<Field.Label for="push-tasks" class="font-normal"
>푸시 알림</Field.Label
>
</Field.Field>
<Field.Field orientation="horizontal">
<Checkbox id="email-tasks" />
<Field.Label for="email-tasks" class="font-normal"
>이메일 알림</Field.Label
>
</Field.Field>
</Field.Group>
</Field.Set>
</Field.Group>
</div> Responsive Layout
- 수직 필드: 기본 방향은 라벨, 컨트롤, 도움말 텍스트를 쌓습니다. 모바일 우선 레이아웃에 이상적입니다.
- 수평 필드:
Field에orientation="horizontal"을 설정하여 라벨과 컨트롤을 나란히 정렬합니다. 설명을 정렬 상태로 유지하려면Field.Content와 함께 사용하세요. - 반응형 필드: 컨테이너 인식 부모 내에서 자동 컬럼 레이아웃을 위해
orientation="responsive"를 설정합니다. 특정 중단점에서 방향을 전환하려면Field.Group에@container/field-group클래스를 적용하세요.
<script lang="ts">
import * as Field from "$lib/components/ui/field/index.js";
import { Button } from "$lib/components/ui/button/index.js";
import { Input } from "$lib/components/ui/input/index.js";
import { Textarea } from "$lib/components/ui/textarea/index.js";
</script>
<div class="w-full max-w-4xl">
<form>
<Field.Set>
<Field.Legend>프로필</Field.Legend>
<Field.Description>프로필 정보를 입력하세요.</Field.Description>
<Field.Separator />
<Field.Group>
<Field.Field orientation="responsive">
<Field.Content>
<Field.Label for="name">이름</Field.Label>
<Field.Description>
신원 확인을 위해 전체 이름을 입력하세요
</Field.Description>
</Field.Content>
<Input id="name" placeholder="홍길동" required />
</Field.Field>
<Field.Separator />
<Field.Field orientation="responsive">
<Field.Content>
<Field.Label for="message">메시지</Field.Label>
<Field.Description>
여기에 메시지를 작성할 수 있습니다. 짧게 작성하는 것이 좋으며,
100자 이내로 작성하세요.
</Field.Description>
</Field.Content>
<Textarea
id="message"
placeholder="안녕하세요!"
required
class="min-h-[100px] resize-none sm:min-w-[300px]"
/>
</Field.Field>
<Field.Separator />
<Field.Field orientation="responsive">
<Button type="submit">제출</Button>
<Button type="button" variant="outline">취소</Button>
</Field.Field>
</Field.Group>
</Field.Set>
</form>
</div> 유효성 검사 및 오류
- 전체 블록을 오류 상태로 전환하려면
Field에data-invalid를 추가하세요. - 보조 기술을 위해 입력 자체에
aria-invalid를 추가하세요. - 오류 메시지를 필드와 정렬 상태로 유지하려면 컨트롤 바로 다음 또는
FieldContent내부에FieldError를 렌더링하세요.
<Field.Field data-invalid>
<Field.Label for="email">이메일</Field.Label>
<Input id="email" type="email" aria-invalid />
<Field.Error>유효한 이메일 주소를 입력하세요.</Field.Error>
</Field.Field> 접근성
Field.Set과Field.Legend는 키보드 및 보조 기술 사용자를 위해 관련 컨트롤을 그룹화합니다.Field는role="group"을 출력하므로 중첩된 컨트롤이 결합될 때Field.Label및Field.Legend에서 라벨링을 상속합니다.- 스크린 리더가 명확한 섹션 경계를 만나도록
Field.Separator를 절제하여 적용하세요.