Explain the difference between native DOM events (click/input/submit...) and component events emitted with $emit/emit in Vue. Cover what happens when you write @click on a real element vs on a component tag, how propagation differs (DOM bubbling vs parent-only emits), and the Vue 2 .native vs Vue 3 fallthrough/emits behavior. Explicit emits improve clarity; test propagation and accessibility of custom components.
What is the difference between native and component events in Vue?
Use guided tracks for structured prep, then practice company-specific question sets when you want targeted interview coverage.
Core idea
Native events are browser DOM events fired by real elements (<button>, <input>, ...).
Component events are custom events emitted by a Vue component (emit('eventName', payload)) and received by the parent.
Aspect | Native (DOM) event | Component event (emit) |
|---|---|---|
Where it originates | Browser fires it on a real DOM node | Component code emits it explicitly |
How you listen | On an element: | On a component tag: |
Propagation | Bubbles/captures through the DOM tree | Goes to the direct parent only (no DOM bubbling) |
Payload | Usually a real | Whatever you emit (could be an Event, but often custom data) |
Default behavior | May have defaults (submit navigates, link navigates, etc.) | No browser default; it’s app-level messaging |
Example: native DOM event
<!-- Parent.vue -->
<script setup>
function onNativeClick(e) {
console.log('native click', e.type);
}
</script>
<template>
<button type="button" @click="onNativeClick">Native click</button>
</template>
Example: component event (custom)
The parent listens on the component tag, but it only fires if the child emits it.
<!-- MyButton.vue -->
<script setup>
const emit = defineEmits(['press']);
function handleClick(e) {
emit('press', { originalEvent: e, ts: Date.now() });
}
</script>
<template>
<button type="button" @click="handleClick">
<slot>Press</slot>
</button>
</template>
<!-- Parent.vue -->
<script setup>
function onPress(payload) {
console.log('component event press', payload.ts);
}
</script>
<template>
<MyButton @press="onPress">Component press</MyButton>
</template>
The confusing part: @click on a component
When you write <MyButton @click=... />, it does not automatically mean “listen to the internal DOM click”. It depends on Vue version and how the component is authored.
Case | What @click means | How to make it work intentionally |
|---|---|---|
Vue 2: | Listens for a component-emitted | Emit it from the child: |
Vue 2: | Attach a native listener to the component’s root DOM element | Use |
Vue 3: listener fallthrough (single-root component) | If the event name is not declared in | Rely on fallthrough only when you really want “wrapper behaves like a DOM element”; otherwise use explicit custom events |
Vue 3: declared in |
| Declare + emit: |
Gotcha | Why it happens | Rule of thumb |
|---|---|---|
Assuming DOM bubbling for component events | Custom emits don’t bubble through DOM; only parent receives it | If grandparents need it, re-emit upward or use a store |
Using DOM event modifiers on custom events (.stop/.prevent) | Those call | Use modifiers mainly for DOM events; for custom events, design payloads and logic explicitly |
Naming a component event the same as a native event ('click') | Can be valid but easily confuses “is this DOM or emitted?” (plus fallthrough rules in Vue 3) | Prefer semantic names: |
Not declaring | Vue can’t validate emitted events and listeners may fall through to DOM unintentionally | Declare |
Interview-ready takeaway
Native event = browser event on a real element (bubbles in DOM).
Component event = explicit emit from child to parent (no DOM bubbling).
On a component tag, @event primarily means “listen for an emitted event”, except Vue 3 can also treat undeclared listeners as fallthrough attributes to the root element.
Practical scenario
A custom BaseButton should emit click to parent components while still handling native DOM events internally.
Common pitfalls
- Using native events on component tags without emitting.
- Relying on Vue 2
.nativebehavior in Vue 3. - Not declaring
emits, which hides event contracts.
Explicit emits add clarity but more boilerplate. Test both DOM and emitted events with unit tests.