Wat is een template ref?
Normaal beschrijf je in Vue wat de UI moet doen, en Vue regelt het zelf. Maar soms heb je directe toegang tot een DOM-element nodig:
- Focus zetten op een input bij het laden
- Scrollen naar een specifiek element
- Een video starten of pauzeren
- Een canvas tekenen
- Een externe library initialiseren (Chart.js, Leaflet, etc.)
Daarvoor gebruik je een template ref: een ref die naar een DOM-element verwijst in plaats van een waarde.
Vue vs React: dit is Vue's tegenhanger van useRef() in React. Zelfde idee, andere syntax. Het verwarrende: in Vue heet het ook gewoon ref() — dezelfde functie die je voor state gebruikt.
De drie stappen
Een template ref opzetten doe je in drie stappen:
<script setup>
import { ref, onMounted } from 'vue'
// 1. Maak een ref met dezelfde naam als het attribute
const inputEl = ref(null)
// 2. Gebruik hem in onMounted
onMounted(() => {
console.log(inputEl.value) // → het <input> DOM-element
inputEl.value.focus()
})
</script>
<template>
<!-- 3. Koppel via het ref attribute -->
<input ref="inputEl" />
</template>
Belangrijk:
- De ref begint op
null— tijdens setup bestaat het DOM-element nog niet - De
ref="inputEl"in de template moet matchen met de naam van je ref-variabele - Pas vanaf
onMountedheeftinputEl.valuehet echte element
Waarom moet het in onMounted?
Een component levensloop:
<script setup>draait — refs worden aangemaakt- De template wordt naar HTML omgezet
- HTML wordt in de pagina gezet → nu pas bestaan de DOM-elementen
onMounteddraait
Tussen stap 1 en 3 is je ref null — er is nog geen DOM-element om naar te verwijzen. Daarom werkt dit niet:
<script setup>
import { ref } from 'vue'
const inputEl = ref(null)
inputEl.value.focus() // ❌ inputEl.value is null
</script>
En dit wel:
<script setup>
import { ref, onMounted } from 'vue'
const inputEl = ref(null)
onMounted(() => {
inputEl.value.focus() // ✅ DOM bestaat nu
})
</script>
Vergeet deze regel niet: alle code die de echte DOM aanraakt moet in onMounted (of een event handler die later draait).
Voorbeeld 1 — Focus op input bij laden
De klassieke use case. Een zoekbalk of login-form: zodra de pagina laadt, kan de gebruiker meteen typen.
<script setup>
import { ref, onMounted } from 'vue'
const searchInput = ref(null)
onMounted(() => {
searchInput.value.focus()
})
</script>
<template>
<input
ref="searchInput"
type="search"
placeholder="Zoek..."
/>
</template>
Focus na een actie
Na het toevoegen van een todo: focus terug op het input:
<script setup>
import { ref } from 'vue'
const newTodo = ref('')
const todos = ref([])
const inputEl = ref(null)
const addTodo = () => {
if (!newTodo.value) return
todos.value.push(newTodo.value)
newTodo.value = ''
// Focus terug op input voor de volgende todo
inputEl.value.focus()
}
</script>
<template>
<form @submit.prevent="addTodo">
<input ref="inputEl" v-model="newTodo" />
<button type="submit">Voeg toe</button>
</form>
<ul>
<li v-for="t in todos">{{ t }}</li>
</ul>
</template>
Wat heb je nu? De gebruiker kan tien todos achter elkaar toevoegen zonder elke keer op het input te klikken. Kleine details als deze maken een app meteen prettiger.
Voorbeeld 2 — Scrollen naar een element
Een chat-app die naar het laatste bericht scrollt, of een knop "Naar boven":
<script setup>
import { ref, onMounted } from 'vue'
const chatBottom = ref(null)
const scrollToBottom = () => {
chatBottom.value.scrollIntoView({ behavior: 'smooth' })
}
onMounted(() => {
scrollToBottom()
})
</script>
<template>
<div class="chat">
<div v-for="msg in messages" :key="msg.id">{{ msg.text }}</div>
<!-- Onzichtbare ankertag onderaan -->
<div ref="chatBottom"></div>
</div>
<button @click="scrollToBottom">Naar onder ↓</button>
</template>
Voorbeeld 3 — Video controleren
Soms moet je een media-element programmatic besturen — bijv. play/pause via een eigen UI:
<script setup>
import { ref } from 'vue'
const videoEl = ref(null)
const isPlaying = ref(false)
const togglePlay = () => {
if (isPlaying.value) {
videoEl.value.pause()
} else {
videoEl.value.play()
}
isPlaying.value = !isPlaying.value
}
</script>
<template>
<video ref="videoEl" src="/movie.mp4"></video>
<button @click="togglePlay">
{{ isPlaying ? '⏸ Pauze' : '▶ Play' }}
</button>
</template>
Refs in een v-for — een array van elementen
Voor een lijst kun je een ref op elk item zetten. Vue verzamelt ze in een array:
<script setup>
import { ref, onMounted } from 'vue'
const itemRefs = ref([])
const items = ['Anna', 'Bram', 'Chloé']
onMounted(() => {
console.log(itemRefs.value) // → array van 3 <li> elementen
itemRefs.value[0].focus() // eerste item
})
</script>
<template>
<ul>
<li
v-for="item in items"
:key="item"
ref="itemRefs"
>
{{ item }}
</li>
</ul>
</template>
Let op: de volgorde van elementen in de array komt overeen met de volgorde in v-for, maar is niet gegarandeerd stabiel bij wijzigingen. Voor de meeste cases is dat geen probleem.
Belangrijke Regels
- Maak een ref met
ref(null)bovenaan je script - Koppel via
ref="naam"in de template — moet matchen met de ref-variabele - Gebruik
.valuein JS, net als bij gewone refs - Code die de DOM aanraakt moet in
onMounted - In een
v-forkrijg je een array van elementen
Veelgemaakte Fouten
Fout — gebruik in <script setup> op top-level:
<script setup>
const inputEl = ref(null)
inputEl.value.focus() // ❌ inputEl.value is null tijdens setup
</script>
Goed — wacht op onMounted:
<script setup>
const inputEl = ref(null)
onMounted(() => inputEl.value.focus()) // ✅
</script>
Fout — naam mismatch:
<script setup>
const inputEl = ref(null)
</script>
<template>
<input ref="input" /> <!-- ❌ "input" ≠ "inputEl" -->
</template>
Goed — exact dezelfde naam:
<input ref="inputEl" /> <!-- ✅ -->
Fout — : (v-bind) voor het ref-attribute:
<input :ref="inputEl" />
<!-- ❌ verwacht een dynamische waarde, niet een naam -->
Goed — gewoon attribute zonder ::
<input ref="inputEl" />
<!-- ✅ "inputEl" als string, Vue koppelt aan de variabele -->