What Are Vue Refs?
Vue refs serve two primary purposes in Vue 3:
- Template Refs - Direct access to DOM elements or component instances after mounting
- Reactive Refs - Wrapping primitive values to make them reactive in the Composition API
This guide focuses on template refs, which enable imperative DOM manipulation when Vue's declarative model isn't sufficient. Understanding how to leverage Vue's reactivity system effectively is essential for building performant web applications that integrate seamlessly with the broader JavaScript ecosystem. For teams looking to enhance their development workflow, our AI automation services can help streamline repetitive coding tasks and improve overall productivity.
When to Use Template Refs
Template refs are appropriate when you need to:
- Programmatically focus form inputs
- Measure element dimensions or positions
- Integrate with non-Vue libraries (charts, maps, animations)
- Access component instance methods
- Trigger imperative animations
Avoid using refs for tasks Vue handles declaratively like class or style bindings.
Essential patterns for working with Vue refs
useTemplateRef()
Modern Vue 3.5+ API for accessing template refs with proper type inference and reactivity tracking.
Component Refs
Access child component instances and their exposed methods using refs with defineExpose().
Refs in v-for
Automatic array behavior when using refs inside loops for bulk element access.
Function Refs
Dynamic ref registration with cleanup callbacks for advanced use cases.
Creating Template Refs with Composition API
In Vue 3.5 and later, the recommended approach uses useTemplateRef():
<script setup>
import { useTemplateRef, onMounted } from 'vue'
const inputRef = useTemplateRef('input-element')
onMounted(() => {
inputRef.value.focus()
})
</script>
<template>
<input ref="input-element" type="text" />
</template>
Key Points
- The string argument to
useTemplateRef()must match therefattribute value - Access the DOM element via
.valueproperty - Only available after component is mounted
- Check
valueis not null before accessing
This modern approach provides better TypeScript support and clearer code organization compared to the legacy ref pattern.
Accessing Refs Before Mounting
Template refs are undefined until after the component mounts. Use lifecycle hooks or watchers to safely access DOM elements:
<script setup>
import { ref, watch, onMounted } from 'vue'
const elementRef = ref(null)
// Safe to access in onMounted
onMounted(() => {
console.log(elementRef.value) // DOM element
})
// For watching ref changes, handle null case
watch(elementRef, (newRef) => {
if (newRef) {
newRef.classList.add('loaded')
}
})
</script>
When working with refs in Vue.js applications, always remember that the ref value is null until the component mounts. Attempting to access elementRef.value directly in script setup at the top level will return null.
Refs on Components
When used on child components, refs access the component instance rather than DOM elements:
<!-- ParentComponent.vue -->
<script setup>
import { useTemplateRef, onMounted } from 'vue'
import ChildComponent from './ChildComponent.vue'
const childRef = useTemplateRef('child')
onMounted(() => {
childRef.value.somePublicMethod()
})
</script>
<template>
<ChildComponent ref="child" />
</template>
Exposing Component Internals
Components using <script setup> are private by default. Use defineExpose() to explicitly control what parent components can access:
<!-- ChildComponent.vue -->
<script setup>
import { ref } from 'vue'
const count = ref(0)
defineExpose({
count,
resetCount: () => {
count.value = 0
}
})
</script>
This pattern creates a clear public API contract between components and is essential for building maintainable Vue applications with proper encapsulation.
Refs Inside v-for Loops
When using ref inside v-for, Vue automatically creates an array of references:
<script setup>
import { ref, useTemplateRef, onMounted } from 'vue'
const items = ref(['Apple', 'Banana', 'Cherry'])
const itemRefs = useTemplateRef('list-items')
onMounted(() => {
console.log(itemRefs.value.length) // 3
itemRefs.value[0].classList.add('first-item')
})
</script>
<template>
<ul>
<li v-for="item in items" :key="item" ref="list-items">
{{ item }}
</li>
</ul>
</template>
Function Refs for Dynamic Behavior
Function refs provide more control over when refs are set and can handle dynamic registration and cleanup:
<script setup>
import { ref } from 'vue'
const containerRef = ref(null)
function registerRef(el) {
if (el) {
containerRef.value = el
el.dataset.registered = 'true'
} else {
containerRef.value = null
}
}
</script>
<template>
<div :ref="registerRef" class="container">
Dynamic content
</div>
</template>
Function refs receive the element as an argument when mounted and null when unmounted, making them ideal for scenarios requiring manual lifecycle management.
Ref vs Reactive: Choosing the Right Approach
Vue 3 offers two main reactivity primitives, and understanding when to use each is crucial for building maintainable applications:
<script setup>
import { ref, reactive } from 'vue'
// Use ref for:
const count = ref(0) // Primitive values
const user = ref(null) // Objects that might be replaced
const items = ref([]) // Arrays
// Use reactive for:
const state = reactive({
user: { name: 'John' },
settings: { theme: 'dark' }
})
</script>
When to Prefer Refs
Refs are preferred when:
- Working with primitive values (numbers, strings, booleans)
- The value might be completely replaced entirely
- You need to pass state between components
- Using TypeScript and wanting explicit type annotations
The .value Property
The .value property is essential for Vue's reactivity tracking system:
<script setup>
import { ref } from 'vue'
const count = ref(0)
// .value is needed in JavaScript
count.value++
// But NOT in templates (automatic unwrapping)
</script>
<template>
<span>{{ count }}</span>
</template>
Vue automatically unwraps refs in templates, making the syntax cleaner for display while maintaining full reactivity for updates.
| Feature | ref() | reactive() |
|---|---|---|
| Works with primitives | Yes | No |
| Works with objects | Yes | Yes |
| Access syntax | .value property | Direct property access |
| Can be reassigned | Yes | No (proxy replacement) |
| Array support | Yes | Limited |
| TypeScript inference | Explicit | Implicit |
| Best for | Isolated values, APIs | Complex state objects |
Performance Considerations
While refs themselves are lightweight, improper usage can impact application performance and maintainability:
Minimize Ref Scope
Only create refs for elements you genuinely need to access:
<!-- Avoid: Refing everything -->
<template>
<div ref="container">
<span ref="title">Content</span>
<button ref="btn">Action</button>
</div>
</template>
<!-- Prefer: Only ref what you need -->
<template>
<div>
<span>Content</span>
<button ref="actionBtn">Action</button>
</div>
</template>
Avoid Over-Using Component Refs
Component refs can create tight coupling between parent and child components:
<!-- Anti-pattern: Over-reliance on component refs -->
<template>
<FormInput ref="firstName" />
<FormInput ref="lastName" />
</template>
<!-- Better: Use props and events -->
<template>
<FormInput
v-model="form.firstName"
:error="errors.firstName"
@validate="validateField('firstName')"
/>
</template>
Cleanup in onUnmounted
When refs access external resources, clean up properly to prevent memory leaks:
<script setup>
import { useTemplateRef, onUnmounted } from 'vue'
const chartRef = useTemplateRef('chart')
let chartInstance = null
onMounted(() => {
if (chartRef.value) {
chartInstance = new ChartLibrary(chartRef.value)
}
})
onUnmounted(() => {
if (chartInstance) {
chartInstance.destroy()
chartInstance = null
}
})
</script>
Proper cleanup is essential when integrating third-party libraries in Vue.js projects to ensure optimal performance.
Practical Use Cases
Form Focus Management
<script setup>
import { ref, useTemplateRef } from 'vue'
const emailInput = useTemplateRef('email')
const errorRef = useTemplateRef('error')
function handleSubmit() {
if (!emailInput.value?.value) {
errorRef.value.textContent = 'Email is required'
emailInput.value.focus()
return
}
errorRef.value.textContent = ''
}
</script>
<template>
<form @submit.prevent="handleSubmit">
<div ref="error" class="error"></div>
<input ref="email" v-model="email" type="email" />
<button type="submit">Submit</button>
</form>
</template>
Third-Party Library Integration
<script setup>
import { useTemplateRef, onMounted, onUnmounted } from 'vue'
import Chart from 'chart.js/auto'
const canvasRef = useTemplateRef('chart')
let chartInstance = null
onMounted(() => {
if (canvasRef.value) {
chartInstance = new Chart(canvasRef.value, {
type: 'bar',
data: {
labels: ['Q1', 'Q2', 'Q3', 'Q4'],
datasets: [{
label: 'Revenue',
data: [12000, 19000, 15000, 23000]
}]
}
})
}
})
onUnmounted(() => {
if (chartInstance) {
chartInstance.destroy()
}
})
</script>
<template>
<div class="chart-container">
<canvas ref="chart"></canvas>
</div>
</template>
Integrating libraries like Chart.js demonstrates how refs serve as the bridge between Vue's declarative model and imperative JavaScript APIs, enabling rich data visualizations in modern web applications.
TypeScript Considerations
Vue's IDE support and vue-tsc provide excellent type inference for refs, reducing the need for manual type annotations:
<script setup lang="ts">
import { useTemplateRef, ref } from 'vue'
// TypeScript infers HTMLInputElement automatically
const inputRef = useTemplateRef('my-input')
// For component refs, import the component type
import ChildComponent from './ChildComponent.vue'
const childRef = useTemplateRef('child')
// Manual type assertion when needed for specific DOM types
const canvasRef = useTemplateRef('canvas') as Ref<HTMLCanvasElement | null>
</script>
For developers working with TypeScript in Vue, understanding refs is essential for type-safe web application development. Our guide on how to use Vue 3 with TypeScript provides deeper insights into combining these powerful technologies for robust, type-safe applications.
Best Practices Summary
Use useTemplateRef() for template refs
The modern Vue 3.5+ API provides better type inference and follows Vue's composition patterns.
Always check .value exists
Template refs are undefined until mounted. Use optional chaining and lifecycle hooks.
Clean up in onUnmounted
When refs access external resources like libraries, properly destroy them to prevent memory leaks.
Prefer props/events over component refs
Component refs create tight coupling. Use Vue's declarative props/events for better maintainability.
Keep refs scoped
Only create refs for elements you actually need to access. Over-refing impacts performance.
Let Vue handle what it can
Refs are an escape hatch. Start with Vue's declarative features and reach for refs only when necessary.
Conclusion
Vue refs provide essential imperative access to DOM elements and component instances when Vue's declarative model isn't sufficient. By understanding template refs, component refs, and when to choose ref() vs reactive(), you can build more sophisticated Vue applications that integrate seamlessly with the broader JavaScript ecosystem.
Mastering refs is a key skill for Vue developers working on complex web applications. They enable integration with third-party libraries, form management, and advanced DOM manipulation while maintaining Vue's reactivity benefits.
For teams building modern web applications, combining Vue's reactivity system with AI-powered development tools can significantly accelerate development cycles and improve code quality.
Remember: refs are an escape hatch for when you need direct DOM access. Start with Vue's declarative features and reach for refs only when necessary.