Game Rig

Home/Blog/Reduced motion

prefers-reduced-motion for games

A game is motion, so the setting cannot mean stop the game — the work is deciding which of the things moving on screen carry information.

Four of the sixteen games here ask the browser one question at boot: whether the player has already told their operating system they want less movement. Each keeps the answer in a variable called reduced and consults it in three or four places. Choosing what the boolean should switch off took three passes, and the first was wrong.

It read the setting as an instruction to make the game still, which for a runner means removing the game. The specification does not say that. Media Queries Level 5 gives prefers-reduced-motion two values and defines reduce as a user who wants an interface that removes or replaces the kinds of motion causing discomfort for people with vestibular sensitivity, or distraction for people with attention deficits.

The brief is narrower than it sounds, and more useful. Motion that exists to be noticed is negotiable. Motion that exists to be read is not.

A canvas has no CSS animation to shorten

The stylesheet understood the setting long before any game did. A block near the bottom of style.css collapses every CSS animation to 0.01 ms and a single iteration, and sets scroll-behavior back to auto. For a header, a focus ring or a sliding panel, that is the correct treatment and the whole of it.

None of it reaches a canvas. A canvas is one element with one style rule, and every pixel inside it was written by game code during the last frame. There is no keyframe to cancel, because none was ever declared. The blanket rule ends the page's animation and leaves the running game untouched.

So the four games that honour the preference do it in JavaScript. Each calls window.matchMedia("(prefers-reduced-motion: reduce)") once, inside a try, and stores .matches. That guard matters: an engine which does not recognise the query can throw rather than return false, and a page that fails to boot is worse than one that shakes.

MDN's description is the best design note on the subject: the user wants an interface that removes, reduces or replaces motion-based animations. Its own example does not delete the animation, it swaps a scaling pulse for a slow change in opacity. Deleting is one way to obey the instruction and usually the wrong one.

Sorting the motion by what it tells the player

The road in Road Dodge scrolls because that scroll is how speed is communicated. Stop it and the scene does not become calm, it becomes unreadable: the car is still moving, the fuel is still burning, and the only cue for how fast any of it is happening has gone. The same holds for the runner in Neon Dash.

Everything else in a frame is feedback, and feedback can be delivered a second time by something that does not move. A hit does not need twenty-two particles when the score has already changed and the sound has played. A fuel warning does not need a bar blinking five times a second when the bar has turned red.

What the reduce branch changes in each game

What the reduce branch changes in the four games that read the preference
GameMotionDefaultWith reduce on
Neon DashParticle burst on a pickup22 particles, spread at 380 px/s3 particles
Neon DashDust thrown up while slidingA burst on about half the sliding framesNone at all
Road DodgeDebris thrown from a wreckThe full count, wide spreadOne third of the count
Road DodgeFuel bar below 18 per centFlashes at about 5 HzSolid red, no flash
Drift CircuitTyre smokeUp to 130 live puffsNone at all
Flak Battery 3DShrapnel from a burst, and the camera kick on every shotSixteen points of debris and a recoil on the frameNo debris, and no recoil

Two rows are the pattern worth copying. The fuel bar keeps its colour change and loses only the blink, so the warning still arrives without pulsing in the corner of an eye. The outline around a fuel can is the other case: Road Dodge pulses it on a timer, and under reduce the outline appears only when the car is close.

Drift Circuit takes the cleanest route and drops its smoke system outright rather than shrinking it. The numbers elsewhere are arbitrary: three particles, one third of a count, a cap of 130, all chosen because they looked about right.

The one motion I left switched on

Screen shake survives in the three games that predate this post, and it should not. Road Dodge and Neon Dash both displace the whole canvas by a random offset each frame while a shake timer runs. Neon Dash sets that timer to 16 on a crash, a jump of up to eight pixels in any direction, decaying at roughly 34 pixels per second. No reduced branch in either file touches it.

That is backwards. Shake is the most vestibular thing a 2D game does, because the whole viewport moves at once and no object on screen can be credited with it. It survives because the offset lives in the draw call beside the transform, while every other effect lives in a particle system the earlier pass already went through.

The fix is one multiplication. Take the amplitude to zero when reduced is set and leave the wreck feedback otherwise intact; the crash still reads, because the car has stopped and the end panel has appeared. Flak Battery 3D was written after this was found, so it does that multiplication from the start: its camera never moves under the preference, and its bursts lose their debris while the flash that says a drone was hit stays.

Five seconds, and the rule under the media query

The media query carries no obligation of its own, so a @media block is not compliance. WCAG 2.2.2 is Level A and applies whether or not the operating system switch is on, because it is about content that starts moving without being asked. Three conditions hold at once: automatic start, more than five seconds, and other content alongside it. A canvas under a header and a HUD meets all three, and so does the scrolling road inside it.

Five seconds was picked as long enough to catch an eye and short enough to wait out; the exception is movement essential to the activity. A game's scroll is essential, and the criterion still expects a way to pause, stop or hide it, which the P key bound in every game here provides. The auto-updating clause has no five-second allowance and covers the score and the fuel number, both of which freeze while the run is paused.

Animation from Interactions, 2.3.3, is the other half and is Level AAA: motion triggered by an interaction must be disableable unless it is essential to the functionality or the information conveyed. Almost everything a game draws is triggered by an interaction and almost none of it is essential. That criterion is also where the severity is documented, in a note describing dizziness and nausea rather than mild annoyance. That note is why the shake above is a defect and not a nicety.

Emulating the setting, and the path no test walks

Checking the work means turning the preference on, and the switch moves: on iOS it is under Accessibility then Motion, on Windows 11 under Accessibility then Visual Effects. Because these games read the value at boot, flipping the system setting does nothing until the page reloads, and Chrome's rendering panel can force the media feature instead.

One path is never walked. The smoke harness that boots every game in Node stubs matchMedia to answer one query, the one about coarse pointers, and returns matches: false for everything else, so no automated run here has ever executed a line inside a reduced branch. That gap belongs with the rest of the limits of testing without a browser. The same is true of the controls: an accessible path only a mouse can reach is no path, as argued in making a canvas game playable by keyboard.

Pros and cons

Pros

  • One matchMedia call at boot costs nothing per frame and adds no branch to the hot loop.
  • Cutting particle counts keeps the meaning of an effect and removes most of the pixels that move.
  • The pause the media query cannot enforce is already there for WCAG 2.2.2, so no new control has to be designed.
  • Reading the value once puts the whole decision in one visible line at the top of a file.

Cons

  • The sandbox stubs matchMedia to false, so the reduce path is the one branch no automated run executes.
  • A player who turns the setting on mid-session still has to reload in the three older games.
  • Three particles and a 130-puff cap are arbitrary numbers with nothing behind them but a look at the screen.
  • Screen shake, the strongest trigger of the four, still runs unchanged in the three older games.

Sources