In a Vue 3 `<script setup>` component, how do you define and use a `v-focus` directive, and which hook should do the focusing?
answer
- v-prefixed camelCase variable
- object of hooks
- element must be in the DOM
- mounted, not created
- shorthand also runs on update
basics
~20 sDeclare 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 sIn `<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<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
Remember the vFocus naming rule and that mounted is where the element can be focused.
Explain the hook order and why the function shorthand maps to both mounted and updated.
Judge when a shared directive beats a template ref in each component, and avoid focus-stealing on re-render.
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