Data input
Input OTP
A one-time code field: one native text input shown as a row of digit slots.
| Class name | Type | Description |
|---|---|---|
input-otpplf:input-otp | Component | Holds the input and its slots |
input-otp-inputplf:input-otp-input | Part | The native text input, beside input |
input-otp-slotsplf:input-otp-slots | Part | The slot row the script adds |
input-otp-groupplf:input-otp-group | Part | Slots the script joins into one box |
input-otp-slotplf:input-otp-slot | Part | One digit's box |
input-otp-separatorplf:input-otp-separator | Part | The mark the script adds between groups |
Default
The script draws one slot per digit, from maxlength, so without JavaScript the field is an ordinary Input.
Paste a code or edit it with the keyboard.
<div class="field">
<label class="field-label" for="input-otp-default">Six-digit code</label>
<div class="input-otp" data-plf-input-otp>
<input class="input input-otp-input" type="text" id="input-otp-default" name="code" maxlength="6" pattern="[0-9]{6}" inputmode="numeric" autocomplete="one-time-code" aria-describedby="input-otp-default-help">
</div>
<p class="field-help" id="input-otp-default-help">Paste a code or edit it with the keyboard.</p>
</div><div class="plf:field">
<label class="plf:field-label" for="input-otp-default">Six-digit code</label>
<div class="plf:input-otp" data-plf-input-otp>
<input class="plf:input plf:input-otp-input" type="text" id="input-otp-default" name="code" maxlength="6" pattern="[0-9]{6}" inputmode="numeric" autocomplete="one-time-code" aria-describedby="input-otp-default-help">
</div>
<p class="plf:field-help" id="input-otp-default-help">Paste a code or edit it with the keyboard.</p>
</div>import { Field, InputOTP } from '@pelatform/react';
export function Example() {
return (
<Field label="Six-digit code" help="Paste a code or edit it with the keyboard.">
<InputOTP name="code" maxLength={6} />
</Field>
);
}import { Field, InputOTP } from '@pelatform/react/prefixed';
export function Example() {
return (
<Field label="Six-digit code" help="Paste a code or edit it with the keyboard.">
<InputOTP name="code" maxLength={6} />
</Field>
);
}Separator
data-plf-groups splits the slots into groups, with a separator between them.
Paste a code or edit it with the keyboard.
<div class="field">
<label class="field-label" for="input-otp-separator">Grouped code</label>
<div class="input-otp" data-plf-input-otp data-plf-groups="3 3">
<input class="input input-otp-input" type="text" id="input-otp-separator" name="code" maxlength="6" pattern="[0-9]{6}" inputmode="numeric" autocomplete="one-time-code" aria-describedby="input-otp-separator-help">
</div>
<p class="field-help" id="input-otp-separator-help">Paste a code or edit it with the keyboard.</p>
</div><div class="plf:field">
<label class="plf:field-label" for="input-otp-separator">Grouped code</label>
<div class="plf:input-otp" data-plf-input-otp data-plf-groups="3 3">
<input class="plf:input plf:input-otp-input" type="text" id="input-otp-separator" name="code" maxlength="6" pattern="[0-9]{6}" inputmode="numeric" autocomplete="one-time-code" aria-describedby="input-otp-separator-help">
</div>
<p class="plf:field-help" id="input-otp-separator-help">Paste a code or edit it with the keyboard.</p>
</div>import { Field, InputOTP } from '@pelatform/react';
export function Example() {
return (
<Field label="Grouped code" help="Paste a code or edit it with the keyboard.">
<InputOTP name="code" maxLength={6} groups={[3, 3]} />
</Field>
);
}import { Field, InputOTP } from '@pelatform/react/prefixed';
export function Example() {
return (
<Field label="Grouped code" help="Paste a code or edit it with the keyboard.">
<InputOTP name="code" maxLength={6} groups={[3, 3]} />
</Field>
);
}Length
Set maxlength to 4 or 6; a group of 1 is a separate box.
Paste a code or edit it with the keyboard.
Paste a code or edit it with the keyboard.
<div class="field">
<label class="field-label" for="input-otp-six">Six-digit code</label>
<div class="input-otp" data-plf-input-otp>
<input class="input input-otp-input" type="text" id="input-otp-six" name="code" maxlength="6" pattern="[0-9]{6}" inputmode="numeric" autocomplete="one-time-code" aria-describedby="input-otp-six-help">
</div>
<p class="field-help" id="input-otp-six-help">Paste a code or edit it with the keyboard.</p>
</div>
<div class="field">
<label class="field-label" for="input-otp-four">Four-digit code</label>
<div class="input-otp" data-plf-input-otp data-plf-groups="1 1 1 1">
<input class="input input-otp-input" type="text" id="input-otp-four" name="code" maxlength="4" pattern="[0-9]{4}" inputmode="numeric" autocomplete="one-time-code" aria-describedby="input-otp-four-help">
</div>
<p class="field-help" id="input-otp-four-help">Paste a code or edit it with the keyboard.</p>
</div><div class="plf:field">
<label class="plf:field-label" for="input-otp-six">Six-digit code</label>
<div class="plf:input-otp" data-plf-input-otp>
<input class="plf:input plf:input-otp-input" type="text" id="input-otp-six" name="code" maxlength="6" pattern="[0-9]{6}" inputmode="numeric" autocomplete="one-time-code" aria-describedby="input-otp-six-help">
</div>
<p class="plf:field-help" id="input-otp-six-help">Paste a code or edit it with the keyboard.</p>
</div>
<div class="plf:field">
<label class="plf:field-label" for="input-otp-four">Four-digit code</label>
<div class="plf:input-otp" data-plf-input-otp data-plf-groups="1 1 1 1">
<input class="plf:input plf:input-otp-input" type="text" id="input-otp-four" name="code" maxlength="4" pattern="[0-9]{4}" inputmode="numeric" autocomplete="one-time-code" aria-describedby="input-otp-four-help">
</div>
<p class="plf:field-help" id="input-otp-four-help">Paste a code or edit it with the keyboard.</p>
</div>import { Field, InputOTP } from '@pelatform/react';
export function Example() {
return (
<>
<Field label="Six-digit code" help="Paste a code or edit it with the keyboard.">
<InputOTP name="code" maxLength={6} />
</Field>
<Field label="Four-digit code" help="Paste a code or edit it with the keyboard.">
<InputOTP name="code" maxLength={4} groups={[1, 1, 1, 1]} />
</Field>
</>
);
}import { Field, InputOTP } from '@pelatform/react/prefixed';
export function Example() {
return (
<>
<Field label="Six-digit code" help="Paste a code or edit it with the keyboard.">
<InputOTP name="code" maxLength={6} />
</Field>
<Field label="Four-digit code" help="Paste a code or edit it with the keyboard.">
<InputOTP name="code" maxLength={4} groups={[1, 1, 1, 1]} />
</Field>
</>
);
}Disabled
A disabled fieldset or aria-disabled="true" blocks editing as disabled does.
Paste a code or edit it with the keyboard.
<div class="field">
<label class="field-label" for="input-otp-disabled">Disabled code</label>
<div class="input-otp" data-plf-input-otp>
<input class="input input-otp-input" type="text" id="input-otp-disabled" name="code" maxlength="6" pattern="[0-9]{6}" inputmode="numeric" autocomplete="one-time-code" value="246810" disabled aria-describedby="input-otp-disabled-help">
</div>
<p class="field-help" id="input-otp-disabled-help">Paste a code or edit it with the keyboard.</p>
</div><div class="plf:field">
<label class="plf:field-label" for="input-otp-disabled">Disabled code</label>
<div class="plf:input-otp" data-plf-input-otp>
<input class="plf:input plf:input-otp-input" type="text" id="input-otp-disabled" name="code" maxlength="6" pattern="[0-9]{6}" inputmode="numeric" autocomplete="one-time-code" value="246810" disabled aria-describedby="input-otp-disabled-help">
</div>
<p class="plf:field-help" id="input-otp-disabled-help">Paste a code or edit it with the keyboard.</p>
</div>import { Field, InputOTP } from '@pelatform/react';
export function Example() {
return (
<Field label="Disabled code" help="Paste a code or edit it with the keyboard.">
<InputOTP name="code" maxLength={6} defaultValue="246810" disabled />
</Field>
);
}import { Field, InputOTP } from '@pelatform/react/prefixed';
export function Example() {
return (
<Field label="Disabled code" help="Paste a code or edit it with the keyboard.">
<InputOTP name="code" maxLength={6} defaultValue="246810" disabled />
</Field>
);
}Read-only
A read-only code can still be focused, selected and copied.
Paste a code or edit it with the keyboard.
<div class="field">
<label class="field-label" for="input-otp-readonly">Read-only code</label>
<div class="input-otp" data-plf-input-otp data-plf-groups="3 3">
<input class="input input-otp-input" type="text" id="input-otp-readonly" name="code" maxlength="6" pattern="[0-9]{6}" inputmode="numeric" autocomplete="one-time-code" value="038172" readonly aria-describedby="input-otp-readonly-help">
</div>
<p class="field-help" id="input-otp-readonly-help">Paste a code or edit it with the keyboard.</p>
</div><div class="plf:field">
<label class="plf:field-label" for="input-otp-readonly">Read-only code</label>
<div class="plf:input-otp" data-plf-input-otp data-plf-groups="3 3">
<input class="plf:input plf:input-otp-input" type="text" id="input-otp-readonly" name="code" maxlength="6" pattern="[0-9]{6}" inputmode="numeric" autocomplete="one-time-code" value="038172" readonly aria-describedby="input-otp-readonly-help">
</div>
<p class="plf:field-help" id="input-otp-readonly-help">Paste a code or edit it with the keyboard.</p>
</div>import { Field, InputOTP } from '@pelatform/react';
export function Example() {
return (
<Field label="Read-only code" help="Paste a code or edit it with the keyboard.">
<InputOTP name="code" maxLength={6} groups={[3, 3]} defaultValue="038172" readOnly />
</Field>
);
}import { Field, InputOTP } from '@pelatform/react/prefixed';
export function Example() {
return (
<Field label="Read-only code" help="Paste a code or edit it with the keyboard.">
<InputOTP name="code" maxLength={6} groups={[3, 3]} defaultValue="038172" readOnly />
</Field>
);
}Invalid
Set aria-invalid="true" on the input, not the container, and link the error with aria-describedby.
This code has expired. Request a new code.
<div class="field">
<label class="field-label" for="input-otp-invalid">Expired code</label>
<div class="input-otp" data-plf-input-otp>
<input class="input input-otp-input" type="text" id="input-otp-invalid" name="code" maxlength="6" pattern="[0-9]{6}" inputmode="numeric" autocomplete="one-time-code" value="000000" aria-invalid="true" aria-describedby="input-otp-invalid-error">
</div>
<p class="field-error" id="input-otp-invalid-error">This code has expired. Request a new code.</p>
</div><div class="plf:field">
<label class="plf:field-label" for="input-otp-invalid">Expired code</label>
<div class="plf:input-otp" data-plf-input-otp>
<input class="plf:input plf:input-otp-input" type="text" id="input-otp-invalid" name="code" maxlength="6" pattern="[0-9]{6}" inputmode="numeric" autocomplete="one-time-code" value="000000" aria-invalid="true" aria-describedby="input-otp-invalid-error">
</div>
<p class="plf:field-error" id="input-otp-invalid-error">This code has expired. Request a new code.</p>
</div>import { Field, InputOTP } from '@pelatform/react';
export function Example() {
return (
<Field label="Expired code" error="This code has expired. Request a new code.">
<InputOTP name="code" maxLength={6} defaultValue="000000" />
</Field>
);
}import { Field, InputOTP } from '@pelatform/react/prefixed';
export function Example() {
return (
<Field label="Expired code" error="This code has expired. Request a new code.">
<InputOTP name="code" maxLength={6} defaultValue="000000" />
</Field>
);
}Verification form
Checking the code and sending a new one stay with your server. PLF.Field.error() shows your message under the field until the next edit; this example’s preview accepts 123456.
<form class="card max-w-md" id="otp-verification-form" novalidate>
<div class="card-header">
<h3 class="card-title">Verify your email</h3>
<p class="card-description">Enter the six-digit code for m@example.com.</p>
</div>
<div class="card-body flex flex-col gap-4">
<div class="field">
<label class="field-label" for="otp-verification-input">
Verification code
<span class="field-required">*</span>
</label>
<div class="input-otp" data-plf-input-otp data-plf-groups="3 3" id="otp-verification">
<input class="input input-otp-input" type="text" id="otp-verification-input" name="otp-verification-code" maxlength="6" pattern="[0-9]{6}" inputmode="numeric" autocomplete="one-time-code" required aria-describedby="otp-verification-help" aria-errormessage="otp-verification-error">
</div>
<p class="field-help" id="otp-verification-help">Six digits. You can paste the entire code.</p>
<p class="field-error" id="otp-verification-error" hidden></p>
</div>
</div>
<div class="card-actions flex-col">
<button class="btn btn-primary w-full" type="submit">
<span class="btn-label">Verify code</span>
<span class="btn-loading">
<span class="btn-icon btn-icon-loading"></span>
Verifying…
</span>
</button>
<div class="flex flex-wrap items-center gap-4 w-full justify-between">
<button class="btn btn-ghost" type="button" id="otp-resend">Resend code</button>
<button class="btn btn-ghost" type="reset">Reset</button>
</div>
</div>
</form>
<script>
const form = document.getElementById('otp-verification-form');
const otp = document.getElementById('otp-verification');
const input = document.getElementById('otp-verification-input');
form.addEventListener('submit', async event => {
event.preventDefault();
const code = PLF.InputOTP.getInstance(otp);
if (!code.getState().complete) {
PLF.Field.error(input, 'Enter the complete six-digit code.');
code.focus();
return;
}
// Your request; the button shows its loading part until it settles.
const response = await PLF.Button.loading(event.submitter, fetch('/verify', { method: 'POST', body: new FormData(form) }));
if (!response.ok) PLF.Field.error(input, 'That code did not match. Check it and try again.');
// Otherwise the code is verified: continue, for example to the account page.
});
document.getElementById('otp-resend').addEventListener('click', async () => {
// Your request for a new code; once it is sent, the boxes and the message clear.
const response = await fetch('/resend', { method: 'POST' });
if (!response.ok) return;
const code = PLF.InputOTP.getInstance(otp);
code.clear();
// clear() sends no input event, so the message is cleared here.
PLF.Field.error(input, '');
code.focus();
});
</script><form class="plf:card plf:max-w-md" id="otp-verification-form" novalidate>
<div class="plf:card-header">
<h3 class="plf:card-title">Verify your email</h3>
<p class="plf:card-description">Enter the six-digit code for m@example.com.</p>
</div>
<div class="plf:card-body plf:flex plf:flex-col plf:gap-4">
<div class="plf:field">
<label class="plf:field-label" for="otp-verification-input">
Verification code
<span class="plf:field-required">*</span>
</label>
<div class="plf:input-otp" data-plf-input-otp data-plf-groups="3 3" id="otp-verification">
<input class="plf:input plf:input-otp-input" type="text" id="otp-verification-input" name="otp-verification-code" maxlength="6" pattern="[0-9]{6}" inputmode="numeric" autocomplete="one-time-code" required aria-describedby="otp-verification-help" aria-errormessage="otp-verification-error">
</div>
<p class="plf:field-help" id="otp-verification-help">Six digits. You can paste the entire code.</p>
<p class="plf:field-error" id="otp-verification-error" hidden></p>
</div>
</div>
<div class="plf:card-actions plf:flex-col">
<button class="plf:btn plf:btn-primary plf:w-full" type="submit">
<span class="plf:btn-label">Verify code</span>
<span class="plf:btn-loading">
<span class="plf:btn-icon plf:btn-icon-loading"></span>
Verifying…
</span>
</button>
<div class="plf:flex plf:flex-wrap plf:items-center plf:gap-4 plf:w-full plf:justify-between">
<button class="plf:btn plf:btn-ghost" type="button" id="otp-resend">Resend code</button>
<button class="plf:btn plf:btn-ghost" type="reset">Reset</button>
</div>
</div>
</form>
<script>
const form = document.getElementById('otp-verification-form');
const otp = document.getElementById('otp-verification');
const input = document.getElementById('otp-verification-input');
form.addEventListener('submit', async event => {
event.preventDefault();
const code = PLF.InputOTP.getInstance(otp);
if (!code.getState().complete) {
PLF.Field.error(input, 'Enter the complete six-digit code.');
code.focus();
return;
}
// Your request; the button shows its loading part until it settles.
const response = await PLF.Button.loading(event.submitter, fetch('/verify', { method: 'POST', body: new FormData(form) }));
if (!response.ok) PLF.Field.error(input, 'That code did not match. Check it and try again.');
// Otherwise the code is verified: continue, for example to the account page.
});
document.getElementById('otp-resend').addEventListener('click', async () => {
// Your request for a new code; once it is sent, the boxes and the message clear.
const response = await fetch('/resend', { method: 'POST' });
if (!response.ok) return;
const code = PLF.InputOTP.getInstance(otp);
code.clear();
// clear() sends no input event, so the message is cleared here.
PLF.Field.error(input, '');
code.focus();
});
</script>'use client';
import { useRef, useState, type FormEvent } from 'react';
import { Button, CardActions, CardBody, CardDescription, CardHeader, CardTitle, Field, InputOTP } from '@pelatform/react';
export function Example() {
const input = useRef<HTMLInputElement>(null);
const [code, setCode] = useState('');
const [error, setError] = useState('');
const [verifying, setVerifying] = useState(false);
async function verify(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
if (code.length < 6) {
setError('Enter the complete six-digit code.');
input.current?.focus();
return;
}
setVerifying(true);
try {
// Your request; the button shows its loading part until it settles.
const response = await fetch('/verify', { method: 'POST', body: new FormData(event.currentTarget) });
if (!response.ok) setError('That code did not match. Check it and try again.');
// Otherwise the code is verified: continue, for example to the account page.
} finally {
setVerifying(false);
}
}
function clear() {
setCode('');
setError('');
}
async function resend() {
// Your request for a new code; once it is sent, the boxes and the message clear.
const response = await fetch('/resend', { method: 'POST' });
if (!response.ok) return;
clear();
input.current?.focus();
}
return (
<form className="card max-w-md" noValidate onSubmit={verify} onReset={clear}>
<CardHeader>
<CardTitle>Verify your email</CardTitle>
<CardDescription>Enter the six-digit code for m@example.com.</CardDescription>
</CardHeader>
<CardBody className="flex flex-col gap-4">
<Field label="Verification code" required requiredLabel="*" help="Six digits. You can paste the entire code." error={error}>
<InputOTP
ref={input}
name="otp-verification-code"
maxLength={6}
groups={[3, 3]}
value={code}
onValueChange={next => {
// The next edit clears the message.
setCode(next);
setError('');
}}
/>
</Field>
</CardBody>
<CardActions className="flex-col">
<Button color="primary" className="w-full" type="submit" loading={verifying} loadingText="Verifying…">
Verify code
</Button>
<div className="flex flex-wrap items-center gap-4 w-full justify-between">
<Button variant="ghost" type="button" onClick={resend}>
Resend code
</Button>
<Button variant="ghost" type="reset">
Reset
</Button>
</div>
</CardActions>
</form>
);
}'use client';
import { useRef, useState, type FormEvent } from 'react';
import { Button, CardActions, CardBody, CardDescription, CardHeader, CardTitle, Field, InputOTP } from '@pelatform/react/prefixed';
export function Example() {
const input = useRef<HTMLInputElement>(null);
const [code, setCode] = useState('');
const [error, setError] = useState('');
const [verifying, setVerifying] = useState(false);
async function verify(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
if (code.length < 6) {
setError('Enter the complete six-digit code.');
input.current?.focus();
return;
}
setVerifying(true);
try {
// Your request; the button shows its loading part until it settles.
const response = await fetch('/verify', { method: 'POST', body: new FormData(event.currentTarget) });
if (!response.ok) setError('That code did not match. Check it and try again.');
// Otherwise the code is verified: continue, for example to the account page.
} finally {
setVerifying(false);
}
}
function clear() {
setCode('');
setError('');
}
async function resend() {
// Your request for a new code; once it is sent, the boxes and the message clear.
const response = await fetch('/resend', { method: 'POST' });
if (!response.ok) return;
clear();
input.current?.focus();
}
return (
<form className="plf:card plf:max-w-md" noValidate onSubmit={verify} onReset={clear}>
<CardHeader>
<CardTitle>Verify your email</CardTitle>
<CardDescription>Enter the six-digit code for m@example.com.</CardDescription>
</CardHeader>
<CardBody className="plf:flex plf:flex-col plf:gap-4">
<Field label="Verification code" required requiredLabel="*" help="Six digits. You can paste the entire code." error={error}>
<InputOTP
ref={input}
name="otp-verification-code"
maxLength={6}
groups={[3, 3]}
value={code}
onValueChange={next => {
// The next edit clears the message.
setCode(next);
setError('');
}}
/>
</Field>
</CardBody>
<CardActions className="plf:flex-col">
<Button color="primary" className="plf:w-full" type="submit" loading={verifying} loadingText="Verifying…">
Verify code
</Button>
<div className="plf:flex plf:flex-wrap plf:items-center plf:gap-4 plf:w-full plf:justify-between">
<Button variant="ghost" type="button" onClick={resend}>
Resend code
</Button>
<Button variant="ghost" type="reset">
Reset
</Button>
</div>
</CardActions>
</form>
);
}JavaScript
Input OTP needs JavaScript. The Full script (pelatform-ui.min.js) starts every component on page load, with no code of your own; with ESM imports, call init() yourself, as in Installation. For markup you add later, see Dynamic content.
<link rel="stylesheet" href="pelatform-ui.min.css">
<script src="pelatform-ui.min.js" defer></script><link rel="stylesheet" href="core/pelatform-ui-core.min.css">
<link rel="stylesheet" href="components/button.min.css">
<link rel="stylesheet" href="components/form.min.css">
<link rel="stylesheet" href="components/card.min.css">
<link rel="stylesheet" href="components/input-otp.min.css">
<script src="core/pelatform-ui-core.min.js" defer></script>
<script src="components/input-otp.min.js" defer></script>
<script src="components/button.min.js" defer></script>
<script src="components/field.min.js" defer></script>
<script>document.addEventListener('DOMContentLoaded', () => PLF.init());</script><link rel="stylesheet" href="pelatform-ui.prefixed.min.css">
<script src="pelatform-ui.min.js" defer></script><link rel="stylesheet" href="core/pelatform-ui-core.prefixed.min.css">
<link rel="stylesheet" href="components/button.prefixed.min.css">
<link rel="stylesheet" href="components/form.prefixed.min.css">
<link rel="stylesheet" href="components/card.prefixed.min.css">
<link rel="stylesheet" href="components/input-otp.prefixed.min.css">
<script src="core/pelatform-ui-core.min.js" defer></script>
<script src="components/input-otp.min.js" defer></script>
<script src="components/button.min.js" defer></script>
<script src="components/field.min.js" defer></script>
<script>document.addEventListener('DOMContentLoaded', () => PLF.init());</script>| Attribute | Purpose |
|---|---|
data-plf-input-otp |
The container, holding one <input type="text"> with maxlength="4" or "6" |
data-plf-groups |
On the container: group sizes separated by spaces that add up to maxlength, such as 3 3; a group of 1 is a separate box. One group of every digit by default |
data-plf-input-otp-active |
Set by the script on the active slot |
inputmode="numeric", spellcheck="false", pattern |
Added to the input when missing; the script also sets dir="ltr", so digits stay left to right in right-to-left layouts |
autocomplete="one-time-code" |
Added when missing; only a hint, since whether a phone or password manager offers the code depends on the browser and device |
| Method | Behavior |
|---|---|
PLF. |
Returns the instance, creating it for valid markup; invalid markup logs an error. Calling it again keeps the instance and its value |
PLF. |
Returns the instance or null |
instance. |
Returns the digits entered |
instance. |
Returns {value, length, filledCount, complete, disabled, readOnly} |
instance. |
Keeps the digits of value, up to the length; returns false when nothing changed, the change was canceled or the input is disabled or read-only |
instance. |
Empties the field; returns false in the same cases |
instance. |
Focuses the input with the caret at index, or after the last digit; returns false when the caret is already there or the input is disabled or hidden |
instance. |
Removes listeners, the slots and the attributes the script added; the value stays. Destroy and initialize again to change the length or groups |
With ESM, import InputOTP from @pelatform/ui/components/input-otp.
After setting input.value in code, dispatch an input event, or use setValue(), so the slots update.
| Event | When |
|---|---|
plf: |
Before a new value is accepted; cancelable |
plf: |
After the value changed |
plf: |
After a change that fills every slot; the form is not submitted |
Each event’s detail:
| Property | Events | Value |
|---|---|---|
instance |
All | The Input OTP instance |
reason |
All | api, or trigger for typing and pasting |
trigger |
All | The input, or null from the API |
state |
All | The new state, shaped like getState |
previousState |
before-change, change |
The state before the change |
Events bubble from the container. Canceling before-change restores the previous value and selection. Initialization, a native form reset (which empties the slots) and destroy() send no events.
Keyboard
| Key | Action |
|---|---|
| Tab, Shift+Tab | Moves to and from the field; it is one tab stop |
| Digits | Enter a digit at the caret; other characters are dropped |
| Arrow keys, Home, End | Move the caret, natively |
| Backspace, Delete | Remove digits, natively |
| Enter | Submits the form, natively |
| Click on a filled slot | Selects its digit for replacement |
React
InputOTP from @pelatform/react renders the input with the slots the script draws, and takes typing, pasting and the selection as the script does; inside a Field the input takes the field’s label, help and error.
| Prop | Values | Default |
|---|---|---|
maxLength |
4 or 6, the number of digits and slots |
Required |
groups |
Group sizes that add up to maxLength, such as [3, 3], with a separator between groups; a group of 1 is a separate box |
One group |
value |
The digits, which the application changes in onValueChange; keeping them refuses the edit, as canceling before-change does |
Its own state |
defaultValue |
The digits it starts with when the application does not set value |
Empty |
onValueChange |
(value), called with the digits when typing or pasting changes them |
None |
onComplete |
(value), called after a change that fills every slot; the form is not submitted |
None |
containerClassName |
Classes for the input-otp container, such as w-full, as className goes to the input |
None |
The other props are the native input’s, className and ref included; it adds inputMode, autoComplete, spellCheck and pattern as the script does.
CSS variables
Override these variables on a root or on a single Input OTP.
| Variable | Default |
|---|---|
--plf-input-otp-slot-size |
--plf-control-height-md, 2.25rem (36px), the slot width and height; slots shrink to fit a narrow container |
--plf-input-otp-gap |
Space 2, 0.5rem (8px), between groups or separate slots |
--plf-input-otp-font-size |
Large font size, 1.125rem (18px) |
--plf-input-otp-bg, --plf-input-otp-color |
--plf-input-bg, else the field color; text |
--plf-input-otp-border |
--plf-input-border, else --plf-color-input |
--plf-input-otp-radius |
Large radius, 0.5rem (8px) |
--plf-input-otp-focus-color |
--plf-focus-color, the active slot’s outline |
--plf-input-otp-invalid-border |
--plf-color-danger |
--plf-input-shadow, --plf-input-font-weight |
Shared with other fields |
There is no pattern option for letters and no masked PIN mode.