# OTP Input

> A one-time code input, with a slot per character.

## Examples

### Basic

`complete` fires once every slot is filled, so the code can be checked without a submit button.

```vue
<script setup lang="ts">
const toast = useToast();
const code = ref("");
const error = ref<string>();

watch(code, () => (error.value = undefined));

function verify(value: string) {
  if (value === "123456") toast.success("Verified.");
  else error.value = "That code didn't work.";
}
</script>

<template>
  <div class="demo-field">
    <UiOtpInput
      v-model="code"
      name="code"
      label="Verification code"
      hint="Try 123456."
      :error="error"
      @complete="verify"
    />
  </div>
</template>
```

### Groups

```vue
<template>
  <div class="demo-field">
    <UiOtpInput label="Sign-in code" :group-size="3" />
  </div>
</template>
```

### Letters and digits

```vue
<template>
  <div class="demo-field">
    <UiOtpInput label="Recovery code" type="alphanumeric" :length="8" :group-size="4" />
  </div>
</template>
```

## API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `label` | `string` | — | Visible label, linked to the control. |
| `hint` | `string` | — | Help text under the control, announced as its description. |
| `error` | `string \| boolean` | — | `true` marks the control invalid; a string also shows it as the message, replacing the hint. |
| `length` | `number` | `6` | Number of characters in the code. |
| `type` | `"alphanumeric" \| "numeric"` | `"numeric"` | `"numeric"` takes digits and shows the number pad; `"alphanumeric"` takes letters too. |
| `groupSize` | `number` | — | Puts a separator after every this many slots, e.g. `3` for `123–456`. |
| `optional` | `boolean` | `false` | Appends the `optional` translation to the label. |
| `disabled` | `boolean` | `false` | Disables the control. |
| `id` | `string` | — | Id for the control; generated with `useId()` when omitted. |
| `modelValue` | `string` | `""` | The code typed so far. |

| Event | Payload | Description |
| --- | --- | --- |
| `complete` | `[code: string]` | Every slot is filled, by typing, pasting or autofill. |
| `update:modelValue` | `[value: string]` | `v-model:modelValue` update. |

| Slot | Props | Description |
| --- | --- | --- |
| `separator` | — | Content between groups. Defaults to a dash. |

## Styling

- Parts (`[data-part="…"]`): `field`, `field-label`, `field-optional`, `otp`, `otp-input`, `otp-slot`, `otp-caret`, `otp-separator`, `field-message`
- State attributes: `data-field-error`, `data-otp-focused`, `data-otp-disabled`, `data-otp-active`, `data-otp-filled`, `data-field-tone`

## Accessibility

- It's one native input with `autocomplete="one-time-code"`, so codes from SMS and password managers fill in, and screen readers announce a single field.
- Pasting a whole code fills every slot; spaces and dashes in it are dropped.
- The slots are hidden from screen readers, which read the input's value instead.
