Skip to content
Home Theme Gallery

Input OTP

The custom element is <hui-input-otp>.

Overview

A one-time-code control of several single-character cells that join into one submitted value. Typing advances to the next cell and pasting distributes the code from the focused cell onward.

Example

One-time code
Source
<hui-input-otp name="code" length="6" aria-label="Verification code"></hui-input-otp>

The Go template that renders it:

<hui-input-otp name="code" length="6" value="{{ .Code }}" aria-label="Verification code"></hui-input-otp>

On a phone

Cells shrink to fit a narrow container, down to 24px each, rather than overflowing it: eight fit a 320px phone.

Eight cells 375 px wide
Source
<hui-input-otp name="code" length="8" aria-label="Recovery code"></hui-input-otp>

API

Attributes5
Attributes
NameTypeDefaultDescription
namestring''The form field name. The cells joint into the single value submitted under it.
disabledbooleanfalseDisables every cell and clears the form value.
lengthnumber6How many cells to render. Reflected.
valuestring''The joined code. Not reflected; the attribute only seeds the reset value.
requiredbooleanfalseReflected; marks the control required for constraint validation.
Properties5
Properties
NameTypeDefaultDescription
namestring''Reflects to the name attribute.
disabledbooleanfalseReflects to the disabled attribute.
lengthnumber6Reflects to the length attribute.
valuestring''The joined value. Not reflected; changing it repaints the cells.
requiredbooleanfalseReflects to the required attribute.
Events2
Events
NameDescription
inputEmitted from the host on typing and on paste, after the joined value updates. bubbles and composed; no detail.
changeEmitted from the host when a cell fires the native change. bubbles and composed; no detail.
CSS custom properties5
CSS custom properties
NameDefaultDescription
--hui-space-sm0.5remGap between cells.
--hui-backgroundoklch(1 0 0)Cell fill.
--hui-inputoklch(0.62 0.019 323.02)Cell border.
--hui-radius0.45remBase radius; the cell uses --hui-radius-lg.
--hui-focus-ring-width3pxWidth of the keyboard focus ring.
::part() hooks2
::part() hooks
NameDescription
groupThe flex wrapper that carries role="group" and the shared keydown handler.
cellEach single-character native input.

States

Four cells filled

A shorter code, filled: length sets how many cells there are and value fills them from the left.

Four cells filled
Source
<hui-input-otp name="code" length="4" value="1234" aria-label="Verification code"></hui-input-otp>

Disabled

Disabled with a partial value: nothing can be typed, and the cells keep what the server rendered.

Disabled
Source
<hui-input-otp name="code" length="6" value="12" disabled aria-label="Verification code"></hui-input-otp>

Server-side mechanics

The server owns name, length, value, required and disabled. The cells join into one value submitted under name; with no name nothing is submitted. Because value is not reflected and the cells are internal, an hx-swap must re-render the value attribute to repopulate the code, and a swap mid-entry loses the partly typed code and the focus position. Server-rendered validation uses required plus aria-invalid="true" or a hui-field invalid wrapper.

Accessibility

  • Every cell is a native text input labelled Digit N, and the wrapper is role="group".
  • Each cell sets autocomplete="one-time-code" and inputmode="numeric" so the platform offers the code and a numeric keyboard.
  • The joined value is the single form entry; there is no per-cell form value.
  • Focus moves with the code as it is typed and pasted, so a keyboard user is never left behind.

Keyboard

Keyboard
KeysAction
TypingFills the focused cell and advances to the next, replacing a digit already there, so a wrong code can be typed over; a multi-character input or paste distributes from the current cell.
Arrow Left, Arrow RightMove focus between cells without changing the value.
Home, EndMove focus to the first and last cell.
BackspaceDeletes the current character; on an empty cell it clears the previous cell and moves focus back to it.

Gotchas

  • value is not reflected, so the joined code lives only on the property and in FormData, never on the attribute.