Skip to content
Kappa
Input OTP
@dicehub/kappav0.4.2

Input OTP

Collects a short verification code with accessible, machine-backed cells.

<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

Typical part hierarchy
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.

  • Root owns the pin-input machine and emits valueChange, valueComplete, valueInvalid, and update:modelValue.
  • Control lays out indexed cells. Render one Input per index from 0 to count - 1.
  • HiddenInput is visually hidden and keeps native name, required, and form behavior.
  • Context exposes the Ark API to a scoped slot. RootProvider accepts an API created with usePinInput.
<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.

Focus leaves the last cell when complete.
<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.

Value: 402816
<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 Label or an explicit aria-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.
  • otp enables one-time-code autocomplete. Use type and pattern to constrain accepted values.
  • invalid, disabled, and readOnly are passed to Ark UI and reflected on root and cell state attributes.
  • Use blurOnComplete when the next step should receive focus after a valid code. Handle valueComplete for 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.

PropTypeDefaultDescription
countnumber—Number of input cells. Pass it for stable SSR output.
modelValue / v-modelstring[]—Controlled array of cell values.
defaultValuestring[][]Initial array for uncontrolled use.
type"numeric" | "alphabetic" | "alphanumeric""numeric"Restricts entered values.
patternstring—Regular expression checked for each entered value and paste.
placeholderstring"○"Placeholder shown in an empty cell.
maskbooleanfalseUses password-style masking for entered cells.
otpbooleanfalseAdds one-time-code autocomplete to the cells.
blurOnCompletebooleanfalseBlurs the active cell after all cells are valid.
autoFocus / autoSubmitbooleanfalseFocuses the first cell or submits the owning form when complete.
disabled / readOnly / required / invalidbooleanfalseNative and validation states passed to Ark UI.
size"sm" | "base" | "lg""base"Kappa visual density for each cell.

Parts

PartElementDescription
InputOtp.RootdivOwns the Ark pin-input machine and root state attributes.
InputOtp.RootProviderdivUses an externally created usePinInput machine.
InputOtp.LabellabelNames the OTP control and focuses its first cell when clicked.
InputOtp.ControldivLayout wrapper for the individual cells.
InputOtp.InputinputOne indexed OTP cell. Render one for each count value.
InputOtp.HiddenInputinputVisually hidden native form input managed by Ark UI.
InputOtp.ContextslotExposes the current Ark input API to a scoped slot.

Events

EventPayloadDescription
update:modelValuestring[]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

AttributeValueDescription
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

ExportDescription
InputOtpObject.assign compound namespace and root component.
InputOtp.Root / RootProviderMachine-owned and provider-owned roots.
InputOtp.Label / Control / Input / HiddenInput / ContextNamed compound parts.
InputOtpProps / InputOtpEmitsKappa root props and Vue event contracts.
InputOtpApi / InputOtpContextValueProvider and context API types.
usePinInput / usePinInputContext / pinInputAnatomyArk UI hooks and anatomy re-exports.
INPUT_OTP_SIZES / INPUT_OTP_DEFAULT_SIZEKappa density values and default.