<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Kevin Fröba</title>
    <description>The latest articles on DEV Community by Kevin Fröba (@kevinfroeba).</description>
    <link>https://dev.to/kevinfroeba</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4024114%2F54f17946-da77-4375-be26-4eace2e4df07.jpg</url>
      <title>DEV Community: Kevin Fröba</title>
      <link>https://dev.to/kevinfroeba</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kevinfroeba"/>
    <language>en</language>
    <item>
      <title>The accessibility failure your CI can't catch — and the media query that fixes most of it</title>
      <dc:creator>Kevin Fröba</dc:creator>
      <pubDate>Sat, 18 Jul 2026 11:33:19 +0000</pubDate>
      <link>https://dev.to/kevinfroeba/the-accessibility-failure-your-ci-cant-catch-and-the-media-query-that-fixes-most-of-it-2pi2</link>
      <guid>https://dev.to/kevinfroeba/the-accessibility-failure-your-ci-cant-catch-and-the-media-query-that-fixes-most-of-it-2pi2</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I ran a static motion-accessibility scan on &lt;strong&gt;196 AI-generated production apps&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;66.3%&lt;/strong&gt; ship at least one infinite animation with &lt;strong&gt;no way to pause it&lt;/strong&gt; → fails &lt;strong&gt;WCAG 2.2.2 (Pause, Stop, Hide), Level A&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;96.9%&lt;/strong&gt; ship motion with no &lt;code&gt;prefers-reduced-motion&lt;/code&gt; guard (a best-practice gap, &lt;em&gt;not&lt;/em&gt; a strict A/AA fail). Only &lt;strong&gt;3.1%&lt;/strong&gt; were clean.&lt;/li&gt;
&lt;li&gt;The fix for most of it is a media query and a pause control. Data + method + open dataset at the bottom.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Disclosure: I build MotionSpec, a tool for this exact problem, and I used AI assistance to help draft this post. So the method is fully open and every number is reproducible from published rules — don't trust me, check it. Platforms are anonymized as cohorts A–E; this isn't a vendor scoreboard.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The gap your pipeline doesn't see
&lt;/h2&gt;

&lt;p&gt;Your CI probably runs an accessibility check — axe, Lighthouse, maybe WAVE. Those are good tools. They also basically don't look at &lt;strong&gt;motion&lt;/strong&gt;. They audit structure and content: contrast, alt text, labels, ARIA. Animation behaviour — reduced-motion support, pausable loops, off-budget motion — falls through, because automated scanners don't reliably evaluate it and visual-regression tools freeze animation on purpose to diff screenshots.&lt;/p&gt;

&lt;p&gt;That's why the field's biggest census, the &lt;strong&gt;WebAIM Million 2026&lt;/strong&gt; (n = 1,000,000 home pages, 95.9% with detectable WCAG failures), lists contrast/alt/labels as the recurring top failures and &lt;strong&gt;never mentions motion&lt;/strong&gt; — not because motion is rare, but because it's unmeasured. So I measured it, on the output that's growing fastest: apps built by AI app-builders.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I found (196 apps)
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Cohort&lt;/th&gt;
&lt;th&gt;Apps&lt;/th&gt;
&lt;th&gt;≥1 unguarded motion&lt;/th&gt;
&lt;th&gt;≥1 loop, no pause&lt;/th&gt;
&lt;th&gt;Median score&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A&lt;/td&gt;
&lt;td&gt;45&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;71.1%&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;B&lt;/td&gt;
&lt;td&gt;45&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;80.0%&lt;/td&gt;
&lt;td&gt;35&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C&lt;/td&gt;
&lt;td&gt;45&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;77.8%&lt;/td&gt;
&lt;td&gt;35&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;D&lt;/td&gt;
&lt;td&gt;21&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;81.0%&lt;/td&gt;
&lt;td&gt;43&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;E&lt;/td&gt;
&lt;td&gt;40&lt;/td&gt;
&lt;td&gt;85.0%&lt;/td&gt;
&lt;td&gt;25.0%&lt;/td&gt;
&lt;td&gt;55&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;All&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;196&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;96.9%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;66.3%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;35&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Four of five cohorts hit &lt;strong&gt;100%&lt;/strong&gt; unguarded — that's a &lt;strong&gt;default&lt;/strong&gt;, not carelessness. Cohort E is the outlier: the only one below 100%, a quarter of the loop-failure rate, and the only one with clean apps. Translation: the gap is a design-system default, &lt;strong&gt;not a technical ceiling.&lt;/strong&gt; One platform already ships the guard often enough to move the numbers.&lt;/p&gt;

&lt;p&gt;(The 0–100 "score" is my own heuristic, not a WCAG conformance rate — I report it because it's reproducible, but the load-bearing numbers are the WCAG-mapped percentages.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Why 66.3% is the number that matters
&lt;/h2&gt;

&lt;p&gt;Two rules are in play:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;WCAG 2.2.2 Pause, Stop, Hide — Level A.&lt;/strong&gt; Anything that moves automatically, runs &amp;gt; 5s, and plays alongside other content needs a pause/stop/hide mechanism. An infinite loop with no pause &lt;strong&gt;fails Level A&lt;/strong&gt; — the minimum bar.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;WCAG 2.3.3 Animation from Interactions — Level AAA.&lt;/strong&gt; This is where "no &lt;code&gt;prefers-reduced-motion&lt;/code&gt; guard" lives. It's best practice, interaction-scoped, and &lt;strong&gt;not&lt;/strong&gt; a strict A/AA failure. That's why I lead with the 66.3%, not the 96.9%.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For someone with a vestibular disorder, this isn't cosmetic — sweeping parallax and infinite loops cause real dizziness, nausea, migraine. The OS-level &lt;em&gt;Reduce Motion&lt;/em&gt; switch only helps if the page listens.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Guard non-essential motion.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@media&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prefers-reduced-motion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="o"&gt;*,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nd"&gt;::before&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nd"&gt;::after&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;animation-duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;.01ms&lt;/span&gt; &lt;span class="cp"&gt;!important&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;animation-iteration-count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="cp"&gt;!important&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;transition-duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;.01ms&lt;/span&gt; &lt;span class="cp"&gt;!important&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="py"&gt;scroll-behavior&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt; &lt;span class="cp"&gt;!important&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Blunt safety net; a real implementation guards specific animations. &lt;strong&gt;2. Give loops an off switch&lt;/strong&gt; — a pause button wired to &lt;code&gt;animation-play-state: paused&lt;/code&gt;, or a finite &lt;code&gt;animation-iteration-count&lt;/code&gt;. That's what moves an app out of the 66.3%.&lt;/p&gt;

&lt;p&gt;If you generate UI with AI — an app-builder, a design-to-code tool, or your own LLM pipeline — add "respect &lt;code&gt;prefers-reduced-motion&lt;/code&gt; and don't ship infinite loops without a pause" to your system prompt or your component defaults. Cohort E proves defaults are the lever.&lt;/p&gt;

&lt;h2&gt;
  
  
  Method (read before quoting)
&lt;/h2&gt;

&lt;p&gt;Static scan of each page's linked CSS + inline styles only → a &lt;strong&gt;lower bound&lt;/strong&gt; (runtime JS/GSAP/WAAPI motion not measured; one page per app). 196 apps, 21–45 per cohort, collected 2026-07-16, provenance recorded, robots.txt respected, login-walls excluded. Neither criterion is fully machine-checkable ("essential" motion is a human call), so these are automatable failure &lt;em&gt;patterns&lt;/em&gt;, not a conformance verdict.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Full write-up + per-cohort detail:&lt;/strong&gt; &lt;a href="https://motionspec.dev/blog/state-of-motion-ai-generated-uis" rel="noopener noreferrer"&gt;https://motionspec.dev/blog/state-of-motion-ai-generated-uis&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Open dataset (CC-BY-4.0, 196 rows, anonymized):&lt;/strong&gt; &lt;a href="https://motionspec.dev/blog/data/state-of-motion-2026.csv" rel="noopener noreferrer"&gt;https://motionspec.dev/blog/data/state-of-motion-2026.csv&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scan your own URL (free, static, nothing stored):&lt;/strong&gt; &lt;a href="https://motionspec.dev/motion-check?utm_source=article&amp;amp;utm_medium=devto" rel="noopener noreferrer"&gt;https://motionspec.dev/motion-check?utm_source=article&amp;amp;utm_medium=devto&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Your turn
&lt;/h2&gt;

&lt;p&gt;How does your stack handle &lt;code&gt;prefers-reduced-motion&lt;/code&gt; today — a global reset, per-component guards, or nothing yet? And if you generate UI, does your pipeline know the rule? Curious what people are doing — drop it below.&lt;/p&gt;

</description>
      <category>a11y</category>
      <category>webdev</category>
      <category>ai</category>
    </item>
    <item>
      <title>Your AI-generated UI probably breaks prefers-reduced-motion</title>
      <dc:creator>Kevin Fröba</dc:creator>
      <pubDate>Tue, 14 Jul 2026 13:07:30 +0000</pubDate>
      <link>https://dev.to/kevinfroeba/your-ai-generated-ui-probably-breaks-prefers-reduced-motion-1akh</link>
      <guid>https://dev.to/kevinfroeba/your-ai-generated-ui-probably-breaks-prefers-reduced-motion-1akh</guid>
      <description>&lt;p&gt;AI coding tools have gotten very good at motion. Ask for a landing page and you get parallax heroes, staggered reveals, spring physics on every card. It looks great in the demo.&lt;/p&gt;

&lt;p&gt;Here's what it almost never ships with: a prefers-reduced-motion path.&lt;/p&gt;

&lt;p&gt;Why this is a real problem, not a checkbox&lt;/p&gt;

&lt;p&gt;Vestibular disorders are common. For the people who have them, large parallax movement, zooming, and spinning UI can trigger dizziness, nausea, and migraines. That's why the media query exists, and why WCAG has two success criteria aimed squarely at this:&lt;/p&gt;

&lt;p&gt;WCAG 2.2.2 (Pause, Stop, Hide) — Level A. Anything that moves automatically for more than 5 seconds needs a way to pause, stop, or hide it. Level A means baseline, not aspirational.&lt;br&gt;
WCAG 2.3.3 (Animation from Interactions) — motion triggered by interaction should be disableable unless it's essential.&lt;/p&gt;

&lt;p&gt;Most AI-generated motion fails both by default. Not because models "don't know" about reduced motion — they'll happily explain it if you ask — but because generation is sampling. Same model, same prompt, different run, different compliance.&lt;/p&gt;

&lt;p&gt;Prompting is hope. Verification is a property.&lt;/p&gt;

&lt;p&gt;"Make it accessible" in the prompt is not a guarantee, it's a suggestion. The pattern that actually works for LLM-generated code — the argument the Bun team made when they rewrote in Rust with heavy agent involvement — is a conformance suite plus mechanical enforcement. The model can write whatever it wants; the output has to pass the checks.&lt;/p&gt;

&lt;p&gt;Motion has no such layer today. axe-core and Lighthouse are excellent, but they largely can't catch a scroll-jacked hero with no reduced-motion path, because statically analyzing dynamic motion behavior is hard. The gap is exactly where AI tools generate the most output.&lt;/p&gt;

&lt;p&gt;What verifying motion looks like&lt;/p&gt;

&lt;p&gt;The trick is treating motion as data instead of as scattered CSS and JS. If motion is declared in a spec, it becomes checkable:&lt;/p&gt;

&lt;p&gt;json{&lt;br&gt;
  "element": "hero-title",&lt;br&gt;
  "primitive": "fade-rise",&lt;br&gt;
  "duration": 600,&lt;br&gt;
  "trigger": "scroll",&lt;br&gt;
  "reducedMotion": "opacity-only"&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;(Simplified for illustration.)&lt;/p&gt;

&lt;p&gt;With motion as a spec, the questions become mechanical:&lt;/p&gt;

&lt;p&gt;Does every animated element define reduced-motion behavior?&lt;br&gt;
Does anything auto-play longer than 5s without a pause/stop/hide mechanism?&lt;br&gt;
Are durations and movement distances inside a sane performance budget?&lt;/p&gt;

&lt;p&gt;Fail any of these and the check fails — deterministically, every run.&lt;/p&gt;

&lt;p&gt;MotionSpec&lt;/p&gt;

&lt;p&gt;That's what MotionSpec does. It's open-core: an MIT-licensed npm package (40 motion primitives, 373 automated tests) plus a hosted verification API. It runs as an HTTP API and an MCP server on Cloudflare Workers — which means AI agents can call it inline while generating UI. The same agent that writes the motion has to pass the check before the motion ships.&lt;/p&gt;

&lt;p&gt;There's a free motion-check on the site, no signup, if you want to see what your current motion setup fails.&lt;/p&gt;

&lt;p&gt;One disambiguation, because the name space is crowded: MotionSpec is not an AI video or animation generator. It's a verification layer for web interface motion — closer to axe-core or Lighthouse than to any of the AI "Motion" tools.&lt;/p&gt;

&lt;p&gt;Honest scope&lt;/p&gt;

&lt;p&gt;It verifies motion that's described as a spec. It won't magically retrofit arbitrary legacy CSS/JS animation you point it at — the check is only as good as the spec describing the motion. That's the trade: declare motion as data, get verifiability in return.&lt;/p&gt;

&lt;p&gt;If you try it, I genuinely want to know where the checks feel too strict or too loose — that calibration is the whole product. → motionspec.dev&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>a11y</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
