a11y-otp-field
v1.0.0
Published
Accessible, progressively enhanced one-time-code input with decorative visual slots.
Maintainers
Readme
A11y OTP Field
An accessible, progressively enhanced one-time-code field. One native text input owns the real form value; the visual character slots are decorative and hidden from assistive technology.
Installation
npm install a11y-otp-field
# or: pnpm add a11y-otp-field
# or: yarn add a11y-otp-fieldUsage
import { createA11yOtpField } from "a11y-otp-field";
import "a11y-otp-field/styles.css";
const root = document.querySelector("[data-a11y-otp]");
if (root instanceof HTMLElement) {
const otp = createA11yOtpField(root, { length: 6 });
console.log(otp.getValue());
}Importing the package never initializes fields automatically. Use createA11yOtpField() for one field or explicitly call initA11yOtpFields() for all matching fields.
CSS
Import a11y-otp-field/styles.css for the default slot presentation. Public custom properties such as --a11y-otp-slot-size, --a11y-otp-focus-color, and --a11y-otp-error-color can be overridden on .a11y-otp.
HTML structure
<div class="a11y-otp" data-a11y-otp data-otp-length="6">
<label class="a11y-otp__label" for="verification-code">Verification code</label>
<p class="a11y-otp__hint" id="verification-code-hint">Enter the six-digit code.</p>
<div class="a11y-otp__control" data-a11y-otp-control>
<input
class="a11y-otp__input"
data-a11y-otp-input
id="verification-code"
name="verificationCode"
type="text"
inputmode="numeric"
autocomplete="one-time-code"
pattern="[0-9]*"
maxlength="6"
aria-describedby="verification-code-hint"
/>
</div>
<p class="a11y-otp__error" data-a11y-otp-error id="verification-code-error"></p>
<p class="a11y-otp__status" data-a11y-otp-status aria-live="polite"></p>
</div>The slots are generated during enhancement. Do not create one input per character.
API
Creation
createA11yOtpField(root, options)enhances one root and returns its existing instance if called again.initA11yOtpFields(options?, scope?)enhances every[data-a11y-otp]within a scope.getA11yOtpField(root)returns an existing instance ornull.A11yOtpFieldis the plugin-specific class.
Options
length(default6, maximum64)type:numeric,alphanumeric, orcustomgroups: decorative visual grouping, for example[3, 3]mask,revealLast, orprivacyModeuppercaseandallowSeparatorsvalidateOn:submit,blur, ormanualnormalizerfor custom valuesmessagesfor validation and status textclearErrorOnInputautoSubmit(defaultfalse) andautoSubmitDelay
Instance methods
getValue(), setValue(), clear(), focus(), blur(), enable(), disable(), setReadonly(), setMasked(), setError(), clearError(), validate(), and destroy().
Events
The root dispatches bubbling a11y-otp-field:init, input, change, complete, incomplete, paste, error, clear, and destroy events. Each event detail includes the plugin instance and current state. The plugin does not emit analytics or persist codes.
Accessibility notes
- The real value lives in one labeled native
type="text"input. - Numeric mode uses
inputmode="numeric"; the component never usestype="number". autocomplete="one-time-code"is preserved or added for OTP autofill.- Slots are decorative and receive
aria-hidden="true". - Native keyboard editing, selection, focus, and clipboard commands remain available.
- Paste is never prevented; formatted pasted codes are normalized after the native paste event.
- Errors synchronize visible text,
aria-invalid, andaria-describedby. - Auto-submit is disabled by default and must be explicitly enabled.
- The unenhanced form remains usable when JavaScript is unavailable.
destroy()restores attributes and author-provided slot content that existed before enhancement.- Masking only changes decorative output; do not treat it as protection from scripts or browser tools.
Examples
Build package output before serving the source examples:
npm run build:dist
python3 -m http.server 4173Docs metadata
import { docs } from "a11y-otp-field/docs";The named export contains package details, usage, selectors, keyboard behavior, API entries, and example metadata for documentation aggregators.
GitHub Pages
Generate the committed Pages site with:
npm run pages:builddocs/ is generated output and must not be edited by hand. In GitHub, configure Settings → Pages → Build and deployment → Deploy from a branch → main → /docs. The expected project URL is https://vmitsaras.github.io/A11y-OTP-Field/.
The npm package build remains in ignored root dist/; only the runtime copy under committed docs/dist/ is published by Pages.
Development
npm run pages:build
npm run typecheck
npm test
npm run pack:checkLicense
MIT © Vasileios Mitsaras
