Docs
Input OTP
Input OTP
Accessible one-time password component with copy paste functionality.
<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>
<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>
About
Input OTP is built on top of Bits UI's PinInput which is inspired by @guilherme_rodz's Input OTP component.
Installation
npx shadcn-svelte@next add input-otp
Install bits-ui
:
npm i bits-ui -D
Copy and paste the component source files linked at the top of this page into your project.
Usage
<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>
Examples
Pattern
Use the pattern
prop to define a custom pattern for the OTP input.
<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}
<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}>
{#snippet children({ cells })}
<InputOTP.Group>
{#each cells 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";
import { REGEXP_ONLY_DIGITS_AND_CHARS } from "bits-ui";
</script>
<InputOTP.Root maxlength={6} pattern={REGEXP_ONLY_DIGITS_AND_CHARS}>
<!-- ... -->
</InputOTP.Root>
Separator
You can use the InputOTP.Separator
component to add a separator between the groups of cells.
<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}
<InputOTP.Slot {cell} />
{/each}
</InputOTP.Group>
<InputOTP.Separator />
<InputOTP.Group>
{#each cells.slice(2, 4) as cell}
<InputOTP.Slot {cell} />
{/each}
</InputOTP.Group>
<InputOTP.Separator />
<InputOTP.Group>
{#each cells.slice(4, 6) 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";
</script>
<InputOTP.Root maxlength={6}>
{#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>
<InputOTP.Separator />
<InputOTP.Group>
{#each cells.slice(4, 6) 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";
</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>
Form
<script lang="ts" module>
import { z } from "zod";
export const formSchema = z.object({
pin: z.string().min(6, {
message: "Your one-time password must be at least 6 characters."
})
});
export type FormSchema = typeof formSchema;
</script>
<script lang="ts">
import SuperDebug, {
type Infer,
type SuperValidated,
superForm
} from "sveltekit-superforms";
import { zodClient } from "sveltekit-superforms/adapters";
import { toast } from "svelte-sonner";
import { browser } from "$app/environment";
import * as InputOTP from "$lib/components/ui/input-otp/index.js";
import * as Form from "$lib/components/ui/form/index.js";
import { page } from "$app/state";
let {
form: data = page.data.inputOtp
}: { form: SuperValidated<Infer<FormSchema>> } = $props();
const form = superForm(data, {
validators: zodClient(formSchema),
onUpdated: ({ form: f }) => {
if (f.valid) {
toast.success(`You submitted ${JSON.stringify(f.data, null, 2)}`);
} else {
toast.error("Please fix the errors in the form.");
}
}
});
const { form: formData, enhance } = form;
</script>
<form action="/?/inputOtp" method="POST" class="w-2/3 space-y-6" use:enhance>
<Form.Field {form} name="pin">
<Form.Control>
{#snippet children({ props })}
<InputOTP.Root maxlength={6} {...props} bind:value={$formData.pin}>
{#snippet children({ cells })}
<InputOTP.Group>
{#each cells as cell}
<InputOTP.Slot {cell} />
{/each}
</InputOTP.Group>
{/snippet}
</InputOTP.Root>
{/snippet}
</Form.Control>
<Form.Description
>Please enter the one-time password sent to your phone.</Form.Description
>
<Form.FieldErrors />
</Form.Field>
<Form.Button>Submit</Form.Button>
{#if browser}
<SuperDebug data={$formData} />
{/if}
</form>
<script lang="ts" module>
import { z } from "zod";
export const formSchema = z.object({
pin: z.string().min(6, {
message: "Your one-time password must be at least 6 characters."
})
});
export type FormSchema = typeof formSchema;
</script>
<script lang="ts">
import SuperDebug, {
type Infer,
type SuperValidated,
superForm
} from "sveltekit-superforms";
import { zodClient } from "sveltekit-superforms/adapters";
import { toast } from "svelte-sonner";
import { browser } from "$app/environment";
import * as InputOTP from "$lib/components/ui/input-otp/index.js";
import * as Form from "$lib/components/ui/form/index.js";
import { page } from "$app/state";
let {
form: data = page.data.inputOtp
}: { form: SuperValidated<Infer<FormSchema>> } = $props();
const form = superForm(data, {
validators: zodClient(formSchema),
onUpdated: ({ form: f }) => {
if (f.valid) {
toast.success(`You submitted ${JSON.stringify(f.data, null, 2)}`);
} else {
toast.error("Please fix the errors in the form.");
}
}
});
const { form: formData, enhance } = form;
</script>
<form action="/?/inputOtp" method="POST" class="w-2/3 space-y-6" use:enhance>
<Form.Field {form} name="pin">
<Form.Control>
{#snippet children({ props })}
<InputOTP.Root maxlength={6} {...props} bind:value={$formData.pin}>
{#snippet children({ cells })}
<InputOTP.Group>
{#each cells as cell}
<InputOTP.Slot {cell} />
{/each}
</InputOTP.Group>
{/snippet}
</InputOTP.Root>
{/snippet}
</Form.Control>
<Form.Description
>Please enter the one-time password sent to your phone.</Form.Description
>
<Form.FieldErrors />
</Form.Field>
<Form.Button>Submit</Form.Button>
{#if browser}
<SuperDebug data={$formData} />
{/if}
</form>
On This Page