Features triggered by shaking or tilting a device need an on-screen alternative and a way to switch the motion off.
Some features respond to the device being shaken, tilted or moved, or to the user's own movement picked up by a camera, such as a hand wave. WCAG 2.1 success criterion 2.5.4 says that anything operated by device motion or user motion must also be operable through regular interface components (a button, a toggle), and the motion response must be possible to turn off so it cannot trigger by accident.
Two exceptions exist. One is when the motion works through an accessibility-supported interface, such as a platform feature that an assistive technology provides. The other is when the motion is essential and removing it would invalidate the activity, as with a step counter. The criterion does not cover motion used for other things like location, aiming a camera at a QR code, or typing and touching the screen.
Typical cases are a "shake to undo" in a form app, a transit app that refreshes arrivals when the phone is shaken, a kiosk or video-visit app that answers on a wave, or an inspection app that scrolls a checklist by tilting. A resident who cannot make that motion is shut out of the feature, and one who makes it accidentally can lose their entered data. Native apps are in scope under the Title II rule (see mobile apps).
window.addEventListener("devicemotion", e => {
const a = e.accelerationIncludingGravity;
if (Math.abs(a.x) > 25) clearApplication(); // only way to clear
});<button type="button" id="clear-app">Clear application</button>
<label><input type="checkbox" id="shake-on"> Allow shake to clear</label>
<script>
const clearBtn = document.getElementById("clear-app");
clearBtn.addEventListener("click", clearApplication);
window.addEventListener("devicemotion", e => {
if (!document.getElementById("shake-on").checked) return;
if (Math.abs(e.accelerationIncludingGravity.x) > 25) clearApplication();
});
</script>window.addEventListener("devicemotion", shakeDetected => reloadArrivals());<button type="button" id="refresh">Refresh arrivals</button>
<script>
document.getElementById("refresh").addEventListener("click", reloadArrivals);
if (!userPrefs.motionDisabled) {
window.addEventListener("devicemotion", onShake); // optional shortcut
}
</script>window.addEventListener("deviceorientation", e => {
checklist.scrollBy(0, e.beta * 2);
});<button type="button" id="up">Scroll up</button>
<button type="button" id="down">Scroll down</button>
<script>
up.onclick = () => checklist.scrollBy(0, -120);
down.onclick = () => checklist.scrollBy(0, 120);
// Tilt scrolling, if kept, sits behind the same setting that disables motion.
</script>Both platforms hand you raw motion events, so the alternative and the off switch are your job.
// SwiftUI
.onReceive(NotificationCenter.default.publisher(for: .deviceDidShake)) { _ in
draft = "" // custom notification, posted from a UIWindow motionEnded override
}// SwiftUI
// Custom name your app defines; post it from a UIWindow motionEnded override
extension Notification.Name { static let deviceDidShake = Notification.Name("deviceDidShake") }
@AppStorage("shakeToClear") private var shakeToClear = true
Button("Clear draft") { draft = "" }
Toggle("Shake to clear", isOn: $shakeToClear)
.onReceive(NotificationCenter.default.publisher(for: .deviceDidShake)) { _ in
if shakeToClear { draft = "" }
}// Jetpack Compose
DisposableEffect(Unit) {
sensorManager.registerListener(shakeListener, accelerometer, SENSOR_DELAY_UI)
onDispose { sensorManager.unregisterListener(shakeListener) }
}// Jetpack Compose
Button(onClick = { clearDraft() }) { Text("Clear draft") }
Switch(checked = shakeEnabled, onCheckedChange = { shakeEnabled = it })
if (shakeEnabled) {
DisposableEffect(Unit) {
sensorManager.registerListener(shakeListener, accelerometer, SENSOR_DELAY_UI)
onDispose { sensorManager.unregisterListener(shakeListener) }
}
}devicemotion, deviceorientation, motionEnded and sensor listeners is the practical check.