OtpField New Alpha Light DOM & Tailwind

Open in Storybook

A segmented one-time-code input that renders individual character cells with autofill and validation support.

OverviewUsageStandalone or inside a FieldExamplesAccessibilityAPI referenceDesign guidelines
This component is considered alpha. We're still finalising the API, so it may include a breaking change at any time — use it at your own risk.
Loading...

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:

Copy code
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:

InlineWith Field

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 whose for points at the control's id (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 / required validation 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 invalid and 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

PropertyAttribute Description TypeDefault
lengthlengthThe number of OTP cells. Must be a positive integer.number6
typetypeControls the allowed characters, inputmode and pattern. numeric accepts digits; alphanumeric accepts letters and digits.OtpFieldType'numeric'
readonlyreadonlyPrevents the user from changing the value while keeping navigation and focus.booleanfalse
maskmaskObscures each cell (renders type="password") for use on shared screens.booleanfalse
placeholderplaceholderPlaceholder character or string shown in every empty cell.string | undefined
invalidinvalidMarks 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.booleanfalse
autocompleteautocompleteThe 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'
groupSizegroup-sizeRenders 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.number0
valuevalueThe 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''
sizesizeThe size of the component.'s' | 'm' | 'l''m'
requiredrequiredDetermines 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.booleanfalse
disableddisabledMakes the component disabled. This prevents users from being able to interact with the component, and conveys its inactive state to assistive technologies.booleanfalse
namenameThe name of the form component.string | undefined
formformGets 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

PropertyAttribute Description TypeDefault
labellabelLabel for the control. Ignored when the control is wrapped in a <nord-field>, which provides the label via <nord-field-label>.string''
hinthintOptional hint text shown with the control. Ignored inside a <nord-field>, which provides it via <nord-field-description>.string | undefined
hintBelowhint-belowRenders the hint below the control and any error instead of below the label.booleanfalse
hideLabelhide-labelVisually hide the label, but still expose it to assistive technologies.booleanfalse
errorerrorOptional error shown with the control. Ignored inside a <nord-field>, which provides it via <nord-field-error>.string | undefined
hideRequiredhide-requiredVisually hide the required indicator, but still expose the required state to assistive technologies.booleanfalse

Methods

Method nameParameters Description
focus(options?: FocusOptions) => voidoptions: FocusOptionsFocus the field by moving focus into its active cell.
EventDetail TypeDescription
inputNordEventDispatched whenever the OTP value changes through user interaction (typing, Backspace/Delete, paste, autofill).
changeNordEventDispatched alongside input when the committed value changes via user interaction.
completeOtpFieldCompleteEventDispatched after the value updates when every cell becomes filled.
value-invalidOtpFieldInvalidEventDispatched 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.

PropertyDescriptionDefault
--n-otp-field-gapGap between cells (and between groups when group-size is used).var(--n-space-s)
--n-otp-field-cell-sizeControls 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-backgroundCell background.var(--n-color-surface)
--n-otp-field-border-colorCell border color (resting).var(--n-color-border-strong)
--n-otp-field-border-radiusCell corner radius.var(--n-border-radius-s)
--n-otp-field-font-sizeCell 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.

AttributeDescription
data-invalidPresent while the field is invalid (mirrors the \invalid\ property). Style hook for the whole control.
data-focusedPresent while any cell has focus. Style hook for focus-within treatments.
Light DOM & Tailwind. This component renders in the light DOM, so you can style it directly with your own CSS or Tailwind utility classes — there is no shadow boundary, and its default styles are low specificity, so your utilities win.
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:

Copy code
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:

Copy code
<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.

Copy code
<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.

Copy code
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, and type="numeric" for digit-only codes.
  • Set autocomplete="one-time-code" (the default) so SMS and keychain autofill works.
  • Use mask when 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 for pointing at the OTP field's id, 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 length to mismatch the code the user is expected to enter — every cell should map to one character of the code.

Was this page helpful?

Yes No

We use this feedback to improve our documentation.