Skip to content
← All questions
⚡ Intermediate

How do template refs work?

🧩 Components🔗 Composition API

Template refs give you direct access to a DOM element or child component instance. You add a ref attribute in the template and read the corresponding value in script after the component mounts. See the Template Refs guide for full details.

Legacy pattern (before Vue 3.5) ​

The variable name in script must match the ref attribute exactly. If they don't match, the ref is silently null with no warning.

vue
<script setup>
import { ref, onMounted } from 'vue'

const inputEl = (ref < HTMLInputElement) | (null > null)

onMounted(() => {
  inputEl.value?.focus()
})
</script>

<template>
  <!-- name MUST be "inputEl" to match the variable -->
  <input ref="inputEl" />
</template>
Open in Vue Playground

The fragile part: rename the variable during refactoring and the connection breaks silently.

useTemplateRef (Vue 3.5+) ​

useTemplateRef decouples the variable name from the ref attribute. The string argument is the connection point, and the variable can be called anything.

vue
<script setup>
import { useTemplateRef, onMounted } from 'vue'

const searchInput = useTemplateRef('search-box')

onMounted(() => {
  searchInput.value?.focus()
})
</script>

<template>
  <input ref="search-box" type="search" />
</template>
Open in Vue Playground

Benefits: IDE auto-completion for ref names, better TypeScript inference, and typos cause visible errors instead of silent nulls.

Refs with v-for ​

When used inside v-for, the ref is automatically populated with an array of elements.

Caveat: The ref array does not guarantee the same order as the source array. If you need to match elements to source items by index, use a different strategy such as a Map keyed by item ID or a function ref.

vue
<script setup>
import { useTemplateRef, onMounted } from 'vue'

const itemRefs = useTemplateRef<HTMLLIElement[]>('items')

onMounted(() => {
  itemRefs.value?.forEach(el => {
    console.log(el.textContent)
  })
})
</script>

<template>
  <ul>
    <li v-for="item in items" ref="items" :key="item.id">
      {{ item.text }}
    </li>
  </ul>
</template>
Open in Vue Playground

ref() pattern (before Vue 3.5) ​

vue
<script setup>
import { ref, onMounted } from 'vue'

const itemRefs = ref<HTMLLIElement[]>([])

onMounted(() => {
  itemRefs.value.forEach(el => {
    console.log(el.textContent)
  })
})
</script>

<template>
  <ul>
    <li v-for="item in items" ref="itemRefs" :key="item.id">
      {{ item.text }}
    </li>
  </ul>
</template>
Open in Vue Playground

Refs on components ​

A template ref on a child component gives you the component's public instance, not a DOM element. With <script setup>, the child exposes nothing by default. You need defineExpose to make properties accessible.

vue
<!-- ChildForm.vue -->
<script setup>
import { ref } from 'vue'

const formData = ref({ name: '' })

function validate() {
  return formData.value.name.length > 0
}

defineExpose({ validate })
</script>
Open in Vue Playground
vue
<!-- Parent.vue -->
<script setup>
import { useTemplateRef } from 'vue'

const formRef = useTemplateRef('child-form')

function submit() {
  if (formRef.value?.validate()) {
    // proceed
  }
}
</script>

<template>
  <ChildForm ref="child-form" />
  <button @click="submit">Submit</button>
</template>
Open in Vue Playground

When to use template refs ​

SituationUse a ref?
Focus an input, scroll to elementYes
Integrate a non-Vue library (chart, map)Yes
Read element dimensionsYes
Change text or toggle classesNo, use reactive data and bindings
Listen to eventsNo, use @event

See also: What is defineExpose and when is it needed? · Can you use await directly in script setup?

References ​

Released under the MIT License.