import {
InputOTP,
InputOTPGroup,
InputOTPSeparator,
InputOTPSlot,
} from "@/components/ui/input-otp";
export default function InputOTPDemo() {
return (
<InputOTP 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>
);
}
About
Input OTP is built on top of input-otp by @guilherme_rodz.
Installation
npx nore-ui-cli@latest add input-otpUsage
import {
InputOTP,
InputOTPGroup,
InputOTPSeparator,
InputOTPSlot,
} from "@/components/ui/input-otp";
<InputOTP 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>
Examples
Pattern
Use the pattern prop to define a custom pattern for the OTP input.
"use client";
import { REGEXP_ONLY_DIGITS_AND_CHARS } from "input-otp";
import { InputOTP, InputOTPGroup, InputOTPSlot } from "@/components/ui/input-otp";
export default function InputOTPPattern() {
return (
<InputOTP maxLength={6} pattern={REGEXP_ONLY_DIGITS_AND_CHARS}>
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
<InputOTPSlot index={3} />
<InputOTPSlot index={4} />
<InputOTPSlot index={5} />
</InputOTPGroup>
</InputOTP>
);
}
import { REGEXP_ONLY_DIGITS_AND_CHARS } from "input-otp"
...
<InputOTP
maxLength={6}
pattern={REGEXP_ONLY_DIGITS_AND_CHARS}
>
<InputOTPGroup>
<InputOTPSlot index={0} />
{/* ... */}
</InputOTPGroup>
</InputOTP>
Separator
You can use the <InputOTPSeparator /> component to add a separator between the input groups.
import React from "react";
import {
InputOTP,
InputOTPGroup,
InputOTPSeparator,
InputOTPSlot,
} from "@/components/ui/input-otp";
export default function InputOTPWithSeparator() {
return (
<InputOTP maxLength={6}>
<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>
);
}
import {
InputOTP,
InputOTPGroup,
InputOTPSeparator,
InputOTPSlot,
} from "@/components/ui/input-otp"
...
<InputOTP maxLength={4}>
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
</InputOTPGroup>
<InputOTPSeparator />
<InputOTPGroup>
<InputOTPSlot index={2} />
<InputOTPSlot index={3} />
</InputOTPGroup>
</InputOTP>
Controlled
You can use the value and onChange props to control the input value.
Enter your one-time password.
"use client";
import * as React from "react";
import { styled } from "styled-system/jsx";
import { InputOTP, InputOTPGroup, InputOTPSlot } from "@/components/ui/input-otp";
export default function InputOTPControlled() {
const [value, setValue] = React.useState("");
return (
<styled.div css={{ spaceY: "2" }}>
<InputOTP maxLength={6} value={value} onChange={(value) => setValue(value)}>
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
<InputOTPSlot index={3} />
<InputOTPSlot index={4} />
<InputOTPSlot index={5} />
</InputOTPGroup>
</InputOTP>
<styled.div css={{ textAlign: "center", textStyle: "sm" }}>
{value === "" ? <>Enter your one-time password.</> : <>You entered: {value}</>}
</styled.div>
</styled.div>
);
}
Form
"use client";
import { Controller, useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { toast } from "sonner";
import { styled } from "styled-system/jsx";
import { z } from "zod";
import { Button } from "@/components/ui/button";
import { Field, FieldDescription, FieldError, FieldLabel } from "@/components/ui/field";
import { InputOTP, InputOTPGroup, InputOTPSlot } from "@/components/ui/input-otp";
const formSchema = z.object({
pin: z.string().min(6, {
message: "Your one-time password must be 6 characters.",
}),
});
type FormSchema = z.infer<typeof formSchema>;
export default function InputOTPForm() {
const form = useForm<FormSchema>({
resolver: zodResolver(formSchema),
defaultValues: {
pin: "",
},
});
const onSubmit = form.handleSubmit((data) => {
toast("You submitted the following values", {
description: (
<styled.pre css={{ mt: "2", w: "320px", rounded: "md", bg: "neutral.950", p: "4" }}>
<styled.code css={{ color: "white" }}>{JSON.stringify(data, null, 2)}</styled.code>
</styled.pre>
),
});
});
return (
<styled.form onSubmit={onSubmit} css={{ w: "2/3", spaceY: "6" }}>
<Controller
control={form.control}
name="pin"
render={({ field, fieldState }) => (
<Field>
<FieldLabel>One-Time Password</FieldLabel>
<InputOTP maxLength={6} {...field}>
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
<InputOTPSlot index={3} />
<InputOTPSlot index={4} />
<InputOTPSlot index={5} />
</InputOTPGroup>
</InputOTP>
<FieldDescription>
Please enter the one-time password sent to your phone.
</FieldDescription>
<FieldError>{fieldState.error?.message}</FieldError>
</Field>
)}
/>
<Button type="submit">Submit</Button>
</styled.form>
);
}