One-time code
A row of single-character boxes for a verification code. Typing moves on, a paste or an SMS autofill fills them all, and a form gets the joined value.
Six digits, one value for the form
We sent a 6-digit code to your phone.
<div class="field">
<label id="otp-label">Verification code</label>
<div class="otp" role="group" aria-labelledby="otp-label" data-name="code"></div>
<small>We sent a 6-digit code to your phone.</small>
</div>
Know when the code is complete
<form class="stack" id="otp-form">
<div class="otp" role="group" aria-label="Verification code" data-name="code"></div>
<small id="otp-out" aria-live="polite">Type or paste a code.</small>
</form>
<script>
document.getElementById("otp-form").addEventListener("tsunami:otpcomplete", (e) => {
document.getElementById("otp-out").textContent = "Code " + e.detail.value + " is complete.";
});
</script>
Letters and digits, a different length
<div class="otp" role="group" aria-label="Invite code" data-length="4" data-type="text" data-name="invite"></div>
Invalid and disabled
<div class="otp" role="group" aria-label="Wrong code" aria-invalid="true" data-length="4" data-name="wrong"></div>
<div class="otp" role="group" aria-label="Locked code" data-length="4" data-disabled></div>
A smaller size
<div class="otp" role="group" aria-label="Code" data-size="sm" data-length="6"></div>
Behavior
- Typing a character moves to the next box. A character that is not allowed is ignored.
- A paste, or a code your phone offers from an SMS, is spread over the boxes. A whole code replaces what is there; a shorter one goes in from the box you are in.
- The first box carries
autocomplete="one-time-code", so a phone can offer the code from a message. - The hidden input named by
data-nameholds the joined value, so the form sends one field.
API
.otp is started on load, and so is one added to the
page later. tsunami.otp.mount() starts one by hand. A
form reset empties the
boxes.
Methods
| Method | Description |
|---|---|
tsunami.otp.mount(root?) |
Starts every .otp inside root (default: the document) that is not started yet. |
tsunami.otp.value(el) |
The joined value of the .otp element, as a string. |
tsunami.otp.set(el, text) |
Replaces the value. Characters that are not allowed are dropped and extra ones are cut. |
tsunami.otp.clear(el) |
Empties the boxes and focuses the first. |
Events
| Event | On | Description |
|---|---|---|
tsunami:otpcomplete |
the .otp, bubbles |
Sent when every box is filled. event.detail.value is the code. |
input |
a box, bubbles | The browser's own, sent as each box changes. |
Keyboard
| Key | Description |
|---|---|
| Backspace | Clears the box. In an empty box it goes back to the one before and clears that. |
| ← → | Moves to the previous or next box. |
| Home End | Moves to the first or the last box. |
Attributes
| Attribute / element | Values | Description |
|---|---|---|
.otp |
on a div |
The row. Give it role="group" and an aria-label or aria-labelledby. |
data-length |
a number (default 6) | How many boxes the script adds. Leave it out when the boxes are in the markup. |
data-type |
text (none = digits) |
Accepts letters and digits. The boxes get a text keyboard on a phone. |
data-name |
a field name | Adds the hidden input that holds the joined value. |
data-size |
sm |
Smaller boxes. |
data-disabled |
present or absent | Disables the boxes the script adds. For boxes in the markup, put disabled on each. |
aria-invalid |
true |
On .otp: the boxes get a red border. |