<script setup>
import { InputOtp } from "@dicehub/kappa/components/input-otp";
</script>
<template>
<InputOtp.Root aria-label="Verification code" :count="6" otp>
<InputOtp.Label>Verification code</InputOtp.Label>
<InputOtp.Control>
<InputOtp.Input v-for="index in 6" :key="index" :index="index - 1" />
</InputOtp.Control>
<InputOtp.HiddenInput name="verification-code" />
</InputOtp.Root>
</template>Installation
Input OTP is part of the main Kappa package. Kappa wraps the Ark UI Vue pin-input primitive and adds component names, tokens, cell geometry, and state styling.
Barrel
import { InputOtp } from "@dicehub/kappa";Granular
import { InputOtp } from "@dicehub/kappa/components/input-otp";Usage
Set count for stable six-cell output, then render one indexedInputOtp.Input for every cell. Include HiddenInput when the value must submit with a native form. Set otp to enable one-time-code autocomplete.
<script setup>
import { InputOtp } from "@dicehub/kappa/components/input-otp";
</script>
<template>
<InputOtp.Root
aria-label="Deploy confirmation code"
:count="6"
otp
placeholder="·"
>
<InputOtp.Label>Deploy confirmation code</InputOtp.Label>
<InputOtp.Control>
<InputOtp.Input v-for="index in 6" :key="index" :index="index - 1" />
</InputOtp.Control>
<InputOtp.HiddenInput name="deploy-code" />
</InputOtp.Root>
</template>Composition
InputOtp.Root
├── InputOtp.Label
├── InputOtp.Control
│ ├── InputOtp.Input [index=0]
│ ├── InputOtp.Input [index=1]
│ └── InputOtp.Input [index=n]
├── InputOtp.HiddenInput
└── InputOtp.Context (optional)Ark UI owns focus movement, paste handling, validation, hidden form input, and controlled/uncontrolled state. Kappa exposes the same machine through an InputOtp compound namespace and gives each part a stable Kappa slot and token-based style.
Rootowns the pin-input machine and emitsvalueChange,valueComplete,valueInvalid, andupdate:modelValue.Controllays out indexed cells. Render oneInputper index from0tocount - 1.HiddenInputis visually hidden and keeps nativename,required, and form behavior.Contextexposes the Ark API to a scoped slot.RootProvideraccepts an API created withusePinInput.
<script setup>
import { InputOtp, usePinInput } from "@dicehub/kappa/components/input-otp";
const pinInput = usePinInput({ count: 6, otp: true });
</script>
<template>
<InputOtp.RootProvider :value="pinInput">
<InputOtp.Label>Provider-owned code</InputOtp.Label>
<InputOtp.Control>
<InputOtp.Input v-for="index in 6" :key="index" :index="index - 1" />
</InputOtp.Control>
<InputOtp.HiddenInput name="provider-code" />
</InputOtp.RootProvider>
</template>Examples
Pattern and Placeholder
Restrict each cell to numeric input and choose a quiet placeholder glyph.
<script setup>
import { ref } from "vue";
import { InputOtp } from "@dicehub/kappa/components/input-otp";
const code = ref<string[]>([]);
</script>
<template>
<InputOtp.Root
v-model="code"
aria-label="Numeric code"
:count="6"
pattern="[0-9]"
placeholder="○"
type="numeric"
>
<InputOtp.Label>Numeric code</InputOtp.Label>
<InputOtp.Control>
<InputOtp.Input v-for="index in 6" :key="index" :index="index - 1" />
</InputOtp.Control>
<InputOtp.HiddenInput name="numeric-code" />
</InputOtp.Root>
</template>Mask
Use password-style masking when the code must not remain visible.
<script setup>
import { InputOtp } from "@dicehub/kappa/components/input-otp";
</script>
<template>
<InputOtp.Root aria-label="Masked access code" :count="6" mask otp>
<InputOtp.Label>Masked access code</InputOtp.Label>
<InputOtp.Control>
<InputOtp.Input v-for="index in 6" :key="index" :index="index - 1" />
</InputOtp.Control>
<InputOtp.HiddenInput name="masked-code" />
</InputOtp.Root>
</template>Disabled and Invalid
Use the root state props to present validation and unavailable controls.
<script setup>
import { InputOtp } from "@dicehub/kappa/components/input-otp";
</script>
<template>
<InputOtp.Root aria-label="Invalid code" :count="6" invalid>
<InputOtp.Label>Invalid code</InputOtp.Label>
<InputOtp.Control>
<InputOtp.Input v-for="index in 6" :key="index" :index="index - 1" />
</InputOtp.Control>
<InputOtp.HiddenInput name="invalid-code" />
</InputOtp.Root>
<InputOtp.Root
aria-label="Disabled code"
:count="6"
:default-value="['4', '0', '2', '8', '1', '6']"
disabled
>
<InputOtp.Label>Disabled code</InputOtp.Label>
<InputOtp.Control>
<InputOtp.Input v-for="index in 6" :key="index" :index="index - 1" />
</InputOtp.Control>
<InputOtp.HiddenInput name="disabled-code" />
</InputOtp.Root>
</template>Blur on Complete
Move focus away from the final cell and handle valueComplete when the code is ready.
<script setup>
import { ref } from "vue";
import { InputOtp } from "@dicehub/kappa/components/input-otp";
const completed = ref("");
</script>
<template>
<InputOtp.Root
aria-label="Auto-submit code"
:count="6"
blur-on-complete
@value-complete="completed = $event.valueAsString"
>
<InputOtp.Label>Auto-submit code</InputOtp.Label>
<InputOtp.Control>
<InputOtp.Input v-for="index in 6" :key="index" :index="index - 1" />
</InputOtp.Control>
<InputOtp.HiddenInput name="auto-submit-code" />
</InputOtp.Root>
<output>{{ completed }}</output>
</template>Controlled Value
Keep the six-cell array in application state with v-model and value events.
<script setup>
import { ref } from "vue";
import { InputOtp } from "@dicehub/kappa/components/input-otp";
const code = ref(["4", "0", "2", "8", "1", "6"]);
</script>
<template>
<InputOtp.Root v-model="code" aria-label="Controlled code" :count="6">
<InputOtp.Label>Controlled code</InputOtp.Label>
<InputOtp.Control>
<InputOtp.Input v-for="index in 6" :key="index" :index="index - 1" />
</InputOtp.Control>
<InputOtp.HiddenInput name="controlled-code" />
</InputOtp.Root>
<output>Value: {{ code.join("") }}</output>
</template>Accessibility
- Give the root a visible
Labelor an explicitaria-label. - Each cell receives an accessible position label from Ark UI. Keep the cell order and indexes stable.
- Arrow keys move between cells. Backspace clears and moves back; paste distributes valid characters across cells.
otpenables one-time-code autocomplete. Usetypeandpatternto constrain accepted values.invalid,disabled, andreadOnlyare passed to Ark UI and reflected on root and cell state attributes.- Use
blurOnCompletewhen the next step should receive focus after a valid code. HandlevalueCompletefor submission or verification. - Input cells keep visible focus in both themes, reduced-motion mode, and forced-color mode.
See the official Ark UI Pin Input documentationfor the underlying machine behavior and accessibility contract.
API Reference
InputOtp.Root
The root accepts Ark UI pin-input props plus Kappa's visual size.
| Prop | Type | Default | Description |
|---|---|---|---|
count | number | — | Number of input cells. Pass it for stable SSR output. |
modelValue / v-model | string[] | — | Controlled array of cell values. |
defaultValue | string[] | [] | Initial array for uncontrolled use. |
type | "numeric" | "alphabetic" | "alphanumeric" | "numeric" | Restricts entered values. |
pattern | string | — | Regular expression checked for each entered value and paste. |
placeholder | string | "○" | Placeholder shown in an empty cell. |
mask | boolean | false | Uses password-style masking for entered cells. |
otp | boolean | false | Adds one-time-code autocomplete to the cells. |
blurOnComplete | boolean | false | Blurs the active cell after all cells are valid. |
autoFocus / autoSubmit | boolean | false | Focuses the first cell or submits the owning form when complete. |
disabled / readOnly / required / invalid | boolean | false | Native and validation states passed to Ark UI. |
size | "sm" | "base" | "lg" | "base" | Kappa visual density for each cell. |
Parts
| Part | Element | Description |
|---|---|---|
InputOtp.Root | div | Owns the Ark pin-input machine and root state attributes. |
InputOtp.RootProvider | div | Uses an externally created usePinInput machine. |
InputOtp.Label | label | Names the OTP control and focuses its first cell when clicked. |
InputOtp.Control | div | Layout wrapper for the individual cells. |
InputOtp.Input | input | One indexed OTP cell. Render one for each count value. |
InputOtp.HiddenInput | input | Visually hidden native form input managed by Ark UI. |
InputOtp.Context | slot | Exposes the current Ark input API to a scoped slot. |
Events
| Event | Payload | Description |
|---|---|---|
update:modelValue | string[] | Emitted as the controlled value changes. |
valueChange | { value, valueAsString } | Emitted for each valid value change. |
valueComplete | { value, valueAsString } | Emitted when every cell is filled with a valid value. |
valueInvalid | { value, index } | Emitted when a value fails type or pattern validation. |
Data Attributes
| Attribute | Value | Description |
|---|---|---|
data-slot | "input-otp" / "input-otp-control" / … | Stable Kappa selectors for named parts. |
data-size | "sm" | "base" | "lg" | Resolved Kappa density on the root. |
data-filled | "" | Present on a cell with a value (from Ark UI). |
data-invalid / data-disabled | "" | Present on invalid or disabled roots and cells. |
data-complete | "" | Present when all cells are filled. |
Exports
| Export | Description |
|---|---|
InputOtp | Object.assign compound namespace and root component. |
InputOtp.Root / RootProvider | Machine-owned and provider-owned roots. |
InputOtp.Label / Control / Input / HiddenInput / Context | Named compound parts. |
InputOtpProps / InputOtpEmits | Kappa root props and Vue event contracts. |
InputOtpApi / InputOtpContextValue | Provider and context API types. |
usePinInput / usePinInputContext / pinInputAnatomy | Ark UI hooks and anatomy re-exports. |
INPUT_OTP_SIZES / INPUT_OTP_DEFAULT_SIZE | Kappa density values and default. |