Why is this secret trapdoor triggering on every nested click?
<div id="trapdoor" style="padding: 24px; background: #332;">
<button id="latch">Unlock Hatch</button>
<span id="status">Locked</span>
</div>
<script>
const trapdoor = document.querySelector("#trapdoor");
const status = document.querySelector("#status");
trapdoor.addEventListener("click", (e) => {
if (e.target.id === "trapdoor") {
status.textContent = "Trapdoor sprung!";
}
}, true);
</script>
Reply with what is broken and how you would fix it.
The issue is that the click listener uses capture: true, but the main bug is the condition: it only triggers when e.target.id === “trapdoor”. Clicking the button or status text would not match because they are the actual event targets. If you want clicks anywhere inside the trapdoor to trigger it, remove the condition. If you only want clicks on the trapdoor’s background, keep the condition and use the default bubbling phase instead of capture.
Interesting thoughts on the event phases and target. We’ll post the solution later today.
The trapdoor trigger not resetting after a save load is a classic one. I’ve definitely made that mistake before with event flags.
Spot the Bug answer: The event listener for the trapdoor is using capture phase (true) and checking e.target.id, which causes it to trigger for clicks on child elements when the event bubbles up.
The fix:
Change the third argument of addEventListener to false or remove it, and check e.currentTarget.id instead of e.target.id.
Why:
When the third argument of addEventListener is true, the event listener is triggered during the capturing phase, meaning it fires before the event reaches its target. The e.target property always refers to the element that originated the event (e.g., the button or span), not the element the listener is attached to. Therefore, the condition e.target.id === ‘trapdoor’ will only be true if the click directly hits the trapdoor div itself, not its children, even though the listener is in the capture phase.
Got it: @emmawalter5 
First-answer leaderboard
- @emmawalter5 - 12 (firsts)

- @kirupa - 8 (firsts)
- @Apexcodes - 6 (firsts)
- @adnanahmed - 2 (firsts)
Nice catch, Emma. The e.target vs e.currentTarget distinction is exactly the part that trips people up here — clicking the latch button or the status text looks like it should count, but e.target is the nested element, not the trapdoor itself.
I also liked that you gave both fixes instead of just one: drop the condition if you want any click inside to count, or keep it and switch back to bubbling. That’s the practical version of the answer, not just the textbook one.