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
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.
Source
<hui-input-otp name="code" length="8" aria-label="Recovery code"></hui-input-otp>API
Attributes5
| Name | Type | Default | Description |
|---|---|---|---|
name | string | '' | The form field name. The cells joint into the single value submitted under it. |
disabled | boolean | false | Disables every cell and clears the form value. |
length | number | 6 | How many cells to render. Reflected. |
value | string | '' | The joined code. Not reflected; the attribute only seeds the reset value. |
required | boolean | false | Reflected; marks the control required for constraint validation. |
Properties5
| Name | Type | Default | Description |
|---|---|---|---|
name | string | '' | Reflects to the name attribute. |
disabled | boolean | false | Reflects to the disabled attribute. |
length | number | 6 | Reflects to the length attribute. |
value | string | '' | The joined value. Not reflected; changing it repaints the cells. |
required | boolean | false | Reflects to the required attribute. |
Events2
| Name | Description |
|---|---|
input | Emitted from the host on typing and on paste, after the joined value updates. bubbles and composed; no detail. |
change | Emitted from the host when a cell fires the native change. bubbles and composed; no detail. |
CSS custom properties5
| Name | Default | Description |
|---|---|---|
--hui-space-sm | 0.5rem | Gap between cells. |
--hui-background | oklch(1 0 0) | Cell fill. |
--hui-input | oklch(0.62 0.019 323.02) | Cell border. |
--hui-radius | 0.45rem | Base radius; the cell uses --hui-radius-lg. |
--hui-focus-ring-width | 3px | Width of the keyboard focus ring. |
::part() hooks2
| Name | Description |
|---|---|
group | The flex wrapper that carries role="group" and the shared keydown handler. |
cell | Each 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.
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.
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 isrole="group". - Each cell sets
autocomplete="one-time-code"andinputmode="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
| Keys | Action |
|---|---|
Typing | Fills 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 Right | Move focus between cells without changing the value. |
Home, End | Move focus to the first and last cell. |
Backspace | Deletes the current character; on an empty cell it clears the previous cell and moves focus back to it. |
Gotchas
valueis not reflected, so the joined code lives only on the property and in FormData, never on the attribute.