# How to Fix OTP Input Focus Order When Users Type Out of Sequence in Vue 3

> Fix OTP input focus order in Vue 3 by setting shouldFocusOrder to true and updating logic to find the first empty input. Ensure seamless user experience for out-of-sequence typing.

- Repository: [Ejiro Asiuwhu/vue3-otp-input](https://github.com/ejirocodes/vue3-otp-input)
- Tags: tutorial
- Published: 2026-03-01

---

**Set the `shouldFocusOrder` prop to `true` and replace the `focusOrder` function in [`src/components/vue3-otp-input.vue`](https://github.com/ejirocodes/vue3-otp-input/blob/main/src/components/vue3-otp-input.vue) with logic that locates the first empty input index using `findIndex`, ensuring the cursor always jumps to the next required digit regardless of where the user clicked.**

The `vue3-otp-input` component by ejirocodes provides a multi-digit OTP entry field for Vue 3 applications, but the default focus behavior breaks when users click out of sequence or type into non-consecutive boxes. This guide explains how to fix OTP input focus order when users type out of sequence in Vue 3 by replacing the component's internal focus management logic with a deterministic "first empty slot" algorithm.

## Understanding the Focus Order Problem in Vue 3 OTP Inputs

When building OTP inputs in Vue 3, the expected user experience is sequential: type the first digit, auto-focus moves to the second box, and so on. However, users often interact non-linearly—they might click directly into the fourth box, paste a full code, or use arrow keys to navigate.

In the `vue3-otp-input` repository, the default implementation relies on an `activeInput` ref to track which input should receive focus. When `shouldFocusOrder` is enabled, the component attempts to correct the focus after each keystroke, but the original logic uses a `setTimeout` with a hard-coded length calculation (`otp.value.join("").length`) that does not account for gaps in the input sequence. This causes the cursor to land on the wrong index when users skip ahead or fill boxes out of order.

## How the Current Focus Logic Works in vue3-otp-input

The OTP input consists of two tightly-coupled components: `Vue3OtpInput` (the orchestrator) and `SingleOtpInput` (the individual box). Understanding their interaction is key to fixing the focus order.

### Active Input Tracking

In [`src/components/vue3-otp-input.vue`](https://github.com/ejirocodes/vue3-otp-input/blob/main/src/components/vue3-otp-input.vue), the component maintains a reactive reference to the currently focused index:

```typescript
const activeInput = ref<number>(0);

```

This `activeInput` determines which `SingleOtpInput` instance receives the cursor. When a user types into a box, the `handleOnChange` event updates the OTP array and, by default, calls `focusNextInput()`, which simply increments `activeInput` by one.

### The Broken focusOrder Implementation

The existing `focusOrder` function attempts to handle out-of-sequence typing but relies on flawed timing logic:

```typescript
// Current problematic implementation (src/components/vue3-otp-input.vue)
const focusOrder = (index: number) => {
  if (!props.shouldFocusOrder) return;
  
  setTimeout(() => {
    const length = otp.value.join("").length;
    activeInput.value = length;
  }, 100);
};

```

This approach fails because `length` represents the total count of filled characters, not the position of the first empty slot. If a user types into box 3 while box 1 is empty, the length is 1, so `activeInput` becomes 1 (pointing to the second box), but the cursor should actually move to box 0 (the first empty one).

## Implementing the Fix for Out-of-Sequence Typing

To fix OTP input focus order when users type out of sequence, replace the length-based logic with a deterministic "first empty slot" algorithm.

### Replace focusOrder with First-Empty-Slot Logic

Update the `focusOrder` function in [`src/components/vue3-otp-input.vue`](https://github.com/ejirocodes/vue3-otp-input/blob/main/src/components/vue3-otp-input.vue) to locate the first unfilled index using `findIndex`:

```typescript
const focusOrder = (currentIndex: number) => {
  if (!props.shouldFocusOrder) return;

  // Locate the first empty cell; if all filled, target the last input
  const firstEmpty = otp.value.findIndex((char) => !char);
  const target = firstEmpty === -1 ? props.numInputs - 1 : firstEmpty;

  // Update the reactive focus index
  activeInput.value = target;

  // Optional: clear the out-of-order value to prevent duplicates
  if (currentIndex !== target) {
    otp.value[currentIndex] = "";
  }
};

```

**Why this works:** `findIndex` scans the OTP array from the start and returns the exact position of the first missing character. By setting `activeInput` to this index, the component ensures the cursor always jumps to the logical next position, even if the user clicked ahead or pasted into a later box. Clearing the out-of-order value prevents duplicate characters when the user later returns to that box.

### Updating handleOnKeyDown for Immediate Focus

The `handleOnKeyDown` function in [`src/components/vue3-otp-input.vue`](https://github.com/ejirocodes/vue3-otp-input/blob/main/src/components/vue3-otp-input.vue) must call `focusOrder` directly without wrapping it in `setTimeout`:

```typescript
const handleOnKeyDown = (event: KeyboardEvent, index: number) => {
  switch (event.key) {
    case 'Backspace':
      // existing backspace logic
      break;
    case 'Delete':
      // existing delete logic
      break;
    case 'ArrowLeft':
      // existing left navigation
      break;
    case 'ArrowRight':
      // existing right navigation
      break;
    default:
      // Immediate focus correction without setTimeout
      focusOrder(index);
      break;
  }
};

```

Removing the asynchronous delay ensures the focus shift happens synchronously with the keystroke, preventing race conditions where the user might type a second character before the focus updates.

### Optional Watcher for Pre-filled Values

If your application loads an OTP from an API or query parameter, add a watcher in [`src/components/vue3-otp-input.vue`](https://github.com/ejirocodes/vue3-otp-input/blob/main/src/components/vue3-otp-input.vue) to re-calculate the correct focus position when the value changes:

```typescript
import { watch } from 'vue';

watch(
  () => otp.value.join(''),
  () => {
    if (props.shouldFocusOrder) {
      const firstEmpty = otp.value.findIndex((c) => !c);
      activeInput.value = firstEmpty === -1 ? props.numInputs - 1 : firstEmpty;
    }
  }
);

```

This ensures that when a user pastes a full code or the component receives a pre-filled value, the `activeInput` updates to the correct position, maintaining synchronization between the data model and the visual focus state.

## Complete Working Examples

### Basic Usage with Robust Focus Order

Enable the fixed behavior by setting `shouldFocusOrder` to `true`:

```vue
<template>
  <Vue3OtpInput
    v-model="otp"
    :numInputs="6"
    :shouldFocusOrder="true"
    placeholder="○"
  />
</template>

<script setup lang="ts">
import { ref } from 'vue';
import Vue3OtpInput from '@/components/vue3-otp-input.vue';

const otp = ref('');
</script>

```

With this configuration, users can click into any box, type a digit, and the focus immediately jumps to the first empty input, preventing duplicate entries and confusion.

### Handling Pre-filled OTP Codes

When loading an existing OTP (e.g., from an API or auto-fill), combine `shouldFocusOrder` with the watcher logic to ensure proper cursor placement:

```vue
<template>
  <Vue3OtpInput
    v-model="otp"
    :numInputs="4"
    :shouldFocusOrder="true"
    :shouldAutoFocus="true"
  />
</template>

<script setup lang="ts">
import { ref, onMounted } from 'vue';
import Vue3OtpInput from '@/components/vue3-otp-input.vue';

const otp = ref('');

// Simulate fetching a partial OTP from an API
onMounted(async () => {
  const fetchedCode = await Promise.resolve('12'); // User already has "12"
  otp.value = fetchedCode;
  // The component will automatically focus the third box (index 2)
});
</script>

```

This pattern ensures that even with incomplete or full pre-filled values, the input focus remains aligned with the first available empty slot.

## Summary

- **The default focus logic** in `vue3-otp-input` increments the active index linearly, which fails when users click or type into non-consecutive boxes.
- **The root cause** is the `focusOrder` function's reliance on `setTimeout` and character length rather than the first empty index.
- **The solution** replaces the length-based calculation with `otp.value.findIndex((c) => !c)` to locate the first missing digit, updating `activeInput` immediately.
- **Implementation requires** modifying [`src/components/vue3-otp-input.vue`](https://github.com/ejirocodes/vue3-otp-input/blob/main/src/components/vue3-otp-input.vue) to update `focusOrder` and `handleOnKeyDown`, and optionally adding a watcher for pre-filled values.
- **Enable the fix** by setting the `shouldFocusOrder` prop to `true` when using the component.

## Frequently Asked Questions

### What causes the focus order to break when typing out of sequence?

The focus order breaks because the original `focusOrder` function in [`src/components/vue3-otp-input.vue`](https://github.com/ejirocodes/vue3-otp-input/blob/main/src/components/vue3-otp-input.vue) uses a `setTimeout` with a hard-coded length calculation (`otp.value.join("").length`). When a user types into box 3 while box 1 remains empty, the length becomes 1, causing the focus to move to index 1 (the second box) instead of index 0 (the first empty box). This length-based approach cannot account for gaps in the input sequence.

### How does the shouldFocusOrder prop work in vue3-otp-input?

The `shouldFocusOrder` prop acts as a feature flag in [`src/components/vue3-otp-input.vue`](https://github.com/ejirocodes/vue3-otp-input/blob/main/src/components/vue3-otp-input.vue). When set to `true`, it enables the `focusOrder` function to execute during keyboard input events. In the fixed implementation, this function uses `findIndex` to locate the first empty character slot in the OTP array and updates the `activeInput` ref to that index. Without this prop set to `true`, the component ignores focus order corrections and relies solely on linear incrementing.

### Can users still navigate manually with arrow keys after implementing this fix?

Yes, manual navigation remains fully functional. The `handleOnKeyDown` function in [`src/components/vue3-otp-input.vue`](https://github.com/ejirocodes/vue3-otp-input/blob/main/src/components/vue3-otp-input.vue) explicitly handles `ArrowLeft` and `ArrowRight` keys to move the cursor between inputs. The `focusOrder` logic only executes for default keystrokes (alphanumeric input) when `shouldFocusOrder` is enabled. Arrow key events bypass the automatic focus correction, allowing users to move freely between boxes to correct mistakes or review entries without interference from the first-empty-slot algorithm.

### Will this fix work with pasted OTP codes?

Yes, the fix handles pasted codes effectively, especially when combined with a watcher for pre-filled values. When a user pastes a complete or partial OTP string, the [`single-otp-input.vue`](https://github.com/ejirocodes/vue3-otp-input/blob/main/single-otp-input.vue) components emit change events that populate the OTP array. With the updated `focusOrder` logic, the component immediately identifies the first empty slot after the paste operation. For API-loaded or auto-filled values, adding a `watch` on the joined OTP string ensures the `activeInput` updates to the correct position when the component receives external data, maintaining synchronization between the data model and the visual focus state.