Lit's @event=${handler} binding supports the same listener options native addEventListener does. This lesson covers the object-based syntax for configuring capture, once, and passive.
Passing Listener Options in a Template
By default, @event=${handler} attaches a listener with the browser's default options (bubble phase, not passive, not one-time). To pass additional addEventListener options — capture, once, or passive — provide an object with handleEvent and the option flags, instead of a plain function.
This object-based form is exactly what addEventListener itself accepts as a second argument (any object with a handleEvent method works as a listener), so Lit isn't introducing new syntax — it's exposing the platform's own capability declaratively.
passive: true tells the browser this listener will never call preventDefault(), letting it optimize scroll performance.
The Listener Options Object
@event=${{
handleEvent: (event) => { /* your logic */ },
capture: true, // listen during the capture phase
once: true, // automatically remove after firing once
passive: true, // promise not to call preventDefault()
}}
handleEvent replaces the plain function you'd normally pass — it receives the event exactly the same way.
capture: true listens during the event's capture phase (top-down) instead of the default bubble phase (bottom-up).
once: true automatically removes the listener after it fires a single time.
passive: true is a performance hint for scroll/touch listeners that never call preventDefault().
Event Options Cheat Sheet
When each addEventListener option is actually useful.
Option
Use Case
capture: true
Intercepting an event before a child's own handler runs
once: true
A one-time confirmation click, or a tutorial dismiss button
passive: true
High-frequency scroll/touchmove listeners for smoother scrolling
(none — default)
The vast majority of ordinary click/input/change handlers
Changing Options Requires a New Listener Object
Lit detects whether to re-attach a listener by comparing the value passed to @event between renders. If you pass the same plain function reference every render, Lit reuses the existing listener efficiently. With the options-object form, make sure the object (or at least its handleEvent reference and option flags) is stable across renders if you don't want Lit to remove and re-add the listener unnecessarily.
// Stable across renders — defined once as a class field
#scrollListener = { handleEvent: () => this.#onScroll(), passive: true };
render() {
return html`<div @scroll=${this.#scrollListener}>...</div>`;
}
Using the Capture Phase
The capture phase runs before an event reaches its target and before any bubble-phase listeners on descendants — useful when a container needs to intercept or log an interaction before a specific child element's own handler runs.
capture, once, and passive behave exactly as they do with a manually attached listener.
Keep the options object reference stable across renders to avoid unnecessary listener churn.
passive: true is specifically valuable for high-frequency scroll/touch listeners.
Pro Tip
Reach for these advanced options only when you have a concrete reason (a measured scroll-jank issue, a genuine capture-phase requirement) — the plain @event=${handler} function form is simpler and correct for the overwhelming majority of event bindings.
You've completed the events series. Next, learn how slots let you compose components with projected content.