Input OTP
Component for OTP Input.
Default
Renders an OTP input with the default six fields.
<template>
<HLInputOtp @onComplete="onCompleteHandler" @onChange="onChangeHandler" />
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { HLInputOtp } from '@platform-ui/highrise'
const otpValue = ref('')
const onChangeHandler = (value: string) => {
// do something
}
const onCompleteHandler = (value: { otp: string; state: 'completed' }) => {
otpValue.value = value.otp
}
</script>Fields
Sets the number of input fields with the fields prop.
<template>
<HLInputOtp size="sm" :fields="4" @onComplete="onCompleteHandler" @onChange="onChangeHandler" />
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { HLInputOtp } from '@platform-ui/highrise'
const otpValue = ref('')
const onChangeHandler = (value: string) => {
// do something
}
const onCompleteHandler = (value: { otp: string; state: 'completed' }) => {
otpValue.value = value.otp
}
</script>Sizes
The size prop scales the fields. It accepts lg, md (default), sm, xs, 2xs, and 3xs.
<template>
<HLInputOtp size="lg" :fields="4" />
<HLInputOtp size="md" :fields="4" />
<HLInputOtp size="sm" :fields="4" />
<HLInputOtp size="xs" :fields="4" />
<HLInputOtp size="2xs" :fields="4" />
<HLInputOtp size="3xs" :fields="4" />
</template>
<script setup lang="ts">
import { HLInputOtp } from '@platform-ui/highrise'
</script>Separator Position
Inserts a separator after the field index given by separatorPosition.
<template>
<HLInputOtp size="sm" :fields="6" :separatorPosition="3" @onComplete="onCompleteHandler" @onChange="onChangeHandler" />
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { HLInputOtp } from '@platform-ui/highrise'
const otpValue = ref('')
const onChangeHandler = (value: string) => {
// do something
}
const onCompleteHandler = (value: { otp: string; state: 'completed' }) => {
otpValue.value = value.otp
}
</script>Custom Placeholder
Replaces the default placeholder character with the placeholder prop.
<template>
<HLInputOtp size="sm" :fields="4" placeholder="#" @onComplete="onCompleteHandler" @onChange="onChangeHandler" />
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { HLInputOtp } from '@platform-ui/highrise'
const otpValue = ref('')
const onChangeHandler = (value: string) => {
// do something
}
const onCompleteHandler = (value: { otp: string; state: 'completed' }) => {
otpValue.value = value.otp
}
</script>Verification Form
The most common use is verifying a code sent to the user. Listen for @onComplete to validate the full code, and toggle status to error when it doesn't match. Wrapping the field in an HLFormItem renders the error message. In this demo the expected code is 123456.
<template>
<HLFormItem label="Enter the 6-digit code" :validation-status="status" :feedback="feedback">
<HLInputOtp :fields="6" :status="status" @onComplete="handleVerify" @onChange="handleChange" />
</HLFormItem>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { HLInputOtp, HLFormItem } from '@platform-ui/highrise'
const EXPECTED_CODE = '123456'
const status = ref<'default' | 'error'>('default')
const feedback = ref('')
const handleVerify = (value: { otp: string; state: 'completed' }) => {
if (value.otp === EXPECTED_CODE) {
status.value = 'default'
feedback.value = ''
// proceed — code is valid
} else {
status.value = 'error'
feedback.value = 'That code is incorrect. Try again.'
}
}
// Clear the error as soon as the user edits the code
const handleChange = () => {
if (status.value === 'error') {
status.value = 'default'
feedback.value = ''
}
}
</script>Disabled
Prevents input when the disabled prop is set.
<template>
<HLInputOtp size="sm" :fields="4" disabled @onComplete="onCompleteHandler" @onChange="onChangeHandler" />
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { HLInputOtp } from '@platform-ui/highrise'
const otpValue = ref('')
const onChangeHandler = (value: string) => {
// do something
}
const onCompleteHandler = (value: { otp: string; state: 'completed' }) => {
otpValue.value = value.otp
}
</script>Event Testing
This example logs the events the component emits as you type. @onChange fires on every keystroke with the current combined value; @onComplete fires once all fields are filled.
Event Log:
<template>
<HLInputOtp
size="sm"
:fields="4"
@onChange="val => addEventLog('@onChange → ' + val)"
@onComplete="val => addEventLog('@onComplete → ' + val.otp)"
/>
<div class="text-sm">
<p class="font-bold mb-2">Event Log:</p>
<div v-if="eventLog.length === 0" class="text-gray-500">No events logged yet. Type a code above.</div>
<div v-for="(log, index) in eventLog" :key="index" class="text-gray-700">{{ log.timestamp }}: {{ log.event }}</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { HLInputOtp } from '@platform-ui/highrise'
const eventLog = ref<{ event: string; timestamp: string }[]>([])
const addEventLog = (event: string) => {
eventLog.value.unshift({ event, timestamp: new Date().toLocaleTimeString() })
if (eventLog.value.length > 5) {
eventLog.value.pop()
}
}
</script>Design Guidelines
Input components use a box-shadow to render their focus ring. Box-shadows render outside the element's bounds and may be clipped by any ancestor using overflow: hidden (e.g. Tab Panels or Dropdown Menus).
To prevent this, add a small gutter padding to the component's wrapper to ensure there is enough room for the focus ring to render without being cut off.
<div class="p-[3px]">
<!-- Your component here -->
</div>Accessibility
- Describe the OTP request via
aria-label/aria-labelledbyon the container (e.g., “Enter the 6-digit code”). - Give each cell an
aria-labelthat announces its index (“Digit 2 of 6”) or hook helper text througharia-describedby. - Announce expired or incorrect codes inside an
aria-live="assertive"region near the group.
Imports
import { HLInputOtp } from '@platform-ui/highrise'Props
| Name | Type | Default | Description |
|---|---|---|---|
| id | string | Auto-generated | Unique identifier for the container. When omitted, an id is generated for accessibility. |
| fields | number | 6 | Number of input fields to display |
| disabled | boolean | false | Disables the OTP input |
| placeholder | string | '0' | Placeholder character shown in each empty field |
| size | '3xs' | '2xs' | 'xs' | 'sm' | 'md' | 'lg' | 'md' | Size of the OTP fields. Inherits from a surrounding form when unset. |
| status | 'default' | 'error' | 'default' | Visual state of the fields. The error styling renders when the OTP is inside an HLFormItem with validation-status="error" — see Verification Form. |
| separatorPosition | number | undefined | undefined | Inserts a separator after this many fields (e.g. 3 splits 6 fields into two groups of three). |
Emits
| Name | Parameters | Description |
|---|---|---|
@onChange | (otp: string) | Fired on every keystroke with the current combined value. |
@onComplete | ({ otp: string, state: 'completed' }) | Fired once all fields are filled, with the full code and completion state. |