OTP input
An OTP input takes a short one-time code, such as an SMS or email verification code, a two-factor code, or a PIN. It shows one box per character over a single real field.
<tk-field label="Enter the 6-digit code we sent you" helper="Sent to +1 ••• ••• 4321"> <tk-otp-input name="code"></tk-otp-input> </tk-field>
Anatomy
| Part | What it is |
|---|---|
tk-otp-input |
the element; one Tab stop |
field part |
the row of boxes |
box part |
one character box, 48×48px |
input part |
the one real <input>, invisible, over the row |
The label, help text, and error message come from a
Length, type, and mask
<tk-field label="Enter your 4-digit PIN"> <tk-otp-input name="pin" length="4" mask></tk-otp-input> </tk-field>
<tk-field label="Enter a backup code"> <tk-otp-input name="backup" length="8" type="alphanumeric"></tk-otp-input> </tk-field>
lengthsets the number of boxes: 6 by default, 4 for a PIN.type:numeric(default) accepts 0–9 and shows the number pad;alphanumericaccepts letters and digits and keeps their case.maskshows filled boxes as dots. Use it only for a code people know, such as a PIN: it can stop the phone from filling in an SMS code.
Typing and pasting
- Typing fills a box and moves to the next. Typing on a filled box replaces it.
- Backspace clears and steps back. Arrow keys, Home, and End move between boxes. Tab always leaves the field.
- Pasting fills every box, from any position, and drops spaces and hyphens:
"98 76-54" becomes
987654. - An SMS code from the phone's autofill fills every box at once.
- The active box shows a blinking caret, or a steady one when reduced motion is on.
States
<tk-otp-input aria-label="Verification code" value="482913" readonly></tk-otp-input> <tk-otp-input aria-label="Verification code" disabled></tk-otp-input>
<tk-field label="Enter the 6-digit code we sent you" error="That code is incorrect. Check it and try again."> <tk-otp-input name="code" value="123456"></tk-otp-input> </tk-field>
The boxes don't change on hover. The active box takes the focus color and a thicker border; that is the focus indicator.
- Error: set
erroron the Field. Every box turns red, the active one too, and the message shows under the row. The OTP input doesn't check the code; you decide when and what to say. readonly: people can focus and copy the code, but not change it.disabled: the field can't be focused or edited, and it's left out of the form.- Required: set
requiredon the Field. The code must be complete to submit. - A correct code shows no green state. Move people on to the next screen.
Completion
When the last box fills, the field fires tk-complete with the code in detail.value.
It doesn't submit or move focus by itself.
- You can submit on
tk-complete, but people using a screen reader or keyboard may want to check the code first. A verify button is safer. - If the code is wrong, set
erroron the Field and keep focus in the field.
When to use
- Use an OTP input for a fixed-length one-time code: a verification code, a two-factor code, a PIN.
- Use an
Input for other text, including a long reference number. A password goes intk-input type="password". - A single field works too. An Input with
inputmode="numeric"andautocomplete="one-time-code"is simpler and just as accessible. Use the OTP input when the boxed look helps people see the code's length. - Build the surroundings yourself: the resend link and its countdown, the verify button, the "sent to" text, and the success screen.
Content
- Label: name the code and its length: "Enter the 6-digit code we sent you".
- Help text: where the code was sent, or how long it's valid. No links.
- Error: say what happened and what to do: "That code is incorrect. Check it and try again", or "Enter the 6-digit code" for an empty submit. No "please" or "sorry".
More in
Do / Don't
| ✅ Do | ❌ Don't |
|---|---|
| Name the code and its length in the label | Leave the field without a name |
| Let people paste the whole code | Block paste or require one character per box |
Keep one-time-code autofill on |
Build the code from separate inputs |
Use mask for a PIN |
Mask an SMS code |
| Show a wrong code with the Field's error | Turn the boxes green on success |
| Use a single Input when that's simpler | Force the boxed look where it doesn't help |
Related
Field : the label, help text, and error around the code.Input : any other text, and the single-field option for a code.
API
Attributes
| Attribute | Type | Default | What it does |
|---|---|---|---|
length |
number | 6 |
the number of boxes |
type |
numeric | alphanumeric |
numeric |
the allowed characters and the on-screen keyboard |
mask |
boolean | false |
shows filled boxes as dots |
value |
string | '' |
the code; submits under name |
name |
string | — | the form field name |
autocomplete |
string | one-time-code |
lets the phone fill in an SMS code; set off to turn it off |
inputmode |
string | numeric or text |
the on-screen keyboard; follows type |
required |
boolean | false |
the browser blocks submitting until the code is complete |
readonly |
boolean | false |
focusable and copyable, not editable |
disabled |
boolean | false |
not focusable, not editable, left out of the form |
Events
| Event | Detail | When |
|---|---|---|
input |
— | whenever the code changes: typing, pasting, autofill, deleting |
change |
— | on blur after an edit |
tk-complete |
{ value } |
when the last box fills |
Resetting the form restores the first value without firing change.
Methods
focus(): moves focus into the field.
CSS
::part(field): the row of boxes.::part(box): each box.::part(input): the real<input>.
Tokens
| Role | Rest | Active box | Error | Disabled | Read-only |
|---|---|---|---|---|---|
| box border | border/subtle |
border/accent/focus |
border/error |
border/disabled |
border/subtle |
| box background | surface/default |
surface/default |
surface/default |
surface/disabled |
surface/subtle |
| character | text/default |
text/default |
text/default |
text/disabled |
text/default |
The caret is border/accent/focus, red on an error. Characters are font-size-lg, medium
weight. Boxes are radius-8, space-8 apart. Values per theme:
Accessibility
Built in:
- One real
<input>: one Tab stop, one field for screen readers, and native caret, paste, and autofill. autocomplete="one-time-code"by default, so SMS codes fill in.- The keyboard follows
type: the number pad for numeric codes. - Typing never moves focus out of the field, and Tab always leaves it.
- A composing Field names the field, links the help text and error, and marks it invalid on error.
- Enter submits the form the field is in.
Your part:
- Give every OTP input a name: a Field label, or
aria-label. - Don't submit the moment the code is complete without a way to check it.
- Use
maskonly for codes people know.
| Key | Action |
|---|---|
Tab |
moves focus into the field, and out of it |
← / →, Home / End |
move between boxes |
Backspace |
clears the box and steps back |
Enter |
submits the form |
Contrast limits
The resting box border shares the Input's limits: see