InputOTP
One-time password input with individual character slots, auto-advance, and paste support.
Quick Start
<%= kui(:input_otp, length: 6, name: "otp_code") do %>
<%= kui(:input_otp, :group) do %>
<%= kui(:input_otp, :slot) %>
<%= kui(:input_otp, :slot) %>
<%= kui(:input_otp, :slot) %>
<% end %>
<%= kui(:input_otp, :separator) %>
<%= kui(:input_otp, :group) do %>
<%= kui(:input_otp, :slot) %>
<%= kui(:input_otp, :slot) %>
<%= kui(:input_otp, :slot) %>
<% end %>
<% end %>
Locals
input_otp (root)
| Local | Type | Default | Description |
|---|---|---|---|
length |
Integer | 6 |
Number of character slots |
name |
String | nil |
Form field name for submission |
id |
String | nil |
ID attribute for the inner input (for label association) |
value |
String | nil |
Pre-filled value |
aria_label |
String | nil |
Accessible label for the inner input |
pattern |
String | "\\d" |
Regex pattern for allowed characters |
disabled |
Boolean | false |
Disables the input |
autocomplete |
String | "one-time-code" |
Autocomplete hint for mobile autofill |
css_classes |
String | "" |
Additional CSS classes |
input_otp :slot
| Local | Type | Default | Description |
|---|---|---|---|
size |
Symbol | :md |
Size: :sm, :md, :lg |
css_classes |
String | "" |
Additional CSS classes |
input_otp :group
| Local | Type | Default | Description |
|---|---|---|---|
css_classes |
String | "" |
Additional CSS classes |
input_otp :separator
| Local | Type | Default | Description |
|---|---|---|---|
css_classes |
String | "" |
Additional CSS classes |
Usage
Sizes
Pass size: to each slot.
<%= kui(:input_otp, length: 4, name: "pin") do %>
<%= kui(:input_otp, :group) do %>
<% 4.times do %>
<%= kui(:input_otp, :slot, size: :lg) %>
<% end %>
<% end %>
<% end %>
With Separator
Group slots and place separators between groups.
<%= kui(:input_otp, length: 6, name: "code") do %>
<%= kui(:input_otp, :group) do %>
<%= kui(:input_otp, :slot) %>
<%= kui(:input_otp, :slot) %>
<% end %>
<%= kui(:input_otp, :separator) %>
<%= kui(:input_otp, :group) do %>
<%= kui(:input_otp, :slot) %>
<%= kui(:input_otp, :slot) %>
<% end %>
<%= kui(:input_otp, :separator) %>
<%= kui(:input_otp, :group) do %>
<%= kui(:input_otp, :slot) %>
<%= kui(:input_otp, :slot) %>
<% end %>
<% end %>
Alphanumeric
Change the pattern: to accept letters and numbers.
<%= kui(:input_otp, length: 6, name: "code", pattern: "[a-zA-Z0-9]") do %>
<%= kui(:input_otp, :group) do %>
<% 6.times do %>
<%= kui(:input_otp, :slot) %>
<% end %>
<% end %>
<% end %>
With Field
Wrap in a field for labels and descriptions.
<%= kui(:field) do %>
<%= kui(:field, :label) { "Verification Code" } %>
<%= kui(:input_otp, length: 6, name: "verification_code") do %>
<%= kui(:input_otp, :group) do %>
<% 3.times do %>
<%= kui(:input_otp, :slot) %>
<% end %>
<% end %>
<%= kui(:input_otp, :separator) %>
<%= kui(:input_otp, :group) do %>
<% 3.times do %>
<%= kui(:input_otp, :slot) %>
<% end %>
<% end %>
<% end %>
<%= kui(:field, :description) { "Enter the 6-digit code sent to your email." } %>
<% end %>
Disabled
<%= kui(:input_otp, length: 6, name: "otp", disabled: true, value: "123456") do %>
...
<% end %>
Form Helpers
The transparent input has the name: attribute and submits naturally with Rails forms.
<%= form_with(model: @verification) do |f| %>
<%= kui(:input_otp, length: 6, name: "verification[code]") do %>
...
<% end %>
<%= kui(:button) { "Verify" } %>
<% end %>
Events
The Stimulus controller dispatches custom events on the root element.
| Event | Detail | Description |
|---|---|---|
kiso--input-otp:change |
{ value: string } |
Fired when the value changes |
kiso--input-otp:complete |
{ value: string } |
Fired when all slots are filled |
<%= kui(:input_otp, length: 6, name: "otp",
data: { action: "kiso--input-otp:complete->verification#submit" }) do %>
...
<% end %>
Theme
Kiso::Themes::InputOtp # Root container
Kiso::Themes::InputOtpGroup # Group wrapper
Kiso::Themes::InputOtpSlot # Individual character slot (size axis)
Kiso::Themes::InputOtpSeparator # Separator between groups
Accessibility
| Feature | Implementation |
|---|---|
| Screen reader | Real <input> is accessible; visual slots are aria-hidden |
| Mobile autofill | autocomplete="one-time-code" for SMS codes |
| Numeric keyboard | inputmode="numeric" when pattern is digits-only |
| Focus indicator | Active slot shows ring and blinking caret |
| Disabled state | has-disabled:opacity-50 dims entire component |
Keyboard
| Key | Action |
|---|---|
0-9 / a-z |
Enter character in current slot, advance to next |
Backspace |
Delete previous character, move back |
← / → |
Navigate between character positions |
Ctrl+V / Cmd+V |
Paste fills all slots at once |