Template Refs
What are Template Refs?
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 }}×{{ 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>
Ready to Level Up Your Skills?
Explore 500+ free tutorials across 20+ languages and frameworks.