Difference between event.target and event.currenttarget in JavaScript
event.target is the element that actually fired the event. event.currentTarget is the element that holds the event handler.
Theory
TL;DR
event.targetis where the event started (the button you clicked inside a div).event.currentTargetis where youraddEventListenerlives (the div itself).- During bubbling,
targetstays fixed;currentTargetchanges with each handler. - Need to know what was clicked? Use
target. Need the element with the handler? UsecurrentTarget. - For event delegation on dynamic lists,
targetis what you want.
Quick example
<div id="parent" style="padding: 20px; background: lightblue;">
<button id="child">Click me</button>
</div>const parent = document.getElementById('parent');
parent.addEventListener('click', (event) => {
console.log(event.target.id); // "child" — the button was clicked
console.log(event.currentTarget.id); // "parent" — handler lives here
});Click the button: target points to the button, currentTarget points to the div. The handler is on the div, but the click happened on the button inside it.
Key difference
When an event bubbles up the DOM, event.target stays locked to the original element where the event fired. event.currentTarget updates at every step and always reflects the element whose handler is currently running. So if you have nested divs with handlers on each, target is the same in all of them, but currentTarget is different in each one.
When to use
- Dynamic children (event delegation): use
target. Attach one listener to a parent<ul>and checke.targetto find which<li>was clicked. Works even for items added after page load. - Handler's own element: use
currentTarget. Toggling a class or reading dimensions of the element that owns the handler. - More than 10 dynamic children: delegation with
targetsaves attaching individual listeners to each one. - Single element with a handler:
currentTargetis predictable and needs no extra checks.
Comparison table
event.target | event.currentTarget | |
|---|---|---|
| Points to | Element that fired the event | Element with the handler attached |
| During bubbling | Fixed (does not change) | Updates at each handler |
Equals this in handler? | Not always | Yes (non-arrow functions) |
| Use for | Event delegation, detecting source | Operating on the handler's own element |
How the browser sets these
The browser dispatches events following the DOM Level 2/3 spec: capture phase down, target phase, bubble phase up. Each time a handler runs, the engine sets currentTarget to the element that registered that specific listener. target is set once at dispatch and never changed again. That is why after stopPropagation(), target is still the original element.
Common mistakes
Mistake: styling target when you mean the handler's element
// Handler on div; user clicks a nested img inside the div
elem.addEventListener('click', (e) => {
e.target.style.background = 'red'; // Paints the img, not the div
});Fix: e.currentTarget.style.background = 'red'.
Mistake: toggling a class on target without a guard
parent.addEventListener('click', (e) => {
e.target.classList.toggle('active'); // Toggles on any child: span, p, img...
});Fix: add a check first. if (e.target.matches('button')) e.currentTarget.classList.toggle('active').
Mistake: assuming target === currentTarget always
They are only equal when you click directly on the element that holds the handler. The moment a nested child is clicked, they diverge. I have seen this break tooltip close-handlers where clicking a span inside a tooltip wrapper closes the tooltip itself, because the handler checked e.target instead of e.currentTarget.
Real-world usage
- React:
SyntheticEvent.targetfor form delegation (readinge.target.valuein a sharedonChange).currentTargetfor accessing the form element itself. - Vue:
$event.targetinsidev-onfor dynamic slot content. - Vanilla delegation: one listener on a table,
e.target.closest('tr')to get the clicked row. - jQuery migration:
$(event.target).closest()patterns map directly toe.target.closest()in vanilla.
Follow-up questions
Q: What happens to target and currentTarget in the capture phase?
A: Both are set during capture too. currentTarget matches the capturing element; target is still the original dispatcher.
Q: Does stopPropagation() change target?
A: No. target is fixed at dispatch. stopPropagation() only stops the event from moving to the next element in the chain.
Q: In a non-arrow handler function, what does this equal?
A: this equals currentTarget. That is why older code used this instead of event.currentTarget. Arrow functions break this because they do not bind their own this.
Q: What if the clicked element is the same as the handler's element?
A: Then target === currentTarget. This happens when you click directly on the element you attached the listener to, with no child elements in between.
Q: In React Portals, where does target point?
A: target points to the portal child. Even though the Portal renders outside the parent DOM tree, React's synthetic event system re-dispatches through the React component tree, so delegation still works across the portal boundary.
Examples
Event delegation on a dynamic list
const list = document.getElementById('list');
// One listener handles all items, including ones added later
list.addEventListener('click', (e) => {
if (e.target.tagName === 'LI') {
console.log('Clicked:', e.target.textContent);
e.target.classList.toggle('selected');
}
// e.currentTarget is always the #list element
});
// This new item works immediately — no extra listener needed
const newItem = document.createElement('li');
newItem.textContent = 'New item';
list.appendChild(newItem);One handler on the parent covers all items, current and future. e.target tells you which specific item was clicked; e.currentTarget stays locked to the list.
Form with a shared handler using both properties
const form = document.getElementById('signup-form');
form.addEventListener('change', (e) => {
const field = e.target; // Which input changed
const formEl = e.currentTarget; // The form element itself
console.log('Field name:', field.name);
console.log('New value:', field.value);
// Re-validate the whole form using the form element
const allInputs = formEl.querySelectorAll('input');
const allFilled = [...allInputs].every(i => i.value.trim() !== '');
formEl.querySelector('#submit').disabled = !allFilled;
});e.target identifies which field changed. e.currentTarget gives access to the whole form for re-validation. Both properties doing different jobs inside the same handler.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.