Chapter 63: Vue $refs Object
The $refs object (also written as this.$refs in Options API or simply refs via template refs in modern Composition API)
This is not the same as the ref() function you use for reactive state — although both are called “ref” (yes, Vue naming can be confusing on purpose).
$refs is the object that collects all template refs you placed in the template with the ref=”someName” attribute.
It gives you direct access to:
- Real DOM elements (<input>, <div>, <canvas>, etc.)
- Vue component instances (so you can call methods they expose)
In other words: $refs is your bridge from declarative Vue code to imperative DOM/component control when you really need it.
1. Why do we need $refs at all?
Vue wants you to stay declarative as much as possible:
- Use v-model instead of manually setting .value
- Use v-if instead of el.style.display = ‘none’
- Use @click instead of el.addEventListener
But sometimes you must go imperative:
- Focus an input field on mount or button click
- Scroll to a specific message in chat
- Measure element size/position (custom tooltip, popover, sticky header)
- Call methods on third-party widgets (Chart.js .update(), Swiper .slideTo(), Leaflet .invalidateSize())
- Play/pause <video> or <audio>
- Get canvas context (getContext(‘2d’))
- Call exposed methods on child components
That’s when template refs + $refs become necessary.
2. Modern Vue 3 Way (<script setup> – 2026 standard)
You almost never write this.$refs anymore. Instead you create individual refs directly.
Pattern 1 – Single element / component
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
<template> <div> <input ref="searchInput" type="text" placeholder="Search..." /> <button @click="focusAndSelect"> Focus & Select </button> </div> </template> <script setup lang="ts"> import { ref, onMounted } from 'vue' // 1. Create ref (starts as null) const searchInput = ref<HTMLInputElement | null>(null) // 2. Use it imperatively function focusAndSelect() { if (searchInput.value) { searchInput.value.focus() searchInput.value.select() // highlight text } } // 3. Safe after mount onMounted(() => { console.log('Input element:', searchInput.value) searchInput.value?.focus() // auto-focus example }) </script> |
Pattern 2 – Multiple refs in v-for (very common)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
<template> <div class="chat"> <div v-for="(msg, index) in messages" :key="msg.id" ref="messageElements" class="message" > {{ msg.text }} </div> <button @click="scrollToLatest"> Scroll to Latest </button> </div> </template> <script setup lang="ts"> import { ref, onMounted, nextTick, watch } from 'vue' const messages = ref([ { id: 1, text: 'Hello' }, { id: 2, text: 'How are you?' }, // ... ]) const messageElements = ref<HTMLElement[]>([]) // Scroll to last message async function scrollToLatest() { await nextTick() const last = messageElements.value[messageElements.value.length - 1] last?.scrollIntoView({ behavior: 'smooth', block: 'end' }) } // Auto-scroll on new message watch(messages, scrollToLatest, { deep: true }) </script> |
3. Legacy / Options API – Where this.$refs Is Still Common
In older code or when you see legacy projects:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
<template> <div> <input ref="usernameInput" type="text" /> <button @click="focusUsername">Focus</button> </div> </template> <script> export default { mounted() { console.log(this.$refs) // → { usernameInput: <input> } this.$refs.usernameInput.focus() // ← classic usage }, methods: { focusUsername() { this.$refs.usernameInput?.focus() } } } </script> |
Important differences in Vue 3 vs Vue 2
- Vue 2 → $refs was always an object with direct element/component references
- Vue 3 → with fragments (multiple root nodes), $refs behaves the same, but you almost always use individual template refs instead
4. When You Actually Reach for Refs in 2026
Real cases where template refs are the right tool:
- Auto-focus input on mount / button click
- Scroll to element (last chat message, jump link, accordion open)
- Measure element (custom tooltip position, popover, infinite scroll trigger)
- Third-party library (Chart.js .update(), Mapbox .resize(), Swiper .slideTo())
- Media control — <video ref=”vid”> → vid.value.play()
- Canvas / WebGL — canvas.value.getContext(‘2d’)
- Call exposed method on child — childRef.value.someMethod()
- Fix stacking/z-index issues — get root element and adjust styles
5. Quick Cheat Sheet – Template Refs (2026)
| Goal | Code pattern | When ref.value is ready? |
|---|---|---|
| Auto-focus input | ref=”input” + onMounted(() => input.value.focus()) | After onMounted |
| Scroll to element | ref=”lastMsg” + lastMsg.value.scrollIntoView() | After mount / nextTick |
| Measure size | const rect = el.value.getBoundingClientRect() | After mount |
| Control 3rd-party widget | ref=”chart” + chart.value?.update() | After mount |
| Call child method | defineExpose({ play }) + child.value.play() | After mount |
| Multiple refs in v-for | ref=”items” → items.value[index] | After mount |
Pro Tips from Real Projects (Hyderabad 2026)
- Always check if (ref.value) — refs are null before mount & after unmount
- Use nextTick() when manipulating DOM after state change
- Prefer declarative (v-model, v-if, transitions) → use refs only when you must go imperative
- For third-party libraries — template refs are usually the main integration point
- In TypeScript → type your refs → avoids “Property ‘focus’ does not exist” errors
Your mini practice:
- Create an input that auto-focuses on mount (template ref + onMounted)
- Add a long scrollable list → button to scroll to last item (ref on last element)
- Add button that focuses the input again
Any part confusing? Want full examples for:
- Template refs + <Transition> for animated focus?
- Accessing child component methods with TypeScript?
- Common bug with v-if + ref?
- Real Chart.js / Google Maps integration using refs?
Just tell me — we’ll build the next practical ref example together 🚀
