cf.completefrontendCode editorOpen lab
THE JAVASCRIPT FIELD GUIDE

Keyboard & focus

Handle keyboard events, focus movement, focus traps, disclosures, and live regions without breaking accessibility.

By the end you can
  • 01
    Read keyboard eventsUse keydown, keyup, key, code, modifiers, repeat, and composition correctly.
  • 02
    Manage focusExplain focus order, tabindex, bubbling focus events, activeElement, and focus({ preventScroll }).
  • 03
    Build accessible controlsTrap focus in dialogs, return focus to the opener, and announce changes with aria-expanded and live regions.

Keyboard access is real access

A pointer can click what it sees. A keyboard moves through the page one focused control at a time, then sends key events to that focused place. If your interface cannot be reached, opened, escaped, and understood from a keyboard, many people cannot use it comfortably at all.

This lesson connects two systems that work together: keyboard events tell you what happened on the keyboard, and focus tells you which element receives those events. We will build inspectors, shortcuts, logs, a modal trap, and a live status widget using the browser’s real behavior.

Real-life analogyFocus is a stage spotlight

Think of focus as the page’s spotlight. Keyboard events go to the actor in the light. Tab and Shift+Tab move the light. A modal dialog is a smaller stage: the spotlight must stay there until the scene is over.

In real life: One actor is lit
In JavaScript: One element is document.activeElement
In real life: Tab moves the spotlight
In JavaScript: Normal focus order follows readable, focusable controls
In real life: A dialog keeps the scene contained
In JavaScript: A focus trap keeps Tab inside until the dialog closes
In real life: The spotlight returns to the opener
In JavaScript: After closing, focus should go back to the button that opened it

Where the analogy stops: Real spotlights can light several actors at once. Browser focus normally has one active element per document, with extra details for shadow trees and iframes.

The one-sentence definition

Keyboard and focus code handles keys according to the element that currently has focus, while preserving native text entry, Tab order, Escape, and assistive-technology announcements.

keydown and keyup

INTERACTIVE

The browser fires keydown when a key goes down and keyup when it comes back up. If you hold a key, most browsers repeat keydown; repeated events have event.repeat set to true. The matching keyup fires once when you release.

The old keypress event is deprecated. It mixes “a key was pressed” with “text might be typed” and does not handle modern input well. For text entry, listen to input. It covers typing, paste, autocomplete, mobile keyboards, and input method editors.

Key inspector: press real keys
Key inspector listenersPop out in the code editor (opens in a new tab)JavaScript
box.addEventListener("keydown", (event) => {  if (event.key === " " || event.key === "ArrowDown") event.preventDefault();  show({    key: event.key,    code: event.code,    repeat: event.repeat,    shiftKey: event.shiftKey,    ctrlKey: event.ctrlKey,    altKey: event.altKey,    metaKey: event.metaKey,    capsLock: event.getModifierState("CapsLock"),    isComposing: event.isComposing,  });}); box.addEventListener("keyup", showRelease);// `keypress` is deprecated; use `input` for text and `keydown`/`keyup` for keys.
Real event output0 recent events
Focus meTry A, Shift+A, Space, arrows, CapsLock, and holding a key.
    Try it yourself

    Focus the box, then press keys.

    The focusable demo box uses tabindex="0". Space and ArrowDown are prevented only inside the box so the page does not scroll while you inspect them. IME composition depends on your keyboard/input method.

    Notice the modifier flags: shiftKey, ctrlKey, altKey, and metaKey. Caps Lock is different: ask event.getModifierState("CapsLock"). During IME text composition, event.isComposing can be true, so shortcut code should avoid treating those keystrokes like final text.

    key vs code

    STEP THROUGH

    event.key is the meaning of the key for the user’s current layout: "a", "A", "Enter", "ArrowLeft", " " for Space, or sometimes "Dead" for a dead key waiting to combine with another character. event.code names the physical seat: values like "KeyA", "Digit1", "Space", and "ShiftLeft".

    Real-life analogyKeycap vs keyboard seat

    The key is like the letter printed on the keycap for the current layout. The code is the chair that key occupies on the keyboard.

    In real life: Printed keycap
    In JavaScript: event.key: what the user meant in this layout
    In real life: Physical seat on the board
    In JavaScript: event.code: where the key sits
    In real life: French layout changes the printed letter
    In JavaScript: QWERTY Q position can report key as a but code as KeyQ
    In real life: Game controls may care about a seat
    In JavaScript: WASD-style movement often prefers code

    Where the analogy stops: The browser cannot always know unusual hardware perfectly, and mobile keyboards often do not map to physical seats at all. For text, prefer the input event.

    Replay a respectful Ctrl/Command+K shortcut
    Step 0 of 6Ready
    Your turn: follow the blue line

    Replay a keyboard shortcut handler that respects focused text fields.

    Running in
    1. script
    Next: line 1
    Click the blue line to take the next stepPop out in the code editor (opens in a new tab)JavaScript
    const event = { key: "k", metaKey: true, ctrlKey: false, repeat: false };const editingText = target === "input";const commandKey = event.metaKey || event.ctrlKey;const opensPalette = commandKey && event.key.toLowerCase() === "k" && !editingText;console.log(opensPalette ? "open palette" : "let the browser type");
    CallStoreChangeResultRun = next line. Ran = already executed.
    Recent returnsNothing yet. Start with the blue line.
    Where is focus when the shortcut happens?

    Change the setting, then replay. A good shortcut handler opens the palette on the surface and ignores the same keys while the learner is typing.

    A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

    Shortcuts should be scoped. This lesson’s command palette listens only inside its demo surface, handles Command on macOS and Ctrl elsewhere, and calls preventDefault() only for the exact shortcut it owns.

    Shortcut builder: open the tiny palette
    Scoped shortcut handlerPop out in the code editor (opens in a new tab)JavaScript
    demo.addEventListener("keydown", (event) => {  const editing = event.target.matches("input, textarea, [contenteditable]");  const command = event.metaKey || event.ctrlKey;  if (command && event.key.toLowerCase() === "k" && !editing) {    event.preventDefault();    openPalette();  }});
    Demo surface (focusable)palette closed
      Try it yourself

      Focus the demo surface and press Ctrl+K or Command+K. Then try the input: typing wins there.

      The listener is scoped to this demo, not the whole page. It ignores shortcuts while text controls have focus.
      Key or code?
      • A shortcut labeled Ctrl+Z
      • Detecting Enter to submit a search
      • A WASD game that cares about physical positions
      • A browser piano using the row KeyA through KeyL
      • Typing a person's name into a field
      • Counting characters in a comment box
      Try it yourself
      0 of 6 correct

      Sort each scenario by the event signal you should reach for first.

      Choose a category for every card. You can change an answer at any time; Reset clears them all.

      Focus, blur, focusin and focusout

      INTERACTIVE

      Focus is not only a visual ring. It is the browser’s routing table for keyboard input. Native controls such as links, buttons, inputs, selects, and textareas are focusable. A custom element can join normal Tab order with tabindex="0". Use tabindex="-1" for elements you will focus only with code. Positive tabindex values are discouraged because they create a surprising order.

      Focus events at a glance
      EventBubbles?Good use
      focusNoReact to one element receiving focus, or listen in capture.
      blurNoReact to one element losing focus, or listen in capture.
      focusinYesLog or delegate focus changes from a container.
      focusoutYesLog or delegate focus leaving a container.
      Focus event log
      Focus listenersPop out in the code editor (opens in a new tab)JavaScript
      container.addEventListener("focus", log, true);container.addEventListener("blur", log, true);container.addEventListener("focusin", log);container.addEventListener("focusout", log); laterButton.focus({ preventScroll: true });console.log(document.activeElement);
      Focusable controlsactive: (none yet)
      Custom panel with tabindex 0
        Try it yourself

        document.activeElement is (none yet). Tab through the controls and compare non-bubbling focus/blur with bubbling focusin/focusout.

        relatedTarget can be null when focus enters from, or leaves to, outside this document/window.

        document.activeElement tells you the active element after focus settles. relatedTarget is the element focus is moving from or to; it can be null when focus crosses document or browser boundaries. When you move focus with code, use element.focus({ preventScroll: true }) if scrolling would be disorienting.

        Style focus clearly. Modern browsers expose :focus-visible so you can show a strong ring for keyboard focus without forcing the same treatment on every mouse click.

        Focus traps and dialogs

        INTERACTIVE

        A modal dialog interrupts the page. While it is open, Tab should cycle through the dialog’s focusable controls, Escape should close it unless there is a good reason not to, and focus should return to the opener. Otherwise keyboard users can fall behind the overlay or lose their place.

        Focus trap: keep the spotlight in the modal
        Manual trap corePop out in the code editor (opens in a new tab)JavaScript
        const focusables = [...dialog.querySelectorAll("button, input, [href]")];dialog.addEventListener("keydown", (event) => {  if (event.key === "Escape") closeDialog();  if (event.key !== "Tab") return;  const first = focusables[0];  const last = focusables.at(-1);  if (event.shiftKey && document.activeElement === first) {    event.preventDefault();    last.focus();  } else if (!event.shiftKey && document.activeElement === last) {    event.preventDefault();    first.focus();  }});
        Modal stageclosed
        Try it yourself

        Open the modal. A native <dialog>.showModal() gives you browser-managed inertness; this demo shows the manual trap logic you must get right when hand-building one.

        For production, prefer native <dialog> when it fits. Hand-built traps also need background inertness, labelling, Escape handling, and focus return.
        Native dialog vs hand-written trap
        ChoiceWhat it gives youWhat to still check
        dialog.showModal()Makes outside page content inert in supporting browsers and handles Escape through a cancel event.Label the dialog, place useful initial focus, and explicitly return focus because browser details can differ.
        Manual trapWorks for custom overlays when native dialog is not a fit.Cycle Tab/Shift+Tab, handle Escape, set background inert, label the dialog, and restore focus.
        Pure focus wrap modelPop out in the code editor (opens in a new tab)JavaScript
        const focusables = ["open", "cancel", "save"];let activeIndex = 0;function moveFocus(direction) {  activeIndex = (activeIndex + direction + focusables.length) % focusables.length;  return focusables[activeIndex];}console.log(moveFocus(1));console.log(moveFocus(-1));

        aria-expanded and aria-live

        INTERACTIVE

        When a button opens and closes a panel, keep the relationship visible to assistive technology: put aria-expanded on the button and point aria-controls at the panel. The button still needs real button behavior; ARIA describes state, it does not add click or keyboard behavior by itself.

        Real-life analogyaria-live is a stadium announcer

        A live region is like an announcer who speaks important updates while the spotlight stays on the current control.

        In real life: Announcer reads score updates
        In JavaScript: A live region announces changed text
        In real life: Fans keep watching the field
        In JavaScript: Focus does not move to the status
        In real life: Routine updates wait for a pause
        In JavaScript: aria-live="polite" or role="status"
        In real life: Emergency announcement interrupts
        In JavaScript: assertive or role="alert" for urgent messages

        Where the analogy stops: Screen readers and settings vary. The standard intent is announcement without focus movement; do not use live regions as a substitute for testing with assistive technology.

        Disclosure plus live status
        Disclosure and live regionPop out in the code editor (opens in a new tab)JavaScript
        button.setAttribute("aria-expanded", String(open));button.setAttribute("aria-controls", "filter-panel");panel.hidden = !open; status.textContent = `3 results found`;// The live region existed before this text changed.
        Filter widget3 results found
        • Buttons
        • Dialogs
        • Live regions
        Try it yourself

        The button says aria-expanded=false. The live status text is present from the start and now says: 3 results found.

        role="status" is usually polite. Use role="alert" or assertive live regions only for urgent messages.

        Put the live region in the document before changing its text. Use polite for ordinary updates such as “3 results found.” Use assertive live regions or role="alert" sparingly for urgent information because the standard intent is to interrupt.

        Where you will use this

        Keyboard and focus details show up in almost every serious interface: search boxes that submit on Enter, menus that close on Escape, command palettes, skip links, form validation summaries, disclosure panels, autocomplete lists, and modals.

        A respectful shortcut patternPop out in the code editor (opens in a new tab)JavaScript
        const target = "page";const event = { key: "k", metaKey: true, ctrlKey: false, repeat: false };const editingText = target === "input";const commandKey = event.metaKey || event.ctrlKey;const opensPalette = commandKey && event.key.toLowerCase() === "k" && !editingText;console.log(opensPalette ? "open palette" : "let the browser type");
        • Use keydown for commands, not text validation.
        • Check the focused target before stealing a shortcut.
        • Prevent the browser default only for the key combination you own.
        • Return focus after temporary UI closes.

        Common misconceptions

        • “keypress is the text event.” It is deprecated; use input for text changes.
        • “key and code are interchangeable.” key follows meaning and layout; code follows physical position.
        • “A div with click is a button.” A real button already has focus, Enter/Space behavior, disabled state, and semantics.
        • “focus and blur bubble.” They do not; use capture or the bubbling focusin/focusout pair.
        • “A modal is accessible once it is visually centered.” It also needs focus management, inert background, labeling, Escape behavior, and return focus.
        • “Live regions are magic.” They communicate standard intent, but assistive technology behavior varies and must be tested.

        Practice exercises

        5 EXERCISES
        Exercise 1 · Warm-upPredict the key event

        Answer from memory: which keyboard event repeats while a key is held down?

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 2 · PracticeRead the output

          Run the snippet in your head. What exact string is logged first?

          Starter codePop out in the code editor (opens in a new tab)JavaScript
          const event = { key: "Enter", repeat: false };
          console.log(event.key);
          console.log(event.repeat);

          Answer, then press Check. Spacing and letter case don’t matter.

            Exercise 3 · PracticeWrite a safe shortcut guard

            Write or explain a helper that opens a command palette for Ctrl/Command+K but not while typing in an input.

            Starter codePop out in the code editor (opens in a new tab)JavaScript
            function shouldOpenPalette(event, editingText) {
              const command = event.metaKey || event.ctrlKey;
              return command && event.key.toLowerCase() === "k" && !editingText;
            }
            console.log(shouldOpenPalette({ key: "k", metaKey: true, ctrlKey: false }, false));
            console.log(shouldOpenPalette({ key: "k", metaKey: true, ctrlKey: false }, true));
              Exercise 4 · PracticeFind the focus bug

              A form section listens for focus on the container and expects to hear child inputs. Which bubbling event should it use instead?

              Answer, then press Check. Spacing and letter case don’t matter.

                Exercise 5 · ChallengePlan a modal close

                You made a custom panel that should be reachable with Tab in the normal reading order. What tabindex value does that?

                Answer, then press Check. Spacing and letter case don’t matter.

                  Check your understanding

                  7 QUESTIONS
                  Keyboard and focus quiz · 7 questionsScore: first tries count
                  1. Question 1 of 7What happens when a physical key is held down in most browsers?

                    Choose an answer to see the explanation.

                  2. Question 2 of 7What does this print for event.key?

                    Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                    const event = { key: "Enter", code: "NumpadEnter" };
                    console.log(event.key);

                    Choose an answer to see the explanation.

                  3. Question 3 of 7On a French AZERTY keyboard, pressing the physical QWERTY Q position may produce key: "a" and code: "KeyQ". Which property tells what the user meant to type?

                    Choose an answer to see the explanation.

                  4. Question 4 of 7Which event should you prefer for text entry in an input?

                    Choose an answer to see the explanation.

                  5. Question 5 of 7Which focus events bubble from children to a container?

                    Choose an answer to see the explanation.

                  6. Question 6 of 7What does this focus-wrap snippet print?

                    Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                    const focusables = ["cancel", "save"];
                    let index = 0;
                    index = (index - 1 + focusables.length) % focusables.length;
                    console.log(focusables[index]);

                    Choose an answer to see the explanation.

                  7. Question 7 of 7Which statement about live regions is accurate?

                    Choose an answer to see the explanation.

                  Key takeaways

                  • keydown starts a key action, can repeat, and is good for commands; keyup marks release.
                  • key means “what the user intended”; code means “which physical key seat.”
                  • Use input for text changes, especially for IME, paste, mobile keyboards, and autocorrect.
                  • focus/blur do not bubble; focusin/focusout do.
                  • Modals must keep focus inside, support Escape when appropriate, make the background inert, and return focus.
                  • aria-expanded describes disclosure state; live regions announce updates without moving focus.
                  Final definition

                  Keyboard-friendly JavaScript lets the focused element receive meaningful key commands while focus management and ARIA keep the interface reachable and understandable.

                  Up next: Page & resource loading.

                  CompleteFrontend Clear concepts. Working examples.