OtpField New Alpha Light DOM & Tailwind
A segmented one-time-code input that renders individual character cells with autofill and validation support.
Usage
OTP Field is a segmented one-time-code input. It renders length individual character cells backed by a visually-hidden input that holds the full value, enabling SMS / keychain autofill and native validation. Typing fills and auto-advances, Backspace clears and moves back, the arrow keys navigate, and pasting a code distributes its characters across the cells.
OTP Field renders its built-in label, hint and error on its own. Wrapped in a Field, the Field takes over with a Field Label (connected with for), Field Description and Field Error, and the built-in versions step aside automatically.
Importing the component registers its custom element:
import "@nordhealth/components/lib/OtpField"
Pair it with a Field and set the number of cells with length:
Standalone or inside a Field
Use the control on its own with its built-in label, hint and error, or wrap it in a Field — the Field then provides the labelling. Both render the same control:
Examples
Alphanumeric
Set type="alphanumeric" to accept letters and digits, for backup or recovery codes.
Grouped cells
Use group-size to split the cells into groups with a separator between them — for example 3 renders the code as 123-456.
Placeholder
Set placeholder to show a hint character in each empty cell.
Placeholder until focus
Keep the placeholder hint visible in each cell until its slot becomes the active one, so users always see the expected format.
Masked
Add mask to obscure the entered characters, for codes typed on shared screens.
Custom normalization
Provide a normalize function to transform each typed or pasted character before it lands in a cell — for example upper-casing input or stripping unwanted characters.
Form integration
The hidden input carries the full value with its name, so OTP Field submits and validates inside a <form> like any native control.
Validation and errors
Mark the Field as invalid and the control as invalid, then put the message in a Field Error.
RTL
OTP Field follows the document or container direction. Toggle the direction to see the layout mirror.
Accessibility
- Give OTP Field an accessible name with its built-in
label, or — inside a Field — a Field Label whoseforpoints at the control'sid(applied to the first cell). - Each cell carries a localized per-cell accessible label describing its position (for example "Character 1 of 6"), and the hidden input holds the full value so SMS / keychain autofill and native
pattern/requiredvalidation work. - Keyboard support follows the expected pattern: typing auto-advances, Backspace clears and steps back, the arrow keys move between cells, and pasting a full code distributes it across the cells.
- For validation, mark both the Field and the control as
invalidand put the message in a Field Error so it is announced and styled consistently.
API reference
OtpField
OTP Field is a segmented one-time-code input: it renders length individual
character cells plus a visually-hidden input that holds the full value, enabling
SMS / keychain autofill and native pattern/required validation. Typing fills
and auto-advances, Backspace clears and moves back, the arrow keys navigate, and
pasting a code distributes its characters across the cells.
OTP Field is a control only — it has no built-in label, hint or error. Compose
it inside a Field with a Field Label
(connected with for) to give it an accessible name, helper text and an error
message, exactly like a native input.
<nord-otp-field></nord-otp-field>Props
| Property | Attribute | Description | Type | Default |
|---|---|---|---|---|
length | length | The number of OTP cells. Must be a positive integer. | number | 6 |
type | type | Controls the allowed characters, inputmode and pattern. numeric accepts
digits; alphanumeric accepts letters and digits. | OtpFieldType | 'numeric' |
readonly | readonly | Prevents the user from changing the value while keeping navigation and focus. | boolean | false |
mask | mask | Obscures each cell (renders type="password") for use on shared screens. | boolean | false |
placeholder | placeholder | Placeholder character or string shown in every empty cell. | string | undefined | — |
invalid | invalid | Marks the field invalid, applying error styling and aria-invalid to every
cell and the hidden input. Pair with a nord-field-error in the Field. | boolean | false |
autocomplete | autocomplete | The autocomplete token applied to the first cell and the hidden input (all
other cells get autocomplete="off"). Defaults to one-time-code to enable
SMS / keychain OTP autofill. | string | 'one-time-code' |
groupSize | group-size | Renders a decorative separator after every N cells (for example, 3 shows
123-456). 0 disables grouping. The separator is aria-hidden and does
not affect the value or navigation. | number | 0 |
value | value | The OTP value, for example '123456'. Whitespace is stripped, characters are
filtered by type, and the result is clamped to length. Set programmatically
to prefill. | string | '' |
size | size | The size of the component. | 's' | 'm' | 'l' | 'm' |
required | required | Determines whether the control is required or not.
A required control is announced as such to assistive technology and, inside
a <nord-field>, drives the required indicator on the <nord-field-label>.
When using this property you need to also set “novalidate” attribute on a form element to prevent browser from displaying its own validation errors. | boolean | false |
disabled | disabled | Makes the component disabled. This prevents users from being able to interact with the component, and conveys its inactive state to assistive technologies. | boolean | false |
name | name | The name of the form component. | string | undefined | — |
form | form | Gets the form, if any, associated with the form element. The setter accepts a string, which is the id of the form. | HTMLFormElement | null | — |
Field properties
| Property | Attribute | Description | Type | Default |
|---|---|---|---|---|
label | label | Label for the control. Ignored when the control is wrapped in a
<nord-field>, which provides the label via <nord-field-label>. | string | '' |
hint | hint | Optional hint text shown with the control. Ignored inside a <nord-field>,
which provides it via <nord-field-description>. | string | undefined | — |
hintBelow | hint-below | Renders the hint below the control and any error instead of below the label. | boolean | false |
hideLabel | hide-label | Visually hide the label, but still expose it to assistive technologies. | boolean | false |
error | error | Optional error shown with the control. Ignored inside a <nord-field>,
which provides it via <nord-field-error>. | string | undefined | — |
hideRequired | hide-required | Visually hide the required indicator, but still expose the required state to assistive technologies. | boolean | false |
Methods
| Method name | Parameters | Description |
|---|---|---|
focus(options?: FocusOptions) => void | options: FocusOptions | Focus the field by moving focus into its active cell. |
| Event | Detail Type | Description |
|---|---|---|
input | NordEvent | Dispatched whenever the OTP value changes through user interaction (typing, Backspace/Delete, paste, autofill). |
change | NordEvent | Dispatched alongside input when the committed value changes via user interaction. |
complete | OtpFieldCompleteEvent | Dispatched after the value updates when every cell becomes filled. |
value-invalid | OtpFieldInvalidEvent | Dispatched when typed or pasted text contained characters rejected by type. |
CSS Properties
CSS Custom Properties provide more fine grain control over component presentation. We advise utilizing existing properties on the component before using these.
| Property | Description | Default |
|---|---|---|
--n-otp-field-gap | Gap between cells (and between groups when group-size is used). | var(--n-space-s) |
--n-otp-field-cell-size | Controls the block-size and inline-size of each square cell. When unset, size maps to the shared control block sizes. | var(--n-space-xl) |
--n-otp-field-background | Cell background. | var(--n-color-surface) |
--n-otp-field-border-color | Cell border color (resting). | var(--n-color-border-strong) |
--n-otp-field-border-radius | Cell corner radius. | var(--n-border-radius-s) |
--n-otp-field-font-size | Cell character font size. | var(--n-font-size-m) |
State attributes
These data-* attributes are set by the component to reflect its current state. Use them as styling hooks — they are read-only and should not be set manually.
| Attribute | Description |
|---|---|
data-invalid | Present while the field is invalid (mirrors the \invalid\ property). Style hook for the whole control. |
data-focused | Present while any cell has focus. Style hook for focus-within treatments. |
Design guidelinesFor designers
Usage
OTP Field is a control only — it has no built-in label, hint or error. Pair it with a Field to give it a label, helper text and an error message, exactly like a native input. Connect the Field Label to the OTP field with the for attribute so it gets an accessible name and click-to-focus. The label names the first cell (the OTP field forwards its accessible name onto it), while the remaining cells announce their position (for example, "Character 2 of 6").
Import the OTP field and the Field parts you need — each import registers its custom element:
import '@nordhealth/components/lib/OtpField'
import '@nordhealth/components/lib/Field'
import '@nordhealth/components/lib/FieldLabel'
import '@nordhealth/components/lib/FieldDescription'
import '@nordhealth/components/lib/FieldError'
Then compose the Field around the OTP field:
<nord-field>
<nord-field-label for="verification-code">Verification code</nord-field-label>
<nord-otp-field id="verification-code" length="6"></nord-otp-field>
<nord-field-description>Enter the 6-character code we sent to your device.</nord-field-description>
<!-- show a Field Error and mark the Field invalid instead, when validation fails -->
</nord-field>
OTP Field renders length individual character cells plus a visually-hidden input that holds the full value. The hidden input carries autocomplete="one-time-code", so SMS and keychain one-time-code autofill works, and it carries the native pattern, minlength, maxlength and required so form validation works. Typing fills and auto-advances, Backspace clears and moves back, the arrow keys navigate, and pasting a code distributes its characters across the cells.
OTP Field renders in the light DOM, so you can style it directly with your own CSS or Tailwind utilities, and size it with the size attribute (s | m | l) or the --n-otp-field-* custom properties.
Set name to submit the value with a form. The value submits under the host name; the inner cell inputs and the hidden input carry no name, so the browser never double-submits.
<form>
<nord-field>
<nord-field-label for="otp">Verification code</nord-field-label>
<nord-otp-field id="otp" name="verificationCode" length="6" required></nord-otp-field>
</nord-field>
</form>
Listen for the complete event to react when every cell is filled — for example, to submit the form automatically.
document.querySelector('nord-otp-field').addEventListener('complete', (event) => {
console.log('Code entered:', event.value)
})
Do
- Use for short verification or one-time codes, such as 2FA, email confirmation, or recovery codes.
- Pair it with a Field and a Field Label connected with
for, so it has an accessible name, helper text and an error message. - Use
type="alphanumeric"for codes that mix letters and digits, andtype="numeric"for digit-only codes. - Set
autocomplete="one-time-code"(the default) so SMS and keychain autofill works. - Use
maskwhen the code should be obscured on shared screens.
Don't
- Don't use for free-form or long text entry, see Input instead.
- Don't add a Field Label without a
forpointing at the OTP field'sid, or the first cell will have no accessible name. - Don't use for passwords or secrets longer than a few characters, see Input instead.
- Don't change
lengthto mismatch the code the user is expected to enter — every cell should map to one character of the code.