<script lang="ts">
import * as InputOTP from "$lib/components/ui/input-otp/index.js";
</script>
<InputOTP.Root maxlength={6}>
{#snippet children({ cells })}
<InputOTP.Group>
{#each cells.slice(0, 3) as cell (cell)}
<InputOTP.Slot {cell} />
{/each}
</InputOTP.Group>
<InputOTP.Separator />
<InputOTP.Group>
{#each cells.slice(3, 6) as cell (cell)}
<InputOTP.Slot {cell} />
{/each}
</InputOTP.Group>
{/snippet}
</InputOTP.Root> 소개
Input OTP는 @guilherme_rodz의 Input OTP 컴포넌트에서 영감을 받은 Bits UI의 PinInput을 기반으로 구축되었습니다.
설치
bits-ui를 설치합니다:
다음 코드를 프로젝트에 복사하여 붙여넣습니다.
사용법
<script lang="ts">
import * as InputOTP from "$lib/components/ui/input-otp/index.js";
</script> <InputOTP.Root maxlength={6}>
{#snippet children({ cells })}
<InputOTP.Group>
{#each cells.slice(0, 3) as cell}
<InputOTP.Slot {cell} />
{/each}
</InputOTP.Group>
<InputOTP.Separator />
<InputOTP.Group>
{#each cells.slice(3, 6) as cell}
<InputOTP.Slot {cell} />
{/each}
</InputOTP.Group>
{/snippet}
</InputOTP.Root> 예제
패턴
pattern prop을 사용하여 OTP 입력의 사용자 정의 패턴을 정의할 수 있습니다.
<script lang="ts">
import * as InputOTP from "$lib/components/ui/input-otp/index.js";
import { REGEXP_ONLY_DIGITS_AND_CHARS } from "bits-ui";
</script>
<InputOTP.Root maxlength={6} pattern={REGEXP_ONLY_DIGITS_AND_CHARS}>
{#snippet children({ cells })}
<InputOTP.Group>
{#each cells as cell (cell)}
<InputOTP.Slot {cell} />
{/each}
</InputOTP.Group>
{/snippet}
</InputOTP.Root> <script lang="ts">
import * as InputOTP from "$lib/components/ui/input-otp/index.js";
import { REGEXP_ONLY_DIGITS_AND_CHARS } from "bits-ui";
</script>
<InputOTP.Root maxlength={6} pattern={REGEXP_ONLY_DIGITS_AND_CHARS}>
<!-- ... -->
</InputOTP.Root> 구분자
InputOTP.Separator 컴포넌트를 사용하여 셀 그룹 사이에 구분자를 추가할 수 있습니다.
<script lang="ts">
import * as InputOTP from "$lib/components/ui/input-otp/index.js";
</script>
<InputOTP.Root maxlength={6}>
{#snippet children({ cells })}
<InputOTP.Group>
{#each cells.slice(0, 2) as cell (cell)}
<InputOTP.Slot {cell} />
{/each}
</InputOTP.Group>
<InputOTP.Separator />
<InputOTP.Group>
{#each cells.slice(2, 4) as cell (cell)}
<InputOTP.Slot {cell} />
{/each}
</InputOTP.Group>
<InputOTP.Separator />
<InputOTP.Group>
{#each cells.slice(4, 6) as cell (cell)}
<InputOTP.Slot {cell} />
{/each}
</InputOTP.Group>
{/snippet}
</InputOTP.Root> <script lang="ts">
import * as InputOTP from "$lib/components/ui/input-otp/index.js";
</script>
<InputOTP.Root maxlength={4}>
{#snippet children({ cells })}
<InputOTP.Group>
{#each cells.slice(0, 2) as cell}
<InputOTP.Slot {cell} />
{/each}
</InputOTP.Group>
<InputOTP.Separator />
<InputOTP.Group>
{#each cells.slice(2, 4) as cell}
<InputOTP.Slot {cell} />
{/each}
</InputOTP.Group>
{/snippet}
</InputOTP.Root> 제어 컴포넌트
일회용 비밀번호를 입력하세요.
<script lang="ts">
import * as InputOTP from "$lib/components/ui/input-otp/index.js";
let value = $state("");
</script>
<div class="space-y-2">
<InputOTP.Root maxlength={6} bind:value>
{#snippet children({ cells })}
<InputOTP.Group>
{#each cells.slice(0, 6) as cell (cell)}
<InputOTP.Slot {cell} />
{/each}
</InputOTP.Group>
{/snippet}
</InputOTP.Root>
<div class="text-center text-sm">
{value === "" ? "일회용 비밀번호를 입력하세요." : `입력한 값: ${value}`}
</div>
</div> 폼
<script lang="ts" module>
import { z } from "zod/v4";
const formSchema = z.object({
pin: z.string().min(6, {
message: "일회용 비밀번호는 최소 6자 이상이어야 합니다."
})
});
</script>
<script lang="ts">
import { defaults, superForm } from "sveltekit-superforms";
import { zod4 } from "sveltekit-superforms/adapters";
import { toast } from "svelte-sonner";
import * as InputOTP from "$lib/components/ui/input-otp/index.js";
import * as Form from "$lib/components/ui/form/index.js";
const form = superForm(defaults(zod4(formSchema)), {
validators: zod4(formSchema),
SPA: true,
onUpdate: ({ form: f }) => {
if (f.valid) {
toast.success(`제출 완료: ${JSON.stringify(f.data, null, 2)}`);
} else {
toast.error("양식의 오류를 수정해 주세요.");
}
}
});
const { form: formData, enhance } = form;
</script>
<form method="POST" class="w-2/3 space-y-6" use:enhance>
<Form.Field {form} name="pin">
<Form.Control>
{#snippet children({ props })}
<Form.Label>일회용 비밀번호</Form.Label>
<InputOTP.Root maxlength={6} {...props} bind:value={$formData.pin}>
{#snippet children({ cells })}
<InputOTP.Group>
{#each cells as cell (cell)}
<InputOTP.Slot {cell} />
{/each}
</InputOTP.Group>
{/snippet}
</InputOTP.Root>
{/snippet}
</Form.Control>
<Form.Description
>휴대폰으로 전송된 일회용 비밀번호를 입력하세요.</Form.Description
>
<Form.FieldErrors />
</Form.Field>
<Form.Button>제출</Form.Button>
</form>