Template Refs for DOM Access

+15 Mana ✨

Template refs provide direct access to DOM elements and child component instances, essential for DOM manipulation and imperative operations.

Basic DOM Ref

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

// Create a ref with same name as template ref
const inputRef = ref<HTMLInputElement | null>(null)

onMounted(() => {
  // Access the DOM element
  inputRef.value?.focus()
})

function selectAll() {
  inputRef.value?.select()
}
</script>

<template>
  <!-- Connect via ref attribute -->
  <input ref="inputRef" type="text" />
  <button @click="selectAll">Select All</button>
</template>

Important: Refs are null until mounted

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

const divRef = ref<HTMLDivElement | null>(null)

// ❌ null here - component not mounted yet
console.log(divRef.value)  // null

onMounted(() => {
  // ✅ Element exists now
  console.log(divRef.value)  // <div>...</div>
})

// Also works with watchEffect (runs after mount)
watchEffect(() => {
  if (divRef.value) {
    console.log('Div dimensions:', divRef.value.offsetWidth)
  }
})
</script>

Multiple Refs with v-for

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

const items = ref(['A', 'B', 'C'])

// Array of refs
const itemRefs = ref<HTMLLIElement[]>([])

onMounted(() => {
  console.log(itemRefs.value)  // [li, li, li]
  console.log(itemRefs.value.length)  // 3
})
</script>

<template>
  <ul>
    <li v-for="item in items" :key="item" ref="itemRefs">
      {{ item }}
    </li>
  </ul>
</template>

Note: The array order is NOT guaranteed to match source array order.

Function Refs

For more control, use a function:

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

const items = ref(['A', 'B', 'C'])
const itemRefs = ref<Map<string, HTMLElement>>(new Map())

function setItemRef(el: HTMLElement | null, key: string) {
  if (el) {
    itemRefs.value.set(key, el)
  } else {
    itemRefs.value.delete(key)  // Cleanup on unmount
  }
}
</script>

<template>
  <ul>
    <li 
      v-for="item in items" 
      :key="item"
      :ref="(el) => setItemRef(el, item)"
    >
      {{ item }}
    </li>
  </ul>
</template>

Component Refs

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

const childRef = ref<InstanceType<typeof ChildComponent> | null>(null)

onMounted(() => {
  // Access exposed properties/methods
  console.log(childRef.value?.count)
  childRef.value?.increment()
})
</script>

<template>
  <ChildComponent ref="childRef" />
</template>

Child Component with defineExpose

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

const count = ref(0)
const privateData = ref('secret')

function increment() {
  count.value++
}

// Only exposed properties are accessible via ref
defineExpose({
  count,
  increment
})
</script>

Common Use Cases

Focus Management

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

const inputRef = ref<HTMLInputElement | null>(null)
const showInput = ref(false)

async function openAndFocus() {
  showInput.value = true
  await nextTick()  // Wait for DOM update
  inputRef.value?.focus()
}
</script>

<template>
  <button @click="openAndFocus">Add Item</button>
  <input v-if="showInput" ref="inputRef" />
</template>

Scroll Into View

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

const sectionRefs = ref<HTMLElement[]>([])

function scrollToSection(index: number) {
  sectionRefs.value[index]?.scrollIntoView({ 
    behavior: 'smooth',
    block: 'start'
  })
}
</script>

Third-Party Library Integration

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

const listRef = ref<HTMLElement | null>(null)
let sortable: Sortable | null = null

onMounted(() => {
  if (listRef.value) {
    sortable = new Sortable(listRef.value, {
      animation: 150,
      onEnd: handleSort
    })
  }
})

onUnmounted(() => {
  sortable?.destroy()
})
</script>

<template>
  <ul ref="listRef">
    <li v-for="item in items" :key="item.id">{{ item.name }}</li>
  </ul>
</template>

Canvas/Video Access

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

const canvasRef = ref<HTMLCanvasElement | null>(null)

onMounted(() => {
  const ctx = canvasRef.value?.getContext('2d')
  if (ctx) {
    ctx.fillStyle = 'blue'
    ctx.fillRect(0, 0, 100, 100)
  }
})
</script>

<template>
  <canvas ref="canvasRef" width="400" height="300" />
</template>
✓ Completed