Chapter 60: Vue $el Object
The $el object (also written as this.$el in Options API or accessed via template refs in Composition API)
This is not something you use every day in modern Vue 3 code — but understanding it deeply will make you much better at debugging, integrating third-party libraries, writing custom directives, and understanding how Vue actually talks to the real DOM.
1. What exactly is $el? (Very clear mental model)
$el is Vue’s name for the real, live DOM element (or root DOM fragment) that this component instance has rendered into the browser.
In other words:
- You write a blueprint in <template>
- Vue compiles it and mounts it to the DOM
- The resulting physical DOM node(s) that now exist in the browser = this component’s $el
Important facts right away:
- $el only exists after the component has mounted (i.e. after onMounted / mounted hook)
- Before mount → $el is null or undefined
- After unmount → $el becomes null again
- $el is read-only — you never assign to it
- In Vue 3 with fragments (multiple root nodes) → $el can be a comment node or fragment, but most often it’s still the first real element
2. Where do you see $el in real life?
You almost never write $el in modern <script setup> code — but you see it in these situations:
- Vue Devtools — select any component → “$el” tab shows the exact DOM node(s)
- Template refs — ref=”myDiv” → myDiv.value is basically that element’s version of $el
- Options API / legacy code — this.$el is still very common in old projects
- Custom directives — many directives receive el (which is the same as $el for that element)
- Third-party library integration — libraries often need the real DOM node → you give them this.$el or ref.value
- Debugging weird rendering bugs — “Why is this element not updating?” → inspect $el
3. Modern Vue 3 (<script setup>) – You Almost Never Use $el Directly
In Composition API + <script setup>, you never get a $el object automatically.
Instead you use template refs to get exactly what you need:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
<template> <div ref="rootEl" class="container"> <input ref="inputEl" type="text" placeholder="Type here" /> </div> </template> <script setup lang="ts"> import { ref, onMounted } from 'vue' const rootEl = ref<HTMLElement | null>(null) const inputEl = ref<HTMLInputElement | null>(null) onMounted(() => { console.log('Root element:', rootEl.value) // → <div class="container"> console.log('Input element:', inputEl.value) // → <input type="text"> // Real usage: focus input inputEl.value?.focus() // Measure size if (rootEl.value) { const rect = rootEl.value.getBoundingClientRect() console.log('Component width:', rect.width) } }) </script> |
→ No $el needed — you just create refs where you actually need DOM access.
4. Legacy / Options API – Where $el Is Still Very Common
In old-style code or when you see legacy projects:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
<template> <div class="container"> <p>Some content</p> </div> </template> <script> export default { mounted() { console.log(this.$el) // → the <div class="container"> element console.log(this.$el.className) // "container" // Real usage: add a class dynamically this.$el.classList.add('highlight') // Or integrate third-party library new SomeLibrary(this.$el) } } </script> |
Important differences in Vue 3 vs Vue 2
- Vue 2 → $el was always a single element (single root requirement)
- Vue 3 → with fragments (multiple root nodes), $el can be a comment node or fragment anchor
- In practice → most components still have one root → $el is usually the root <div>
5. When You Actually Need $el / Template Refs in 2026
Real cases where you reach for it:
- Auto-focus input on mount
- Scroll to element (chat last message, jump to section)
- Measure element (custom tooltip position, popover, sticky header)
- Third-party library integration (Chart.js, Mapbox, Swiper, Video.js, Masonry grid…)
- Canvas / WebGL → el.getContext(‘2d’)
- Call method on child component → via defineExpose + ref
- Fix z-index / stacking context issues — get root element and adjust styles
- Debugging — “Why is this element’s style not what I expect?” → inspect $el or ref.value
6. Quick Summary Table – $el vs Template Refs (2026)
| Question | $el (Options API / legacy) | Template refs (Composition API / modern) | What you should use in 2026 |
|---|---|---|---|
| How to access? | this.$el | ref=”myEl” → myEl.value | Template refs |
| Exists before mount? | No | No (null) | — |
| TypeScript friendly? | Poor | Excellent (ref<HTMLElement |
null>) |
| Works with fragments? | Sometimes a comment node | You control exactly which element | Template refs |
| Used in new code? | Almost never | Daily (focus, scroll, 3rd-party libs…) | Template refs |
| Seen in Devtools? | Yes (“$el” tab) | Yes (under “refs” section) | — |
Final 2026 Advice from Real Projects
- In new code → you will almost never write this.$el — just use template refs
- When you see $el in code → it means someone is using Options API (legacy style)
- When debugging → look at Vue Devtools → Elements tab or Refs tab — you see the same thing as $el
- Never teach beginners $el as something they should use — teach template refs
- For third-party libraries — template refs are almost always the correct integration point
Your mini practice:
- Create a component with an input → use template ref + onMounted to auto-focus it
- Add a long scrollable list → use ref on last item + button to scroll to bottom
- Open Vue Devtools → select the component → compare “$el” tab vs “Refs” tab
Any part confusing? Want to see:
- Template refs + <Transition> for animated focus?
- Accessing child component methods with TypeScript?
- Common bug with v-if + ref?
- Real Chart.js or Google Maps integration using refs?
Just tell me — we’ll build the next practical example together 🚀
