Before this lesson

Understand buttons, links, DOM events, focus and the hidden attribute.

What you will be able to do

Implement a two-step language picker with deliberate selection and predictable keyboard closing.

Understand the mechanism

The trigger controls visibility and option links perform navigation. Clicking the trigger only expands choices; it does not change the URL or language. Keep aria-expanded synchronized with hidden and point aria-controls at the actual container.

This is a disclosure of ordinary links, not a full application menu. Native buttons support Enter and Space, and links support Tab. Escape closes and returns focus; outside clicks or focus leaving the region also close it.

A real site preserves article ID, search parameters and fragment while replacing only lang. The simplified links demonstrate interaction; the snippet itself has no translation engine and must connect to bilingual content.

Runnable example

<div id="picker">
  <button id="trigger" type="button" aria-expanded="false" aria-controls="choices">Language ▾</button>
  <div id="choices" hidden><a href="?lang=zh">中文</a> <a href="?lang=en">English</a></div>
</div>
<script>
const root = document.querySelector('#picker');
const trigger = document.querySelector('#trigger');
const choices = document.querySelector('#choices');
function setOpen(open) { choices.hidden = !open; trigger.setAttribute('aria-expanded', String(open)); }
trigger.addEventListener('click', () => setOpen(choices.hidden));
document.addEventListener('click', event => { if (!root.contains(event.target)) setOpen(false); });
root.addEventListener('keydown', event => {
  if (event.key === 'Escape') { setOpen(false); trigger.focus(); }
});
root.addEventListener('focusout', event => {
  if (event.relatedTarget && !root.contains(event.relatedTarget)) setOpen(false);
});
</script>

Trace and verify

First click: URL unchanged and options visible. Second click: closed. Open again, Tab to an option, and Escape back to the trigger. Outside clicks and tabbing away close it. Only selecting a link navigates.

Common mistakes and boundaries

Never navigate on hover or change language when merely opening the picker. hidden removes invisible links from keyboard navigation; opacity alone does not.

Practice challenge

Construct links with URL and searchParams, preserve the hash, and mark the active language with aria-current.

Reference solution and reasoning

Copy location.href into a URL, set lang on the copy and assign href. Changing only lang preserves other parameters. Mark the current option with aria-current=true while retaining a usable link.

Further reading