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).