Skip to content
Astera Technology

Find the intended action when a nested element is clicked

Resolve a nested click to its action button within the delegated container.

Download complete case notes and answer keys ↓

Inspect the fixed example

The fixed click targets a span inside the preview button. The listener belongs to tools. Resolve the nearest action button before reading data-action.

Complete body and fixed script
<style>html{font-size:16px}body{margin:12px;font:1rem/1.5 system-ui,sans-serif;color:#17332d;background:#fff}p{overflow-wrap:anywhere}output{display:block;margin-block:12px;overflow-wrap:anywhere;white-space:pre-wrap}button,input{font:inherit;max-width:100%;box-sizing:border-box}button{margin:4px;padding:8px}label{display:block;margin-block:8px}button:focus-visible,input:focus-visible{outline:3px solid #23715d;outline-offset:2px}ul{padding-inline-start:24px}li{overflow-wrap:anywhere}.highlight{border:2px solid green}</style>
<section id="tools"><button type="button" data-action="preview"><span id="label">Preview source</span></button><p id="plain">Plain note</p></section><output id="result"></output>
<script>const tools=document.querySelector('#tools');
tools.addEventListener('click',event=>{const button=event.target.closest('button[data-action]');if(button&&tools.contains(button))document.querySelector('#result').textContent=button.dataset.action;});
document.querySelector('#label').click();console.log(document.querySelector('#result').textContent);
</script>
Exact console output
preview
Inputs and native results
InspectionSupplied input or actionResult
target#label spanHas no data-action
currentTarget#tools sectionOwns listener
closestbutton[data-action]preview

Follow the native mechanism

event.target identifies the original target; currentTarget identifies the listener’s container during the callback. closest includes the target itself and searches its ancestors. The contains check keeps the selected button inside tools. This fixed event target is an Element; a general handler must check that assumption.

Compare the material alternative

Reading target.dataset.action directly sees undefined on the span, although the enclosing button holds preview.

Fresh counterexample source
<style>html{font-size:16px}body{margin:12px;font:1rem/1.5 system-ui,sans-serif;color:#17332d;background:#fff}p{overflow-wrap:anywhere}output{display:block;margin-block:12px;overflow-wrap:anywhere;white-space:pre-wrap}button,input{font:inherit;max-width:100%;box-sizing:border-box}button{margin:4px;padding:8px}label{display:block;margin-block:8px}button:focus-visible,input:focus-visible{outline:3px solid #23715d;outline-offset:2px}ul{padding-inline-start:24px}li{overflow-wrap:anywhere}.highlight{border:2px solid green}</style>
<section id="tools"><button type="button" data-action="preview"><span id="label">Preview source</span></button></section><output id="result"></output>
<script>let observed;document.querySelector('#tools').addEventListener('click',event=>{observed=event.target.dataset.action;});document.querySelector('#label').click();
const result={actionType:typeof observed};
document.querySelector('#result').textContent=JSON.stringify(result);
console.log(JSON.stringify(result));
</script>
Counterexample output
{"actionType":"undefined"}

Work the boundary

Will clicking a plain paragraph write an action, and does clicking the button itself still work?

Show the worked answer

The paragraph is ignored. The button matches closest itself and resolves preview; the trace identifies both targets and the shared container.

Fresh boundary source
<style>html{font-size:16px}body{margin:12px;font:1rem/1.5 system-ui,sans-serif;color:#17332d;background:#fff}p{overflow-wrap:anywhere}output{display:block;margin-block:12px;overflow-wrap:anywhere;white-space:pre-wrap}button,input{font:inherit;max-width:100%;box-sizing:border-box}button{margin:4px;padding:8px}label{display:block;margin-block:8px}button:focus-visible,input:focus-visible{outline:3px solid #23715d;outline-offset:2px}ul{padding-inline-start:24px}li{overflow-wrap:anywhere}.highlight{border:2px solid green}</style>
<section id="tools"><button id="action" type="button" data-action="preview"><span id="label">Preview source</span></button><p id="plain">Plain note</p></section><output id="result"></output>
<script>const tools=document.querySelector('#tools');const trace=[];tools.addEventListener('click',event=>{const button=event.target.closest('button[data-action]');trace.push({target:event.target.id,currentTarget:event.currentTarget.id,action:button&&tools.contains(button)?button.dataset.action:null});});
document.querySelector('#plain').click();document.querySelector('#action').click();const result=trace;
document.querySelector('#result').textContent=JSON.stringify(result);
console.log(JSON.stringify(result));
</script>
Boundary output
[{"target":"plain","currentTarget":"tools","action":null},{"target":"action","currentTarget":"tools","action":"preview"}]

Original example files