Input Otp
Anatomy
One-time password input with grouped slots supporting digits, alphanumeric, and custom regex patterns.
maud_ui::primitives::input_otp
Props
Generated from public Rust fields. Defaults are evaluated from the implementation.
| Prop | Type | Default | Description |
|---|---|---|---|
length | usize | 6 | Total number of digits |
group_size | usize | 3 | Group size for visual separator (e.g., 3 for a 3-3 layout on 6 digits) |
name | String | "otp" | No description |
id | String | "otp" | No description |
disabled | bool | false | No description |
pattern | OtpPattern | Digits | Accepted character pattern per slot. Defaults to OtpPattern::Digits. |
aria_invalid | bool | false | When true, emits aria-invalid="true" on each slot — for announcing incorrect-code validation without triggering native form validation. |
Examples
Verify your email
We sent a code to sofia@example.com
Enter PIN
Your transaction PIN
Confirms payments over $100. Never share this PIN.
2FA locked
Too many failed attempts. Try again in 15 minutes.
Redeem coupon
Alphanumeric code — pattern: DigitsAndChars.
Wrong code
aria_invalid: true — announces failure to screen readers.
Usage and composition
Import
use maud_ui::primitives::input_otp::{self, Props, OtpPattern};
Example
use maud::html;
use maud_ui::primitives::input_otp;
html! {
(input_otp::render(input_otp::Props {
length: 6,
group_size: 3,
name: "verification-code".to_string(),
id: "otp".to_string(),
pattern: input_otp::OtpPattern::Digits,
aria_invalid: false,
..Default::default()
}))
}
OtpPattern
pub enum OtpPattern {
Digits, // [0-9], inputmode="numeric"
DigitsAndChars, // [0-9A-Za-z], inputmode="text"
Custom(String), // Custom regex, inputmode="text"
}
Slots
Server-rendered slots are single-character <input> elements. With JavaScript in Standard, they remain independently editable and focusable. With JavaScript in Nova, one native aggregate input owns focus, selection and editing; the existing slot IDs and values are retained as readonly, aria-hidden visual mirrors. Programmatic focus on a slot forwards to the aggregate input; slot input events are not a substitute for the aggregate input’s native events. Without JavaScript, a single named native text input replaces the visible slots and submits the entered code directly.
Each server-rendered slot has:
maxlength="1"— prevents multi-char entry.pattern=".."— HTML5 validation.autocomplete="one-time-code"— browser password manager integration.aria-label="Digit N"— ordinal labeling.aria-invalid=[Some("true")]— whenaria_invalid: true.
Enhanced Standard uses a hidden input with the configured name to collect the final value. Enhanced Nova submits its native aggregate input under that name; the disabled legacy hidden input mirrors its value and is restored on cleanup. Without JavaScript the hidden mirror remains disabled, so the fallback input is the only successful control. Enhanced Nova uses a hidden, nameless, readonly form-associated reset helper when available; it appears in form.elements but adds no FormData entry. Older engines use deferred reset handling and cannot distinguish late cancellation.
Grouping
Visual separator (<div class="mui-input-otp__separator">) inserted before every group_size-th slot. Set group_size=0 to disable grouping.
Related
Shadcn Reference
Mirrors shadcn's InputOTP with OtpPattern enum (Digits, DigitsAndChars, Custom) and slot-based rendering.
Accessibility
- In Standard the container has
role="group"andaria-label="One-time password", and each editable slot is labeledDigit N. Enhanced Nova exposes the aggregate input directly without an extra group. - Without JavaScript the visible fallback is one native input labeled
One-time password; the slot mirrors are hidden from layout and accessibility APIs. - Separator has
role="separator". aria_invalid: trueannounces validation failure without native form submission.