skip to content

Element & Child Refs

The ref attribute hands you a DOM element or child instance after mount, via useTemplateRef or a same-named ref. Interviewers probe refs in v-for, function refs, and what defineExpose reveals.

part ofVue.jsoverview, primer and where to startread it →
on this pageshow

explore

questions

5

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
open as a page

In Vue 3, a parent holds a template ref to a `<script setup>` child and calls its `reset()`; why does that fail until the child uses defineExpose?

level: middleimportance: should knowfreq 55%

basics

~20 s

A script setup component is closed by default: a parent's template ref receives a proxy that exposes none of its setup bindings. The child must call defineExpose({ reset }) before any await, and the parent then calls childRef.value.reset() after mount.

open as a page

In Vue 3, what does a template ref placed on an element inside `v-for` hold, and why must you not rely on its order?

level: middleimportance: should knowfreq 42%

basics

~20 s

A ref inside v-for collects an array of all rendered elements or component instances. Vue does not guarantee that the array matches the source list's order, so look items up by an identifier, not by index.

open as a page

A Vue 3 component's template ref reads null exactly when code needs the element; what usually causes that, and how do you fix each cause?

level: seniorimportance: should knowfreq 45%

basics

~20 s

A template ref is null before mount, while its element sits in an unrendered v-if branch, right after state changes but before the re-render, or when the ref key or name does not match. Read it later, await nextTick(), or fix the link.

open as a page

In Vue 3, when is a function ref such as `:ref="(el) => ..."` called, and what arguments does it receive?

level: middleimportance: nice to knowfreq 30%

basics

~20 s

A function bound with :ref is called with the element or component instance when it mounts, again on each update of that element, and with null when it unmounts. You decide where to store the value, which suits maps keyed by id.

open as a page