Command Palette

Search for a command to run...

Hectal
Course outline
Topic 8.4Intermediate ~21 min · step 4 of 6

Keyboard Controls

In one line

Every pointer action gets a keyboard twin: arrows turn, plus and minus zoom, Tab walks through the parts, Enter chooses. The canvas needs a way to take focus, a visible focus ring, and no keyboard trap.

0/6 · 0%

Think of it like this

A lift with a button panel. You can press the buttons with a finger, an elbow or a stick, and a blind passenger finds them by touch and braille. A viewer that works only with a mouse is a lift with one button nobody can reach.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

keyboard twin
A key or a button that does the same job as a mouse or touch action, such as arrows instead of dragging.
focus
The one element on the page that receives key presses. Tab moves it to the next one.
keyboard trap
A place where focus gets stuck: you can press Tab as often as you like and never leave.
live region
An element marked aria-live. When its text changes, a screen reader speaks the new text.
tabindex="0"
A way to make any element reachable with Tab, in the order of the page.

Step by step

01Let the container take focus

Add tabindex="0", role="group" and an aria-label such as "Bike viewer. Arrow keys turn, plus and minus zoom." Style :focus-visible with a blue outline so a keyboard user can see where they are.

02Handle only the keys you own

In the keydown handler, return at once if Ctrl, Alt or Meta is down. Set a handled flag for the arrows, plus, minus and Escape. At the end, call preventDefault() only if handled is true.

03Make steps predictable

Turn in fixed steps (15 degrees is a good size) and zoom in fixed steps with a minimum and a maximum, so repeated presses stop at a limit instead of passing through the bike.

04Build the parts list from real buttons

One button per part, in a list. click chooses, focus lights the part, aria-pressed shows the state. A key press on a focused button produces a normal click event, so you write no key code at all.

Try it yourself

  1. 1

    Accept = as well as +

    In the keys example the handler already treats "=" like "+". Change the two press("+") calls to press("="): the line + twice still ends in camera distance 3. On a US keyboard the + key is Shift and =, so a user who presses it without Shift sends "=".

  2. 2

    Bigger steps

    Change const STEP = THREE.MathUtils.degToRad(15); to degToRad(15) * 2. Two presses of ArrowRight now print 60 degrees, and the three ArrowLeft presses end at -30 degrees. 15 degrees gives 24 stops for a full turn.

Code & diagrams

Arrow keys turn, plus and minus zoom New tab

Click the canvas or Tab to it (a blue ring shows the focus), then press the arrows, + and -. The text output comes from simulated key presses on the same handler.

idle

Console

Run the scene to see its console.

Expected console output

ArrowRight x2 -> 30 degrees
ArrowLeft x3  -> -15 degrees
+ twice       -> camera distance 3
+ twenty more -> camera distance 1.5 (stops at the limit)
Tab handled?  -> false (false = left alone, so the browser moves focus on)
Ctrl+ArrowLeft handled? false | turn is still -15 degrees
Tab through the parts, and a keyboard trap New tab

Press Tab and then Enter: the buttons are real, so they work from the keyboard with no key code. The last two lines show a trap and its fix.

idle

Console

Run the scene to see its console.

Expected console output

tab stops: bell -> saddle -> badge
selected: saddle
selected: nothing
selected: badge
handler that calls preventDefault on every key: TRAP: Tab is swallowed
handler that only takes arrow keys: Tab works

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Swallow every key

In the keys example change else handled = false; to else handled = true;.

terminal
$ Run
── what you'll see ──
Tab handled? -> true (false = left alone, so the browser moves focus on)
The handler now calls preventDefault() on Tab.

Myth vs fact

Myth

A keyboard user can use the mouse handlers, since Tab and Enter click things.

Fact

Enter and Space only click focusable elements. A canvas is not focusable, and a click handler on it never fires from the keyboard. You write the twin.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    A held key repeats keydown (event.repeat is true), so holding ArrowRight steps again and again. For smooth turning, record which keys are down on keydown and keyup, and move by speed * delta in the loop (Topic 3.2). Clear the record on blur, or a key that was released while focus was elsewhere stays stuck.

Remember this

  1. 1

    The problem this solves: Ravi uses only a keyboard, and others cannot hold a mouse steady. WCAG 2.1.1 (Keyboard) says everything a page does must be possible from the keyboard. A canvas is a picture: it does nothing for the keyboard unless you build it, so a 3D viewer is usually the part of a page that fails first.

  2. 2

    The twins. Dragging to turn becomes ArrowLeft and ArrowRight. The mouse wheel or a pinch becomes + and -. Hover becomes keyboard focus. A click becomes Enter or Space. Escape clears a choice. Each pointer action in this phase has one of these.

  3. 3

    Make the canvas focusable. A canvas cannot take focus by itself. Put tabindex="0" on its container, listen for keydown there, and give the container an aria-label that says what the keys do.

  4. 4

    Keys to use, keys to leave alone. Accept = as well as +, since + is Shift and = on many keyboards. Ignore the event when Ctrl, Alt or Meta is held: Ctrl and + is the browser's zoom. Call preventDefault() only for keys you handled, so arrows do not also scroll the page, while Tab, Escape and the rest keep their normal job.

  5. 5

    Tab through the parts. Do not invent your own focus system. A list of real button elements, one per part, gives you Tab order, Enter and Space, :focus-visible styling and screen reader names for free. When a button gets focus, light up its part; when it is pressed, choose it. Now the keyboard reaches the same result as hover and click.

  6. 6

    Focus must be visible, and never trapped. Style :focus-visible with a clear outline (WCAG 2.4.7) and never remove the default one without a replacement. A keyboard trap is a handler that stops Tab from leaving, usually by calling preventDefault() on every key. WCAG 2.1.2 requires that focus can always move away. Handle the keys you need, and return early for everything else.

Explain it without notes

01

Name the keyboard twin of: dragging, the mouse wheel, hover and a click.

02

Why must the key handler ignore events with Ctrl held, and why must it not call preventDefault() on Tab?

Practice

01

Write a keydown handler for a container that handles ArrowUp and ArrowDown by changing state.tilt by 5 degrees, limited to −30 … 30, and leaves every other key alone.

02

Write the CSS so that a button shows a clear ring only for keyboard focus, not for mouse clicks.

Done when you can

  • Give the viewer a focus stop, a label and a visible focus ring

  • Write a keyboard twin for turn, zoom, hover and click

  • Ignore modifier keys and call preventDefault() only for keys you handled

  • Build a Tab-reachable list of parts from real buttons