Animations should respect the reduced-motion setting
Large moving, zooming or parallax effects can make people with vestibular disorders dizzy or nauseous. They switch on "reduce motion" in their device settings, and a shop that ignores the setting can drive them away.
- Rule id
- vs-motion
- Source
- Validiscan check
- Impact
- Minor
- Fix tier
- AUTO
- on Shopify, Shopware, WooCommerce and Magento
- Status
- runs
What the check does
Detects CSS and script animations, parallax and zoom effects that ignore the prefers-reduced-motion user setting. Reported as best practice (related to 2.3.3 Animation from Interactions, level AAA); flashing content is covered by the manual checklist (2.3.1).
Pages load with no motion preference. The check reads computed animation and transition properties of up to 5,000 elements in the body, ignoring hidden ones. Candidates are running CSS animations that repeat forever or have an animation-duration over 5 seconds, and transitions over 5 seconds.
The page is switched to prefers-reduced-motion: reduce, scripts get 150 ms to react, and every candidate is reread. One that is removed, paused, hidden, or made finite and 5 seconds or shorter passes, even through a cross-origin stylesheet. The rest become findings, at most 20 per page. If the switch fails, a prefers-reduced-motion query in a stylesheet defining or applying the animation counts as a guard.
An autoplaying video goes to needs review unless the setting hides it, or pauses it while playing. So does one reminder to check flashing by hand if the page shows video, canvas, .gif images or animation. The scanner does not turn needs review into findings today. Motion a script drives frame by frame is not measured.
How to fix it
Wrap non-essential animations and transitions in @media (prefers-reduced-motion: no-preference), or switch them off inside @media (prefers-reduced-motion: reduce), and check the setting in JavaScript before starting animations.
WCAG success criteria
Best practice: not tied to a WCAG success criterion.
Where a recipe fixes it
Shopify: recipe reduced-motion-guard
Show the before and after (1)
assets/base.css.liquid Changes to assets/base.css.liquid: 13 added, 0 removed
| Line before | Line after | Change | Code |
|---|---|---|---|
| 1866 | 1866 | } | |
| 1867 | 1867 | ||
| 1868 | 1868 | /* ----------------------------------------------------------------- motion */ | |
| 1869 | Added | ||
| 1870 | Added | /* validiscan:reduced-motion-guard */ | |
| 1871 | Added | @media (prefers-reduced-motion: reduce) { | |
| 1872 | Added | *, | |
| 1873 | Added | *::before, | |
| 1874 | Added | *::after { | |
| 1875 | Added | animation-duration: 0.01ms !important; | |
| 1876 | Added | animation-iteration-count: 1 !important; | |
| 1877 | Added | transition-duration: 0.01ms !important; | |
| 1878 | Added | scroll-behavior: auto !important; | |
| 1879 | Added | } | |
| 1880 | Added | } | |
| 1881 | Added | /* /validiscan:reduced-motion-guard */ |
Shopware: recipe reduced-motion-guard
Show the before and after (1)
Resources/app/storefront/src/main.js Changes to Resources/app/storefront/src/main.js: 1 added, 1 removed
| Line before | Line after | Change | Code |
|---|---|---|---|
| 139 | 139 | }); | |
| 140 | 140 | }); | |
| 141 | 141 | ||
| 142 | Removed | if (options.autoplay === true) { | |
| 142 | Added | if (options.autoplay === true && !window.matchMedia('(prefers-reduced-motion: reduce)').matches) { | |
| 143 | 143 | window.setInterval( | |
| 144 | 144 | function () { | |
| 145 | 145 | show(current + 1); |
Resources/app/storefront/src/scss/base.scss Changes to Resources/app/storefront/src/scss/base.scss: 14 added, 0 removed
| Line before | Line after | Change | Code |
|---|---|---|---|
| 1708 | 1708 | margin: 8px 0 0; | |
| 1709 | 1709 | font-size: 0.875rem; | |
| 1710 | 1710 | } | |
| 1711 | Added | ||
| 1712 | Added | /* validiscan:reduced-motion-guard */ | |
| 1713 | Added | /* Honour the visitor's reduced-motion setting: animations and transitions end at once, no smooth scrolling. */ | |
| 1714 | Added | @media (prefers-reduced-motion: reduce) { | |
| 1715 | Added | *, | |
| 1716 | Added | *::before, | |
| 1717 | Added | *::after { | |
| 1718 | Added | animation-duration: 0.01ms !important; | |
| 1719 | Added | animation-iteration-count: 1 !important; | |
| 1720 | Added | transition-duration: 0.01ms !important; | |
| 1721 | Added | scroll-behavior: auto !important; | |
| 1722 | Added | } | |
| 1723 | Added | } | |
| 1724 | Added | /* /validiscan:reduced-motion-guard */ |
WooCommerce: recipe reduced-motion-guard
Show the before and after (1)
assets/css/main.css Changes to assets/css/main.css: 13 added, 0 removed
| Line before | Line after | Change | Code |
|---|---|---|---|
| 794 | 794 | color: #999; | |
| 795 | 795 | cursor: pointer; | |
| 796 | 796 | } | |
| 797 | Added | ||
| 798 | Added | /* validiscan:reduced-motion-guard */ | |
| 799 | Added | @media (prefers-reduced-motion: reduce) { | |
| 800 | Added | *, | |
| 801 | Added | *::before, | |
| 802 | Added | *::after { | |
| 803 | Added | animation-duration: 0.01ms !important; | |
| 804 | Added | animation-iteration-count: 1 !important; | |
| 805 | Added | transition-duration: 0.01ms !important; | |
| 806 | Added | scroll-behavior: auto !important; | |
| 807 | Added | } | |
| 808 | Added | } | |
| 809 | Added | /* /validiscan:reduced-motion-guard */ |
Magento: recipe reduced-motion-guard
Show the before and after (1)
app/design/frontend/Validiscan/fixture/web/css/source/_theme.less Changes to app/design/frontend/Validiscan/fixture/web/css/source/_theme.less: 9 added, 0 removed
| Line before | Line after | Change | Code |
|---|---|---|---|
| 1264 | 1264 | height: 18px; | |
| 1265 | 1265 | stroke-width: 3; | |
| 1266 | 1266 | } | |
| 1267 | Added | ||
| 1268 | Added | /* validiscan:reduced-motion-guard */ | |
| 1269 | Added | @media (prefers-reduced-motion: reduce) { | |
| 1270 | Added | .product-label, | |
| 1271 | Added | .slick-track { | |
| 1272 | Added | animation: none; | |
| 1273 | Added | } | |
| 1274 | Added | } | |
| 1275 | Added | /* /validiscan:reduced-motion-guard */ |
app/design/frontend/Validiscan/fixture/web/css/styles-m.css Changes to app/design/frontend/Validiscan/fixture/web/css/styles-m.css: 9 added, 0 removed
| Line before | Line after | Change | Code |
|---|---|---|---|
| 1232 | 1232 | height: 18px; | |
| 1233 | 1233 | stroke-width: 3; | |
| 1234 | 1234 | } | |
| 1235 | Added | ||
| 1236 | Added | /* validiscan:reduced-motion-guard */ | |
| 1237 | Added | @media (prefers-reduced-motion: reduce) { | |
| 1238 | Added | .product-label, | |
| 1239 | Added | .slick-track { | |
| 1240 | Added | animation: none; | |
| 1241 | Added | } | |
| 1242 | Added | } | |
| 1243 | Added | /* /validiscan:reduced-motion-guard */ |
Start with a free check of your shop.
Send your shop’s address. Validiscan scans it and replies with every failure the scan can prove, with the page and the code.
Request a free checkOr write to info@validiscan.com.
What the free check covers, and what it leaves out- You send
- your shop’s address
- Pages scanned
- up to 15
- You get
- every failure the scan can prove, with the page and the code
- On your shop
- public pages only; no login, nothing bought, no forms sent
- What it is not
- a full audit