Skip to content

About

Segmented authentication code input component for Frost UI with validation, keyboard navigation, and optional form submission.

Topics

Resources

Stars

0 stars

Watchers

1 watching

Forks

Repository files navigation

Frost UI AuthCodeInput

CI codecov npm version npm downloads JS gzip size license

Segmented authentication-code input for Frost UI with filtering, keyboard navigation, paste distribution, accessibility attributes, and optional form submission.

Highlights

  • Configurable character count or divided segment layout
  • Numeric codes by default, with custom per-character regular expressions
  • Multi-character paste and one-time-code autofill distribution
  • Arrow-key navigation, backspace synchronization, focus redirection, and managed tab order
  • Native Ctrl/Cmd shortcuts and read-only input support
  • Silent programmatic updates and form-reset synchronization
  • Outline and filled Frost UI v4 input styles
  • System-aware light and dark themes with RTL behavior
  • Native AuthCodeInput class and authcodeinput fQuery plugin
  • Existing-instance reuse with frozen resolved options
  • Prebuilt ESM and UMD bundles with source maps
  • No component-specific CSS or Sass
  • JSDoc-powered IntelliSense

Explore the demo for interactive examples.

Installation

Browser projects / bundlers

npm i @fr0st/ui-authcodeinput

Frost UI AuthCodeInput's package entry point is ESM-only and requires a browser DOM. Import the default AuthCodeInput export and the stylesheets in browser projects and bundlers.

import '@fr0st/ui/dist/frost-ui.min.css';
import AuthCodeInput from '@fr0st/ui-authcodeinput';

@fr0st/ui and @fr0st/query are peer dependencies so the component shares the application's instances.

Browser (ESM)

The ESM bundle imports @fr0st/ui and @fr0st/query. fQuery also imports @fr0st/core, so map all three dependencies when loading the bundle directly in a browser:

<link
    rel="stylesheet"
    href="https://cdn.jsdelivr.net/npm/@fr0st/ui@latest/dist/frost-ui.min.css">
<script type="importmap">
{
    "imports": {
        "@fr0st/core": "https://cdn.jsdelivr.net/npm/@fr0st/core@latest/dist/frost-core.esm.min.js",
        "@fr0st/query": "https://cdn.jsdelivr.net/npm/@fr0st/query@latest/dist/fquery.esm.min.js",
        "@fr0st/ui": "https://cdn.jsdelivr.net/npm/@fr0st/ui@latest/dist/frost-ui.esm.min.js"
    }
}
</script>
<script type="module">
    import AuthCodeInput from 'https://cdn.jsdelivr.net/npm/@fr0st/ui-authcodeinput@latest/dist/frost-ui-authcodeinput.esm.min.js';
</script>

Browser (UMD)

Load the bundles from your own copy or a CDN:

<link
    rel="stylesheet"
    href="/path/to/dist/frost-ui.min.css">
<script src="/path/to/dist/frost-ui-bundle.min.js"></script>
<script src="/path/to/dist/frost-ui-authcodeinput.min.js"></script>
<!-- or -->
<link
    rel="stylesheet"
    href="https://cdn.jsdelivr.net/npm/@fr0st/ui@latest/dist/frost-ui.min.css">
<script src="https://cdn.jsdelivr.net/npm/@fr0st/ui@latest/dist/frost-ui-bundle.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@fr0st/ui-authcodeinput@latest/dist/frost-ui-authcodeinput.min.js"></script>
<script>
    const { AuthCodeInput } = globalThis.UI;
</script>

The UMD bundle adds AuthCodeInput to the existing globalThis.UI object. Load Frost UI's all-in-one bundle first; it supplies the UI and fQuery globals.

The package root resolves to the prebuilt ESM bundle. Published files under dist/ and src/ are also available through matching package subpaths.

Usage

Start with a normal input. AuthCodeInput inserts the visible character inputs before it and keeps the original input synchronized for form submission:

<form id="verification-form">
    <label for="verification-code">Verification code</label>
    <input
        id="verification-code"
        name="verificationCode"
        inputmode="numeric"
        maxlength="6"
        required>

    <button class="btn btn-primary" type="submit">Verify</button>
</form>
import AuthCodeInput from '@fr0st/ui-authcodeinput';

const authCodeInput = AuthCodeInput.init(
    document.querySelector('#verification-code'),
    {
        length: [3, 3],
        style: 'outline',
    },
);

console.log(authCodeInput.getValue());

Options

Options are resolved in this order:

  1. Component defaults
  2. The element's data-ui-* attributes
  3. Options passed to AuthCodeInput.init()

Resolved instance.options are shallow-frozen.

Option Type Default Description
autoSubmit boolean false Submit the original input's associated form when user input completes the code.
getAriaLabel (index: number) => string (index) => `Character ${index}` Create the accessible label for each generated input. The index starts at 1.
length number | number[] [3, 3] Set the total character count or the character counts for divided segments.
regExp string '[0-9]' Regular-expression source used to accept or reject each character.
style 'filled' | 'outline' 'outline' Select the Frost UI input style.

A numeric length renders one uninterrupted group. An array inserts a vertical rule between segments. If the original input has a shorter valid maxlength, that value caps the rendered character count and produces one group.

const authCodeInput = AuthCodeInput.init(node, {
    autoSubmit: false,
    getAriaLabel: (index) => `Verification digit ${index}`,
    length: [2, 2, 2],
    regExp: '[A-Z0-9]',
    style: 'filled',
});

Data attributes

Use kebab-case data-ui-* attributes for serializable options. Arrays and objects use JSON. Supply callbacks and DOM nodes through JavaScript.

Attribute Example
data-ui-auto-submit data-ui-auto-submit="true"
data-ui-length data-ui-length="[3, 3]"
data-ui-reg-exp data-ui-reg-exp="[A-Z0-9]"
data-ui-style data-ui-style="filled"
<input
    id="verification-code"
    name="verificationCode"
    data-ui-toggle="authcodeinput"
    data-ui-auto-submit="true"
    data-ui-length="[3, 3]"
    data-ui-reg-exp="[0-9]"
    data-ui-style="filled">
import $ from '@fr0st/query';
import '@fr0st/ui-authcodeinput';

$('[data-ui-toggle="authcodeinput"]').authcodeinput();

Data attributes configure options; they do not initialize AuthCodeInput by themselves. Initialize the component through the class or fQuery plugin. Changing an option's data attribute after initialization does not reconfigure the existing instance.

Methods

Method Returns Description
AuthCodeInput.init(node, options?) AuthCodeInput Return the existing instance for an element or create one.
clear() void Clear the original and generated inputs.
disable() void Disable the original and generated inputs.
dispose() void Remove generated markup and registered state, then restore the original input.
enable() void Enable the original and generated inputs.
getValue() string Return the synchronized value from the original input.
setValue(value) void Filter, truncate, and distribute a value across the generated inputs.
authCodeInput.setValue('123456');
console.log(authCodeInput.getValue()); // "123456"

authCodeInput.clear();
authCodeInput.disable();
authCodeInput.enable();
authCodeInput.dispose();

Lifecycle

Calling AuthCodeInput.init() again for the same element returns its existing instance. Dispose the current instance before reinitializing with different options.

An instance exposes its original element as instance.node and its shallow-frozen resolved configuration as instance.options. Both become null after disposal.

dispose() releases resources owned by the component and removes its registered instance. Repeated disposal is safe and does not affect a new instance initialized on the same element. Use a new instance before calling other methods after disposal.

If initialization fails, the component releases resources it created and removes its registered instance before rethrowing the error. The element can then be initialized again.

Events

AuthCodeInput emits one namespaced fQuery event from the original input when user interaction changes the synchronized value:

Event Description
change.ui.authcodeinput The generated character inputs produced a new value.
import $ from '@fr0st/query';

$.addEvent(
    '#verification-code',
    'change.ui.authcodeinput',
    (event) => {
        console.log(event.currentTarget.value);
    },
);

The underlying native event type is change; fQuery exposes event.namespace as ui.authcodeinput. Re-entering the same value does not emit another event. Initialization, programmatic setValue() and clear(), and form-reset synchronization do not emit a change event or automatically submit the form, including when values are filtered or truncated.

Calling dispose() from a change listener is supported. The component stops further automatic submission and focus handling after that callback.

fQuery API

Importing AuthCodeInput registers authcodeinput on fQuery.QuerySet:

import $ from '@fr0st/query';
import '@fr0st/ui-authcodeinput';

const authCodeInput = $('#verification-code').authcodeinput({
    length: [3, 3],
});

$('#verification-code').authcodeinput('setValue', '123456');

const value = $('#verification-code').authcodeinput('getValue');

$('#verification-code').authcodeinput('clear');
$('#verification-code').authcodeinput('disable');
$('#verification-code').authcodeinput('enable');
$('#verification-code').authcodeinput('dispose');

Pass an options object to initialize every matched element, or pass a public method name followed by its arguments. The first component or method result is returned.

Accessibility

  • Generated inputs receive sequential labels from getAriaLabel(index).
  • The first generated input uses autocomplete="one-time-code"; remaining inputs use autocomplete="off".
  • aria-describedby, aria-errormessage, aria-invalid, and aria-required are synchronized from the original input, including later changes and removals.
  • Native disabled, required, and readonly state is synchronized to the generated inputs. Changes to disabled ancestor fieldsets are also observed, respecting the first legend exemption.
  • Read-only inputs remain selectable and support copying and navigation; typing, paste, and backspace cannot change their values. Programmatic setValue() and clear() still work.
  • Native Ctrl/Cmd keyboard shortcuts are preserved. Pasted characters still pass through the configured filter.
  • The original inputmode is preserved. Without one, the default [0-9] expression uses numeric; other expressions use text.
  • Focus is redirected to the next incomplete input, and unavailable future positions are removed from the tab order.
  • The original input remains the submitted form field and is visually hidden while the component is active.
  • Disposal restores the original input's pre-existing hidden state and tabindex while preserving its other classes.

Applications remain responsible for meaningful labels, instructions, error messages, and validation feedback. Use getAriaLabel when “Character 1” through “Character N” is not appropriate for the surrounding language or context.

Observed attribute changes synchronize asynchronously. Component enable() and disable() methods update the generated inputs immediately. Presentation attributes such as dir and inputmode are read at initialization.

After setting the original input's value directly, dispatch a native input or change event to synchronize the character inputs. Synchronization filters and truncates the value without emitting an additional change event or automatically submitting the form. Assigning the value alone does not refresh the character inputs.

Forms

Submission

Set autoSubmit: true to call requestSubmit() on the original input's associated form when user input or paste completes the code:

AuthCodeInput.init(document.querySelector('#verification-code'), {
    autoSubmit: true,
    length: 6,
});

requestSubmit() follows the form's normal validation and submit-event path. The associated form is taken from the original input's form property at initialization, including a form linked through the HTML form attribute. Nothing is submitted when the input has no associated form. Dispose and reinitialize the component if you change its form association.

The change event runs before automatic submission. Submission proceeds only if the instance remains active and its value still matches the completed code after change listeners have run. A listener that clears the code, leaves a different value, or disposes the component prevents that automatic submission.

Programmatic setValue() and clear() never automatically submit the form, even when filtering or truncation produces a complete code.

Reset

Calling the associated form's reset() method or activating a reset button restores the original input's default value. AuthCodeInput then filters and distributes that value across the generated inputs and updates their tab order.

Synchronization is deferred until after the browser finishes resetting the form, so the generated inputs are updated asynchronously. It does not emit a change event or automatically submit the form. Canceled resets are ignored, and disposal removes the reset listener and prevents pending refreshes from running.

Themes and RTL

Frost UI follows the user's preferred color scheme by default. Set data-ui-theme="light" or data-ui-theme="dark" on the document or an ancestor to select a theme explicitly.

AuthCodeInput uses Frost UI's form, flex, spacing, typography, vertical-rule, and visibility utilities. It does not ship a separate stylesheet.

<section data-ui-theme="dark">
    <input id="verification-code" data-ui-style="filled">
</section>

Normal document and ancestor direction is respected. A dir attribute placed directly on the original input is also copied to the generated container:

<input id="verification-code" dir="rtl">

In RTL layouts, the visual order reverses and Arrow Left/Arrow Right continue to move in their physical screen directions.

Development

Install dependencies with npm ci, then install Playwright browsers with npx playwright install --with-deps.

npm test
npm run lint
npm run build

npm test rebuilds the bundles, then runs the Playwright suite in Chromium, Firefox, and WebKit. npm run test:browser runs the suite against the existing bundles, so rebuild after changing source files.

After building, npm run test:coverage runs Chromium tests and writes coverage reports to coverage/.

npm run test:headed and npm run test:ui also use the existing bundles and open headed browsers or the Playwright UI.

To view the demo, open demo/index.html in your browser after building.

License

Frost UI AuthCodeInput is released under the MIT License.

About

Segmented authentication code input component for Frost UI with validation, keyboard navigation, and optional form submission.

Topics

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages