Skip to content
Destyler UI Destyler UI Destyler UI

OTP Input

The otp input is optimized for entering a sequence of digits or letters. The input fields allow one character at a time. When the digit or letter is entered, focus transfers to the next input in the sequence, until every input is filled.

Features

Install

Install the component from your command line.

Terminal window
      
        
npm install @destyler/otp-input @destyler/vue
Terminal window
      
        
npm install @destyler/otp-input @destyler/react
Terminal window
      
        
npm install @destyler/otp-input @destyler/svelte
Terminal window
      
        
npm install @destyler/otp-input @destyler/solid

Anatomy

Import all parts and piece them together.

<script setup lang="ts">
import * as otpInput from '@destyler/otp-input'
import { normalizeProps, useMachine } from '@destyler/vue'
import { computed, useId } from 'vue'
const [state, send] = useMachine(otpInput.machine({
id: useId(),
}))
const api = computed(() => otpInput.connect(state.value, send, normalizeProps))
</script>
<template>
<div v-bind="api.getRootProps()">
<input v-bind="api.getInputProps({ index: 0 })">
<input v-bind="api.getInputProps({ index: 1 })">
<input v-bind="api.getInputProps({ index: 2 })">
</div>
<button @click="api.clearValue" />
</template>
import * as otpInput from '@destyler/otp-input'
import { normalizeProps, useMachine } from '@destyler/react'
import { useId, useMemo } from 'react'
export default function OtpInput() {
const [state, send] = useMachine(otpInput.machine({
id: useId(),
}))
const api = useMemo(() => otpInput.connect(state, send, normalizeProps), [state, send])
return (
<div {...api.getRootProps()} >
<input {...api.getInputProps({ index:0 })}/>
<input {...api.getInputProps({ index:1 })}/>
<input {...api.getInputProps({ index:2 })}/>
</div>
<button onClick={api.clearValue}></button>
)
}
<script lang="ts">
import * as otpInput from "@destyler/otp-input";
import { normalizeProps, useMachine } from "@destyler/svelte";
const id = $props.id();
const [state, send] = useMachine(otpInput.machine({
id,
}));
const api = $derived(otpInput.connect(state, send, normalizeProps));
</script>
<div {...api.getRootProps()}>
<input {...api.getInputProps({ index:0 })} />
<input {...api.getInputProps({ index:1 })} />
<input {...api.getInputProps({ index:2 })} />
</div>
<button onclick={api.clearValue}></button>
import * as otpInput from '@destyler/otp-input'
import { normalizeProps, useMachine } from '@destyler/solid'
import { createMemo, createUniqueId } from 'solid-js'
export default function OtpInput() {
const uniqueId = createUniqueId()
const [state, send] = useMachine(otpInput.machine({
id: uniqueId,
}))
const api = createMemo(() => otpInput.connect(state, send, normalizeProps))
return (
<div {...api().getRootProps()}>
<input {...api().getInputProps({ index: 0 })}/>
<input {...api().getInputProps({ index: 1 })}/>
<input {...api().getInputProps({ index: 2 })}/>
</div>
<button onClick={() => api().clearValue()}></button>
)
}

Setting a default value

To set the initial value of the otp input, pass the value property to the destyler’s context.

const [state, send] = useMachine(
otpInput.machine({
value: ["1", "2", ""],
}),
)

Changing the placeholder

To customize the default otp input placeholder (○) for each input, pass the placeholder prop and set it to your desired value.

const [state, send] = useMachine(
otpInput.machine({
placeholder: "*",
}),
)

Blur on complete

By default, the last input maintains focus when filled and we invoke the onComplete callback. To blur the last input when the user completes the input, set the blurOnComplete: true in the destyler’s context.

const [state, send] = useMachine(
otpInput.machine({
blurOnComplete: true,
}),
)

Allowing alphanumeric values

By default, the otp input accepts only number values but you can choose between numeric, alphanumeric and alphabetic values. To change the input mode, pass the type context property and set its value to alphanumeric.

const [state, send] = useMachine(
otpInput.machine({
type: "alphanumeric",
}),
)

Using OTP mode

To trigger smartphone OTP auto-suggestion, it is recommended to set the autocomplete attribute to “one-time-code”. The pin-input machine provides support for this automatically when you set the otp context property to true.

const [state, send] = useMachine(
otpInput.machine({
otp: true,
}),
)

Securing the text input

When collecting private or sensitive information using the otp input, you might need to mask the value entered, similar to <input type="password"/>. Pass the mask context property and set it to true.

const [state, send] = useMachine(
otpInput.machine({
mask: true,
}),
)

Listening for changes

The otp input machine invokes several callback functions when the user enters:

  • onValueChange — Function invoked when the value is changed.

  • onValueComplete — Function invoked when all fields have been completed (by typing or pasting).

  • onValueInvalid — Function invoked when an invalid value is entered into the input. An invalid value is any value that doesn’t match the specified “type”.

const [state, send] = useMachine(
otpInput.machine({
onValueChange(value) {
// value => string[]
console.log("value changed to:", value)
},
onValueComplete(details) {
// details => { value: string[], valueAsString: string }
console.log("completed value:", details)
},
onValueInvalid(details) {
// details => { index: number, value: string }
console.log("invalid value:", details)
},
}),
)

RTL support

The pin input machine supports RTL writing directions. To set the dir property in the destyler’s context.

When this attribute is set, we attach a dir attribute to the root part.

const [state, send] = useMachine(
otpInput.machine({
dir: "rtl",
}),
)

Styling guide

Earlier, we mentioned that each OTP Input part has a data-part attribute added to them to select and style them in the DOM.

Completed state

When all values have been filled, we attach a data-complete attribute to the root and input parts.

[data-part="root"][data-complete] {
/* styles for when all value has been filled */
}
[data-part="input"][data-complete] {
/* styles for when all value has been filled */
}

Invalid state

When an invalid value is entered, we attach a data-invalid attribute to the affected input part.

[data-part="input"][data-invalid] {
/* styles for when the input is invalid */
}

Disabled state

When the pin-input is disabled, we attach a data-disabled attribute to the root and input parts.

[data-part="root"][data-disabled] {
/* styles for when the input is disabled */
}
[data-part="input"][data-invalid] {
/* styles for when the input is disabled */
}

Methods and Properties

Machine Context

The otp input machine exposes the following context properties:

name
string
The name of the input element. Useful for form submission.
form
string
The associate form of the underlying input element.
pattern
string
The regular expression that the user-entered input value is checked against.
ids
Partial<{ root: string; hiddenInput: string; label: string; control: string; input(id: string): string; }>
The ids of the elements in the otp input. Useful for composition.
disabled
boolean
Whether the inputs are disabled
placeholder
string
The placeholder text for the input
autoFocus
boolean
Whether to auto-focus the first input.
invalid
boolean
Whether the otp input is in the invalid state
required
boolean
Whether the otp input is required
readOnly
boolean
Whether the otp input is in the valid state
otp
boolean
If `true`, the otp input component signals to its fields that they should use `autocomplete="one-time-code"`.
value
string[]
The value of the the otp input.
type
"alphanumeric" | "numeric" | "alphabetic"
The type of value the otp-input should allow
onValueComplete
(details: ValueChangeDetails) => void
Function called when all inputs have valid values
onValueChange
(details: ValueChangeDetails) => void
Function called on input change
onValueInvalid
(details: ValueInvalidDetails) => void
Function called when an invalid value is entered
mask
boolean
If `true`, the input's value will be masked just like `type=password`
blurOnComplete
boolean
Whether to blur the input when the value is complete
selectOnFocus
boolean
Whether to select input value when input is focused
translations
IntlTranslations
Specifies the localized strings that identifies the accessibility elements and their states
dir
"ltr" | "rtl"
The document's text/writing direction.
id
string
The unique identifier of the machine.
getRootNode
() => ShadowRoot | Node | Document
A root node to correctly resolve document in custom environments. E.x.: Iframes, Electron.

Machine API

The otp input api exposes the following methods:

value
string[]
The value of the input as an array of strings.
valueAsString
string
The value of the input as a string.
complete
boolean
Whether all inputs are filled.
setValue
(value: string[]) => void
Function to set the value of the inputs.
clearValue
() => void
Function to clear the value of the inputs.
setValueAtIndex
(index: number, value: string) => void
Function to set the value of the input at a specific index.
focus
() => void
Function to focus the otp-input. This will focus the first input.

Data Attributes

Root

name
desc
data-scope
otp-input
data-part
root
data-invalid
Present when invalid
data-disabled
Present when disabled
data-complete
Present when the otp-input value is complete
data-readonly
Present when read-only

Label

name
desc
data-scope
otp-input
data-part
label
data-invalid
Present when invalid
data-disabled
Present when disabled
data-complete
Present when the label value is complete
data-readonly
Present when read-only

Input

name
desc
data-scope
otp-input
data-part
input
data-disabled
Present when disabled
data-complete
Present when the input value is complete
data-invalid
Present when invalid

Accessibility

Keyboard Interaction

name
desc
ArrowLeft
Moves focus to the previous input
ArrowRight
Moves focus to the next input
Backspace
Deletes the value in the current input and moves focus to the previous input
Delete
Deletes the value in the current input
Control + V
Pastes the value into the input fields