Skip to content

Vue Autofocus Directive ​

A custom Vue directive that automatically focuses the first focusable element and selects its text content on mount. When applied to a wrapper element (like a form or div), it finds the first focusable descendant rather than requiring direct application to the input.

When to use ​

Use this directive on any element that should receive focus when it mounts — dialogs, form fields, or wrapper elements. It finds the first focusable descendant, focuses it, and selects its text so the user can immediately start typing a replacement.

The pattern ​

Apply v-autofocus to any element. The directive finds the first focusable descendant, focuses it, and selects its text content.

On a form ​

The first <input> inside the form receives focus automatically.

vue
<template>
  <form v-autofocus @submit.prevent="form.submit()">
    <Input label="Name" v-model="form.data.name" />
    <Input label="Email" v-model="form.data.email" />
    <button type="submit">Save</button>
  </form>
</template>

On an input directly ​

When applied directly to an input, querySelector finds the element itself (it matches input), focuses it, and selects any existing text.

vue
<template>
  <input v-autofocus v-model="search" />
</template>

In a dialog ​

The directive works on any container, including <dialog> elements that render conditionally.

vue
<template>
  <dialog v-if="open" v-autofocus>
    <Input
      label="Account Name"
      v-model="form.data.name"
    />
    <button @click="close">Cancel</button>
  </dialog>
</template>

Implementing the autofocus directive ​

Use a mounted directive hook that queries for focusable elements, focuses the first match, and optionally selects its text.

javascript
const AutofocusDirective = {
  mounted(element) {
    const focusableSelector = `
      button, [href], input, select, textarea,
      [tabindex]:not([tabindex="-1"])
    `;
    const focusable = element.querySelector(focusableSelector);
    focusable?.focus?.();
    focusable?.select?.();
  },
};

Register the directive globally on the app instance for use in any template, or locally in <script setup> by assigning it to a v-prefixed variable — Vue resolves vAutofocus as the v-autofocus directive automatically.

javascript
// Global registration
app.directive("autofocus", AutofocusDirective);

// Local registration in <script setup>
const vAutofocus = AutofocusDirective;

The directive doesn't assume element is focusable. It queries for the first focusable descendant using a standard selector that covers buttons, links, inputs, selects, textareas, and elements with explicit tabindex. Elements with tabindex="-1" are programmatically focusable but not in the tab order, so they're excluded from the auto-focus search.

Optional chaining guards ​

focusable?.focus?.() and focusable?.select?.() guard against null matches and elements that don't support select() (like buttons). No error is thrown if there's nothing to focus.

Mounted-only behavior ​

The directive runs once when the element enters the DOM. It doesn't re-focus on updates. For re-focusing on visibility changes, use a watcher instead.

Trade-offs ​

  • First focusable only — Always focuses the first match. If the desired target isn't the first focusable element, reorder the DOM or apply the directive directly to the target element.
  • No delay option — Focus happens synchronously in mounted. If the element is inside a transition or animation, it may receive focus before it's visually ready. Use nextTick or setTimeout in a custom variant if timing is an issue.
  • select() on all inputs — Calls select() on the focused element, which selects all text in inputs. This is helpful for edit forms but unexpected for empty inputs (where it's a no-op anyway).