Input OTP <input>
A single native <input> with visual digit cells for one-time passcode entry. The <l-input-otp> wrapper renders individual bordered cells over a hidden input that handles keyboard, paste, and autocomplete.
<l-input-otp>Options
Default
Code
<label
for="otp-default"
class="block mb-1.5 text-sm font-medium"
>Verification code</label
>
<l-input-otp>
<input id="otp-default" />
</l-input-otp>Digit count
Set --digits on <l-input-otp> to change the number of cells. The element automatically sets maxlength and pattern on the input.
Code
<label
for="otp-pin"
class="block mb-1.5 text-sm font-medium"
>PIN code</label
>
<l-input-otp style="--digits: 4">
<input id="otp-pin" />
</l-input-otp>Separator
Add separator-after attribute to insert a visual dash after the specified position (e.g., separator-after="3" for a 3-3 grouping).
Code
<label
for="otp-separator"
class="block mb-1.5 text-sm font-medium"
>Verification code</label
>
<l-input-otp separator-after="3">
<input id="otp-separator" />
</l-input-otp>Size
Set the size attribute on <l-input-otp>: sm, md (default), lg.
Code
<div class="flex flex-col gap-4 items-start">
<l-input-otp size="sm">
<input aria-label="Small OTP" />
</l-input-otp>
<l-input-otp>
<input aria-label="Medium OTP" />
</l-input-otp>
<l-input-otp size="lg">
<input aria-label="Large OTP" />
</l-input-otp>
</div>Custom colors
Override the --cell-* properties to retheme. Set --cell-focus-ring to a full box-shadow value (or none) to customize the active state. Target .l-input-otp-cell for typography tweaks.
Code
<style>
#demo-otp-custom {
--cell-size: 3.5rem;
--cell-gap: 0.75rem;
--cell-bg-color: white;
--cell-border-color: oklch(0.92 0.01 270);
--cell-border-radius: 1rem;
--cell-focus-color: oklch(0.55 0.25 275);
--cell-focus-ring: 0 0 0 4px color-mix(in oklab, var(--cell-focus-color) 22%, transparent);
}
#demo-otp-custom .l-input-otp-cell {
font-family: inherit;
font-weight: 700;
}
</style>
<label
for="otp-custom"
class="block mb-2 text-sm font-semibold text-slate-700"
>Label</label
>
<l-input-otp
id="demo-otp-custom"
style="--digits: 4"
>
<input
id="otp-custom"
value="4200"
/>
</l-input-otp>Not defined
Before JS loads (:not(:defined)), CSS reserves the exact box the cells will occupy — a single soft-tinted rectangle, not per-cell artwork. Width tracks --digits, --cell-size, and --cell-gap so layout doesn't shift on hydration, and the tint follows --cell-bg-color so custom themes carry over.
Code
<l-input-otp>
<input />
</l-input-otp>Once upgraded, the custom element replaces the input with its visual cells container.
Disabled
Native disabled attribute.
Code
<l-input-otp>
<input
value="384291"
disabled
aria-label="Verification code"
/>
</l-input-otp>Accessibility
Criteria
- Accessible name
Requires
<label>oraria-labelto describe the purpose of the code inputWCAG1.3.1RGAA11.1- Input purpose
autocomplete="one-time-code"identifies the field for browser and password manager autofillWCAG1.3.5- Validation
patternandmaxlengthprovide client-side validation;inputmode="numeric"triggers the numeric keyboard on mobileWCAG3.3.1- Visual cells
Cell container is
aria-hidden="true"— screen readers interact with the native input onlyWCAG4.1.2
Keyboard interactions
Why light DOM?
The native <input> lives in the light DOM so it stays directly accessible for <label> association, form participation, and external CSS. The visual cells are purely decorative (aria-hidden="true") — screen readers only see the real input.
API reference
Importing
@import 'luxen-ui/css/input-otp';import 'luxen-ui/input-otp';Attributes & Properties
sizeAttribute- Control size:
sm,md(default),lg separator-afterAttribute- Position after which to insert a visual separator dash (e.g.,
3for a 3-3 grouping)
CSS custom properties
Set --digits on <l-input-otp> to change the digit count.
--digitsName- Number of digit cells (default:
6). Drivesmaxlengthandpatternautomatically --cell-sizeName- Cell width and height (default:
2.75rem). Font size scales automatically from this value --cell-gapName- Space between cells (default:
0.5rem) --cell-bg-colorName- Cell background color
--cell-border-colorName- Cell border color
--cell-border-radiusName- Cell border-radius
--cell-focus-colorName- Border + ring color of the active (focused) cell
--cell-focus-ringName- Full
box-shadowvalue of the active cell ring (default:0 0 0 1px var(--cell-focus-color)). Set tononeto disable