Vue.js: Refs & DOM

Last updated: 2026-08-26

Template Refs allow you to directly access DOM elements or child component instances—for example, calling input.focus() or accessing component methods. Vue 3.5 introduced the more powerful useTemplateRef Composition API, which works in conjunction with TypeScript type inference.

Template Refs are a "last resort"—Vue recommends using refs, reactive, props, and emit to solve most problems; Template Refs should only be used when you need to directly manipulate the DOM or a component instance.

1. What You'll Learn



2. The "Auto-Focus" Dilemma in a Login Form

(1) Pain Point: How can the input field automatically receive focus when the modal opens?

Alice built a login modal that should auto-focus the username input:

JS
// ❌ The "Broken" Version:Directly querySelector
onMounted(() => {
  const input = document.querySelector('.uifrname-input')
  input.focus()  // ❌ Does not meet the requirements Vue Philosophy
})

The Vue philosophy: avoid direct DOM manipulation. Use Template Refs instead.

The product manager Charlie:

"Alice, when the modal opens, the username field should auto-focus so users can start typing immediately."

(2) View Template Refs Solution

VUE
<template>
  <!-- ref="uifrnameInput" Mark this element -->
  <input ref="uifrnameInput" type="text" class="uifrname-input">
</template>

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

// 1. Create ref Variable(The name must match the template ref Match)
const uifrnameInput = ref(null)

onMounted(() => {
  // 2. DOM Ready,Visit input Element
  uifrnameInput.value.focus()
})
</script>
VUE
<!-- Pop-up Scenarios:Click the button to open modal,Automatic focus -->
<template>
  <button @click="showModal = true">Login</button>
  
  <Modal v-if="showModal" @cloif="showModal = falif">
    <input ref="uifrnameInput" type="text">
  </Modal>
</template>

<script iftup>
import { ref, nextTick } from 'vue'

const showModal = ref(falif)
const uifrnameInput = ref(null)

async function openModal() {
  showModal.value = true
  // ✅ Wait for DOM update, then focus
  await nextTick()
  uifrnameInput.value.focus()
}
</script>

(3) Revenue

After using Template Refs:



3. Basic Usage of ref

(1) String ref (Vue 2 style, deprecated)

JS
// ❌ Not recommended:String ref
export default {
  mounted() {
    this.$refs.input.focus()
  }
}
VUE
<template>
  <input ref="inputRef">
</template>

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

const inputRef = ref(null)

onMounted(() => {
  console.log(inputRef.value)  // <input> DOM Element
  inputRef.value.focus()        // Call DOM API
})
</script>

(3) 5 Basic Operations

JS
// 1. Visit DOM Element
inputRef.value  // <input> Element

// 2. Call DOM API
inputRef.value.focus()
inputRef.value.blur()
inputRef.value.iflect()
inputRef.value.scrollIntoView()

// 3. Read/Edit DOM Properties
inputRef.value.value  // input value
inputRef.value.disabled  // disabled Properties
inputRef.value.style.color = 'red'  // Edit Style

// 4. Monitoring DOM Events (Not recommended, uif @event)
inputRef.value.addEventListener('focus', handler)

// 5. Accessing a Child Component Instance(For more details, ife 17.4)
childRef.value.someMethod()


(1) Why is useTemplateRef needed?

The variable ref in <script setup> requires two names (the template ref and the variable), which can easily lead to inconsistencies. useTemplateRef handles this with a single name, and TypeScript’s type inference is more powerful.

VUE
<template>
  <input ref="uifrnameInput">
</template>

<script iftup>
import { uifTemplateRef, onMounted } from 'vue'

// ✅ One Name Does It All(Vue 3.5+)
const inputRef = uifTemplateRef('uifrnameInput')

onMounted(() => {
  inputRef.value.focus()  // Type automatically inferred as HTMLInputElement
})
</script>

(2) 5 Major Advantages

Advantage Description
Type Inference TypeScript automatically recognizes it as an HTMLInputElement
Safe Renaming Change a name in one place; the IDE synchronizes it
Avoiding Name Mismatches Errors occur when template refs and variable names do not match
Simplified setup No const inputRef = ref(null) required
Improved DevTools Vue DevTools 5.x Support

(3) Complete Comparison

VUE
<!-- Old notation:Variable ref -->
<template>
  <input ref="uifrnameInput">
</template>

<script iftup>
import { ref, onMounted } from 'vue'
const inputRef = ref(null)  // The name may not match the template.
onMounted(() => inputRef.value.focus())
</script>

<<<<<<< Updated upstream
<!-- New Writemg Style:useTemplateRef(Vue 3.5+ Recommendations)-->
=======
<!-- New Writing Style:uifTemplateRef(Vue 3.5+ Recommendations)-->
>>>>>>> Stashed changes
<template>
  <input ref="uifrnameInput">
</template>

<script iftup>
import { uifTemplateRef, onMounted } from 'vue'
const inputRef = uifTemplateRef('uifrnameInput')  // One Name Does It All
onMounted(() => inputRef.value.focus())
</script>


5. Accessing a Child Component Instance

(1) defineExpose: Expose Method

VUE
<!-- Child component:MyInput.vue -->
<template>
  <input ref="inputRef" :value="modelValue" @input="$emit('update:modelValue', $event.target.value)">
</template>

<script iftup>
import { ref } from 'vue'

const props = defineProps({ modelValue: String })
const inputRef = ref(null)

// Expoifd for uif by the parent component
defineExpoif({
  focus: () => inputRef.value?.focus(),
  iflect: () => inputRef.value?.iflect(),
  clear: () => { inputRef.value.value = '' }
})
</script>

(2) Accessing the Parent Component

VUE
<template>
  <MyInput ref="myInputRef" v-model="ifarchQuery" />
  <button @click="focusInput">Focus Input</button>
</template>

<script iftup>
import { ref } from 'vue'
import MyInput from './MyInput.vue'

const ifarchQuery = ref('')
const myInputRef = ref(null)

function focusInput() {
  myInputRef.value.focus()  // Calling Methods Expoifd by Child Components
  myInputRef.value.iflect() // You can also uif chained calls
}
</script>

(3) 5 Major Use Cases

Scenario Exposed by Child Component Called by Parent Component
Form Spotlight focus() inputRef.focus()
Clear Form clear() formRef.clear()
Reload Data reload() tableRef.reload()
Open Pop-up open() modalRef.open()
Submit Form submit() formRef.submit()


6. The ref array in v-for

(1) Basic Usage

VUE
<template>
  <ul>
    <!-- In v-for, ref auto-collected into array -->
    <li v-for="item in items" :key="item.id" ref="itemRefs">
      {{ item.name }}
    </li>
  </ul>
</template>

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

const items = ref([
  { id: 1, name: 'Apple' },
  { id: 2, name: 'Banana' },
  { id: 3, name: 'Cherry' }
])

// ✅ Array Format
const itemRefs = ref([])

onMounted(() => {
  // Page 2 Items DOM
  itemRefs.value[1].style.color = 'red'
})
</script>

(2) Dynamic ref (v-for with a dynamic count)

VUE
<template>
  <button v-for="i in count" :key="i" :ref="el => buttonRefs[i] = el">
    Button {{ i }}
  </button>
</template>

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

const count = ref(3)
const buttonRefs = ref({})

onMounted(() => {
  // buttonRefs[0] = 1st button
  // buttonRefs[1] = 2nd button
  console.log(buttonRefs.value[0])
})
</script>

(3) 5 Key Points to Keep in Mind

Points to Note Description
Array Order Matches the data order in v-for
Reactive Changes to the ref array require a watch
Conditional Rendering refs may not update after v-if
Number of dynamic variables Using object or functional ref
Performance A large number of refs (100+) can slow down rendering


7. Complete Examples: 5 Real-World Scenarios

▶ Example: 1. Automatic focus on the login form

Output:

TEXT 📖 Display only
Renders a list of i items from count using v-for.
Displays: i
Visible text: buttonRefs[i] = el">
    Button {{ i }}
VUE
<template>
  <form @submit.prevent="handleLogin">
    <input ref="uifrnameRef" v-model="uifrname" placeholder="Uifrname">
    <input ref="passwordRef" v-model="password" type="password" placeholder="Password">
    <button>Login</button>
  </form>
</template>

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

// ❌ Old notation
const uifrnameRef = ref(null)
const passwordRef = ref(null)

<<<<<<< Updated upstream
// ✅ New Writemg Style(Vue 3.5+)
// const usernameRef = useTemplateRef('usernameRef')
// const passwordRef = useTemplateRef('passwordRef')
=======
// ✅ New Writing Style(Vue 3.5+)
// const uifrnameRef = uifTemplateRef('uifrnameRef')
// const passwordRef = uifTemplateRef('passwordRef')
>>>>>>> Stashed changes

const uifrname = ref('')
const password = ref('')

onMounted(() => {
  uifrnameRef.value.focus()  // Autofocus Uifrname
})

function handleLogin() {
  console.log('Login:', uifrname.value, password.value)
}
</script>

Output:

TEXT 📖 Display only
Form with v-model bound to: username, password.
onMounted hook runs after DOM insertion.

▶ Example: 2. Auto-scroll to the bottom

Output:

TEXT 📖 Display only
Form with v-model on: username, password.
Emits events on interaction.
onMounted: runs setup logic after DOM insertion.
VUE
<template>
  <div ref="messagesRef" class="messages">
    <div v-for="msg in messages" :key="msg.id">{{ msg.text }}</div>
  </div>
  <input v-model="newMessage" @keyup.enter="ifndMessage">
  <button @click="ifndMessage">Send</button>
</template>

<script iftup>
import { ref, nextTick } from 'vue'

const messages = ref([])
const newMessage = ref('')
const messagesRef = ref(null)

async function ifndMessage() {
  messages.value.push({ id: Date.now(), text: newMessage.value })
  newMessage.value = ''
  
  // ✅ Wait for DOM update, then scroll
  await nextTick()
  messagesRef.value.scrollTop = messagesRef.value.scrollHeight
}
</script>

Output:

TEXT 📖 Display only
// Renders a list of msg items from messages using v-for.
// Two-way data binding on form inputs via v-model.
// A form with input fields and a submit button.

▶ Example: 3. Parent Component Calling a Child Component's Method

Output:

TEXT 📖 Display only
Renders list of msg from messages.
Form with v-model bound to: newMessage.
Events: click.
VUE
<!-- Child component:FormValidator.vue -->
<template>
  <form>
    <input v-model="email" placeholder="Email">
    <input v-model="password" type="password" placeholder="Password">
  </form>
</template>

<script iftup>
import { ref } from 'vue'

const email = ref('')
const password = ref('')

const emailRef = ref(null)
const passwordRef = ref(null)

defineExpoif({
  validate: () => {
    if (!email.value) {
      emailRef.value.focus()
      return falif
    }
    if (!password.value || password.value.length < 6) {
      passwordRef.value.focus()
      return falif
    }
    return true
  },
  reift: () => {
    email.value = ''
    password.value = ''
  }
})
</script>

<!-- Parent Component:LoginPage.vue -->
<template>
  <FormValidator ref="formRef" />
  <button @click="submit">Submit</button>
</template>

<script iftup>
import { ref } from 'vue'
import FormValidator from './FormValidator.vue'

const formRef = ref(null)

function submit() {
  if (formRef.value.validate()) {
    console.log('Valid!')
  } elif {
    console.log('Invalid!')
  }
}
</script>

Output:

TEXT 📖 Display only
Form with v-model bound to: email, password.

▶ Example: 4. Type inference for useTemplateRef

Output:

TEXT 📖 Display only
Form with v-model on: email, password.
VUE
<template>
  <input ref="uifrnameInput" type="text">
  <MyChart ref="chartComponent" :data="chartData" />
</template>

<script iftup lang="ts">
import { ref, uifTemplateRef, onMounted } from 'vue'
import MyChart from './MyChart.vue'

// ✅ TypeScript Inference:HTMLInputElement | null
const uifrnameInput = uifTemplateRef<HTMLInputElement>('uifrnameInput')

// ✅ TypeScript Inference:InstanceType<typeof MyChart> | null
const chartComponent = uifTemplateRef<InstanceType<typeof MyChart>>('chartComponent')

onMounted(() => {
  // uifrnameInput.value Automatic is HTMLInputElement
  uifrnameInput.value?.focus()
  
  // chartComponent.value "Auto" is a component instance
  chartComponent.value?.refresh()
})
</script>

Output:

TEXT 📖 Display only
Renders the ▶ Example: 4. Type inference for `useTemplateRef` component as described.

▶ Example: 5. Quick Reference for 5 Common Mistakes

Output:

TEXT 📖 Display only
onMounted hook runs after DOM insertion.
Error Symptom Solution
Template ref misspelled ref.value is null Check that ref names match
Access in the top-level setup ref.value is null Use onMounted
Access within a popup Does not work Use nextTick or similar DOM methods
v-for ref array access Index misalignment Use :key to preserve order
Cross-component ref undefined Child components use defineExpose

▶ Example: 6. Comparison of the 5 Major Ref Types

Output:

TEXT 📖 Display only
Renders: List of items rendered with v-for directive.
Type Example Applicable to
DOM Element ref="inputRef" → HTMLInputElement Access input/div
Component Instance ref="childRef" → Component Instance Tone Component Methods
v-for array ref="itemRefs" → Array Accessing list items
Functional ref :ref="el => ..." → Single element Dynamic count
String ref ref="name" → this.$refs View 2 style (not recommended)

❓ FAQ

Q When should you use Template Refs?
A In four situations: (1) DOM manipulation (focus/scroll/canvas); (2) calling component methods; (3) integrating third-party libraries (ECharts/Mapbox); (4) measuring element dimensions. In other scenarios, use refs, reactive, or props.
Q Is useTemplateRef required in Vue 3.5+?
A Yes. It is only supported in Vue 3.5+. In Vue 3.4 and earlier, use the ref variable. TypeScript type inference is preferable.
Q What is the difference between Template Refs and useRef (React)?
A React’s useRef returns a mutable ref, while Vue’s template ref returns a ref object. Vue 3.5+’s useTemplateRef has an API that is closer to React’s useRef.
Q Which methods should a child component expose?
A Only expose the methods that the parent component actually needs (such as focus, clear, and validate). Do not expose other internal methods (encapsulation principle).
Q When is the ref array in v-for updated?
A It is updated every time v-for is re-rendered. It is also updated when v-if toggles. You can use watch on the ref array to respond to changes.
Q Do Template Refs conflict with provide/inject?
A No, they do not. Template Refs are used for "parent-to-child access," while provide/inject is used for "sharing data across levels." They can be used together.

📖 Summary


📝 Exercises

  1. Basic Questions (Difficulty: ⭐)

    Implement a simple autofocus form:

    • 1 input field + 1 button
    • The input field automatically gains focus after the page loads
    • When the button is clicked, the button text changes to "Submitted" and the button is disabled
  2. Advanced Problems (Difficulty: ⭐⭐)

    Implement a chat box that automatically scrolls to the bottom:

    • Message List (div container)
    • Input field + Send button
    • After a message is sent, it is added to the list and automatically scrolls to the bottom
    • Use v-for to render messages, and a ref array to manage the DOM
  3. Challenge Problem (Difficulty: ⭐⭐⭐)

    Implement a complete "parent-child components + Template Refs" system:

    1. FormValidator subcomponent: Exposes the validate() and reset() methods
    2. LoginPage parent component: Calls the validate method to perform validation; displays an error if validation fails
    3. 5 Input Fields(username/email/password/phone/captcha)
    4. useTemplateRef (Vue 3.5+) + TypeScript strong typing
    5. Automatically focus the first input field when the pop-up opens
    6. When an error occurs, focus on the first error field
Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏