OtpField
A one-time-code field.
View code
let code: string | null = null;
<form method="post" action="/login/verify" mix={[vstack({ gap: 5, align: "stretch" })]}>
<div mix={[vstack({ gap: 2, align: "stretch" })]}>
<Label htmlFor="otp">One-time code</Label>
<OtpField id="otp" name="code" aria-describedby="otp-hint" />
<Description id="otp-hint">
We sent a six-digit code to sergio@example.com. <Link href="/login/resend">Resend</Link>.
</Description>
</div>
<div mix={[vstack({ gap: 2, align: "stretch" })]}>
<Header mix={[m(0)]}>As separate slots</Header>
{/* Each slot takes focus on its own and rings itself, so the row carries the group's
name and its layout and leaves the focus indicator to whichever slot holds it. */}
<div
role="group"
aria-label="One-time code"
mix={[
hstack({ gap: 2, align: "center" }),
otpSlots(),
on<HTMLDivElement, "ui:otp-complete">("ui:otp-complete", (event) => {
code = event.code;
void handle.update();
}),
]}
>
{SLOTS.map((position) => (
<OtpField
key={position}
length={1}
data-otp-slot
aria-label={"Digit " + position}
autoComplete={position === 1 ? "one-time-code" : "off"}
mix={[is("2.75rem")]}
/>
))}
</div>
<Description>
Typing moves to the next slot, Backspace returns to the last one, and pasting the whole
code fills every slot at once.
</Description>
</div>
<Button type="submit" disabled={code === null}>
{code === null ? "Enter the code" : "Verify " + code}
</Button>
<p mix={[m(0), text("xs")]}>
The single field submits as one value; the slot group reports its own through the
mixin's completion event.
</p>
</form>Installation
An ordinary dependency: install it, import the theme once, and import the component where it is used.
npm add @sdxc/uiimport "@sdxc/ui/theme.css";Usage
import { OtpField } from "@sdxc/ui";Renders a single native <input> for entering a one-time verification
code, built on Input, sized as one full-width control since a
single <input> is what carries autoComplete="one-time-code" reliably.
Examples
<OtpField
id="otp"
name="code"
aria-describedby="otp-error"
aria-invalid="true"
/>
<FieldError id="otp-error">{t("auth.otp.invalid")}</FieldError><OtpField
id="invite-code"
name="inviteCode"
length={8}
inputMode="text"
autoComplete="off"
pattern="[A-Za-z0-9]*"
/>Props
Read from the component's own types, so every prop, every default and every allowed value is listed.
| Prop | Type | Description |
|---|---|---|
length? | number | The one-time code's character count, applied as the control's native maxLength. Defaults to DEFAULT_LENGTH. |
inputMode? | Input.Props["inputMode"] | Virtual-keyboard hint passed straight through to the native inputMode attribute. Defaults to DEFAULT_INPUT_MODE, requesting the numeric keypad; set it to "text" for a code that mixes in letters. |
autoComplete? | Input.Props["autoComplete"] | Platform autofill hint passed straight through to the native autoComplete attribute. Defaults to DEFAULT_AUTO_COMPLETE, which a browser or OS keyboard looks for to offer an SMS or email code inline. |
Also accepts everything in Omit<
Input.Props,
"type" | "list" | "role" | "inputMode" | "autoComplete" | "maxLength"
>.