skip to content

In a Vue 3 `<script setup>` component, how do you define and use a `v-focus` directive, and which hook should do the focusing?

level: juniorimportance: should knowfreq 55%

answer

  1. v-prefixed camelCase variable
  2. object of hooks
  3. element must be in the DOM
  4. mounted, not created
  5. shorthand also runs on update

basics

~20 s

Declare const vFocus = { mounted: (el) => el.focus() } in script setup and write v-focus on the element. Use mounted because the element is in the document only then; the function shorthand would refocus on every update.

solid answer

~40 s

In `<script setup>`, any camelCase variable starting with `v` can be used as a directive, so `const vFocus = { mounted: (el) => el.focus() }` enables `<input v-focus />` with no extra registration. A directive is an object of optional hooks named like component hooks, each receiving the element. Focusing needs the element to be in the document, which is guaranteed in `mounted`; `created` runs before attributes and listeners are applied and `beforeMount` before insertion. Avoid the function shorthand (`const vFocus = (el) => el.focus()`): a function directive runs in both `mounted` and `updated`, so the input would steal focus every time its component re-renders. Unlike the `autofocus` attribute, the directive also works when Vue inserts the element later, such as under a `v-if`.

code

vue · 16 lines
vue
<script setup>
import { ref } from 'vue'

const editing = ref(false)

const vFocus = {
  mounted(el, binding) {
    if (binding.value !== false) el.focus()
  }
}
</script>

<template>
  <button @click="editing = true">Rename</button>
  <input v-if="editing" v-focus />
</template>

go deeper

for a junior

Remember the vFocus naming rule and that mounted is where the element can be focused.

for a middle

Explain the hook order and why the function shorthand maps to both mounted and updated.

for a senior

Judge when a shared directive beats a template ref in each component, and avoid focus-stealing on re-render.

for a principal

Decide which small DOM behaviours a team standardises as shared directives and how they are documented.

## What a custom directive is Vue ships built-in directives such as `v-model` and `v-show`. A **custom directive** is your own reusable piece of low-level DOM logic attached to an element. It is defined as an object whose optional **hooks** mirror the component lifecycle and receive the element as their first argument. The Vue guide describes them as intended for "reusing logic that involves low-level DOM access on plain elements", and cites `v-focus` as the standard example. ## Defining and using v-focus in script setup ```vue <script setup> const vFocus = { mounted: (el) => el.focus() } </script> <template> <input v-focus placeholder="Search" /> </template> ``` The naming rule does the registration: - In `<script setup>`, any **camelCase variable that starts with the `v` prefix** can be used as a directive. - `vFocus` becomes `v-focus` in the template; `vClickOutside` becomes `v-click-outside`. - An imported directive works the same way: `import { vFocus } from './directives'`. - Without `<script setup>`, you would list it in the `directives` option; app-wide registration goes through `app.directive`. ## Choosing the hook A directive object may define seven hooks. For focusing, the question is when the element is actually in the document: | Hook | When it runs | Can it focus? | |---|---|---| | `created` | before the element's attributes and event listeners are applied | no: not inserted yet | | `beforeMount` | right before the element is inserted into the DOM | no: not inserted yet | | `mounted` | after the owning component and its children are mounted | yes | | `updated` | after the owning component and its children update | yes, but repeatedly | `mounted` is the right hook: it runs once per element insertion, after the element is in the document. ## The function shorthand trap Vue lets you define a directive as a single function. That function is used for **both `mounted` and `updated`**: ```js // looks tidy, but refocuses on every re-render const vFocus = (el) => el.focus() ``` `updated` runs whenever the component that owns the template re-renders, not only when the directive's value changes. So typing in another field of the same form, which updates state and re-renders, would yank focus back to this input. The shorthand suits directives that should re-apply on every update, such as setting a colour from `binding.value`, not one-time actions. ## Why not the autofocus attribute The HTML `autofocus` attribute acts when the page loads. Elements that Vue inserts later, for example a search field revealed by `v-if` or an input inside a dialog opened by a click, do not get focus from it. The directive's `mounted` hook runs whenever Vue inserts the element, so it covers those cases. ## Variations worth knowing 1. **Conditional focus:** pass a value, `v-focus="isEditing"`, and focus only when `binding.value` is true. 2. **Refocus on a change:** add an `updated` hook that compares `binding.value` with `binding.oldValue` and focuses only when it flips to true. 3. **Selecting text:** call `el.select()` after focusing for inline-edit fields. ## Common mistakes - Naming the variable `focus` or `VFocusDirective` and wondering why `v-focus` is unknown. - Using `created` or `beforeMount`, where `focus()` does nothing because the element is not in the document. - Using the function shorthand for a one-time action. - Using a directive where a template ref plus `onMounted` in the same component is simpler; a directive pays off when many components need the same behaviour. ## The full hook order at a glance For one element carrying a directive, the hooks run in this order over its life: 1. `created` — the element exists but its attributes and listeners are not applied yet. 2. `beforeMount` — attributes are applied; the element is about to be inserted. 3. `mounted` — after the owning component and all its children are mounted. 4. `beforeUpdate` and `updated` — once per re-render of the owning component, for as long as the element stays. 5. `beforeUnmount` and `unmounted` — when the element is removed, for example by `v-if`. Step 4 is tied to the **owner's** re-renders, not to the directive's value, which is exactly why a function shorthand repeats its work.

  • In Vue 3, why does `const vFocus = (el) => el.focus()` make typing elsewhere in the same form jump back to that input?
    A function directive is registered for both `mounted` and `updated`. `updated` runs whenever the component owning the template re-renders, and typing in another bound field re-renders it, so the function runs again and moves focus. Use an object with only `mounted` for a one-time action.
  • How would you make a Vue 3 `v-focus` directive focus again whenever its bound value turns true?
    Add an `updated` hook that checks `binding.value && !binding.oldValue` and then calls `el.focus()`. `oldValue` is available in `beforeUpdate` and `updated`, whether or not the value changed, so the comparison is what limits focusing to the moment the value flips to true.

A v-focus directive is like a note on a desk that says 'switch me on when installed': mounted reads it once at installation, while the function shorthand rereads it at every office tidy-up.

saying these in an interview costs you the question

  • Focuses in created or beforeMount before the element is inserted
  • Uses the function shorthand for a one-time focus
  • Names the variable focus and expects v-focus to resolve
  • Thinks updated runs only when the directive's own value changes
  • Assumes the autofocus attribute handles elements Vue inserts later