<cs-otp-input>
ExperimentalFormsSince 0.1

OTP inputs collect one-time passcodes, PINs, and other fixed-length codes, one character per segment. Use them for SMS verification, two-factor authentication, and invite codes.

This component works with standard <form> elements. See form controls for form submission and client-side validation.

Examples

Label

Use the label attribute to give the field an accessible label. For labels that contain HTML, use the label slot instead.

Hint

Add descriptive hint text with the hint attribute. For hints that contain HTML, use the hint slot instead.

Length

Use the length attribute to change the number of segments. The default is 6.

Type

Use the type attribute to restrict which characters are accepted.

TypeAcceptsBest for
numeric defaultDigits 0–9SMS and 2FA codes, PINs
alphaLetters A–ZLetter-only codes
alphanumericLetters and digitsInvite codes, serial numbers

The numeric type also sets the inputmode attribute on the underlying input, so mobile devices show the numeric keyboard.

Format

Use the format attribute to arrange segments into groups with literal separators. The # character marks a segment; any other character becomes a visual separator. Setting format overrides length, so there is no need to specify both.

Case

Use the case attribute to transform characters as they are entered. The default is preserve. Use upper to force uppercase or lower to force lowercase.

Mask

Add the mask attribute to display entered characters using --mask-char (a bullet, , by default) instead of their real value. The value remains accessible via the value property, masking is display-only, and only visual: screen readers still announce entered characters.

Add the with-mask attribute to also show --mask-char as a hint in each empty segment, so the field reads like a password field even before anything is typed.

Customize the character with the --mask-char custom property. It must be a quoted string.

Appearance

Use the appearance attribute to change the visual style of the segments. The default is outlined.

Size

Use the size attribute to change the size of each segment. The default is m.

Disabled

Use the disabled attribute to prevent interaction.

Readonly

Use the readonly attribute to display a value without allowing edits. Unlike disabled, a readonly field still receives focus and participates in form submission.

Initial Value

Use the value attribute to prefill the segments — for example, when a code arrives in a link’s query parameter.

Pasting

Pasting a full code fills all segments in one step. Characters that don’t match the type attribute are silently dropped, so pasting "ABC-123" into a numeric field produces 123.

Copy code: 314159

Autofill

The autocomplete attribute defaults to one-time-code, which tells browsers and operating systems to offer autofill for SMS-delivered verification codes. Set autocomplete="off" to disable this — for example, when the field is used for a PIN that shouldn’t be suggested by the browser.

On Android, Chrome can also read the code from an incoming SMS with the WebOTP API, no manual entry required. Feature-detect it and set the field’s value from the result:

<cs-otp-input id="sms-code" label="Verification code"></cs-otp-input>

<script>
  if ('OTPCredential' in window) {
    navigator.credentials
      .get({ otp: { transport: ['sms'] } })
      .then(otp => {
        document.getElementById('sms-code').value = otp.code;
      })
      .catch(() => {
        // The prompt was dismissed or timed out
      });
  }
</script>

Autosubmit

Add the autosubmit attribute to submit the containing form automatically when the last segment is filled. The cs-complete event fires first and is cancelable — call preventDefault() to stop the submission.

To run your own logic on completion instead — verify the code over the network, unlock a button — listen for the cs-complete event without setting autosubmit.

Validation

Add the required attribute to require a value before submission. A partial entry (some segments filled, but not all) is always invalid regardless of required, with the tooShort validity flag set.


Continue

Custom Validity

Use the setCustomValidity() method to set a custom validation message. This will prevent the form from submitting and make the browser display the error message you provide. To clear the error, call this function with an empty string.


Verify

Customizing

Use the --segment-size, --segment-gap, and --segment-border-radius custom properties along with CSS parts to style the segments, including their border and background.

Combine CSS parts with custom states to react to what the control is doing. For example, coloring the segments green once the code is fully entered (--filled), or red while it’s invalid (invalid). This example uses invalid rather than user-invalid so the customization is visible right away, without requiring you to interact with the field first. Type a full code to see it turn green instead:

Accessibility Considerations

The component uses a single visually hidden <input> as the focus and form target — the visible segments are decorative. Screen readers announce it as one text field, named by the label attribute or slot. Always provide a label; without one, the field has no accessible name.

Keyboard interaction follows the single-input model:

KeyBehavior
Move between segments
TabMoves focus to the next form control
EnterSubmits the containing form
BackspaceClears the current segment and moves back (no character shift)
DeleteClears the current segment without moving

API

Importing

If you're using the autoloader or a hosted project, components load on demand — no manual import needed. To cherry-pick this component, use one of the following snippets.

npm Self-Hosted React
import '@cruglobal/cornerstone-components/components/otp-input/otp-input.js';
import './cornerstone/components/otp-input/otp-input.js';
import CsOtpInput from '@cruglobal/cornerstone-components/react/otp-input/index.js';

Slots

Learn more about using slots.

NameDescription
hintOptional hint text. Use this for hints that contain HTML. Takes precedence over the hint attribute, which is its fallback.
labelAn optional label. Use this for labels that contain HTML. Takes precedence over the label attribute, which is its fallback.

Attributes & Properties

Learn more about attributes and properties.

PropertyAttributeDescriptionTypeDefaultReflects
appearanceappearanceVisual appearance of the segments.'outlined' | 'filled' | 'filled-outlined' | 'contained''outlined'Yes
autocompleteautocompleteThe autocomplete attribute forwarded to the underlying input.string'one-time-code'Yes
autofocusautofocusAutomatically focuses the field when the page loads.booleanfalse
autosubmitautosubmitWhen true, the form is submitted automatically once all segments are filled.booleanfalseYes
casecaseCase transformation applied to entered characters.'preserve' | 'upper' | 'lower''preserve'Yes
defaultValuevalueThe default value. Used to restore the field on form reset. Reflects the value HTML attribute.string | nullYes
disableddisabledDisables the form control.booleanfalse
effectiveLengthNumber of segments derived from format (count of #) or length.number
formBy default, form controls are associated with the nearest containing <form> element. This attribute allows you to place the form control outside of a form and associate it with the form that has this id. The form must be in the same document or shadow root for this to work.HTMLFormElement | null
formatformatSegment format string using # as a segment placeholder and any other character as a literal separator. Setting format overrides length (the segment count is derived from the number of # characters).string''
hinthintHint text shown below the segments. Use the hint slot for HTML content.string''
inputThe real <input> used for form association and validation (visually hidden).(HTMLElement & { value: unknown }) | HTMLInputElement | HTMLTextAreaElement | undefined
labellabelA label shown above the segments. Use the label slot for HTML content.string''
lengthlengthNumber of character segments to display. Overridden by format when set.number6Yes
maskmaskWhen true, entered characters are displayed as --mask-char instead of their real value.booleanfalseYes
namenameThe name of the input, submitted as a name/value pair with form data.string | nullnullYes
readonlyreadonlyMakes the field readonly — the value displays but cannot be edited by the user.booleanfalseYes
requiredrequiredMakes the field required. A partially-filled field is always invalid regardless of this attribute.booleanfalseYes
sizesizeThe size of each segment.'xs' | 's' | 'm' | 'l' | 'xl''m'Yes
ssrHintssr-hintOnly required for SSR. Set to true if you're slotting in a hint element.booleanfalse
ssrLabelssr-labelOnly required for SSR. Set to true if you're slotting in a label element.booleanfalse
typetypeAllowed character class.'numeric' | 'alpha' | 'alphanumeric''numeric'Yes
validationTargetOverride this to change where constraint validation popups are anchored.undefined | HTMLElement
validatorsValidators are static because they have observedAttributes, essentially attributes to "watch" for changes. Whenever these attributes change, we want to be notified and update the validator.Validator[][]
valueThe current value of the OTP field, submitted as a name/value pair with form data.string
withMaskwith-maskWhen true, empty segments show --mask-char as a hint instead of appearing blank, similar to how a password field communicates its expected length before anything is typed.booleanfalseYes

Methods

Learn more about methods.

NameDescriptionArguments
blur()Removes focus from the field.
clear()Clears the current value and returns focus to the field.
focus()Focuses the field.options: FocusOptions
formStateRestoreCallback()Called when the browser is trying to restore element’s state to state in which case reason is "restore", or when the browser is trying to fulfill autofill on behalf of user in which case reason is "autocomplete". In the case of "restore", state is a string, File, or FormData object previously set as the second argument to setFormValue.state: string | File | FormData | null, reason: 'autocomplete' | 'restore'
resetValidity()Reset validity is a way of removing manual custom errors and native validation.
select()Selects all entered characters in the hidden input.
setCustomValidity()Do not use this when creating a "Validator". This is intended for end users of components. We track manually defined custom errors so we don't clear them on accident in our validators.message: string

Events

Learn more about events.

NameDescription
blurEmitted when the control loses focus.
changeEmitted when the value changes and the field loses focus.
cs-clearEmitted when the control's value is cleared.
cs-completeEmitted once when all segments are filled. Cancelable — call preventDefault() to stop autosubmit from submitting the form for this completion.
cs-invalidEmitted when the form control has been checked for validity and its constraints aren't satisfied.
focusEmitted when the control gains focus.
inputEmitted when a character is entered or removed.

CSS Custom Properties

Learn more about CSS custom properties.

NameDescriptionDefault
--mask-charCharacter shown in place of entered values when mask is set, and as a hint in empty segments when with-mask is set.'•'
--segment-border-radiusCorner radius of each segment.var(--cs-form-control-border-radius)
--segment-gapGap between segments (not used in contained appearance).var(--cs-space-xs)
--segment-sizeWidth and height of each segment cell.2.5em

Custom States

Learn more about custom states.

NameDescriptionCSS selector
--blankApplied when no characters have been entered.:state(--blank)
--filledApplied when all segments are filled.:state(--filled)
disabledApplied when the component is disabled.:state(disabled)
readonlyApplied when the component is readonly.:state(readonly)
user-invalidApplied when validation fails after interaction.:state(user-invalid)

CSS Parts

Learn more about CSS parts.

NameDescriptionCSS selector
hintThe hint element.::part(hint)
labelThe label element.::part(label)
segmentAn individual character segment cell.::part(segment)
segment-literalInert literal text between segment groups (e.g. space or dash).::part(segment-literal)
segmentsThe wrapper around all segment cells and separators.::part(segments)