Input OTP

A polished OTP field for verification and two-factor auth flows.

Loading preview...

Examples

Practical examples and common states for the same installable component.

4-digit PIN

Short numeric codes for device PINs and quick unlock flows.

examples/input-otp-pin.tsx
import { InputOtp } from "@/components/wensity/input-otp";
export function InputOtpPin() {
return (
<InputOtp
label="Device PIN"
mode="numeric"
length={4}
masked
hint="Use your PIN."
/>
);
}

8-digit backup code

Alphanumeric recovery codes with custom group separators.

examples/input-otp-backup-code.tsx
import { InputOtp } from "@/components/wensity/input-otp";
export function InputOtpBackupCode() {
return (
<InputOtp
label="Backup code"
mode="alphanumeric"
length={8}
groups={[4, 4]}
separator="-"
hint="Use backup code."
/>
);
}

Alphanumeric

Backup codes accept letters and numbers.

examples/input-otp-alphanumeric.tsx
import { InputOtp } from "@/components/wensity/input-otp";
export function InputOtpAlphanumeric() {
return (
<InputOtp
label="Backup code"
mode="alphanumeric"
length={6}
hint="Alphanumeric only."
/>
);
}

Read-only verified

Show a confirmed code without the muted disabled treatment.

examples/input-otp-readonly.tsx
import { InputOtp } from "@/components/wensity/input-otp";
export function InputOtpReadOnly() {
return (
<InputOtp
label="Verified code"
mode="numeric"
groups={[3, 3]}
separator=" "
value="120805"
readOnly
status="success"
success="Code verified."
/>
);
}

Controlled reset

Remount with `key={attempt}` to clear slots after external actions.

Requires: button

examples/input-otp-reset.tsx
import { useState } from "react";
import { Button } from "@/components/wensity/button";
import { InputOtp } from "@/components/wensity/input-otp";
export function InputOtpReset() {
const [attempt, setAttempt] = useState(0);
const [value, setValue] = useState("");
return (
<>
<InputOtp
key={attempt}
label="Authentication code"
value={value}
onValueChange={setValue}
/>
<Button
type="button"
variant="secondary"
onClick={() => {
setValue("");
setAttempt((current) => current + 1);
}}
>
Reset field
</Button>
</>
);
}

Try again / resend

Recovery actions after a failed verification attempt.

Requires: button

examples/input-otp-resend.tsx
import { useState } from "react";
import { Button } from "@/components/wensity/button";
import { InputOtp } from "@/components/wensity/input-otp";
export function InputOtpResend() {
const [attempt, setAttempt] = useState(0);
const [value, setValue] = useState("");
const [status, setStatus] = useState<"default" | "error" | "success">("default");
const reset = () => {
setValue("");
setStatus("default");
setAttempt((current) => current + 1);
};
return (
<>
<InputOtp
key={attempt}
label="SMS code"
value={value}
status={status}
onValueChange={setValue}
onFilled={(code) => setStatus(code === "120805" ? "success" : "error")}
error={status === "error" ? "Invalid code." : undefined}
/>
{status === "error" ? (
<>
<Button type="button" variant="secondary" onClick={reset}>
Try again
</Button>
<Button type="button" variant="ghost" onClick={reset}>
Resend code
</Button>
</>
) : null}
</>
);
}

Masked

Hide entered characters for PIN-style entry.

examples/input-otp-masked.tsx
import { InputOtp } from "@/components/wensity/input-otp";
export function InputOtpMasked() {
return (
<InputOtp
label="Security PIN"
mode="numeric"
masked
hint="Hidden entry."
/>
);
}

Separated

Group digits with separators for easier scanning.

examples/input-otp-separated.tsx
import { InputOtp } from "@/components/wensity/input-otp";
export function InputOtpSeparated() {
return (
<InputOtp
label="Authenticator code"
mode="numeric"
groups={[3, 3]}
separator="·"
hint="Grouped code."
/>
);
}

Form

Compose with FormField for labels and validation layout.

Requires: buttonform-field

examples/input-otp-form.tsx
import { Button } from "@/components/wensity/button";
import {
FormActions,
FormField,
FormFieldControl,
} from "@/components/wensity/form-field";
import { InputOtp } from "@/components/wensity/input-otp";
export function InputOtpForm() {
return (
<form onSubmit={(event) => event.preventDefault()}>
<FormField label="Authentication code" required>
<FormFieldControl asChild>
<InputOtp
mode="numeric"
groups={[3, 3]}
hint="Use authenticator code."
/>
</FormFieldControl>
</FormField>
<FormActions className="mt-4">
<Button type="submit">Verify</Button>
</FormActions>
</form>
);
}