Suggest an editImprove this articleRefine the answer for “Template refs in Vue.js”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**Template refs** in Vue.js give you direct access to DOM elements or child component instances. Refs are `null` before mount and available inside `onMounted`. ```vue const inputRef = ref(null) onMounted(() => { inputRef.value?.focus() }) // <input ref="inputRef" /> ``` **Key:** use refs for focus, scroll, canvas, or third-party library setup, not as a replacement for reactive data.Shown above the full answer for quick recall.Answer (EN)Image**Template refs** in Vue.js give you direct access to DOM elements or child component instances from JavaScript. ## Theory ### TL;DR - A template ref is a `ref()` variable that Vue populates with the actual DOM node (or component instance) after the component mounts - The ref value is `null` until the component mounts - Inside `<script setup>`, child components expose nothing by default - they need `defineExpose` to share methods or data with a parent - Use refs for tasks Vue reactivity can't handle on its own: focus, scroll, canvas, initializing third-party libraries - Reactive data solves most things. Refs are for the exceptions. ### Quick example ```vue <script setup> import { ref, onMounted } from 'vue' const inputRef = ref(null) onMounted(() => { // null before mount, HTMLInputElement after inputRef.value?.focus() }) </script> <template> <input ref="inputRef" type="text" /> </template> ``` The string `"inputRef"` in the template matches the variable name in `<script setup>`. Vue connects them automatically. ### When to use template refs Refs make sense when Vue's data model can't solve the problem on its own: - Focus an input on mount or after a user action - Scroll to a specific element (`scrollIntoView`) - Read element dimensions (`getBoundingClientRect`, `offsetHeight`) - Draw on canvas (`getContext('2d')`) - Initialize a third-party library (Chart.js, D3) that needs a real DOM node If you can do it with `v-model`, `:class`, or computed data, do it that way instead. ### Component refs and defineExpose When you put `ref` on a child component, you get its public instance. But `<script setup>` components expose nothing by default. The child must declare what the parent can see: ```vue <!-- ChildComponent.vue --> <script setup> import { ref } from 'vue' const count = ref(0) function reset() { count.value = 0 } defineExpose({ count, reset }) </script> ``` ```vue <!-- Parent.vue --> <script setup> import { ref } from 'vue' import ChildComponent from './ChildComponent.vue' const childRef = ref(null) function handleReset() { childRef.value?.reset() } </script> <template> <ChildComponent ref="childRef" /> <button @click="handleReset">Reset</button> </template> ``` Without `defineExpose`, `childRef.value` is an empty proxy. Calling any method on it does nothing. ### Refs inside v-for When `ref` sits on an element inside `v-for`, the ref variable becomes an array: ```vue <script setup> import { ref, onMounted } from 'vue' const listRefs = ref([]) onMounted(() => { listRefs.value[0]?.scrollIntoView() }) </script> <template> <li v-for="item in items" :key="item.id" ref="listRefs"> {{ item.name }} </li> </template> ``` The array order follows DOM order, not the order items were added. ### Common mistakes **Accessing ref.value before mount.** The ref is `null` outside `onMounted` or a later lifecycle hook. Most bugs here come from calling `.focus()` directly in the component body. ```js // Wrong - ref is null here const inputRef = ref(null) inputRef.value.focus() // TypeError: Cannot read properties of null // Right onMounted(() => { inputRef.value?.focus() }) ``` **Forgetting that v-if resets the ref.** When `v-if` evaluates to `false`, the element is removed from the DOM and the ref returns to `null`. When the condition becomes `true` again, you start a new mount cycle. **Missing defineExpose on the child.** You attach a ref to a child component, try to call a method, and get nothing back. The child needs `defineExpose({ methodName })`. **Reading DOM state manually when reactivity would work.** Grabbing `inputRef.value.value` to read input text and updating it directly - that is what `v-model` is for. ### Follow-up questions **Q:** Why is a template ref `null` before mount? **A:** Vue builds the virtual DOM tree first, then commits it to the real DOM during the mount phase. Before that commit, there is no actual element to point to. **Q:** What is the difference between `ref="inputRef"` and `:ref="fn"`? **A:** The string version binds to the variable named `inputRef` in `<script setup>`. The bound version accepts a function that receives the element directly, which is useful for storing multiple refs in a Map. **Q:** Can you use template refs with the Options API? **A:** Yes. In Options API you access refs via `this.$refs.inputRef`. The behavior is the same - `null` before mount, populated after. **Q:** What happens to a ref inside `v-if` when the condition toggles? **A:** When the condition becomes `false`, Vue unmounts the element and sets the ref to `null`. When it becomes `true` again, Vue mounts a fresh element and repopulates the ref. ## Examples ### Focus management on form mount A login form that focuses the email field immediately after mounting. ```vue <script setup> import { ref, onMounted } from 'vue' const emailRef = ref(null) onMounted(() => { emailRef.value?.focus() }) </script> <template> <form> <input ref="emailRef" type="email" placeholder="Email" /> <input type="password" placeholder="Password" /> <button type="submit">Log in</button> </form> </template> ``` The `?.` optional chaining guards against the edge case where the component unmounts before `onMounted` fires. ### Initializing a chart on a canvas element Third-party libraries like Chart.js need a real DOM node. Passing a reactive variable won't work here. ```vue <script setup> import { ref, onMounted, onUnmounted } from 'vue' import Chart from 'chart.js/auto' const canvasRef = ref(null) let chart = null onMounted(() => { if (!canvasRef.value) return chart = new Chart(canvasRef.value, { type: 'bar', data: { labels: ['Jan', 'Feb', 'Mar'], datasets: [{ label: 'Sales', data: [12, 19, 8] }] } }) }) onUnmounted(() => { chart?.destroy() // prevent memory leaks }) </script> <template> <canvas ref="canvasRef" width="400" height="200" /> </template> ``` I've seen teams try to make Chart.js work with reactive state alone. It never does. The canvas needs a real node, and a template ref is the only clean way to get there.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.