In a Vue 3.5 `<script setup>` component, how do you focus an `<input>` on mount with a template ref, and why not during setup?
answer
- special ref attribute
- 3.5 helper keyed by string
- older: same-named ref(null)
- null during the first render
- set before mounted hooks run
basics
~10 sPut ref="email" on the input, get it with useTemplateRef('email') (Vue 3.5) or a same-named ref(null), and call focus() in onMounted. During setup the element does not exist yet, so the ref is still null.
solid answer
~40 sThe `ref` attribute asks Vue to hand you the element after it is mounted. In Vue 3.5 you write `<input ref="email" />` and `const emailInput = useTemplateRef('email')`; the key string must match the attribute, but the variable name is free. Before 3.5 you declared `const email = ref(null)` whose **variable name** matched the attribute. Either way the value is `null` while `setup` runs and during the first render, because the element is only created by that render; Vue assigns the element after rendering and before `onMounted` callbacks run. So `onMounted(() => emailInput.value?.focus())` is the right place. If the element sits under a `v-if`, the ref also goes back to `null` whenever that branch is removed.
code
vue · 15 lines<script setup>
import { useTemplateRef, onMounted } from 'vue'
const emailInput = useTemplateRef('email')
console.log(emailInput.value) // null: nothing rendered yet
onMounted(() => {
emailInput.value?.focus()
})
</script>
<template>
<label>Email <input ref="email" type="email" /></label>
</template>go deeper
Remember the ref attribute plus useTemplateRef, and that DOM calls belong in onMounted.
Explain when Vue assigns the element relative to render and mounted hooks, and why it resets to null on unmount.
Show how you handle conditional elements and refactor pre-3.5 same-named refs safely.
Set guidance for when imperative DOM access through refs is acceptable in a codebase versus declarative bindings.
## What a template ref is Vue renders the DOM for you, but sometimes you need the real element: to call `focus()`, measure a size, or hand a node to a third-party library. The special `ref` attribute on an element (or component) in a template asks Vue to give you a reference to it **after it is mounted**. In a `<script setup>` component there are two ways to receive it. | Approach | Script | Template | Available since | |---|---|---|---| | `useTemplateRef` | `const emailInput = useTemplateRef('email')` | `<input ref="email" />` | Vue 3.5 | | Same-named ref | `const email = ref(null)` | `<input ref="email" />` | Vue 3.0 | ## useTemplateRef in Vue 3.5 `useTemplateRef(key)` returns a **shallow ref** whose value is kept in sync with the element or component that carries `ref="key"`. Points worth knowing: - The **string key** links script and template, so the variable can have any name (`emailInput`), and the call can live inside a composable that receives the key as an argument. - In development the returned ref is **read-only**; you never assign to it, Vue does. - Calling it twice with the same key in one component triggers a development warning that the key already exists. - Editor tooling and `vue-tsc` infer the element type from where the matching attribute is used. ## The pre-3.5 pattern still works Before 3.5 you declared `const email = ref(null)` and wrote `ref="email"`. Vue looks up a setup binding with that **name** and writes the element into it. Two traps come with it: 1. The link is the variable name, so renaming the variable silently breaks the ref. 2. If the binding is a plain variable instead of a ref (`let email = null`), a development build warns that the template ref is used on a non-ref value and will not work in the production build. New code in 3.5 should prefer `useTemplateRef`; existing same-named refs keep working. ## Why the value is null during setup The order of events on first mount is: 1. `setup` runs and creates the ref with value `null`. 2. The render function runs and Vue creates the DOM elements. 3. After rendering, Vue assigns the element to the ref. 4. `onMounted` callbacks run; the ref now holds the element. So reading `emailInput.value` at the top level of `<script setup>` gives `null`, and a template expression reading it during the first render also sees `null`. The fix is to move DOM work into `onMounted`, or into code that runs later, such as an event handler. ## Refs follow the element's life The ref is not set once and forgotten: - If the element is inside a `v-if` that becomes false, Vue sets the ref back to `null` when the element is unmounted. - When the branch renders again, the ref receives the **new** element. - Code that stored the old element in another variable now holds a detached node. That is why the Vue guide recommends handling `null` whenever you react to a template ref changing, for example `if (emailInput.value) emailInput.value.focus()`. ## Focusing on mount, end to end - Put `ref="email"` on the input. - Create the ref with `useTemplateRef('email')`. - In `onMounted`, call `emailInput.value?.focus()`; optional chaining covers a conditional input that is not rendered. - For a field that appears later (a `v-if` that turns on after a click), wait for the DOM update before focusing, because the ref is only filled after the next render. The same pattern serves any imperative DOM call: `scrollIntoView()`, `select()`, measuring `getBoundingClientRect()`, or mounting a chart library onto a `<div>`. ## Pitfalls interviewers probe - **Why a shallow ref?** `useTemplateRef` returns a `shallowRef`: Vue only needs to track *which* node the ref points at, so only replacing the value is reactive. Nothing inside the element, such as the text a user types, is tracked through the ref. - **Rendering from a ref.** Showing `emailInput.value?.value` in the template is fragile: it is `null` on first render, and typing into the input does not trigger re-renders through the ref. Bind data with `v-model` and use refs only for imperative calls. - **Server rendering.** `onMounted` does not run on the server, so DOM work placed there is automatically client-only, which is another reason to keep ref access in that hook.
- In Vue 3.5, why prefer `useTemplateRef('email')` over a same-named `ref(null)`?The link becomes an explicit string key instead of a matching variable name, so the variable can be renamed freely and the call can sit inside a composable that receives the key. Tooling infers the element type from the template, and the returned ref is read-only in development, so accidental writes are caught.
- In Vue 3, what does a template ref hold after a `v-if` around its element turns false?`null`. Vue clears the ref when the element unmounts and assigns the new element when the branch renders again. Any code that cached the old element elsewhere now holds a detached node, so read the ref at the moment you need it and handle `null`.
saying these in an interview costs you the question
- Calls focus() on the ref at the top level of script setup
- Believes the ref holds the element during the first render
- Thinks the useTemplateRef variable name must match the attribute
- Expects a template ref to keep the old element after v-if removes it
- Declares the target as a plain let variable instead of a ref