Course outline
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.
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
Accept = as well as +
In the keys example the handler already treats
"="like"+". Change the twopress("+")calls topress("="): the line+ twicestill ends incamera distance 3. On a US keyboard the+key is Shift and=, so a user who presses it without Shift sends"=". - 2
Bigger steps
Change
const STEP = THREE.MathUtils.degToRad(15);todegToRad(15) * 2. Two presses of ArrowRight now print60 degrees, and the three ArrowLeft presses end at-30 degrees. 15 degrees gives 24 stops for a full turn.
Code & diagrams
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.
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 degreesPress 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.
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 worksBreak 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;.
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.repeatis true), so holding ArrowRight steps again and again. For smooth turning, record which keys are down onkeydownandkeyup, and move byspeed * deltain the loop (Topic 3.2). Clear the record onblur, or a key that was released while focus was elsewhere stays stuck.
Remember this
- 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
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
Make the canvas focusable. A
canvascannot take focus by itself. Puttabindex="0"on its container, listen forkeydownthere, and give the container anaria-labelthat says what the keys do. - 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. CallpreventDefault()only for keys you handled, so arrows do not also scroll the page, while Tab, Escape and the rest keep their normal job. - 5
Tab through the parts. Do not invent your own focus system. A list of real
buttonelements, one per part, gives you Tab order, Enter and Space,:focus-visiblestyling 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
Focus must be visible, and never trapped. Style
:focus-visiblewith 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 callingpreventDefault()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
Name the keyboard twin of: dragging, the mouse wheel, hover and a click.
Why must the key handler ignore events with Ctrl held, and why must it not call preventDefault() on Tab?
Practice
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.
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 handledBuild a Tab-reachable list of parts from real buttons