ZaidanHomeDocsComponentsBlocksChartsTypesetCreate

Command Palette

Search for a command to run...

GitHub
New
Sections
  • Introduction
  • Components
  • Blocks
  • Installation
  • Customization
  • Dark Mode
  • Typeset
  • Zaidan Skills
  • FAQ
  • Roadmap
  • Changelog
Installation
  • Astro
  • Manual
  • Solid Start
  • TanStack Router
  • TanStack Start
  • Vite
Blocks
  • Data Grid
  • Event Calendar
  • Filters
  • Gantt
  • Image Crop
  • Kanban
  • Message Scroller
  • Questionnaire
  • Sortable
Components
  • Accordion
  • Alert
  • Alert Dialog
  • Aspect Ratio
  • Attachment
  • Avatar
  • Badge
  • Breadcrumb
  • Bubble
  • Button
  • Button Group
  • Calendar
  • Card
  • Carousel
  • Chart
  • Checkbox
  • Collapsible
  • Combobox
  • Command
  • Context Menu
  • Dialog
  • Drawer
  • Dropdown Menu
  • Empty
  • Field
  • Hover Card
  • Input
  • Input Group
  • Input OTP
  • Item
  • Kbd
  • Label
  • Marker
  • Menubar
  • Message
  • Native Select
  • Navigation Menu
  • Pagination
  • Popover
  • Progress
  • Radio Group
  • Resizable
  • Scroll Area
  • Select
  • Separator
  • Sheet
  • Sidebar
  • Skeleton
  • Slider
  • Spinner
  • Switch
  • Table
  • Tabs
  • Textarea
  • Toast
  • Toggle
  • Toggle Group
  • Tooltip

Input OTP

Accessible one-time password component with copy-paste functionality.

Corvu
Corvu

The Input OTP component is built on top of Corvu OTP Field, which provides the accessible one-time password input primitive with copy-paste support.

1
import { createSignal } from "solid-js";
2
import { InputOTP, InputOTPGroup, InputOTPSlot } from "~/components/ui/input-otp";
3
4
export default function InputOTPDemo() {
5
const [value, setValue] = createSignal("123456");
6
7
return (
8
<InputOTP maxLength={6} value={value()} onValueChange={setValue}>
9
<InputOTPGroup>
10
<InputOTPSlot index={0} />
11
<InputOTPSlot index={1} />
12
<InputOTPSlot index={2} />
13
<InputOTPSlot index={3} />
14
<InputOTPSlot index={4} />
15
<InputOTPSlot index={5} />
16
</InputOTPGroup>
17
</InputOTP>
18
);
19
}

Installation

pnpm dlx shadcn@latest add @zaidan/input-otp
npx shadcn@latest add @zaidan/input-otp
yarn dlx shadcn@latest add @zaidan/input-otp
bunx --bun shadcn@latest add @zaidan/input-otp

Usage

1
import {
2
InputOTP,
3
InputOTPGroup,
4
InputOTPSeparator,
5
InputOTPSlot,
6
} from "~/components/ui/input-otp";
1
<InputOTP maxLength={6}>
2
<InputOTPGroup>
3
<InputOTPSlot index={0} />
4
<InputOTPSlot index={1} />
5
<InputOTPSlot index={2} />
6
</InputOTPGroup>
7
<InputOTPSeparator />
8
<InputOTPGroup>
9
<InputOTPSlot index={3} />
10
<InputOTPSlot index={4} />
11
<InputOTPSlot index={5} />
12
</InputOTPGroup>
13
</InputOTP>

Composition

Use the following composition to build an InputOTP:

InputOTP
├── InputOTPGroup
│ ├── InputOTPSlot
│ ├── InputOTPSlot
│ └── InputOTPSlot
├── InputOTPSeparator
├── InputOTPGroup
│ ├── InputOTPSlot
│ ├── InputOTPSlot
│ └── InputOTPSlot
├── InputOTPSeparator
└── InputOTPGroup
├── InputOTPSlot
└── InputOTPSlot

The input only accepts digits by default (Corvu OTP Field's built-in ^\d*$ pattern). Use the pattern prop to customize the accepted characters.

Pattern

Use the pattern prop to define a custom regex for the accepted characters. The default only accepts digits; this example also accepts letters. Pass null to allow all characters.

1
import { Field, FieldLabel } from "~/components/ui/field";
2
import { InputOTP, InputOTPGroup, InputOTPSlot } from "~/components/ui/input-otp";
3
4
const REGEXP_ONLY_DIGITS_AND_CHARS = "^[a-zA-Z0-9]*$";
5
6
export default function InputOTPPattern() {
7
return (
8
<Field class="w-fit">
9
<FieldLabel for="digits-and-chars">Digits and characters</FieldLabel>
10
<InputOTP id="digits-and-chars" maxLength={6} pattern={REGEXP_ONLY_DIGITS_AND_CHARS}>
11
<InputOTPGroup>
12
<InputOTPSlot index={0} />
13
<InputOTPSlot index={1} />
14
<InputOTPSlot index={2} />
15
<InputOTPSlot index={3} />
16
<InputOTPSlot index={4} />
17
<InputOTPSlot index={5} />
18
</InputOTPGroup>
19
</InputOTP>
20
</Field>
21
);
22
}

Alphanumeric

Accept both letters and numbers with an alphanumeric pattern.

1
import {
2
InputOTP,
3
InputOTPGroup,
4
InputOTPSeparator,
5
InputOTPSlot,
6
} from "~/components/ui/input-otp";
7
8
export default function InputOTPAlphanumeric() {
9
return (
10
<InputOTP maxLength={6} pattern="^[a-zA-Z0-9]*$">
11
<InputOTPGroup>
12
<InputOTPSlot index={0} />
13
<InputOTPSlot index={1} />
14
<InputOTPSlot index={2} />
15
</InputOTPGroup>
16
<InputOTPSeparator />
17
<InputOTPGroup>
18
<InputOTPSlot index={3} />
19
<InputOTPSlot index={4} />
20
<InputOTPSlot index={5} />
21
</InputOTPGroup>
22
</InputOTP>
23
);
24
}

Separator

Use the <InputOTPSeparator /> component to add a separator between input groups.

1
import {
2
InputOTP,
3
InputOTPGroup,
4
InputOTPSeparator,
5
InputOTPSlot,
6
} from "~/components/ui/input-otp";
7
8
export default function InputOTPWithSeparator() {
9
return (
10
<InputOTP maxLength={6}>
11
<InputOTPGroup>
12
<InputOTPSlot index={0} />
13
<InputOTPSlot index={1} />
14
</InputOTPGroup>
15
<InputOTPSeparator />
16
<InputOTPGroup>
17
<InputOTPSlot index={2} />
18
<InputOTPSlot index={3} />
19
</InputOTPGroup>
20
<InputOTPSeparator />
21
<InputOTPGroup>
22
<InputOTPSlot index={4} />
23
<InputOTPSlot index={5} />
24
</InputOTPGroup>
25
</InputOTP>
26
);
27
}

Disabled

Use the disabled prop to disable the input.

1
import {
2
InputOTP,
3
InputOTPGroup,
4
InputOTPSeparator,
5
InputOTPSlot,
6
} from "~/components/ui/input-otp";
7
8
export default function InputOTPDisabled() {
9
return (
10
<InputOTP id="disabled" maxLength={6} disabled value="123456">
11
<InputOTPGroup>
12
<InputOTPSlot index={0} />
13
<InputOTPSlot index={1} />
14
<InputOTPSlot index={2} />
15
</InputOTPGroup>
16
<InputOTPSeparator />
17
<InputOTPGroup>
18
<InputOTPSlot index={3} />
19
<InputOTPSlot index={4} />
20
<InputOTPSlot index={5} />
21
</InputOTPGroup>
22
</InputOTP>
23
);
24
}

Controlled

Use the value and onValueChange props to control the input value.

Enter your one-time password.
1
import { createSignal, Show } from "solid-js";
2
import { InputOTP, InputOTPGroup, InputOTPSlot } from "~/components/ui/input-otp";
3
4
export default function InputOTPControlled() {
5
const [value, setValue] = createSignal("");
6
7
return (
8
<div class="space-y-2">
9
<InputOTP maxLength={6} value={value()} onValueChange={setValue}>
10
<InputOTPGroup>
11
<InputOTPSlot index={0} />
12
<InputOTPSlot index={1} />
13
<InputOTPSlot index={2} />
14
<InputOTPSlot index={3} />
15
<InputOTPSlot index={4} />
16
<InputOTPSlot index={5} />
17
</InputOTPGroup>
18
</InputOTP>
19
<div class="text-center text-sm">
20
<Show when={value() !== ""} fallback="Enter your one-time password.">
21
You entered: {value()}
22
</Show>
23
</div>
24
</div>
25
);
26
}

Invalid

Use aria-invalid on the slots to show an error state.

1
import { createSignal } from "solid-js";
2
import {
3
InputOTP,
4
InputOTPGroup,
5
InputOTPSeparator,
6
InputOTPSlot,
7
} from "~/components/ui/input-otp";
8
9
export default function InputOTPInvalid() {
10
const [value, setValue] = createSignal("000000");
11
12
return (
13
<InputOTP maxLength={6} value={value()} onValueChange={setValue}>
14
<InputOTPGroup>
15
<InputOTPSlot index={0} aria-invalid />
16
<InputOTPSlot index={1} aria-invalid />
17
</InputOTPGroup>
18
<InputOTPSeparator />
19
<InputOTPGroup>
20
<InputOTPSlot index={2} aria-invalid />
21
<InputOTPSlot index={3} aria-invalid />
22
</InputOTPGroup>
23
<InputOTPSeparator />
24
<InputOTPGroup>
25
<InputOTPSlot index={4} aria-invalid />
26
<InputOTPSlot index={5} aria-invalid />
27
</InputOTPGroup>
28
</InputOTP>
29
);
30
}

Four Digits

A common pattern for PIN codes: set maxLength={4}.

1
import { InputOTP, InputOTPGroup, InputOTPSlot } from "~/components/ui/input-otp";
2
3
export default function InputOTPFourDigits() {
4
return (
5
<InputOTP maxLength={4}>
6
<InputOTPGroup>
7
<InputOTPSlot index={0} />
8
<InputOTPSlot index={1} />
9
<InputOTPSlot index={2} />
10
<InputOTPSlot index={3} />
11
</InputOTPGroup>
12
</InputOTP>
13
);
14
}

Form

Verify your login
Enter the verification code we sent to your email address: m@example.com.

I no longer have access to this email address.

Having trouble signing in? Contact support
1
/** biome-ignore-all lint/a11y/useValidAnchor: <example file> */
2
import { RefreshCwIcon } from "lucide-solid";
3
import { Button } from "~/components/ui/button";
4
import {
5
Card,
6
CardContent,
7
CardDescription,
8
CardFooter,
9
CardHeader,
10
CardTitle,
11
} from "~/components/ui/card";
12
import { Field, FieldDescription, FieldLabel } from "~/components/ui/field";
13
import {
14
InputOTP,
15
InputOTPGroup,
16
InputOTPSeparator,
17
InputOTPSlot,
18
} from "~/components/ui/input-otp";
19
20
export default function InputOTPForm() {
21
return (
22
<Card class="mx-auto max-w-md">
23
<CardHeader>
24
<CardTitle>Verify your login</CardTitle>
25
<CardDescription>
26
Enter the verification code we sent to your email address:{" "}
27
<span class="font-medium">m@example.com</span>.
28
</CardDescription>
29
</CardHeader>
30
<CardContent>
31
<Field>
32
<div class="flex items-center justify-between">
33
<FieldLabel for="otp-verification">Verification code</FieldLabel>
34
<Button variant="outline" size="xs">
35
<RefreshCwIcon data-icon="inline-start" />
36
Resend Code
37
</Button>
38
</div>
39
<InputOTP maxLength={6} id="otp-verification" required>
40
<InputOTPGroup class="*:data-[slot=input-otp-slot]:h-12 *:data-[slot=input-otp-slot]:w-11 *:data-[slot=input-otp-slot]:text-xl">
41
<InputOTPSlot index={0} />
42
<InputOTPSlot index={1} />
43
<InputOTPSlot index={2} />
44
</InputOTPGroup>
45
<InputOTPSeparator class="mx-2" />
46
<InputOTPGroup class="*:data-[slot=input-otp-slot]:h-12 *:data-[slot=input-otp-slot]:w-11 *:data-[slot=input-otp-slot]:text-xl">
47
<InputOTPSlot index={3} />
48
<InputOTPSlot index={4} />
49
<InputOTPSlot index={5} />
50
</InputOTPGroup>
51
</InputOTP>
52
<FieldDescription>
53
<a href="#">I no longer have access to this email address.</a>
54
</FieldDescription>
55
</Field>
56
</CardContent>
57
<CardFooter>
58
<Field>
59
<Button type="submit" class="w-full">
60
Verify
61
</Button>
62
<div class="text-muted-foreground text-sm">
63
Having trouble signing in?{" "}
64
<a href="#" class="underline underline-offset-4 transition-colors hover:text-primary">
65
Contact support
66
</a>
67
</div>
68
</Field>
69
</CardFooter>
70
</Card>
71
);
72
}

API Reference

See the Corvu OTP Field API reference for primitive props, keyboard interactions, and accessibility details.

On This Page

  • Installation
  • Usage
  • Composition
  • Pattern
  • Alphanumeric
  • Separator
  • Disabled
  • Controlled
  • Invalid
  • Four Digits
  • Form
  • API Reference
Built by Kevin Abatan. The source code is available on GitHub.