Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Template refs

Vue normally manages the DOM itself, so you rarely need elements directly. But some actions exist only on an element: focusing a field, scrolling a list, measuring a size. For this you give the element a ref attribute and get a reference to it in the script — a template ref.

Focusing an input

<!-- src/components/TodoForm.vue -->
<script setup>
import { onMounted, useTemplateRef } from 'vue'
 
const input = useTemplateRef('todo-input')
 
onMounted(() => {
  input.value.focus()
})
</script>
 
<template>
  <input ref="todo-input" placeholder="What needs doing?">
</template>

Use TodoForm in App.vue and open the page — the cursor is already in the field, so you can type without clicking. useTemplateRef appeared in Vue 3.5: it takes the value of the ref attribute and returns an object whose .value holds the DOM element.

When the ref is filled

Before mounting the element does not exist yet, so input.value is null. Access it in onMounted or in event handlers:

<!-- src/components/TodoForm.vue -->
<script setup>
import { ref, useTemplateRef } from 'vue'
 
const emit = defineEmits(['add'])
const text = ref('')
const input = useTemplateRef('todo-input')
 
function submit() {
  if (text.value.trim() === '') return
  emit('add', text.value.trim())
  text.value = ''
  input.value.focus()
}
</script>
 
<template>
  <form @submit.prevent="submit">
    <input ref="todo-input" v-model="text" placeholder="What needs doing?">
    <button>Add</button>
  </form>
</template>

After pressing "Add" the focus returns to the field, and you can type the next task right away.

The older syntax

Before Vue 3.5 a ref was declared with a plain ref named the same as the attribute:

<script setup>
import { ref, onMounted } from 'vue'
 
const todoInput = ref(null)
 
onMounted(() => todoInput.value.focus())
</script>
 
<template>
  <input ref="todoInput">
</template>

This still works, and you will meet it in other people's code.

A ref to a component

The ref attribute can also go on a child component. With <script setup> a component is closed by default: from outside you see only what it exposed with the defineExpose macro:

<!-- src/components/TodoForm.vue -->
<script setup>
// ...the code from the example above
 
function focus() {
  input.value.focus()
}
 
defineExpose({ focus })
</script>

Now a parent that got a ref with useTemplateRef('form') on <TodoForm ref="form" /> can call form.value.focus().

Common mistake

Accessing a ref in top-level <script setup> code:

<script setup>
import { useTemplateRef } from 'vue'
 
const input = useTemplateRef('todo-input')
input.value.focus() // TypeError: Cannot read properties of null
</script>

This code runs before the elements appear on the page. Move such calls into onMounted. If the element is rendered with v-if, check the ref before using it: input.value?.focus().

Thanks for reading!

Donate to the site — we work hard for you.