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.

Further reading