Search for a command to run...
Accessible one-time password component with copy paste functionality.
npx shadcn@latest add @zaidan/input-otppnpx shadcn add @zaidan/input-otpyarn dlx shadcn@latest add @zaidan/input-otpbunx shadcn@latest add @zaidan/input-otpInstall the following dependencies:
npm i @corvu/otp-fieldpnpm add @corvu/otp-fieldyarn add @corvu/otp-fieldbun add @corvu/otp-fieldCopy and paste the following code into your project.
1import OtpField, { type RootProps as OtpFieldRootProps } from "@corvu/otp-field";2import { Minus } from "lucide-solid";3import { type ComponentProps, Show, splitProps } from "solid-js";4
5import { cn } from "~/lib/utils";6
7type InputOTPProps = OtpFieldRootProps &8 ComponentProps<"div"> &9 Pick<ComponentProps<"input">, "disabled" | "required"> & {10 containerClass?: string;11 };12
13const InputOTP = (props: InputOTPProps) => {14 const [local, others] = splitProps(props as InputOTPProps, [15 "class",16 "containerClass",17 "children",18 "id",19 "disabled",20 "required",21 "value",22 "onValueChange",23 ]);24
25 return (26 <OtpField27 data-slot="input-otp"28 spellcheck={false}29 class={cn("z-input-otp flex items-center has-disabled:opacity-50", local.containerClass)}30 {...others}31 >32 <OtpField.Input33 id={local.id}34 data-slot="input-otp-input"35 class={cn("z-input-otp-input disabled:cursor-not-allowed", local.class)}36 spellcheck={false}37 disabled={local.disabled}38 required={local.required}39 value={local.value}40 onChange={(e) => local.onValueChange?.(e.target.value)}41 />42 {local.children}43 </OtpField>44 );45};46
47type InputOTPGroupProps = ComponentProps<"div">;48
49const InputOTPGroup = (props: InputOTPGroupProps) => {50 const [local, others] = splitProps(props, ["class"]);51 return (52 <div53 data-slot="input-otp-group"54 class={cn("z-input-otp-group flex items-center", local.class)}55 {...others}56 />57 );58};59
60type InputOTPSlotProps = ComponentProps<"div"> & {61 index: number;62};63
64const InputOTPSlot = (props: InputOTPSlotProps) => {65 const [local, others] = splitProps(props, ["index", "class"]);66 const context = OtpField.useContext();67
68 const char = () => context.value()[local.index];69 const isActive = () => context.activeSlots().includes(local.index);70 const showCaret = () => isActive() && context.isInserting();71
72 return (73 <div74 data-slot="input-otp-slot"75 data-active={isActive()}76 class={cn(77 "relative z-input-otp-slot flex items-center justify-center data-[active=true]:z-10",78 local.class,79 )}80 {...others}81 >82 {char()}83 <Show when={showCaret()}>84 <div class="pointer-events-none absolute inset-0 z-input-otp-caret flex items-center justify-center">85 <div class="z-input-otp-caret-line h-4 w-px animate-caret-blink bg-foreground" />86 </div>87 </Show>88 </div>89 );90};91
92type InputOTPSeparatorProps = ComponentProps<"div">;93
94const InputOTPSeparator = (props: InputOTPSeparatorProps) => {95 const [local, others] = splitProps(props, ["class"]);96 return (97 <div98 data-slot="input-otp-separator"99 class={cn("z-input-otp-separator flex items-center", local.class)}100 aria-hidden="true"101 {...others}102 >103 <Minus />104 </div>105 );106};107
108export {109 InputOTP,110 InputOTPGroup,111 InputOTPSlot,112 InputOTPSeparator,113 type InputOTPProps,114 type InputOTPGroupProps,115 type InputOTPSlotProps,116 type InputOTPSeparatorProps,117};Here are the source code of all the examples from the preview page:
import { RefreshCcw } from "lucide-solid";import { Example } from "~/components/example";import { Button } from "~/components/ui/button";import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle,} from "~/components/ui/card";import { Field, FieldDescription, FieldLabel } from "~/components/ui/field";import { InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot,} from "~/components/ui/input-otp";function InputOTPForm() { return ( <Example title="Form"> <Card class="mx-auto max-w-md"> <CardHeader> <CardTitle>Verify your login</CardTitle> <CardDescription> Enter the verification code we sent to your email address:{" "} <span class="font-medium">m@example.com</span>. </CardDescription> </CardHeader> <CardContent> <form> <Field> <div class="flex items-center justify-between"> <FieldLabel for="otp-verification">Verification code</FieldLabel> <Button variant="outline" size="xs"> <RefreshCcw data-icon="inline-start" /> Resend Code </Button> </div> <InputOTP maxLength={6} id="otp-verification" required> <InputOTPGroup class="style-lyra:*:data-[slot=input-otp-slot]:h-12 style-maia:*:data-[slot=input-otp-slot]:h-16 style-mira:*:data-[slot=input-otp-slot]:h-12 style-nova:*:data-[slot=input-otp-slot]:h-12 style-vega:*:data-[slot=input-otp-slot]:h-16 style-lyra:*:data-[slot=input-otp-slot]:w-11 style-maia:*:data-[slot=input-otp-slot]:w-12 style-mira:*:data-[slot=input-otp-slot]:w-11 style-nova:*:data-[slot=input-otp-slot]:w-11 style-vega:*:data-[slot=input-otp-slot]:w-12 *:data-[slot=input-otp-slot]:text-xl"> <InputOTPSlot index={0} /> <InputOTPSlot index={1} /> <InputOTPSlot index={2} /> </InputOTPGroup> <InputOTPSeparator /> <InputOTPGroup class="style-lyra:*:data-[slot=input-otp-slot]:h-12 style-maia:*:data-[slot=input-otp-slot]:h-16 style-mira:*:data-[slot=input-otp-slot]:h-12 style-nova:*:data-[slot=input-otp-slot]:h-12 style-vega:*:data-[slot=input-otp-slot]:h-16 style-lyra:*:data-[slot=input-otp-slot]:w-11 style-maia:*:data-[slot=input-otp-slot]:w-12 style-mira:*:data-[slot=input-otp-slot]:w-11 style-nova:*:data-[slot=input-otp-slot]:w-11 style-vega:*:data-[slot=input-otp-slot]:w-12 *:data-[slot=input-otp-slot]:text-xl"> <InputOTPSlot index={3} /> <InputOTPSlot index={4} /> <InputOTPSlot index={5} /> </InputOTPGroup> </InputOTP> <FieldDescription> <a href="#">I no longer have access to this email address.</a> </FieldDescription> </Field> </form> </CardContent> <CardFooter class="flex-col gap-2"> <Button type="submit" class="w-full"> Verify </Button> <div class="text-muted-foreground text-sm"> Having trouble signing in?{" "} <a href="#" class="underline underline-offset-4 transition-colors hover:text-primary"> Contact support </a> </div> </CardFooter> </Card> </Example> );}import { Example } from "~/components/example";import { Field, FieldLabel } from "~/components/ui/field";import { InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot,} from "~/components/ui/input-otp";function InputOTPSimple() { return ( <Example title="Simple"> <Field> <FieldLabel for="simple">Simple</FieldLabel> <InputOTP id="simple" maxLength={6}> <InputOTPGroup> <InputOTPSlot index={0} /> <InputOTPSlot index={1} /> <InputOTPSlot index={2} /> </InputOTPGroup> <InputOTPSeparator /> <InputOTPGroup> <InputOTPSlot index={3} /> <InputOTPSlot index={4} /> <InputOTPSlot index={5} /> </InputOTPGroup> </InputOTP> </Field> </Example> );}import { Example } from "~/components/example";import { Field, FieldLabel } from "~/components/ui/field";import { InputOTP, InputOTPGroup, InputOTPSlot } from "~/components/ui/input-otp";function InputOTPPattern() { return ( <Example title="Digits Only"> <Field> <FieldLabel for="digits-only">Digits Only</FieldLabel> <InputOTP id="digits-only" maxLength={6}> <InputOTPGroup> <InputOTPSlot index={0} /> <InputOTPSlot index={1} /> <InputOTPSlot index={2} /> <InputOTPSlot index={3} /> <InputOTPSlot index={4} /> <InputOTPSlot index={5} /> </InputOTPGroup> </InputOTP> </Field> </Example> );}import { createSignal } from "solid-js";import { Example } from "~/components/example";import { Field, FieldLabel } from "~/components/ui/field";import { InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot,} from "~/components/ui/input-otp";function InputOTPWithSeparator() { const [value, setValue] = createSignal("123456");
return ( <Example title="With Separator"> <Field> <FieldLabel for="with-separator">With Separator</FieldLabel> <InputOTP id="with-separator" maxLength={6} value={value()} onValueChange={setValue}> <InputOTPGroup> <InputOTPSlot index={0} /> <InputOTPSlot index={1} /> </InputOTPGroup> <InputOTPSeparator /> <InputOTPGroup> <InputOTPSlot index={2} /> <InputOTPSlot index={3} /> </InputOTPGroup> <InputOTPSeparator /> <InputOTPGroup> <InputOTPSlot index={4} /> <InputOTPSlot index={5} /> </InputOTPGroup> </InputOTP> </Field> </Example> );}import { Example } from "~/components/example";import { Field, FieldDescription, FieldLabel } from "~/components/ui/field";import { InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot,} from "~/components/ui/input-otp";function InputOTPAlphanumeric() { return ( <Example title="Alphanumeric"> <Field> <FieldLabel for="alphanumeric">Alphanumeric</FieldLabel> <FieldDescription>Accepts both letters and numbers.</FieldDescription> <InputOTP id="alphanumeric" maxLength={6}> <InputOTPGroup> <InputOTPSlot index={0} /> <InputOTPSlot index={1} /> <InputOTPSlot index={2} /> </InputOTPGroup> <InputOTPSeparator /> <InputOTPGroup> <InputOTPSlot index={3} /> <InputOTPSlot index={4} /> <InputOTPSlot index={5} /> </InputOTPGroup> </InputOTP> </Field> </Example> );}import { Example } from "~/components/example";import { Field, FieldLabel } from "~/components/ui/field";import { InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot,} from "~/components/ui/input-otp";function InputOTPDisabled() { return ( <Example title="Disabled"> <Field> <FieldLabel for="disabled">Disabled</FieldLabel> <InputOTP id="disabled" maxLength={6} disabled value="123456"> <InputOTPGroup> <InputOTPSlot index={0} /> <InputOTPSlot index={1} /> <InputOTPSlot index={2} /> </InputOTPGroup> <InputOTPSeparator /> <InputOTPGroup> <InputOTPSlot index={3} /> <InputOTPSlot index={4} /> <InputOTPSlot index={5} /> </InputOTPGroup> </InputOTP> </Field> </Example> );}import { Example } from "~/components/example";import { Field, FieldDescription, FieldLabel } from "~/components/ui/field";import { InputOTP, InputOTPGroup, InputOTPSlot } from "~/components/ui/input-otp";function InputOTPFourDigits() { return ( <Example title="4 Digits"> <Field> <FieldLabel for="four-digits">4 Digits</FieldLabel> <FieldDescription>Common pattern for PIN codes.</FieldDescription> <InputOTP id="four-digits" maxLength={4}> <InputOTPGroup> <InputOTPSlot index={0} /> <InputOTPSlot index={1} /> <InputOTPSlot index={2} /> <InputOTPSlot index={3} /> </InputOTPGroup> </InputOTP> </Field> </Example> );}import { createSignal } from "solid-js";import { Example } from "~/components/example";import { Field, FieldDescription, FieldError, FieldLabel } from "~/components/ui/field";import { InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot,} from "~/components/ui/input-otp";function InputOTPInvalid() { const [value, setValue] = createSignal("000000");
return ( <Example title="Invalid State"> <Field> <FieldLabel for="invalid">Invalid State</FieldLabel> <FieldDescription>Example showing the invalid error state.</FieldDescription> <InputOTP id="invalid" maxLength={6} value={value()} onValueChange={setValue}> <InputOTPGroup> <InputOTPSlot index={0} aria-invalid /> <InputOTPSlot index={1} aria-invalid /> </InputOTPGroup> <InputOTPSeparator /> <InputOTPGroup> <InputOTPSlot index={2} aria-invalid /> <InputOTPSlot index={3} aria-invalid /> </InputOTPGroup> <InputOTPSeparator /> <InputOTPGroup> <InputOTPSlot index={4} aria-invalid /> <InputOTPSlot index={5} aria-invalid /> </InputOTPGroup> </InputOTP> <FieldError errors={[{ message: "Invalid code. Please try again." }]} /> </Field> </Example> );}