Skip to content
← All questions
⚡ Intermediate

How do you type template refs in TypeScript?

🔷 TypeScript🧩 Components👁️ Watchers

Template refs start as null and become the DOM element or component instance after mount. In TypeScript, you must account for this with a union type and guard access with optional chaining or lifecycle hooks.

DOM element refs

vue
<script setup lang="ts">
const inputRef = ref<HTMLInputElement | null>(null)

onMounted(() => {
  inputRef.value?.focus()
})
</script>

<template>
  <input ref="inputRef" />
</template>
Open in Vue Playground

The type is HTMLInputElement | null because the ref is null during setup and becomes the element only after the component mounts.

useTemplateRef (Vue 3.5+)

useTemplateRef separates the template ref name from the variable name and improves type inference:

vue
<script setup lang="ts">
const input = useTemplateRef<HTMLInputElement>('my-input')

onMounted(() => {
  input.value?.focus()
})
</script>

<template>
  <input ref="my-input" />
</template>
Open in Vue Playground

The string 'my-input' matches the ref attribute in the template. The variable name input is independent.

Component refs

Use InstanceType<typeof Component> to type a ref to a child component:

vue
<script setup lang="ts">
import ChildForm from './ChildForm.vue'

const formRef = ref<InstanceType<typeof ChildForm> | null>(null)

function submit() {
  formRef.value?.validate()
}
</script>

<template>
  <ChildForm ref="formRef" />
</template>
Open in Vue Playground

The child must expose the method with defineExpose:

vue
<!-- ChildForm.vue -->
<script setup lang="ts">
function validate() {
  // validation logic
  return isValid.value
}

defineExpose({ validate })
</script>
Open in Vue Playground

Without defineExpose, the parent cannot access any of the child's internal state or methods.

Refs with v-if

When an element is behind v-if, the ref becomes null again when the condition is false:

vue
<script setup lang="ts">
const showModal = ref(false)
const modalRef = ref<HTMLDivElement | null>(null)

watch(modalRef, (el) => {
  if (el) {
    el.focus() // element just mounted
  }
})
</script>

<template>
  <div v-if="showModal" ref="modalRef" tabindex="-1">Modal content</div>
</template>
Open in Vue Playground

Always use optional chaining or null checks when the ref target can be conditionally rendered.

Refs with v-for

With v-for, the ref becomes an array. Use a function ref to populate it:

vue
<script setup lang="ts">
const items = ref(['a', 'b', 'c'])
const itemRefs = ref<(HTMLLIElement | null)[]>([])

onMounted(() => {
  itemRefs.value[0]?.focus()
})
</script>

<template>
  <ul>
    <li
      v-for="(item, index) in items"
      :key="item"
      :ref="
        (el) => {
          itemRefs[index] = el as HTMLLIElement
        }
      "
    >
      {{ item }}
    </li>
  </ul>
</template>
Open in Vue Playground

After async operations

If you await something inside onMounted, the component might have unmounted by the time the promise resolves:

ts
onMounted(async () => {
  await fetchData()

  // component could be gone — check before accessing
  if (inputRef.value) {
    inputRef.value.scrollTop = 0
  }
})

Quick reference

ScenarioType
DOM elementref<HTMLDivElement | null>(null)
Input elementref<HTMLInputElement | null>(null)
Canvas elementref<HTMLCanvasElement | null>(null)
Child componentref<InstanceType<typeof MyComponent> | null>(null)
Array from v-forref<(HTMLLIElement | null)[]>([])
useTemplateRef (3.5+)useTemplateRef<HTMLInputElement>('name')

See also: How do template refs work? · Why is a template ref null inside v-if? · What is defineExpose?

References

Released under the MIT License.