DOM Events
Learn how to listen for user interactions with addEventListener(), what information the event object carries, the most common event types, and how bubbling and capturing work.
Reading Time
16 min
Lesson
Lesson 34 of 48
What is an Event?
An event is a signal that something has happened — the user clicked a button, typed into a field, submitted a form, or the page finished loading. The browser fires these events continuously as the user interacts with the page, and JavaScript can "listen" for specific ones and run code in response. This is the mechanism that makes web pages interactive rather than static documents: without events, a button click would just be a click, with nothing listening or reacting to it.
addEventListener()
addEventListener() is the standard way to react to events. It's called on the element you care about, takes the event type as a string (like "click") and a callback function to run when that event fires. It's preferred over the older approaches — an inline onclick="..." attribute in the HTML, or assigning a function directly to element.onclick — for a simple but important reason: addEventListener() lets you attach multiple listeners for the same event on the same element without one overwriting another. Assigning to .onclick, by contrast, replaces whatever handler was there before, since there can only ever be one.
addEventListener vs. the onclick Property
Console Output
Click “Run” to see the console output here.
The Event Object
Every listener callback automatically receives an event object as its first argument, carrying details about what just happened. event.type tells you the event's name (like "click"), event.target is the actual element the event originated from (the element the user clicked, for instance), and event.currentTarget is the element the listener is attached to — these two can differ when the event bubbles up from a descendant. event.preventDefault() stops the browser's default behavior for that event (like a link navigating, or a form submitting), and event.stopPropagation() stops the event from continuing to bubble up to ancestor elements.
target, currentTarget, preventDefault, stopPropagation
Console Output
Click “Run” to see the console output here.
Common Event Types
| Event | Fires When... |
|---|---|
| click | An element is clicked |
| input | The value of an <input>, <textarea>, or <select> changes, as it happens |
| change | A form control loses focus (or is toggled) after its value changed |
| submit | A <form> is submitted |
| keydown | A key is pressed down |
| keyup | A key is released |
| mouseover / mouseout | The pointer enters or leaves an element |
| focus / blur | An element gains or loses keyboard focus |
| load | A resource (page, image, script) finishes loading |
input vs. change, and keydown
Console Output
Click “Run” to see the console output here.
Removing Listeners with removeEventListener()
removeEventListener() detaches a previously attached listener, but only if you pass it the exact same function reference that was originally added — an anonymous inline function can never be removed, because there's no way to refer back to it afterward. This is the main reason to write listener callbacks as named functions (or store the function in a variable) whenever you might need to remove them later, such as when a component is unmounted or a temporary interaction ends.
A Removable Listener Needs a Named Reference
Console Output
Click “Run” to see the console output here.
Bubbling and Capturing, Briefly
When an event happens on a nested element, it doesn't just fire on that element — it travels through the DOM tree in two phases. The capturing phase runs first, traveling from the document down to the target element; the bubbling phase runs second, traveling back up from the target through each of its ancestors. By default, addEventListener() listens during the bubbling phase; passing true (or { capture: true }) as a third argument listens during the capturing phase instead. This matters because a click on a deeply nested <span> will also trigger click listeners on its parent, grandparent, and so on, all the way up — a behavior the next lesson on event delegation puts to direct, practical use.
A Click Bubbling Up Through Ancestors
Console Output
Click “Run” to see the console output here.
Key Takeaways
- addEventListener() supports multiple listeners per event and is safer than assigning .onclick directly
- The event object carries target, currentTarget, type, and the methods preventDefault() and stopPropagation()
- input fires on every keystroke; change fires once, after the value is committed
- removeEventListener() requires the exact same function reference used in addEventListener()
- Events bubble upward through ancestors by default; capturing runs the other direction, first
Arrow Functions Can't Be Removed Easily
Every time an inline arrow function or anonymous function expression is evaluated, it creates a brand-new function value, even if the code looks identical to one written elsewhere. Passing one directly to addEventListener() means you have no reference to pass to removeEventListener() later. If a listener may ever need to be removed, assign the function to a named variable or declare it with function name() { ... } first.
Use preventDefault() Deliberately
Calling event.preventDefault() on a submit event is extremely common when handling forms with JavaScript (to send data via fetch instead of a full page reload), but it's easy to overuse. Only call it when you genuinely intend to override the browser's default behavior — calling it unnecessarily on things like links or checkboxes can make a page feel broken to users who expect standard behavior.