CODE INPUT: Single-digit-per-cell verification code entry
VARIANTS: Blank | Invalid error | Disabled | 4-cell
COMPOSES: .form-control (each cell is a real form-control input)

Typing a digit auto-advances to the next cell; Backspace on an empty cell moves back and clears the previous digit. Pasting a full code (on any cell) distributes it across the row starting from cell 1. A hidden input keeps the joined digits in sync for form submission. Requires data-controller="code-input".

Blank

Default state: 6 cells split 3+3 by .code-input-sep. Each cell is labelled "Digit N"; the group itself is labelled by the visible field label via aria-labelledby. The first cell carries autocomplete="one-time-code" so browsers/password managers can offer SMS-code autofill.

We've emailed a 6-digit code to a.okafor@…. Enter it below to verify your account.

<div class="code-input" role="group" aria-labelledby="…"><input class="form-control code-input-cell" inputmode="numeric" maxlength="1" autocomplete="one-time-code" aria-label="Digit 1" data-code-input-target="cell" data-action="input->code-input#input keydown->code-input#keydown paste->code-input#paste focus->code-input#selectOnFocus">…<span class="code-input-sep" aria-hidden="true">–</span>…<input type="hidden" data-code-input-target="value"></div>

Invalid code error

Error state: each cell carries .is-invalid (reusing .form-control.is-invalid's red border/focus ring — no bespoke error CSS needed), aria-invalid="true", and aria-describedby pointing at the error text below the row. Editing any cell clears .is-invalid and aria-invalid from every cell and hides the error text (C08 — the error clears on the first correcting keystroke, not on resubmit).

That code isn't valid. Check your email for the latest code and try again.

<input class="form-control code-input-cell is-invalid" aria-describedby="code-input-error" aria-invalid="true" …> … <p class="invalid-feedback" id="code-input-error" data-code-input-target="error">That code isn't valid.</p>

Disabled

Disabled: real [disabled] attribute on every cell — for an already-verified code that's now locked from editing.

Your account is already verified.

4-cell variant

Any cell count: the controller reads whatever data-code-input-target="cell" elements are present — no config value for cell count. Drop 4 cells (a PIN) or 6 (an OTP) and it just works.

Use the 4-digit PIN sent to your registered mobile number.

<div class="code-input" role="group" aria-labelledby="…"><!-- 4 cells, no .code-input-sep --></div>