This lesson focuses specifically on handling events inside templates: the @event=${handler} binding, reading event data, and common handler patterns.
The @event Binding in Detail
The @event=${handler} binding attaches handler as an event listener for event on that specific element, using addEventListener under the hood. The handler receives the native Event object exactly as it would with a manually attached listener, giving you access to event.target, event.currentTarget, and any event-specific properties.
Because this inside a regular method isn't automatically bound to the component instance in JavaScript, Lit handlers are typically written as class fields with arrow functions, or bound explicitly, to ensure this refers to your component rather than the element that dispatched the event.
Both handlers are defined as arrow-function class fields, so this correctly refers to TodoInput even though the browser invokes them as plain event listeners.
Reading Event Data
#onInput = (event) => {
event.target.value // the current value of the <input>
event.currentTarget // the element the listener is attached to
event.type // "input"
event.detail // present only on CustomEvent
};
event.target is the actual element the event originated from, which can differ from currentTarget if the event bubbled from a descendant.
event.currentTarget is always the element the listener is attached to — usually what you want inside a Lit template handler.
Prevent default browser behavior (like form submission) with event.preventDefault().
Stop the event from bubbling further with event.stopPropagation(), when appropriate.
Event Handling Patterns
Common handler patterns you'll reuse across components.
Pattern
Example
Read an input's value
event.target.value
Prevent form submission
event.preventDefault()
Read a checkbox state
event.target.checked
Stop bubbling
event.stopPropagation()
Pass extra context to a handler
Wrap in an arrow: @click=${() => this.#select(item.id)}
Handle Enter key specifically
if (event.key === 'Enter') { ... } inside @keydown
Passing Extra Arguments to a Handler
Sometimes a handler needs more context than the event object alone provides — for example, which list item was clicked. Wrapping the call in an inline arrow function is the standard pattern, accepting a small re-render overhead in exchange for clarity.
For very large lists where this matters, consider a single delegated listener on the parent element instead (event delegation), reading event.target.closest('li').
Handling Form Submission and Keyboard Events
Form-related handling almost always needs event.preventDefault() to stop the browser's default full-page submission, and keyboard handling almost always needs to check event.key to distinguish which key was pressed.
Forgetting event.preventDefault() on a form submit handler, causing an unwanted full-page navigation/reload.
Writing handlers as plain (non-arrow) class methods and calling them in a way where this ends up wrong.
Reading event.target when event.currentTarget is what's actually needed, especially with nested clickable elements.
Checking event.keyCode instead of the modern, more readable event.key for keyboard handling.
Key Takeaways
@event=${handler} attaches a real DOM event listener via addEventListener under the hood.
Arrow-function class fields are the standard way to ensure this refers to the component inside a handler.
event.target and event.currentTarget can differ; know which one you actually need.
Use event.preventDefault() and event.stopPropagation() exactly as you would with any DOM event listener.
Pro Tip
Default every new event handler to an arrow-function class field (#onClick = (event) => {...}) from the start — it's a small, consistent habit that eliminates an entire category of this-binding bugs before they happen.
You now understand event handling inside templates. Next, learn how to dispatch your own custom events.