Before this lesson
Understand click, focusout, event propagation and the language-picker example.
What you will be able to do
Reproduce a disclosure whose options cannot be clicked and avoid removing targets before activation.
Understand the mechanism
Pointer activation includes more than click. Pressing can change focus before release and activation. During transitions, activeElement may temporarily be body. Hiding a menu immediately on focusout can remove the link before its click occurs.
relatedTarget describes the next focus target but can be null. Keep the disclosure open for internal targets, close for known external targets, and do not interpret null as confirmed departure. Outside pointer interaction and Escape provide clearer signals.
Separate disclosure from navigation. Let the trigger control visibility and use real links for options. An outside-pointer handler checks containment, while Escape returns focus and tabbing out closes. Native links preserve standard navigation capabilities.
Test pointer and keyboard paths. Programmatic element.click does not necessarily reproduce press and focus transitions, so it can miss races. Record relevant events and relatedTarget to reason from observed order.
Runnable example
Run HTML examples as separate .html files in a modern browser. Prepare resource files and multi-file modules as described in the lesson.
<!doctype html>
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Honeysuck practice</title></head><body>
<div id="picker"><button id="trigger" type="button" aria-expanded="false" aria-controls="options">Choose section</button>
<div id="options" hidden><a href="#html-topic">HTML</a> <a href="#css-topic">CSS</a></div></div>
<p><a href="#after">Outside link</a></p><h2 id="html-topic">HTML topic</h2><h2 id="css-topic">CSS topic</h2><p id="after">End</p>
<script>
const root=document.querySelector('#picker'),trigger=document.querySelector('#trigger'),options=document.querySelector('#options');
function open(value){options.hidden=!value;trigger.setAttribute('aria-expanded',String(value));}
trigger.addEventListener('click',()=>open(options.hidden));
document.addEventListener('pointerdown',event=>{if(!root.contains(event.target))open(false);});
root.addEventListener('focusout',event=>{if(event.relatedTarget&&!root.contains(event.relatedTarget))open(false);});
root.addEventListener('keydown',event=>{if(event.key==='Escape'){event.preventDefault();open(false);trigger.focus();}});
</script>
</body></html>
Trace and verify
Open with a pointer and select CSS; the fragment must change. Repeat with Tab and Enter. Outside clicking closes; Escape closes and returns focus. Compare focus logs for real activation and programmatic click.
Common mistakes and boundaries
An arbitrary delay merely changes the race window. Avoid canceling every link default action unless intentionally implementing all navigation behavior.
Practice challenge
Add ArrowDown to open and focus the first option. Escape from the second returns to the trigger. Record a failing-before and passing-after sequence.
Reference solution and reasoning
Handle ArrowDown on the trigger, prevent its default, open and focus the first link. Leave other keys and Tab behavior native. Verify actual navigation rather than only a disappearing menu.