- Learn
- Computer Science & IT
- Programming
- JavaScript Programming
- Event Listeners
Event Listeners and Interactive Web Pages
BeginnerLesson 6 of 9 in JavaScript Programming Course
What you will learn
- Make a page react when something happens: attach listeners to elements, read what the event object reports, and decide how long each listener stays active.
- Attach an event listener to an elementUse
addEventListenerto run a function each time a click, key press, form action or page event happens on an element. - Read the information in the event objectUse the event object a listener receives to find out what happened and where: its type, target, current target and event-specific details.
- Remove listeners and control how they runStop listening when a listener is no longer needed, use the
once,signalandpassiveoptions, and fire your own custom events.
- Attach an event listener to an element
- Explain how an event moves down to its target and back up through the page, cancel built-in browser behavior when you replace it, and use bubbling to handle many elements with one listener.
- Explain bubbling and capturingDescribe the three phases of event propagation and predict the order in which listeners on nested elements run.
- Prevent default actions and stop propagationCancel the browser's built-in response to an event when your code replaces it, and stop an event from traveling further only when that is really needed.
- Handle many elements with event delegationUse one listener on a shared ancestor and
event.target.closest()to handle events from many child elements, including elements added later.
- Explain bubbling and capturing
- React to key presses, focus changes, clicks, touches and pen input, and build a drag interaction that works with every kind of pointer.
- Respond to key presses and focus changesUse
keydown,keyupand the focus events, choose betweenevent.keyandevent.code, and build keyboard shortcuts that do not get in the user's way. - Handle clicks, touches and pen input with pointer eventsUse
clickfor activation and pointer events for pressing, moving and releasing with a mouse, finger or pen; read coordinates and buttons; and choose the right hover events. - Build a drag interaction with pointer eventsCombine
pointerdown,pointermoveandpointerupwith pointer capture to move an element, make it work on touch screens and offer a way to do the same without dragging.
- Respond to key presses and focus changes
- Read what users enter as they type, handle form submission in JavaScript, and check input with the browser's validation features and your own rules, with clear error messages.
- Read form fields and react as users typeRead text fields, numbers, checkboxes, radio buttons and selects, use the
inputandchangeevents at the right moments, and collect a whole form withFormData. - Handle form submission in JavaScriptListen for the form's
submitevent, stop the page from reloading, read the submitted data and know what triggers a submission. - Use the browser's built-in validationDescribe rules with HTML attributes, read the result in the
validityobject, check fields from code, and style invalid fields at the right moment. - Write custom validation with clear error messagesAdd rules the HTML attributes cannot express, write helpful messages, show them at the right moment and connect them to their fields.
- Read form fields and react as users type
- Make interactive elements that work with a mouse, a keyboard and a screen reader: prefer native HTML, add roles, keyboard support and state when you build your own, and keep focus where users expect it.
- Prefer native elements and make custom controls focusableKnow what native interactive elements give you for free and what a custom control built from a
<div>or<span>must add: a role, focusability, an accessible name and a visible focus indicator. - Add keyboard support and state to custom controlsMake custom buttons, toggles, disclosures, checkboxes and groups of items work from the keyboard, and keep their ARIA state in step with what users see.
- Keep focus where users expect it when the page changesMove focus on purpose when content opens, closes, is removed or shows errors, and never move it unexpectedly while users are typing or choosing.
- Prefer native elements and make custom controls focusable
Event Listeners study guide
1Respond to events with event listeners
Web pages respond through functions registered for events. Setup chooses the element and event type; each later occurrence brings an event object describing what happened. Reading that object lets a listener act on the right control, even when a click lands on an icon inside it. Listener references and options then let a feature keep its responses active for exactly the time it needs, and custom events let one part of a page notify another.
1.1 Listeners turn actions into code
A page can wait for a person to act. An event reports something that happened, and an event listener is the function called in response. Registering a listener sets up that response; it does not perform the action immediately.
Add the function to the element
Select an element that already exists, choose the event's exact name, and pass a function:
const button = document.querySelector("#like");
let likes = 0;
button.addEventListener("click", function () {
likes = likes + 1;
button.textContent = "Likes: " + likes;
});
The selection, variable setup, and registration run when the script runs. Each later click runs the function body. After three clicks the button displays "Likes: 3". Keeping likes outside the function preserves the total between clicks; putting let likes = 0 inside would restart it each time.
Pass a function reference, such as showMenu, without parentheses. In button.addEventListener("click", showMenu()), JavaScript calls showMenu immediately and passes its return value as the listener. To supply an argument later, wrap the call: button.addEventListener("click", () => showMenu("main"));.
Choose an event that actually occurs
Names are case-sensitive strings without an "on" prefix. Use "click", not "Click" or "onclick". A made-up name is accepted without an error, but the browser will not emit it for a click. Common families include pointer events such as pointerdown and pointerup, keyboard events such as keydown, form events such as input and submit, and focus events such as focusin. DOMContentLoaded has its own exact capitalization.
Set up the right element at the right time
A script in the head can run before a button exists. Selection then returns null, and calling addEventListener on it throws an error. Load an external script with defer, place it after the markup, or register setup on the document's DOMContentLoaded event. An element created later needs setup after creation or a listener on an ancestor.
querySelectorAll returns a NodeList, not one event target. Register on its elements:
document.querySelectorAll(".tab").forEach((tab) => {
tab.addEventListener("click", selectTab);
});
Let listeners coexist
| Method | What registration does |
|---|---|
addEventListener("click", fn) | Adds a listener alongside others and accepts options. |
button.onclick = fn | Replaces the single property handler; assign null to clear it. |
An HTML onclick attribute | Places JavaScript inside the markup. |
Listeners with the same capture setting run in registration order. At the event target, capture listeners run before non-capture listeners. Adding the same function object again with the same type and capture setting does not add a second copy.
Key Takeaways
- Pass the function itself; use a wrapper when the later call needs arguments.
- Event names require exact spelling and capitalization.
- An element must exist when its listener is registered.
- Loop over a NodeList to attach listeners to its elements.
addEventListenerpermits multiple listeners; a handler property holds one.
1.2 Read where an event started
The browser passes an event object to your listener. Its properties explain the event that triggered this particular call. Name the parameter event or e; the parameter name is your choice.
button.addEventListener("click", (event) => {
console.log(event.type);
console.log(event.currentTarget);
});
This logs "click" and the button during the listener call. Logging the whole object can help you discover its other properties.
Separate the origin from the listener
Imagine a button containing an image:
<button id="save" data-id="7">
<img src="disk.svg" alt=""> Save
</button>
| Property | Meaning | When the image is clicked |
|---|---|---|
event.target | Where the event started | The image |
event.currentTarget | Where the running listener was attached | The button, for a button listener |
Clicking the text "Save" makes the button the target too. The text is part of the button; it does not turn into a separate element target.
This difference matters when reading data. The image does not have the button's data-id, so event.target.dataset.id is undefined on an image click. A listener attached directly to the button can read event.currentTarget.dataset.id. A listener on a larger container can find the intended button with event.target.closest("button") and check that a match exists.
Read the details for this event
Every event has a type, a target, and a currentTarget. The Boolean bubbles says whether it travels back up through ancestors. cancelable says whether code may cancel its default action, and defaultPrevented records whether that cancellation has happened. timeStamp records the event's creation time in milliseconds relative to the page's time origin.
Different event families add useful details. A modern click supplies a PointerEvent, which inherits mouse-event properties such as clientX, clientY, and button. A KeyboardEvent adds key, code, and modifier flags. An input listener can use event.target.value to read the field's new text.
For a gallery listener, event.target.tagName might be "IMG" when a photo is clicked or "DIV" when the gap is clicked. Handle the case actually reported. Do not assume every event inside a container came from the same kind of child.
Use the value while the listener runs
In a regular function listener, this equals event.currentTarget. An arrow function takes this from its surrounding code; it does not acquire the element as this. Using event.currentTarget works with either function style.
After dispatch, currentTarget is reset to null. A console may show that later value when you expand a previously logged event. Log event.currentTarget directly inside the listener to inspect the element during the call.
Key Takeaways
targetidentifies the event origin;currentTargetidentifies the running listener’s element.- Read data from the element that actually owns it.
- Event families add properties such as coordinates, key values, and modifiers.
currentTargetis available during the listener and becomesnullafterward.
1.3 Control a listener’s lifetime
A listener should stay active for as long as its feature needs it. For a temporary keyboard interaction, keep the function reference so it can be removed when the interaction closes.
Remove the same function
function onKey(event) {
console.log(event.key);
}
document.addEventListener("keydown", onKey);
// When the feature closes:
document.removeEventListener("keydown", onKey);
Removal matches the event type, function object, and capture setting. An anonymous function can also be removed when its reference is saved, as in const handler = () => save();. Writing a fresh () => save() in the removal call creates a different function, even though the code looks identical.
Choose the registration options
| Option | Effect |
|---|---|
once: true | Removes this listener after its first call. |
signal: controller.signal | Removes the listener when that controller is aborted. |
capture: true | Listens on the capturing path. |
passive: true | Promises the listener will not cancel the default action. |
A one-time welcome banner can use { once: true }. A feature with several listeners can give them one signal:
const controller = new AbortController();
window.addEventListener("pointermove", moveCursor, {
signal: controller.signal
});
window.addEventListener("keydown", onKey, {
signal: controller.signal
});
// Close the feature:
controller.abort();
Aborting removes both registrations. An aborted controller cannot be reset; create a new controller for a new lifetime. For removal by removeEventListener, repeat capture: true if it was used on registration. once, signal, and passive do not form part of the removal match.
A passive listener's preventDefault() call is ignored. Browsers commonly default certain scrolling-related listeners on document-level targets to passive, including wheel, touchstart, and touchmove; explicitly use { passive: false } there when cancellation is needed.
Avoid accumulating callbacks
If each "Start" click adds a fresh anonymous board listener, starting twice makes one board click run two callbacks. Install the board listener once, reuse the same function object, or abort the previous group before replacing it. Distinct once callbacks can still accumulate before they fire.
Detaching an element preserves its listeners. They work if it is reinserted, and dispatchEvent() can reach them while it is detached. The element becomes eligible for garbage collection when it and its listeners are unreachable. A listener on window or document remains attached until explicitly removed.
Send a custom event
const event = new CustomEvent("cart:add", {
detail: { id: 42 },
bubbles: true
});
item.dispatchEvent(event);
Listeners read event.detail.id. Dispatch calls the listeners immediately, before returning. bubbles: true lets ancestors hear it too. An event sent with dispatchEvent() has isTrusted equal to false; browser-dispatched events can be trusted even when an API such as focus() caused them.
Key Takeaways
- Removal matches type, function identity, and capture setting.
onceends one registration after a call; aborting a signal ends its associated registrations.- A passive listener cannot cancel a default action.
- Fresh callbacks added on each setup can accumulate.
- Detaching an element preserves its listeners.
- Custom events carry data in
detailand dispatch synchronously.
2Follow events as they travel through the page
Nested elements share an event path. A click can pass a container before reaching its target and visit it again while bubbling, which makes one listener enough for a changing list. Following that path helps explain why a child action also triggers a parent. Separate cancellation of browser behavior from control of propagation, then use the event origin to choose which actions a shared listener should handle.
2.1 An event’s path through nested elements
A click inside a button can also be heard by the button's ancestors. Propagation is the event's journey through the document tree. Following that journey explains both useful shared listeners and surprising extra actions.
Follow three phases
- Capture: the event travels from
windowdown through the ancestors toward the target. - Target: listeners run at the element where the event began.
- Bubble: a bubbling event travels back up through the ancestors to
window.
The eventPhase property reports 1 for capture, 2 at the target, and 3 for bubbling. A listener registered without options runs at the target or on the bubbling path. A listener with { capture: true } runs on the downward path, including at the target if registered there.
Trace a concrete click
<div id="outer">
<div id="inner">
<button>Go</button>
</div>
</div>
Suppose the button and each div have ordinary click listeners that log their names. Clicking "Go" logs "button", "inner", then "outer". Clicking an empty area of inner logs "inner", then "outer"; the button is not on that event's path.
Now add a capture listener to outer. A button click logs "outer (capture)" first, then "button", "inner", and "outer" from the ordinary listeners. The capture listener sees the click before the descendants do. After outer, the event can continue through body, html, document, and window if listeners are registered there.
It is one event, not a new click created at every ancestor. target remains the button throughout. currentTarget changes as each listener runs. In the outer listener, reading target still identifies the original button.
Check which events bubble
| Bubbles | Does not bubble |
|---|---|
click, input, change, submit | invalid |
keydown, pointerdown | focus, blur |
focusin, focusout | pointerenter, pointerleave |
pointerover, pointerout | mouseenter, mouseleave |
The event's bubbles property tells you whether it has a bubbling phase. Non-bubbling events can still be observed by an ancestor's capture listener. To handle focus changes through an ordinary container listener, choose focusin or focusout.
Explain a nested-action bug
A product card opens a detail view when clicked. Its "Remove" button deletes the product. Clicking that button first deletes it, then reaches the card listener and opens the detail view. The event path explains why both callbacks run.
The card can ignore removal clicks with if (event.target.closest(".remove")) return;. Alternatively, the button can stop propagation. Filtering at the card preserves the event for other ancestors that may need it.
Key Takeaways
- Propagation follows capture, target, and then bubbling for events that bubble.
- Ordinary ancestor listeners hear descendant clicks on the upward path.
targetstays fixed whilecurrentTargetchanges.- Use a bubbling focus event or capture to observe focus from an ancestor.
2.2 Cancel an action or stop a journey
A listener can change what happens after an event, or where the event travels. These are separate decisions. A link may keep bubbling even after navigation has been canceled.
Replace a browser action
A default action is behavior the browser supplies: following a link, submitting a form, toggling a checkbox, inserting text, scrolling with Space, or moving focus with Tab. Use preventDefault() when your code replaces that action:
help.addEventListener("click", (event) => {
event.preventDefault();
openHelpPanel();
});
For a link to /help, this opens the panel without navigating. The link still offers its destination when JavaScript does not run.
Only cancelable events support cancellation. Inspect event.cancelable; defaultPrevented records successful cancellation. input occurs after the value changes and is not cancelable. Calling preventDefault() there does not remove the inserted text. A cancelable beforeinput event is the appropriate place to intercept an edit; canceling a relevant keydown can prevent that key's default effect.
Control propagation separately
| Method | Stops the browser default? | Stops later listeners? |
|---|---|---|
preventDefault() | Yes, if cancelable and not passive | No |
stopPropagation() | No | Stops travel to other elements; listeners on this element still run |
stopImmediatePropagation() | No | Also stops remaining listeners on this element |
A link whose listener only stops propagation still follows its URL. Canceling navigation alone still lets a document click listener hear the event. Returning false from an addEventListener callback does neither. A handler property or inline handler can cancel its default by returning false, but that still does not stop propagation.
Keep shared behavior working
An ancestor may close a menu or count interactions. Stopping propagation hides the event from that code. Often the ancestor can inspect target or defaultPrevented instead.
For example, a menu closes only when a click falls outside both the menu and its opener:
document.addEventListener("click", (event) => {
if (!menu.contains(event.target) &&
!menuButton.contains(event.target)) {
menu.hidden = true;
}
});
contains() includes the element itself and its descendants. Checking the opener matters because its opening click also bubbles to the document. Without that second test, the newly opened menu would close immediately.
Cancel only a handled action
In a key listener, check the key before canceling. Canceling every key blocks ordinary typing and can prevent Tab from moving focus. In a passive listener, cancellation is ignored altogether.
Key Takeaways
preventDefault()cancels an eligible default action while preserving propagation.stopPropagation()stops travel, not the default action.stopImmediatePropagation()also skips remaining listeners on the current element.inputis too late to cancel an edit.- Outside-click checks must include both a popup and its opener.
- Cancel only the specific keyboard actions your code handles.
2.3 One listener for a changing list
Event delegation places a listener on a stable ancestor and uses the event's origin to choose an action. It is useful when a list has many items or when code creates new items after setup.
Find the intended control
A bubbling click reaches the list no matter which of its descendants was clicked. Start at event.target, find the nearest matching control, and reject unrelated clicks:
const list = document.querySelector("#todos");
list.addEventListener("click", (event) => {
const button = event.target.closest(".remove");
if (!button || !list.contains(button)) return;
const item = button.closest("li");
console.log("Removing item " + item.dataset.id);
item.remove();
});
The list contains items such as <li data-id="7">Buy milk <button class="remove">Remove</button></li>. The listener belongs to the list, so a new item with the same markup works immediately after insertion.
Trace an icon click through the code. The target is the icon, closest(".remove") finds its button, and button.closest("li") finds the item. Its data-id identifies the item in the message. Finally, remove() changes the page.
Keep the two guards
| Test | Why it matters |
|---|---|
!button | A click on a list gap or ordinary item text may find no removal button. |
!list.contains(button) | closest() can climb beyond the list and find a matching outer ancestor. |
Testing only event.target.matches(".remove") loses icon clicks because the icon itself does not match. closest() checks the target and walks up its ancestors. In a delegated listener, currentTarget is the list; it does not become the matched button.
Route several actions
Data attributes let one toolbar listener choose among known actions:
const actions = { bold: makeBold, italic: makeItalic,
clear: clearText };
toolbar.addEventListener("click", (event) => {
const button = event.target.closest("button[data-action]");
if (!button || !toolbar.contains(button)) return;
const action = actions[button.dataset.action];
if (typeof action === "function") action();
});
A button with data-action="clear" selects clearText. The function check handles an unknown action without attempting to call undefined.
Choose a suitable event
Delegation works with bubbling events beyond clicks. A single form input listener can pass event.target to a field-checking function. To delegate focus changes, use focusin or focusout; ordinary focus and blur do not bubble. For hover transitions use bubbling over/out events when delegation is required.
A descendant's stopPropagation() can prevent an event from reaching the container. When a delegated listener seems inactive, check the event type, the target, the selector match, and whether something below the container stopped the journey.
Key Takeaways
- Attach the listener to an ancestor that survives changes to its children.
closest()finds a control even when a nested icon is clicked.- Reject missing matches and matches outside the container.
- Delegation automatically includes matching children added later.
- Data attributes can route a shared listener to known actions.
- Delegation needs an event that reaches the ancestor.
3Handle keyboard and pointer input
An interaction can arrive through keys, a mouse, a finger, or a pen. The event you choose determines whether you receive an activation, a changed text value, or the movement of a particular pointer. Coordinate frames and stored starting positions make dragging predictable. Focus and alternative controls keep the same page usable when a person navigates with the keyboard or cannot perform a dragging gesture.
3.1 Keys, text, and focus
Keyboard input goes to the focused element, available as document.activeElement, then bubbles. A document listener can therefore hear keys from fields as well as from other controls. That reach makes keyboard shortcuts useful, but it also means they can interrupt typing.
Choose the property by its meaning
| Property | Reports | Example |
|---|---|---|
key | The produced value, with layout and modifiers | "a", "A", "Enter", " " |
code | The physical key position | "KeyA", "Enter", "Space" |
On an AZERTY keyboard, the position labeled Q on a US keyboard produces key "a" and code "KeyQ". Use key for meaning, such as Escape to close or a letter shortcut. Use code for fixed positions, such as movement keys in a game. The space bar's key is a single space, not "Space".
keydown fires on the initial press and can repeat while held. Repeated calls have event.repeat === true. keyup fires on release. Use keydown instead of the deprecated keypress event.
Handle a shortcut precisely
document.addEventListener("keydown", (event) => {
if ((event.ctrlKey || event.metaKey) &&
event.key.toLowerCase() === "s") {
event.preventDefault();
saveDraft();
}
});
This handles Ctrl+S and Command+S. Lowercasing allows "S" too. Cancellation replaces the browser's Save Page action only for the matching shortcut. Other modifier flags include shiftKey and altKey.
For a page-wide single-letter shortcut, skip typing contexts before acting:
if (event.target.matches("input, textarea, select") ||
event.target.isContentEditable) return;
if (event.repeat) return;
The first guard protects text entry. The second makes an action run once per physical press. Do not cancel Tab across the page; it is how keyboard users move on.
Listen for text after it changes
For an ordinary printable key, the sequence is keydown, beforeinput, input, then keyup. Reading value in keydown gives the value before that character was inserted. Use input for a text preview or character count. It also handles edits such as paste and dictation that need no matching key press.
Observe focus changes
focus and blur report receiving and losing focus without bubbling. focusin and focusout bubble, so a form can observe its fields through one listener. relatedTarget identifies the other element in the change, or null when there is none.
password.addEventListener("focus", () => {
hint.hidden = false;
});
password.addEventListener("blur", () => {
hint.hidden = true;
});
The hint is available when the field receives focus, including through Tab. Calling element.focus() moves focus from code; the next keyboard input then goes to that element.
Key Takeaways
- Use
keyfor the produced meaning andcodefor physical position. - Guard page-wide letter shortcuts against typing contexts and unwanted repeats.
- Use
inputto react to edited text, including paste. focusinandfocusoutbubble;focusandblurdo not.relatedTargetreports the other focus destination or origin.
3.2 Activation and pointer movement
A person may use a mouse, a finger, a pen, or a keyboard. Choose an event by the interaction you need. click expresses activation; pointer events describe pressing, moving, and releasing.
Keep actions available through the keyboard
A native button emits click when activated with Enter or Space. A focused link does so with Enter. Wiring an action only to pointerdown or mousedown misses those keyboard activations. Use click for actions such as opening a panel; use pointerdown when work must begin at the press, such as dragging.
A pointer click follows a press and release. When they occur on different elements, the click can target their nearest shared ancestor. dblclick follows two quick clicks; event.detail counts quick successive clicks. auxclick handles non-primary clicks. contextmenu can come from a secondary click, a touch long press, or a keyboard menu command.
Use one pointer family
pointerdown, pointermove, pointerup, and pointercancel cover mouse, touch, and pen. pointerType identifies the device as "mouse", "touch", or "pen"; pointerId distinguishes active pointers, including two fingers.
Older mouse and touch event families still exist. Browsers may emit compatibility mouse events after pointer events. Registering both pointerdown and mousedown for the same action can therefore run it twice.
Match coordinates to the job
| Coordinates | Origin |
|---|---|
clientX, clientY | Top-left of the visible viewport |
pageX, pageY | Top-left of the document, including scrolling |
offsetX, offsetY | The target element's padding edge |
When the document is scrolled down 300 px and a click is 100 px below the viewport top, clientY is 100 and pageY is 400. Keep both positions in the same coordinate frame when calculating movement. A changing nested target can also change the origin of offset coordinates.
button reports which button changed on a down or up event: 0 primary, 1 middle, 2 secondary. buttons reports the currently held combination: primary contributes 1, secondary 2, and middle 4. Thus buttons === 3 means primary and secondary are held; it does not mean the middle button changed.
Choose hover behavior for a whole control
Over/out events bubble and can fire as the pointer crosses child boundaries. Enter/leave events do not bubble and describe entering or leaving the whole element, including its children. A tooltip can avoid child-boundary flicker with pointerenter and pointerleave.
Touch and keyboard users do not hover. Offer essential information on click or focus too. For a visual hover effect with no extra behavior, CSS :hover is simpler than a listener.
Key Takeaways
- Use
clickfor activation, including native keyboard activation. - Pointer events cover mouse, touch, and pen with one event family.
- Choose coordinates from the same frame for calculations.
buttonidentifies a change;buttonsreports the held combination.- Enter/leave treat an element and its children as a whole.
- Essential information needs access beyond hover.
3.3 A drag that keeps following the pointer
Dragging needs memory across several events. Store the pointer's starting coordinates and the element's starting position. Each move then applies the difference from that starting point, so the element does not jump to place its corner under the pointer.
Work out the displacement
A box starts at left: 100px; top: 50px. The pointer starts at viewport coordinates 220, 140 and moves to 260, 125. The horizontal change is 40 px; the vertical change is −15 px. Adding those changes to the box's starting position gives left: 140px; top: 35px.
Use consistent client coordinates for both pointer readings. The general calculation is element start plus pointer displacement. Do not add each full displacement to the box's already updated position, which would count earlier movement again.
Remember and capture one pointer
For a margin-free, absolutely positioned box inside a relatively positioned container:
let start = null;
box.addEventListener("pointerdown", (event) => {
if (start || event.button !== 0) return;
start = { id: event.pointerId, x: event.clientX,
y: event.clientY, left: box.offsetLeft,
top: box.offsetTop };
box.setPointerCapture(event.pointerId);
});
box.addEventListener("pointermove", (event) => {
if (!start || event.pointerId !== start.id) return;
box.style.left = start.left +
(event.clientX - start.x) + "px";
box.style.top = start.top +
(event.clientY - start.y) + "px";
});
function endDrag(event) {
if (start && event.pointerId === start.id) start = null;
}
box.addEventListener("pointerup", endDrag);
box.addEventListener("pointercancel", endDrag);
The stored ID rejects movement and release from a second pointer. offsetLeft and offsetTop provide starting positions as numbers; styles need the "px" suffix.
Pointer capture keeps subsequent events for that pointer directed to the box even when it moves outside the box. The browser releases capture after pointer up or cancellation. releasePointerCapture(id) can release it earlier.
Handle the browser taking over
A touch gesture may become page scrolling or zooming, causing pointercancel. End the drag there as well as on pointerup; otherwise stale state can keep moving the box later. Apply touch-action: none to the drag surface before interaction to reserve its gesture. Keep it local so the rest of the page can scroll.
Use user-select: none on the draggable surface to avoid selecting text. Set draggable="false" on its images to prevent the native image-drag behavior.
Offer another way to move
Provide keyboard movement for a focused control and clickable or tappable move buttons. Arrow keys alone do not supply a single-pointer alternative to dragging. WCAG 2.2 SC 2.5.7 requires such an alternative unless dragging is essential or is unmodified browser behavior. Native move buttons can serve both keyboard and pointer users.
The separate HTML Drag and Drop API uses dragstart, dragover, and drop to transfer data, such as files. Its event sequence is different from this pointer-based position change.
Key Takeaways
- Compute each position from the stored start plus pointer displacement.
- Capture and track the active pointer ID.
- End the drag on both
pointerupandpointercancel. - Apply touch and selection rules to the drag surface.
- Provide keyboard access and a clickable or tappable alternative.
4Handle forms and validate input before submission
Forms combine several kinds of state: text, numeric values, checked choices, and repeated names. Read those values at the right event, then handle the form’s submission as one action. HTML constraints supply common validation rules, while JavaScript adds relationships between fields and clearer feedback. A useful form preserves the user’s work, explains each correction, and puts focus where the next edit belongs.
4.1 Read values when they change
Form controls expose different kinds of state. A text field's value is a string; a checkbox's checked is a Boolean. Reading the right property is the first step toward reliable calculations and previews.
Convert numbers deliberately
Even <input type="number"> has a string value. With the text "3", adding 1 gives "31". Number(input.value) + 1 gives 4. A number input also exposes valueAsNumber.
Empty input needs its own decision. Number("") gives 0, which can make a missing quantity look like a real zero. Check input.value === "" first, or use valueAsNumber, which gives NaN for an empty number field. Test that result with Number.isNaN().
Read the selected state
| Control | Property to read |
|---|---|
| Text, textarea, select | value |
| Number input used in arithmetic | valueAsNumber, or convert a nonempty value |
| Checkbox | checked for on/off state |
Radio group named size | form.elements.size.value for the selected value |
A checkbox's value is its configured submission text, or "on" if none is supplied. It does not tell you whether it is checked. A select reports the chosen option's value. To find a selected radio element itself, query input[name="size"]:checked.
Match the event to the update
input reports user edits as they happen, including typing and paste. change reports a committed choice: after a changed text field loses focus, or immediately when a checkbox, radio button, or select is changed.
message.addEventListener("input", () => {
counter.textContent = 280 - message.value.length +
" characters left";
});
After "Hello", five characters have been used and the counter reads "275 characters left". A keydown listener would run before the new character enters the value and would miss pasting through a menu. Assigning message.value from code does not fire either input or change; call the update function directly too.
Collect a form's named fields
form.elements.email.value accesses a control by name or ID. Use form.elements["first-name"] for a hyphenated name. Submission uses names: an ID alone does not put a field in the data.
const data = new FormData(form);
const email = data.get("email");
const topics = data.getAll("topics");
FormData collects the current eligible name/value pairs. Unchecked checkboxes, disabled controls, and nameless fields are omitted. get() returns the first value for a name; getAll() preserves repeated values, such as several checked topics. File controls can supply file objects; ordinary text controls supply strings.
Object.fromEntries(data) is convenient for distinct names, but keeps only the last pair for repeated names. Choose getAll() when each selection matters. Because input bubbles, one form listener can read event.target to update whichever field changed.
Key Takeaways
- Convert a field’s string before arithmetic and handle an empty number separately.
- Read checkbox state from
checked. - Use
inputfor live edits andchangefor committed choices. FormDataincludes eligible named controls, not every control.getAll()preserves repeated names; object conversion does not.- Programmatic value assignment does not emit editing events.
4.2 Handle the form’s submission
Submission belongs to the form. Listen for its submit event to handle a submit-button click, Enter in a single-line field, and a scripted requestSubmit() through one route.
Replace navigation with a page update
Normally a form sends data to its action URL and loads the response. Without an action, the destination is the current page. The default GET method places the data in the URL. To update the current page with JavaScript, cancel that default action:
const form = document.querySelector("#new-todo");
form.addEventListener("submit", (event) => {
event.preventDefault();
const data = new FormData(form);
const title = data.get("title").trim();
if (title === "") return;
addTodo(title);
form.reset();
});
This form has an enabled text field named title. The listener trims surrounding spaces, ignores an empty result, adds an item, and resets fields to their initial values. reset() does not always mean empty; a control with an initial value returns to that value.
If the item appears briefly and disappears as the page reloads, check whether preventDefault() is missing. A button click listener alone is the wrong boundary for form handling, especially when another route submits the form.
Know what triggers submission
Implicit submission from Enter can click the enabled default submit button. Enter in a textarea inserts a newline. A button inside a form defaults to a submit button when its type is omitted, so a "Show password" button should explicitly use type="button".
When there are multiple submit buttons, event.submitter identifies the one used. It can be null for routes without a specific button, so code should check before reading it:
const button = event.submitter;
const intent = button ? button.value : "save";
const data = button ? new FormData(form, button) :
new FormData(form);
A named submitter such as name="intent" value="publish" contributes that pair when passed to FormData. A FormData(form) call by itself does not include a submit button's name/value pair.
Preserve the validation route
| Submission route | Automatic validation | submit listener |
|---|---|---|
Submit activation or requestSubmit() | Runs unless skipped by validation attributes | Runs if validation allows it |
form.submit() | Skipped | Skipped |
Built-in validation runs before the submit event. If an empty required field fails, the browser reports the problem and does not dispatch submit. A custom check only inside that listener will not run on this failed attempt. novalidate on the form disables the automatic validation step.
Use requestSubmit() when code needs the normal validation and submit-event path. It requests submission without dispatching a button click. Pass a particular submit button to requestSubmit(button) when that button's submission settings should apply.
Key Takeaways
- Handle
submiton the form, covering user and scripted submission routes. - Cancel the default submission when JavaScript replaces navigation.
- Give non-submit form buttons
type="button". - Use
submitterto distinguish submit buttons and include its data when needed. - Validation precedes
submit;requestSubmit()preserves that route. submit()bypasses validation and the submit event.reset()restores initial values.
4.3 Let HTML describe validation rules
HTML can describe many input rules without a custom JavaScript test. The browser checks those rules during submission and exposes the results for your code. Start with the constraints that match the data you need.
Choose a constraint
| Attribute | Rule |
|---|---|
required | A value or required selection must be supplied. |
type="email" or "url" | The value must have the expected syntax. |
min, max, step | Numeric or date values must fit limits and increments. |
minlength, maxlength | User-entered text must fit a length rule. |
pattern | A nonempty text value must match a whole-value expression. |
For pattern="[0-9]{5}", "12345" passes; "1234", "123456", and "12345a" fail. The pattern covers the whole value, not any matching substring. Empty input skips the pattern check, so add required when omission is not allowed.
Email validation checks syntax, not whether an inbox exists. Both "ann@example.com" and "ann@example" pass the browser's email rule, while "ann.example.com" does not. An additional pattern can impose a more specific format when the application requires it.
Read the reason for failure
A field's validity object contains Boolean flags. valueMissing, typeMismatch, patternMismatch, tooShort, and tooLong describe missing or malformed text. rangeUnderflow, rangeOverflow, and stepMismatch describe numeric constraints. badInput covers an input the browser cannot turn into a usable value, and customError records a custom validity message. valid is true when no constraint fails.
For a number field with min="0" max="10" step="2", 12 is above the maximum, 7 is off the allowed step, and 6 passes. More than one flag can be true; do not assume every invalid field has exactly one problem.
Check quietly or report visibly
checkValidity() returns a Boolean and fires invalid on failing controls without showing the browser's message. reportValidity() checks and also reports the problem to the user. Call either on a field or a form. validationMessage gives the message in the browser's language. The invalid event does not bubble.
Text-length constraints apply to user-entered text. Assigning input.value = "ab" in code does not make tooShort true just because minlength is 3. maxlength normally also prevents further user typing beyond its limit.
Show errors at a helpful time
:invalid applies immediately, so an untouched required field already matches. :user-invalid waits for user interaction or a submission attempt and can avoid an early red field. Use text to explain the error as well as a visual style.
novalidate skips automatic submission validation for the form; formnovalidate skips it for one submit button. They do not remove constraints or disable checkValidity(). Browser checks help users correct mistakes; the server must validate received data again because a request can bypass the page.
Key Takeaways
- Use HTML constraints for required values, syntax, ranges, steps, length, and patterns.
- A pattern matches the whole nonempty value; use
requiredto reject empty input. - Read
validityflags to identify why a field failed. checkValidity()checks;reportValidity()also displays feedback.- Validation-skipping attributes leave the validity API usable.
- Validate received data on the server too.
4.4 Custom rules and useful error messages
A rule involving two fields needs code. For example, a confirmation must match a password, or an end date must follow a start date. Keep the rule separate from how and when its error is displayed.
Set and clear a custom error
A nonempty string passed to setCustomValidity() marks the field invalid and sets customError. Passing "" clears that custom failure; required, pattern, or other constraints can still fail.
function checkMatch() {
const matches = confirm.value === password.value;
confirm.setCustomValidity(matches ? "" :
"The passwords do not match.");
}
password.addEventListener("input", checkMatch);
confirm.addEventListener("input", checkMatch);
Recheck both dependencies. Changing the first password can break a previously correct confirmation. A message that is never cleared keeps the field invalid after the person fixes the mismatch. Run the initial check too when fields can start with values.
Date inputs provide ISO strings such as "2026-03-10", regardless of the displayed local date format. Nonempty values from these controls can be compared as text. If the rule says the end must be after the start, reject end.value <= start.value, including equality. Handle missing dates through required or a separate empty-value check.
Explain the next useful action
A message should identify the problem and its correction. For a missing email, say "Enter your email address." For malformed email text, say "Enter an email address in the form name@example.com." Keep the entered value so the person can edit it.
A required name field accepts spaces because they form a nonempty string. Check name.value.trim() === "" when surrounding whitespace should not count. Clear this custom error when meaningful text appears.
Connect messages to controls
<label for="email">Email</label>
<input id="email" name="email" type="email"
required aria-describedby="email-error">
<p id="email-error"></p>
Place the error in that paragraph and set aria-invalid="true" while the field has an error. Clear the text and remove or set aria-invalid to "false" after correction. aria-describedby connects the explanation to the field for assistive technology. Use text; a red border alone cannot tell everyone what failed.
Choose the validation flow
For fully custom messages, add novalidate and use a form submit listener. Update custom rules, inspect the fields' validity, write their messages, and prevent submission when any fail. Move focus to the first invalid field or to a linked error summary. If JavaScript handles successful submission locally too, cancel that default submission as well.
Avoid reporting incomplete email text on every initial keystroke. A useful sequence is to show errors after blur or submission, then recheck an already reported error on input.
Key Takeaways
- Nonempty custom messages make a field invalid;
""clears only that custom error. - Recheck every field a cross-field rule depends on.
- For a strict date order, reject equality as well as an earlier end.
- Explain a correction in text and associate it with the field.
- Clear visible errors and ARIA error state after correction.
- Custom submission feedback needs an explicit validity and focus flow.
5Build custom controls that everyone can use
Choosing a native element supplies much of a control’s behavior before any listener is written. Custom controls need the same care: a clear name, keyboard access, visible focus, and state that matches what assistive technology reports. Groups need predictable movement between items. When content opens, closes, or disappears, a deliberate focus destination lets the user continue the interaction without searching the page again.
5.1 Start with a native control
A control needs more than a click response. People must be able to find it, reach it, understand its purpose, operate it, and see where focus is. Native HTML supplies much of this behavior already.
Choose the element for the job
Use a <button> for an action, an <a href> for navigation, and a labeled form control for input. <details> with <summary> supplies a disclosure. Style these elements with CSS to match the page. A focused button activates through Enter or Space without an extra key listener.
A div with a click listener has none of the button's built-in semantics or keyboard behavior. Giving it role="button" changes what assistive technology announces, but does not make it focusable or turn key presses into clicks.
| Need | Native button | Custom div acting as a button |
|---|---|---|
| Role | Built in | Add role="button". |
| Keyboard focus | Built in | Add tabindex="0". |
| Enter and Space | Built in | Add keyboard handling. |
| Accessible name | Usually its text | Supply meaningful text or an ARIA name. |
| Disabled behavior | disabled | Check aria-disabled in code. |
Keep the focus order predictable
tabindex="0" adds an element to the normal document-order Tab sequence. tabindex="-1" permits programmatic focus without adding a Tab stop. Positive values move elements ahead of the usual sequence and are difficult to maintain; avoid them.
Neither setting adds activation behavior. A focusable custom button still needs a listener for its keyboard operation. A role, a Tab stop, and a callback solve different parts of the interaction.
Give every control a name
A screen reader uses an accessible name to distinguish controls. A text button can use its visible text. A text field or select needs an associated label, such as <label for="email">Email</label> paired with id="email". aria-labelledby can refer to visible text elsewhere; aria-label can supply a name when there is no suitable visible text.
An icon-only close button can be <button type="button" aria-label="Close">×</button>. The name describes the action instead of making the user infer what the symbol means.
Show and enforce state
Keep a visible focus indicator. If you replace the default outline, supply a clear replacement with :focus-visible, for example outline: 3px solid #1a73e8. Removing it without replacement hides the keyboard user's position.
A native disabled button leaves the Tab sequence and cannot be activated normally. aria-disabled="true" only communicates unavailability. A custom control's click and keyboard action must check that state and return without acting. Centralizing that check in the activation function keeps both input routes consistent.
Key Takeaways
- Use a native element that matches the action or input.
- An ARIA role supplies semantics, not behavior.
- Use
tabindex="0"for normal Tab access and-1for code-only focus. - Provide an accessible name and a visible focus indicator.
- Enforce
aria-disabledin the activation code.
5.2 Keep keyboard behavior and state together
A custom control should give the same result regardless of how it is activated. Keep the action in one function, route keyboard and pointer use to it, and update the state that both styling and assistive technology read.
Give a custom button keyboard activation
fakeButton.addEventListener("click", activate);
fakeButton.addEventListener("keydown", (event) => {
if (event.key === "Enter" || event.key === " ") {
event.preventDefault();
activate();
}
});
This is for a custom focusable button. Canceling Space prevents page scrolling during its action. A native button already produces clicks from keyboard activation, so adding a second key route to it can run the action twice.
Choose the state that matches the control
| Control | State |
|---|---|
| Toggle button with a stable label | aria-pressed="true" or "false" |
| Button opening a disclosure | aria-expanded="true" or "false" |
| Custom checkbox or switch | aria-checked="true" or "false" |
A "Mute" toggle keeps that label and changes aria-pressed. If the design changes its action label to "Unmute", leave out aria-pressed. A checkbox uses role="checkbox" and Space to toggle; a switch uses role="switch" for on/off state.
Attributes contain strings. Compare getAttribute("aria-expanded") with "true", not the Boolean true:
detailsButton.addEventListener("click", () => {
const open = detailsButton.getAttribute(
"aria-expanded") === "true";
detailsButton.setAttribute("aria-expanded", String(!open));
panel.hidden = open;
});
When open was true, the new state is false and the panel becomes hidden. aria-controls can identify the panel by ID. Native <details> and <summary> supply disclosure behavior without this script.
Use the state attribute as the styling source, such as [aria-pressed="true"], so the visible and announced states change together. Close popups on Escape and return focus to their opener.
Give a group one Tab stop
Tabs, toolbars, and radio groups use Tab to enter or leave the group and arrow keys to move inside it. Roving tabindex keeps the current item at 0 and other items at -1. On movement, set the old item to -1, the new item to 0, and focus the new item.
For three items, right from index 2 wraps with (2 + 1) % 3 to 0. Left from 0 uses (0 - 1 + 3) % 3 to reach 2. Add the length before the remainder operation because JavaScript's -1 % 3 is −1.
Check with the keyboard: enter the control, see focus, activate it with the appropriate keys, move within a group, and close a popup with Escape. Check the visible state and the ARIA state on each change.
Key Takeaways
- Route custom button click, Enter, and Space to one action.
- Native buttons already provide keyboard click activation.
- Choose
aria-pressed,aria-expanded, oraria-checkedfor the matching state. - Compare ARIA values as strings and update styling from the same state.
- Roving tabindex gives a group one Tab stop and arrow-key movement.
- Escape closes a popup and restores its opener’s focus.
5.3 Move focus when the page changes
Focus decides where the next keyboard action goes. If the focused control disappears, the user can lose their place. Treat focus as part of the page update, especially when opening a dialog, deleting an item, or reporting an error.
Open a dialog and return to its opener
Remember the focused element before opening. Move into the new interaction, then restore focus on close:
let opener;
openButton.addEventListener("click", () => {
opener = document.activeElement;
dialog.showModal();
});
dialog.addEventListener("close", () => {
if (opener && opener.isConnected) opener.focus();
});
showModal() on a native dialog moves focus inside and makes the rest of the page inert, so it cannot be clicked or focused while the modal is open. Escape normally causes a cancel event followed by closure and a close event. A cancel listener can prevent closing if the application needs that behavior.
Choose a useful initial focus location in the dialog, such as its first relevant control. For a custom popup, code must move focus deliberately. When the original opener no longer exists, choose a nearby useful control instead of trying to focus a detached element.
Make a useful destination focusable
A heading or error summary is not ordinarily focusable. Give it tabindex="-1" before calling .focus(). It can receive the deliberate focus move without becoming an extra stop during normal Tab navigation. Use 0 only when it belongs in that normal sequence.
Preserve position after deletion
Before removing a list item whose delete button has focus, find the next useful destination. Focus the next item's button; if there is no next item, use the previous item's button. If the list becomes empty, focus its heading with tabindex="-1".
Finding the destination before removal avoids losing the relationship to neighboring items. Do not send focus to the top of the document after every deletion; the person should be able to continue working near the change.
Keep focus within a continuing interaction
A popup may close when focus leaves its whole area. The bubbling focusout event provides the destination:
menu.addEventListener("focusout", (event) => {
if (!menu.contains(event.relatedTarget)) {
menu.hidden = true;
}
});
Moving between two menu controls does not close it because the destination remains inside. A null destination is outside too; contains(null) is false. Do not automatically return focus to the opener when the user has intentionally moved to another page control.
Make changes predictable
After failed custom form validation, focus the first invalid field or an error summary that links to the errors. Built-in validation already focuses a failing field. Keep error text available so the person knows what to correct.
Do not jump to a new field while the user is typing or navigate as soon as a select changes. Let a clear action confirm the change. Focus movement should support a deliberate action or recovery, not interrupt ordinary input.
Key Takeaways
- Remember the opener and restore an available destination when a dialog closes.
- Use
tabindex="-1"for a heading or summary focused from code. - After deletion, focus a neighboring item or the empty list’s heading.
- Use
relatedTargetto distinguish internal focus moves from leaving a popup. - Focus an error destination after failed validation; avoid unexpected focus jumps.
Event Listeners examples
1Build and inspect event listeners
13 steps
- 1IntroductionMake a button respond
A listener connects an event to a function. Build a small counter by choosing the button, registering its action, and keeping the total between calls. Then inspect a nested
clickand control the listener’s lifetime.Watch the difference between setup code, which runs now, and the callback, which the browser calls when the chosen event happens.
- 2ExampleStore the total outside the callback
Select the existing button and store its count before registration:
const button = document.querySelector("#like"); let likes = 0; button.addEventListener("click", () => { likes = likes + 1; button.textContent = "Likes: " + likes; });Each call adds to the previous total. After three clicks the label is "Likes: 3". A count declared inside the callback would start again on every call.
- 3TipPass the action without calling it
addEventListener("click", showMenu)passes a function for later use.showMenu()would call it during setup. When arguments are needed, use() => showMenu("main").Load the script after the button exists, such as with
defer, and keep the exact event spelling. A listener for"Click"does not receive ordinary"click"events. - 4ChallengeRepair a registration
A button must open the tips panel on every activation. Setup currently uses
button.addEventListener("click", openTips()). What should change, and why?---reveal---
Pass
openTipswithout parentheses. That supplies the callback to run for a laterclick. CallingopenTips()during setup opens the panel immediately and passes its result instead of preserving the action for the event. - 5ExplanationRead the two element properties
For a button containing an image, clicking the image makes it the
target. In the button’s listener,currentTargetis the button.The
targetsays where the event began; the currenttargetsays where this callback was registered. That difference explains why reading a button’sdatasetfromtargetcan fail when theclicklands on its image. - 6ExampleRead the owning button’s data
Suppose
buttonhasdata-id="7"and contains an icon:button.addEventListener("click", (event) => { console.log(event.currentTarget.dataset.id); });The result is
"7"for an iconclickas well as a button-backgroundclick. The same expression works in an arrow or regular function listener. An arrow function’sthisdoes not automatically identify the button. - 7ChallengeIdentify the listener owner
A section contains a button with a span. A
clicklistener is attached only to the section, and the span is clicked. What dotargetandcurrentTargetidentify during that callback?---reveal---
targetidentifies the span where the event began.currentTargetidentifies the section where the listener was attached. The event’s origin is not replaced by the ancestor that receives it. - 8ExplanationKeep the reference for removal
Removal needs the same event type, callback object, and capture setting:
const handler = () => closePanel(); button.addEventListener("click", handler); button.removeEventListener("click", handler);An anonymous function can be removable when saved this way. Writing a new
() => closePanel()in the removal call creates another object and does not match the earlier registration. - 9ConnectionEnd several registrations together
A temporary feature can pass one
AbortControllersignal to several registrations. Callingcontroller.abort()removes those listeners, including saved or anonymous callbacks.Use a fresh controller for another lifetime. For a single one-time response,
{ once: true }removes that registration after it runs. Neither option prevents distinct callbacks from accumulating before their lifetime ends. - 10ChallengeDiagnose repeated setup
Opening a panel registers a new anonymous document key listener each time. Closing hides the panel but leaves the listener. After three openings, a key triggers three actions. How can the lifecycle be repaired?
---reveal---
Remove the registrations when the panel closes, for example by aborting their shared signal, and create a new controller on reopening. Alternatively, save a callback and remove that same reference. Hiding the panel does not remove a document listener.
- 11Key InsightDispatch a notification immediately
To notify other code, create a
CustomEventwith its data indetail. Setbubbles: trueif ancestors should hear it:item.dispatchEvent(new CustomEvent("cart:add", { detail: { id: 42 }, bubbles: true }));The listeners run before the call returns. The dispatched event has
isTrustedfalse. It is a notification your code sends, not a request for the browser to wait for a user action. - 12ChallengeCombine origin and lifetime
A button’s
clickhandler uses its owndata-keyand should act only on the first activation. It contains an SVG icon. What property and option keep both requirements correct?---reveal---
Read
event.currentTarget.dataset.keyin the direct button listener and register with{ once: true }. The property identifies the owning button even for an iconclick. The option removes that registration after its first call. - 13RecapCheck a listener in three places
When an interaction fails, inspect setup, the callback’s data, and its lifetime. Confirm that the element exists and the event name is exact; inspect
targetandcurrentTargetduring the call; then look for missing removal or repeated registration. These checks distinguish a listener that never receives an event from one that receives it correctly but acts on the wrong element.
2Trace events and delegate actions
13 steps
Save this lesson to follow every Walkthrough step by step.
- 1IntroductionTrace one click through a card
- 2ExampleRead the upward path
- 3ExplanationAdd the downward path
- 4ChallengeTrace a smaller tree
- 5ExplanationReplace navigation
- 6Key InsightStopping travel is a different choice
- 7ChallengeKeep a parent observer active
- 8ExplanationMove the listener to the list
- 9ExampleFind and guard the removal button
- 10ChallengeHandle a nested icon
- 11TipUse an event that reaches the container
- 12ChallengeKeep opening and closing separate
- 13RecapChoose a boundary for each action
3Handle keys and build a pointer drag
16 steps
Save this lesson to follow every Walkthrough step by step.
- 1IntroductionChoose the right input level
- 2ExplanationSeparate meaning from position
- 3TipProtect typing and handle repeats
- 4ChallengeChoose an event for a preview
- 5ConnectionLet activation cover the keyboard
- 6ExampleConvert the coordinate frame
- 7ChallengeInspect a scrolled click
- 8Key InsightKeep hover from becoming the only route
- 9ExampleCalculate one drag movement
- 10ExplanationStore the start and capture its pointer
- 11ExampleApply displacement on every move
- 12TipEnd even if the browser takes over
- 13ChallengePredict a continuing drag
- 14ConnectionOffer movement without dragging
- 15ChallengeDiagnose two drag failures
- 16RecapMatch the event to the task
4Build a complete form interaction
16 steps
Save this lesson to follow every Walkthrough step by step.
- 1IntroductionFollow a form from editing to submission
- 2ExampleRead a number without concatenation
- 3ExplanationRead choices and repeated names
- 4ChallengePreserve every selected topic
- 5ExplanationHandle submission at the form
- 6ExampleRead, add, and reset
- 7ChallengeKeep the normal submission route
- 8ExampleRead independent constraint failures
- 9Key InsightA pattern does not require a value
- 10ChallengeCheck a stepped range
- 11ExplanationAdd a rule connecting two fields
- 12TipSeparate validity from error timing
- 13ChallengeRepair a stale date rule
- 14ConnectionTake responsibility when skipping browser feedback
- 15ChallengeComplete the recovery flow
- 16RecapCheck both valid and invalid routes
5Build controls and preserve keyboard position
14 steps
Save this lesson to follow every Walkthrough step by step.
- 1IntroductionMake an interaction work through the keyboard
- 2ExplanationUse the behavior HTML already supplies
- 3TipSet focusability for the intended route
- 4ChallengeRepair an unreachable action
- 5ExampleKeep one activation function
- 6ExplanationExpress the right kind of state
- 7ChallengeInvert a disclosure correctly
- 8ExampleMove one Tab stop inside a group
- 9ConnectionRemember where a dialog came from
- 10ExampleKeep an internal focus move inside
- 11ChallengeChoose focus after removal
- 12ChallengeTrace a group update
- 13ChallengeKeep a popup predictable
- 14RecapTest the complete control sequence
Event Listeners practice questions
Set 1: Set up and inspect event listeners10 Practice Questions
Question 1 of 10
showHints only when activated. What registration achieves that?Show answerHide answer
click occurs. Writing showHints() calls it during registration and passes its return value. A wrapper written as () => showHints only returns the function; it never invokes the intended action.Question 2 of 10
addEventListener. What loading change lets selection wait until the HTML is parsed?Show answerHide answer
defer runs after document parsing, so the button exists when selected. The original selection returned null because the markup had not been parsed. Calling an immediate function from the same early script does not change that timing.Question 3 of 10
const items = document.querySelectorAll(".choice"); items.addEventListener("click", choose);. All choices already exist. Why does this fail, and how should setup change?Show answerHide answer
addEventListener method. Iterate through its elements and register choose on each, or delegate from an appropriate ancestor. Calling choose() does not turn the collection into an event target and also invokes the function too early.Question 4 of 10
button.onclick = first; button.onclick = second;. What happens when the button is clicked once?Show answerHide answer
onclick property stores one callback, so the second assignment replaces the first. Only second runs from that property on the click. Registering two distinct functions with addEventListener would allow both to coexist, but these statements assign a single handler property.Question 5 of 10
target and currentTarget during that listener?Show answerHide answer
target identifies that span. The listener was attached to the button, making the button currentTarget during the call. The listener’s location does not replace the event origin when a nested element is clicked.Question 6 of 10
<button data-code="r8"><span>Run</span></button> reads event.target.dataset.code. Why can it return undefined, and what direct-listener expression reads the button’s code?Show answerHide answer
data-code. currentTarget identifies the button that owns this listener, so its dataset contains the code. An arrow function does not acquire the button as this, so switching to this.dataset does not reliably fix the problem.Question 7 of 10
currentTarget: null. How can the developer inspect the listener owner during the call?Show answerHide answer
currentTarget for each listener call and resets it afterward. Log the property directly inside the callback to see the owner during that call. A later event-object inspection can show the reset state; target is the origin and may be a different element.Question 8 of 10
{ capture: true }. The same type and function are passed to removeEventListener, but no third argument is given. Why does the callback remain?Show answerHide answer
true or { capture: true }; there is no need to repeat once, passive, or signal to identify that listener.Question 9 of 10
board.addEventListener("click", () => mark()). Beginning twice makes one board click mark twice. What setup change prevents that accumulation?Show answerHide answer
once to two distinct callbacks still leaves two callbacks waiting for the next click, so both can mark before removing themselves.Question 10 of 10
An otherwise empty button gets these registrations, then is clicked twice. How many times does log run in total?
button.addEventListener("click", log, { once: true });
button.addEventListener("click", log);
button.addEventListener("click", log, true);
button.removeEventListener("click", log);
Show answerHide answer
Set 2: Trace nested actions and delegation10 Practice Questions
Save this lesson to see every answer and practice every set.
Question 1 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 2 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 3 of 10
focus listener, but focusing its child input does not call it. How can the form observe descendant focus without using capture?Show answerHide answer
Save this lesson to see this answer.
Question 4 of 10
event.stopPropagation(). What happens to its normal navigation?Show answerHide answer
Save this lesson to see this answer.
Question 5 of 10
preventDefault(). An ordinary submit listener also exists on a surrounding section. What can the section’s listener observe?Show answerHide answer
Save this lesson to see this answer.
Question 6 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 7 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 8 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 9 of 10
closest(".action") inside a container nested in an outer .action element. A gap click finds that outer element. What guard keeps handling local?Show answerHide answer
Save this lesson to see this answer.
Question 10 of 10
closest(".remove") and containment guards. New buttons work, except one row whose icon listener calls stopPropagation(). The same row’s button padding works. Why does this difference occur?Show answerHide answer
Save this lesson to see this answer.
Set 3: Handle keyboard, pointer, and drag input10 Practice Questions
Save this lesson to see every answer and practice every set.
Question 1 of 10
clientY 75. What is its document-relative vertical coordinate?Show answerHide answer
Save this lesson to see this answer.
Question 2 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 3 of 10
keydown. What change addresses both symptoms?Show answerHide answer
Save this lesson to see this answer.
Question 4 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 5 of 10
pointerdown. What event should handle the action?Show answerHide answer
Save this lesson to see this answer.
Question 6 of 10
pointerout. What event better detects leaving the whole card?Show answerHide answer
Save this lesson to see this answer.
Question 7 of 10
left: 65px; top: 90px. Its pointer starts at 180, 210 and moves to 155, 245 in client coordinates. What position should the box reach?Show answerHide answer
Save this lesson to see this answer.
Question 8 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 9 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 10 of 10
Show answerHide answer
Save this lesson to see this answer.
Set 4: Read form data and diagnose validation10 Practice Questions
Save this lesson to see every answer and practice every set.
Question 1 of 10
"6". What does input.value + 2 produce?Show answerHide answer
Save this lesson to see this answer.
Question 2 of 10
topic with values "art" and "code" in document order, an unchecked topic valued "music", and a disabled named note. What does new FormData(form).getAll("topic") return, and why is object conversion risky?Show answerHide answer
Save this lesson to see this answer.
Question 3 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 4 of 10
form.submit(), and the form’s listener that prevents navigation never runs. What call restores the expected flow, and what happens before that field is fixed?Show answerHide answer
Save this lesson to see this answer.
Question 5 of 10
pattern="[A-Z]{3}" and is optional. Does an empty value pass this pattern constraint?Show answerHide answer
Save this lesson to see this answer.
Question 6 of 10
min="2" max="14" step="4". The user enters 12. What fails?Show answerHide answer
Save this lesson to see this answer.
Question 7 of 10
novalidate and checks fields in document order on submit. Its quantity is required with min="2" max="14" step="4"; it now contains 10 and an old custom message was cleared with setCustomValidity(""). The next field is a required email that is empty. What should block this attempt and receive focus?Show answerHide answer
Save this lesson to see this answer.
Question 8 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 9 of 10
end.value < start.value and runs only on end edits. What two changes fix the remaining cross-field faults?Show answerHide answer
Save this lesson to see this answer.
Question 10 of 10
Show answerHide answer
Save this lesson to see this answer.
Set 5: Build keyboard access and preserve focus10 Practice Questions
Save this lesson to see every answer and practice every set.
Question 1 of 10
role="button" and a click listener. What does the role itself provide?Show answerHide answer
Save this lesson to see this answer.
Question 2 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 3 of 10
activate(). It has aria-disabled="true". The function starts with if (button.getAttribute("aria-disabled") === true) return;, yet both routes still act. What repair enforces the disabled state while allowing the string "false" state to act?Show answerHide answer
Save this lesson to see this answer.
Question 4 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 5 of 10
aria-expanded="true" and a visible panel. Its click handler reads the old expanded state. What should the next state be?Show answerHide answer
Save this lesson to see this answer.
Question 6 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 7 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 8 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 9 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 10 of 10
tabindex="-1". The focused delete button in the last item removes that item; later the remaining item is deleted too. What focus sequence supports continued keyboard use?Show answerHide answer
Save this lesson to see this answer.
Stuck on your own question? Homework Help
Key Terms
A
AbortController
abort() method is called.Accessible name
aria-label="Close" to state what its symbol does.Activation
click for pointer and native keyboard use.click whether pressed with Space or a mouse.ARIA role
role="button" still needs code for activation and an appropriate Tab stop.ARIA state
aria-checked value updates when its visible on/off appearance changes.aria-describedby
aria-describedby attribute associates a control with descriptive text by listing the IDs of the elements containing that text.email-error so assistive technology can connect the error to it.aria-invalid
aria-invalid attribute communicates that a control’s entered value has an error; code must update it as the error changes.aria-invalid="true" while its inline correction message is displayed.B
Bubbling
bubbles value is true.input event bubbles to a listener registered on the enclosing form.Button bitmask
buttons property.buttons 3, combining flags 1 and 2.C
cancelable
cancelable property is a Boolean indicating whether an event permits cancellation of its default action through preventDefault().input event reports an edit already made, so its cancelable property is false.Capturing phase
change event
change event reports a committed user change, such as leaving an edited text field or selecting a new checkbox or select state.change when the user leaves it for the next control.checked
checked property is the Boolean state indicating whether a checkbox or radio input is selected, independently of its submission value.checkbox.checked to decide whether to show an optional section.checkValidity
checkValidity() method returns whether constraints pass and fires invalid events for failures without displaying browser feedback.closest
closest() method checks an element and then its ancestors for the nearest selector match, returning null if none exists.closest("button") finds the surrounding action button.code
code property identifies a physical key position independently of the character produced by the current keyboard layout.code when its movement controls should stay in fixed physical positions.Composite group
Constraint validation
contains
contains() method tests whether a node is an element itself or one of its descendants, returning a Boolean result.list.contains(button) before allowing the button’s action.currentTarget
currentTarget property identifies the object whose listener is running and is reset to null after event dispatch ends.currentTarget lets code read that button’s data.Custom validity
setCustomValidity(), where a nonempty message marks the control invalid.CustomEvent
detail and can be configured to bubble to ancestors."cart:add" with its item identifier in the event’s detail.D
Data attribute
data- that stores application information available through an element’s dataset.data-action="clear" as button.dataset.action to select a known action.Default action
defaultPrevented
defaultPrevented property is a Boolean recording whether an event’s default action has been successfully canceled.defaultPrevented before deciding how to handle a click.Disclosure
aria-expanded.Document coordinates
pageX and pageY, including scrolling.pageY 400.Drag state
E
Error summary
Event
click event lets the page respond when a person activates the gallery’s next button.Event delegation
Event listener
Event object
key to decide whether to close a panel.F
Focus
document.activeElement.search.focus() directs the next typed character into the search field.Focus indicator
:focus-visible outline shows which toolbar button will receive the next key action.Focus management
Form reset
form.reset() restores the form’s starting field values.FormData
data.getAll("topics") retrieves all the selected ones together.Function reference
openPanel registers the function; passing openPanel() calls it during setup.H
Handler property
onclick that holds one event callback and replaces it when assigned again.button.onclick replaces that property’s earlier handler.I
Inert background
input event
input event reports a user edit after a control’s value changes, including text edits made without a physical key press.input listener.K
key
key property reports a key’s produced value, taking the keyboard layout and modifiers such as Shift into account.key value from "a" to "A" while its position stays the same.keydown
keydown event reports a key press before its normal editing effect and may repeat while the physical key remains held down.keydown before closing the open panel.L
Listener identity
M
Modal dialog
showModal() opens the native dialog and moves focus into its content.Modifier flag
ctrlKey or metaKey together with the letter S.N
Native control
novalidate
novalidate form attribute skips automatic constraint validation during submission while leaving constraints and validity methods available.novalidate so its submit listener can inspect every field itself.O
once
once listener option requests automatic removal of that registration after its first invocation for the selected event type.once: true for the click that dismisses it for this lifetime.Opener
Outside-click test
P
Passive listener
preventDefault() in a passive touch listener does not stop the browser’s scrolling.pattern
pattern attribute defines a whole-value regular-expression constraint for supported text inputs, applied only when their value is nonempty.required to reject an empty field.Pointer capture
Pointer event
pointermove listener can read movement from a mouse or a touch contact.pointercancel
pointercancel event reports that the browser has canceled the pointer interaction, requiring a drag to clear its active state.pointercancel.pointerId
pointerId property identifies an individual pointer during an interaction so simultaneous contacts can be handled separately.preventDefault
preventDefault() method cancels an eligible event’s default action in a non-passive listener without stopping its propagation.preventDefault() before adding a local item without navigation.Propagation
R
relatedTarget
relatedTarget property identifies the other element involved in a focus transition, or is null when no such element is available.focusout listener checks whether the destination remains inside the menu.reportValidity
reportValidity() method checks constraints and also asks the browser to display validation feedback for unhandled failures.reportValidity() to help the user find a failing field.requestSubmit
requestSubmit() method requests form submission through the normal validation and submit-event path without firing a button click.form.requestSubmit() so the form’s validation and submit listener still run.Roving tabindex
S
Single-pointer alternative
stopImmediatePropagation
stopImmediatePropagation() method stops event travel and also skips remaining listeners registered on the current event target.stopPropagation
stopPropagation() method prevents an event from traveling to further elements while leaving other listeners on its current element active.submit event
submit event is a cancelable form event dispatched during the normal submission path after automatic validation allows submission.submit listener handles a valid submission by adding an item locally and canceling navigation.Submitter
event.submitter when a particular button initiated that route.Synchronous dispatch
dispatchEvent() call, before that method returns control to the calling code.dispatchEvent() appears after messages from its listeners.T
tabindex
tabindex attribute controls ordinary Tab participation and programmatic focusability, with 0 in normal order and −1 outside that sequence.tabindex="-1" so code can focus it without adding a regular Tab stop.target
target property identifies the event’s origin, which can be a nested element inside the control whose action is being handled.target.Target phase
eventPhase 2 while they are called.Toggle button
aria-pressed reporting the current state.aria-pressed value changes between true and false.touch-action
touch-action CSS property declares which browser touch gestures are permitted when a gesture starts on an element.touch-action: none on the drag surface reserves that area’s movement for the drag.V
ValidityState
validity property, with Boolean flags identifying failed constraints.stepMismatch even though it is inside its minimum and maximum.value
value property exposes a form control’s current value as text, including number inputs whose numeric content still needs conversion."8" needs conversion before code adds a numeric increment.valueAsNumber
valueAsNumber property reads a supported input’s numeric value directly, returning NaN when no usable numeric value is present.NaN, which code detects with Number.isNaN().Viewport coordinates
clientX and clientY.W
Whitespace trimming
name.value.trim() === "" catches a name made entirely of spaces.About this lesson
Make your own Study Materials
Turn your notes, slides and documents into Study Guides, Practice Questions, Flashcards and 6 more tools.