A template ref gives code access to a rendered element or child component instance. Use it for imperative browser APIs such as focus, measurement, or third-party widgets when declarative props and state cannot express the action. Refs are unavailable before mount and can become null when conditional rendering removes the target.
Template refs give you direct access to a DOM element or child component instance. Use them when you need to do something that Vue's declarative model can't handle - like focusing an input, measuring element dimensions, or calling a method on a child component.
<template>
<div>
<!-- ref attribute - matches the ref name in script -->
<input ref="inputEl" placeholder="I'll be focused on mount" />
<button @click="focusInput">Focus Input</button>
<!-- Canvas ref -->
<canvas ref="canvasEl" width="300" height="150"></canvas>
<!-- Measure element -->
<div ref="boxEl" class="box">Measure me</div>
<p>Box size: {{ boxSize.width }}x{{ boxSize.height }}</p>
<!-- v-for refs - array of elements -->
<ul>
<li v-for="item in items" :key="item.id" :ref="el => setItemRef(el, item.id)">
{{ item.name }}
</li>
</ul>
<button @click="scrollToItem(2)">Scroll to item 2</button>
</div>
</template>
<script setup>
import { ref, reactive, onMounted, useTemplateRef } from 'vue'
// Modern API (Vue 3.5+)
const inputEl = useTemplateRef('inputEl')
const canvasEl = useTemplateRef('canvasEl')
const boxEl = useTemplateRef('boxEl')
// Or classic ref (same name as ref attribute)
// const inputEl = ref(null)
const items = ref([
{ id: 1, name: 'Item One' },
{ id: 2, name: 'Item Two' },
{ id: 3, name: 'Item Three' },
])
const boxSize = reactive({ width: 0, height: 0 })
const itemRefs = new Map()
onMounted(() => {
// Access DOM element after mount
inputEl.value?.focus()
// Draw on canvas
const ctx = canvasEl.value?.getContext('2d')
if (ctx) {
ctx.fillStyle = '#42b883'
ctx.fillRect(10, 10, 280, 130)
ctx.fillStyle = 'white'
ctx.font = '24px Arial'
ctx.fillText('Vue Canvas!', 80, 80)
}
// Measure element
if (boxEl.value) {
const rect = boxEl.value.getBoundingClientRect()
boxSize.width = Math.round(rect.width)
boxSize.height = Math.round(rect.height)
}
})
function focusInput() {
inputEl.value?.focus()
inputEl.value?.select()
}
function setItemRef(el, id) {
if (el) itemRefs.set(id, el)
else itemRefs.delete(id)
}
function scrollToItem(id) {
itemRefs.get(id)?.scrollIntoView({ behavior: 'smooth' })
}
</script>
<!-- ChildComponent.vue -->
<template>
<div>
<input ref="inputEl" v-model="value" />
<p>Internal count: {{ count }}</p>
</div>
</template>
<script setup>
import { ref, useTemplateRef } from 'vue'
const value = ref('')
const count = ref(0)
const inputEl = useTemplateRef('inputEl')
// defineExpose - explicitly expose what parent can access
// Without defineExpose, <script setup> components are closed by default
defineExpose({
// Expose methods
focus() {
inputEl.value?.focus()
},
clear() {
value.value = ''
},
increment() {
count.value++
},
// Expose data (read-only)
getValue: () => value.value,
// Expose ref directly
count,
})
</script>
<!-- Parent.vue - accessing child component methods -->
<template>
<div>
<ChildComponent ref="childRef" />
<button @click="childRef?.focus()">Focus Child Input</button>
<button @click="childRef?.clear()">Clear Child Input</button>
<button @click="childRef?.increment()">Increment Child Count</button>
<p>Child value: {{ childRef?.getValue() }}</p>
<p>Child count: {{ childRef?.count }}</p>
</div>
</template>
<script setup>
import { useTemplateRef, onMounted } from 'vue'
import ChildComponent from './ChildComponent.vue'
const childRef = useTemplateRef('childRef')
onMounted(() => {
// Access exposed methods after mount
childRef.value?.focus()
})
</script>
Declare a matching ref in script and use it only after onMounted or nextTick when DOM updates must finish first. A ref inside v-for represents multiple elements and should not become the primary data model; identify records in reactive state and use DOM refs only for the final imperative operation.
A component ref exposes internal implementation unless the child deliberately limits its public surface with defineExpose. Prefer props and events for normal communication. Clean up observers, widgets, and listeners associated with a ref when the element changes or the owner unmounts.
Explore 500+ free tutorials across 20+ languages and frameworks.