The agency motion handoff: 10 checks before a client site ships
Most launch checklists have one line for accessibility, and it says "run Lighthouse". That line will not find a single motion problem, because Lighthouse has no audit for animation. So the motion layer ships unreviewed — not because agencies are careless, but because nothing in the standard pre-launch pass looks at it. To find out how much that costs in practice, we pointed our own scanner at the people who build the sites: 22 web-design agency home pages, scanned on 2026-08-29. This guide reports what came back, and turns it into ten checks you can run at handoff.
What we found on 22 agency home pages
MotionSpec static scan of 22 agency home pages, 2026-08-29. Every figure is a lower bound — see the method note below.
The distribution matters more than the median, because it shows this is not a long tail of a few bad pages. It is the whole sample.
| Motion score | Pages | Share | What that band looks like |
|---|---|---|---|
| 0 | 1 | 4.5% | Every category capped out: loops, autoplay, unguarded motion |
| 5 | 2 | 9.1% | Dozens of root causes across several categories |
| 15 | 13 | 59.1% | The modal band: unguarded motion plus infinite loops |
| 35 | 2 | 9.1% | Fewer loops, still no reduced-motion guard |
| 55 | 4 | 18.2% | Best in sample: no infinite loops found, guard still missing |
| 100 | 0 | 0% | — |
Scores are produced by the published scoring-v2 formula, which ships with every result at motionspec.dev/motion-check. The median page carried 28 distinct root causes across 148 occurrences; the range was 6 to 133 root causes.
The finding we did not expect: most of it is not your code
Across the 22 pages the scan grouped 98 root causes of infinite animation with no pause path. When we looked at the keyframe and selector names, a recurring pattern appeared: loaders and spinners from libraries nobody on the project wrote.
| Library (identified by keyframe / selector name) | Root-cause groups | Sites affected |
|---|---|---|
Bootstrap (progress-bar-stripes, spinner-border) | 6 | 4 |
Swiper (swiper-preloader-spin) | 6 | 4 |
Gravity Forms (gformLoader) | 5 | 5 |
Font Awesome (fa-spin) | 3 | 3 |
Splide (splide-loading) | 3 | 3 |
SpinKit (sk-bounce) | 2 | 2 |
| Other WordPress-ecosystem prefixes | 2 | 2 |
| Total attributed by name | 27 of 98 (27.6%) | 13 of 22 |
Attribution is by keyframe or selector name only — a naming heuristic, and therefore a lower bound. The most common single keyframe name in the sample was the generic spin (8 groups), which we did not attribute either way. The remaining 71 groups are bespoke or unidentifiable.
Twenty-eight percent is not a majority, and we are not going to inflate it into one. But the practical lesson holds even at that share: a design review of your own animation will not find these. A form plugin's submit spinner, a slider's preloader and an icon font's fa-spin are invisible in the design file, invisible in the component library, and present in the compiled CSS of the site you hand over. They only show up if something reads the built stylesheet.
<style> blocks — nothing else, and one page per agency. Sample: 22 agency home pages, drawn on 2026-08-29 from the agencies named on the public US web-design listing of Digital Agency Network; where the directory linked to its own profile page we resolved the agency's primary domain by name. Of 26 domains attempted, 22 returned a home page (three returned HTTP 403 to our request and one did not connect) and all 22 were scanned. We did not verify each agency's country of operation, and the directory's US page includes agencies with offices elsewhere. This is a convenience sample of 22, not a study — treat it as a spot check with a documented method, not a population estimate. Lower bound: runtime JavaScript motion is not measured; 7 of the 22 scans explicitly disclosed a detected runtime motion source (GSAP, the Web Animations API, or a requestAnimationFrame loop) that was not audited. Not a verdict: reduced-motion guards sit in WCAG 2.3.3 (AAA) territory and pause paths map to WCAG 2.2.2 (Level A); neither is fully machine-checkable, since whether motion is "essential" is a human judgment. What we report are automatable failure patterns, not a conformance determination for any agency, site or company. No agency is named against a result: pages appear only in aggregate.
The ten checks
Each one is here because the scan found it, and each is something a developer can answer in minutes on a built site.
1. Grep the compiled CSS for infinite — not the source
Your source is Sass, Tailwind or CSS-in-JS; the browser gets a bundle with your dependencies inlined. That difference is where the vendor loaders hide.
curl -s https://client-site.com/ \ | grep -oE 'href="[^"]+\.css[^"]*"' \ | sed 's/href="//; s/"//' \ | while read -r css; do curl -s "$css"; done \ | grep -o 'infinite' | wc -l
A non-zero count is not a failure. It is a list to walk.
2. For every infinite loop: bounded, or pausable?
Two acceptable answers. Bounded means a finite animation-iteration-count that ends the motion within five seconds. Pausable means a visible, keyboard-operable control that halts it. Hover-pause is neither — it ends when the pointer leaves and a keyboard user never triggers it.
3. Does a prefers-reduced-motion block exist, and does it reach the vendor layer?
A guard scoped to your own class names leaves the plugin CSS running. The floor is one blanket rule, late in the cascade:
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
Blunt on purpose. Where an animation carries information, write it a reduced variant instead of letting this flatten it.
4. Is there one pause control on the page?
Not one per widget — one for the page, wired to a root attribute, so a new slider next quarter inherits it:
html[data-motion-paused] *,
html[data-motion-paused] *::before,
html[data-motion-paused] *::after { animation-play-state: paused !important; }
5. Does anything autoplay for more than five seconds beside other content?
Hero video, looping GIF, background canvas. Three of the 22 pages tripped the long-autoplay rule. If it runs alongside content and has no control, it is the 2.2.2 case in its clearest form.
6. Is any content parked at opacity: 0 waiting for a script?
Scroll-reveal patterns often ship the hidden state in CSS and the reveal in JavaScript. If the script fails, is blocked, or never fires because the element started in view, the content is gone for everyone. Ship the finished state as the default and layer motion on top.
7. Is the motion actually in the CSS?
If the build uses GSAP, Motion for React, a scroll library or the Web Animations API, a CSS media query cannot reach it and neither can a CSS scan. Those need the guard in JavaScript — gsap.matchMedia(), <MotionConfig reducedMotion="user">, or an explicit window.matchMedia branch.
8. Did the slider ship its own stylesheet?
Swiper, Splide and Slick all include loader animations you did not ask for. If you use the library for one carousel and import the full stylesheet, you inherit them. Import the module you use, or guard the vendor layer explicitly.
9. Are the animated properties compositor-safe?
All 22 pages animated at least one property outside transform and opacity — width, top, clip-path, background. That is a performance question before it is an accessibility one, and janky, layout-shifting motion drags the INP and CLS metrics your client will ask about a month after launch.
10. Who re-checks this after handoff — and is that written down?
This is the one that decides whether the other nine hold. A site that passes at launch drifts: a plugin updates, marketing adds an embed, a new landing page copies an old component. If nobody owns the re-check, the answer defaults to nobody. Put it in the client's pipeline, or in your own recurring review, and say which in the handoff document.
What this checklist does not do
- It is not an accessibility audit. Motion is one layer. Contrast, keyboard order, focus management, semantics and screen-reader behaviour are all outside it and all matter more often.
- It is not a conformance claim. A scan proves a failing pattern is present. Whether a control qualifies, or whether a given motion is "essential", is a human judgment, and no tool — ours included — can certify a page.
- It is not legal advice. For context on scale: Seyfarth Shaw counted 3,117 federal website-accessibility lawsuits filed in 2025. We are citing that as background only — in the sources we read, none of those filings is described as turning on animation or motion, and we found no basis to claim otherwise. Do not use motion findings to estimate legal exposure.
- It does not cover what the scan cannot see. Seven of our 22 scans disclosed runtime motion they did not audit. On a JavaScript-animated site, check 7 is the whole job.
The free motion check is open and takes a URL at a time — enough for a single handoff, and there is an API at GET /api?url=<url> if you want to script the ten checks above into your own pipeline. If you are re-checking the same set of client domains every month rather than once, that is what the Agency plan covers: up to 10 domains, dated results you can put in a handoff document. Either way, the finding is the product — we show what is there and how to fix it. We do not issue conformance verdicts, and no scan can.
FAQ
What should an agency check for motion before launching a client site?
Four things, in order: every infinite animation in the compiled CSS has either a finite iteration count or a working pause control; a prefers-reduced-motion block exists and covers the vendor stylesheets as well as your own; nothing autoplays for more than five seconds alongside other content; and no content is parked at opacity: 0 waiting for a script. The ten-point list above expands each one.
How much motion on agency sites is actually from third-party code?
In our spot check of 22 agency home pages we found 98 root-cause groups of infinite animation without a pause path. 27 of them — 27.6%, spread across 13 of the 22 sites — carried keyframe or selector names that map to a named third-party library such as Bootstrap, Swiper, Gravity Forms, Font Awesome, Splide or SpinKit. Attribution is by name only, so it is a lower bound.
Does an accessibility statement or overlay cover motion?
Not by itself. A statement records what you tested and found; it does not change what the page does. Motion is a behaviour, and the only thing that changes it is a bounded loop, a pause control or a reduced-motion branch in the code. Check what a given tool or widget actually does to your animation before assuming it is handled.
Who owns motion accessibility after handoff?
Whoever the contract says, and it is worth saying out loud before launch. The practical risk is that a site passes at handoff and drifts afterwards, because a plugin update, a new slider or a marketing embed adds motion nobody re-checks. Put the check in the client's pipeline or in a recurring review, and write down which it is.
Paste a client URL into the free motion check — a static scan against WCAG 2.2.2 and 2.3.3, every finding with its fix, and an explicit disclosure of what it could not see. No signup, nothing stored.
Run the free motion checkOne page tells you. A dated write‑up is what you hand to a client or a boss: the $29 Motion Report cites every candidate to WCAG 2.2.2 / 2.3.3 and ranks your score against 196 AI‑built pages — read a real one first. Fourteen‑day refund, no questions asked.
Re-checking the same client domains every month? See the Agency plan →
MotionSpec checks and compiles UI animation for AI-generated web apps — it does not generate AI video.