Vue 3.5's useTemplateRef eliminates template ref headaches: no more type mismatches, silent typos, or verbose component typing. Here's why it's worth the switch.
Abdel Awad
September 10, 2025
Ever spent hours debugging a template ref issue only to find it was a simple typo? I have.
In this article, I will show you many reasons why you should switch over to the new useTemplateRef
function.
Template refs are a powerful feature in Vue.js that allow you to access the DOM elements or components that are rendered in the template and use their API in your setup script.
But up until Vue 3.5, you had to use the generic ref
function to create template refs.
<template>
<div ref="myRef">Hello</div>
</template>
<script setup>
import { ref } from 'vue';
const myRef = ref();
</script>
So far, this looks straightforward. It works well, but let's throw in types into the mix. We want myRef
to be typed.
Since ref
is a generic function, we can pass in the type of the element we want to ref.
const myRef = ref<HTMLDivElement>();
This is the first problem. This is actually not strict. You can bind that ref to anything that is not a div
element and you won't get any errors.
A common issue is when you have a very specific element bound to a ref and during a refactor you change the element to something else and you don't get any errors.
Sometimes that can be catastrophic. Like in this example where you think you are working with a dialog
element but you are actually working with a div
element.
<template>
<div ref="myRef">Hello</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
const myRef = ref<HTMLDialogElement>();
function openDialog() {
// đź’Ł This will not work, the ref is actually a `div`
// The `open` method is not available on `div` elements
// So this will blow up at runtime
myRef.value?.open();
}
</script>
The type safety issues get even messier when you move beyond DOM elements to components. Here's where things get verbose and error-prone.
<template>
<MyComponent ref="myRef" />
</template>
<script setup>
import { ref } from 'vue';
import MyComponent from './MyComponent.vue';
const myRef = ref();
</script>
This works fine until you need types. Then you hit a wall of TypeScript gymnastics that makes you question your life choices.
To properly type a component ref, you need this monstrosity:
const myRef = ref<InstanceType<typeof MyComponent>>();
Let's break down this mess:
typeof MyComponent
gets the component constructor typeInstanceType<...>
extracts the instance type from that constructorref<...>
Every single component ref requires this verbose incantation. No shortcuts, no clean alternatives.
Remember our debugging nightmare from the intro? Here's how those typos happen.
<template>
<div ref="datRef">Hello</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
const disRef = ref<HTMLElement>();
</script>
Did you spot it? You get no errors, no warnings, no nothing. Since refs aren't available until after mounting, you'll defensively write code like this:
if (disRef.value) {
disRef.value.style.color = 'red';
}
// or optional chaining
const currentColor = disRef.value?.style.color ?? 'blue';
While rare during initial development, this mistake becomes common during refactoring and maintenance. A simple find-and-replace can break this fragile connection between variable name and ref attribute.
So it seems all my problems with ref
is centered around its strictness. Or rather, its lack of strictness.
You cannot guarantee using the right type for the ref nor can you guarantee that the ref name is correct and components are just annoying to type.
useTemplateRef
Vue 3.5 brought us useTemplateRef
, a purpose-built solution that eliminates every pain point we just covered. The difference is night and day.
Its usage is exactly the same as ref
, so you can easily migrate to it:
<template>
<div ref="myRef">Hello</div>
</template>
<script setup lang="ts">
import { useTemplateRef } from 'vue';
const myRef = useTemplateRef('myRef');
</script>
While slightly more verbose, it solves every problem we just covered—and then some.
You can still explicitly type the element if needed:
const el = useTemplateRef<HTMLDivElement>('myRef');
But if you are using:
vue-tsc
to type check your codeYou get automatic type inference for ref elements—no manual typing required.
That means that you get type errors for this faulty example:
<template>
<div ref="myRef">Hello</div>
</template>
<script setup lang="ts">
import { useTemplateRef } from 'vue';
const myRef = useTemplateRef('myRef');
function openDialog() {
// ❌ Type error: Property 'open' does not exist on type 'HTMLElement'
myRef.value?.open();
}
</script>
Here, the Vue language service and vue-tsc
will both catch the mismatch between the ref type and the actual element type and catching this during static analysis means you'll spot it early—in your editor, pre-commit hooks, or CI.
Well, this is a two for one. You get the same type inference for components as you do for elements.
That means you don't need to use the InstanceType
utility type and you don't need the typeof
operator.
This works well for components with public APIs defined with defineExpose
.
<script setup>
import { defineExpose } from 'vue';
defineExpose({
doStuff() {
console.log('doing stuff');
}
});
</script>
<template>
<MyComponent ref="myRef" />
</template>
<script setup lang="ts">
import { useTemplateRef } from 'vue';
import MyComponent from './MyComponent.vue';
const myRef = useTemplateRef('myRef');
// âś… This will work, doStuff() is available and typed!
myRef.value?.doStuff();
// ❌ Type error: Property 'doSomethingElse' does not exist!
myRef.value?.doSomethingElse();
</script>
This even works with libraries that define their component APIs properly.
With proper TypeScript support, useTemplateRef
makes typos virtually impossible. There are actually two things to take a look at here, let's take the typo example from before.
Since we have to pass in the ref name to useTemplateRef
, the editor will show you a type error if you pass in the wrong name.
<template>
<div ref="datRef">Hello</div>
</template>
<script setup lang="ts">
import { useTemplateRef } from 'vue'
// ❌ Type error: Property disRef does not exist
const disRef = useTemplateRef('disRef')
</script>
The vue language service will also help your editor by offering auto-completion for the ref name, so you can choose from the available ref
attribute values in the template. No room for typos here.
The second thing is there is no longer a flimsy relationship between the ref variable name and the ref attribute value. You can rename the ref variable all you want, the useTemplateRef
argument is still type checked.
<template>
<div ref="datRef">Hello</div>
</template>
<script setup lang="ts">
import { useTemplateRef } from 'vue'
// âś… This will work, any variable name is fine
const whateverRef = useTemplateRef('datRef')
</script>
Beyond our main complaints, useTemplateRef
also handles arrays elegantly, and it will infer the type automatically as well.
<template>
<ul>
<li v-for="item in list" :key="item" ref="items">{{ item }}</li>
</ul>
</template>
<script setup lang="ts">
import { onMounted, useTemplateRef } from 'vue'
const list = [1, 2, 3, 4, 5];
// ❌ Before: You had to type it manually.
const items = ref<HTMLLIElement[]>([]);
// âś… After: items.value is typed as `HTMLLIElement[]`.
const items = useTemplateRef('items')
onMounted(() => {
// âś… No Errors!
console.log(items.value?.map((item) => item.textContent))
})
</script>
This even works with nested deep elements inside a v-for
loop.
Here's a bonus feature for library authors: If you are building a library or a composable that requires access to a template ref, you can instead pass in ref name as a string and use useTemplateRef
to get the ref.
Previously, libraries either had to give you the ref themselves and you had to bind it to the template, or you had to pass in the ref to the library. There is a bit more simplicity with just passing in strings.
export function useStuff(refName: string) {
const ref = useTemplateRef(refName);
onMounted(() => {
// Do things with the element ref
})
}
useTemplateRef
is a great addition to Vue.js. It is more strict and type safe and the features you get with static analysis are invaluable.
If you're starting a new Vue 3.5+ project, reach for useTemplateRef
by default. If you're maintaining existing code, consider migrating your most critical template refs first. Your future debugging sessions will thank you.
Get the latest news and updates on developer certifications. Content is updated regularly, so please make sure to bookmark this page or sign up to get the latest content directly in your inbox.
Just Use useTemplateRef for Template Refs
Vue 3.5's useTemplateRef eliminates template ref headaches: no more type mismatches, silent typos, or verbose component typing. Here's why it's worth the switch.
September 10, 2025
Abdel Awad
Closures: private state, tiny surface
JavaScript closures give functions memory: private state without globals, safer APIs, and predictable behavior. See what they are, when to use them, and why.
September 8, 2025
Martin Ferret
Component architecture with inputs, model(), and viewChild()
This article explores three distinct approaches to facilitating communication between a parent component and a dialog component in Angular, using a single interactive example centered on opening and closing a dialog.
September 4, 2025
Alain Chautard
We can help you recruit Certified Developers for your organization or project. The team has helped many customers employ suitable resources from a pool of 100s of qualified Developers.
Let us help you get the resources you need.