Template Refs, nextTick and Direct DOM Access

Intermediate
10 min

Template Refs, nextTick and Direct DOM Access

Vue's declarative rendering covers most needs, but sometimes you must touch a real DOM element: focus an input, measure a box, scroll a list, or hand a canvas to a charting library. Template refs give you that access without breaking Vue's model. In this lesson you will obtain element and component references, understand when they become available, use nextTick to wait for DOM updates, and expose a child component's methods safely.

Getting a Reference to an Element

Add a ref="name" attribute to an element, then read it with useTemplateRef('name') (Vue 3.5+). The returned ref is null until the component is mounted:

vue
<script setup> import { useTemplateRef, onMounted } from 'vue' const input = useTemplateRef('search') onMounted(() => { input.value.focus() }) </script> <template> <input ref="search" placeholder="Search" /> </template>

Before Vue 3.5 the pattern was a plain ref whose variable name matches the attribute: const search = ref(null) with <input ref="search">. It still works, but useTemplateRef is explicit about intent and infers the element type in TypeScript.

Refs are only populated after mount, so accessing them directly in <script setup> top-level code gives null. Use onMounted, a watcher, or an event handler.

Refs Inside v-for and Function Refs

When ref is used on an element rendered by v-for, the ref holds an array of elements. The order is not guaranteed to match the source array, so use it for operations that do not depend on order, or attach data attributes:

vue
<script setup> import { ref, useTemplateRef, onMounted } from 'vue' const items = ref(['a', 'b', 'c']) const rows = useTemplateRef('rows') onMounted(() => console.log(rows.value.length)) // 3 </script> <template> <li v-for="item in items" :key="item" ref="rows">{{ item }}</li> </template>

A function ref gives full control: :ref="(el) => { if (el) registry.set(id, el) }". It is called with the element on mount and with null on unmount, which makes it suitable for keeping a map of elements keyed by id.

Waiting for the DOM with nextTick

Vue batches state changes and updates the DOM asynchronously. If you change state and immediately read the DOM, you see the old version. nextTick returns a promise that resolves after the pending update has been applied:

vue
<script setup> import { ref, useTemplateRef, nextTick } from 'vue' const messages = ref([]) const list = useTemplateRef('list') async function add(text) { messages.value.push(text) await nextTick() list.value.scrollTop = list.value.scrollHeight // scrolls to the new item } </script> <template> <ul ref="list" class="log"> <li v-for="(m, i) in messages" :key="i">{{ m }}</li> </ul> </template>

The same applies when you show an element with v-if and want to focus it: set the flag, await nextTick(), then call focus().

Refs on Components and defineExpose

A ref on a child component tag gives you the component instance. With <script setup>, components are closed by default: the parent sees nothing unless the child explicitly exposes it:

vue
<!-- VideoPlayer.vue --> <script setup> import { useTemplateRef } from 'vue' const video = useTemplateRef('video') function play() { video.value.play() } defineExpose({ play }) </script> <template> <video ref="video" src="/intro.mp4"></video> </template>

The parent calls player.value.play() after obtaining const player = useTemplateRef('player') and rendering <VideoPlayer ref="player" />. Expose only what is needed; imperative calls between components should be the exception, with props and events as the default communication channel.

Tips

  • Prefer declarative solutions first: autofocus, :class, v-show. Reach for refs only when the DOM API is the only way.
  • Guard against null: a ref is null before mount, after unmount, and while its element is hidden by v-if.
  • Do not store DOM elements in reactive state; a template ref is already the right container.
  • Pass the ref's element, never the ref object, to third-party libraries: new Chart(canvas.value, options).
Quick Quiz
Question 1 of 2

When does a template ref receive its element?

Key Takeaways

  • Add ref="name" to an element and read it with useTemplateRef('name'); the value is null until mount.
  • Refs inside v-for hold arrays; function refs give full control over registration.
  • await nextTick() before reading the DOM after a state change.
  • Component refs expose only what defineExpose lists.
  • Use refs for focus, measurement, scrolling and third-party libraries, and keep everything else declarative.

Next lesson: Composables: Reusable Logic with the Composition API — extract stateful logic into use* functions shared across components.

Template Refs, nextTick and Direct DOM Access - Vue.js | CodeYourCraft | CodeYourCraft