skip to content

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?

level: juniorimportance: must knowfreq 68%

answer

  1. special ref attribute
  2. 3.5 helper keyed by string
  3. older: same-named ref(null)
  4. null during the first render
  5. set before mounted hooks run

basics

~10 s

Put 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 s

The `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
vue
<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

for a junior

Remember the ref attribute plus useTemplateRef, and that DOM calls belong in onMounted.

for a middle

Explain when Vue assigns the element relative to render and mounted hooks, and why it resets to null on unmount.

for a senior

Show how you handle conditional elements and refactor pre-3.5 same-named refs safely.

for a principal

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