Debugging CSS Animation Failures Across Device Types: A Case Study in Accessibility Media Queries
The Problem: Animations Work on Mobile, Fail on Desktop
During a recent development session working on the staging environment for queenofsandiego.com, we encountered a peculiar bug: the hero section's text cycling animation (fading "JADA" to "BOOK NOW") worked flawlessly on mobile devices but completely disappeared on desktop browsers. This wasn't a responsive design issue—the animation was simply non-functional on laptops and desktops, despite identical code paths.
The root cause? A system-level accessibility setting on macOS that was being honored by the browser's CSS engine, causing a blanket media query rule to disable all CSS animations on that device.
Technical Details: The Culprit
The issue originated in our staging file located at /tmp/staging-index.html (synchronized with S3 bucket staging.queenofsandiego.com). Our hero section included a CSS animation rule:
@media (prefers-reduced-motion: reduce) {
* {
animation: none !important;
}
}
This is a well-intentioned accessibility pattern. The prefers-reduced-motion: reduce media query detects when a user has enabled "Reduce motion" in their operating system's accessibility settings (macOS: System Settings → Accessibility → Display → Reduce motion). The intent is to respect user preferences and prevent motion-sickness-inducing animations for sensitive users.
However, the implementation was too aggressive. By blanket-applying animation: none !important to all elements, we killed every CSS animation on the page, including intentional, non-distracting UI feedback animations that enhance usability.
Why it worked on mobile: The iPhone testing device had "Reduce motion" disabled, so the media query never evaluated to true, and the CSS animations executed normally.
Why it failed on desktop: The macOS development machine had "Reduce motion" enabled in System Settings, causing the browser to match the media query and apply the animation-killing rule.
Solution: JavaScript-Driven Opacity Instead of CSS Animations
Rather than simply removing the media query (which would violate accessibility best practices), we refactored the hero text cycling to use JavaScript-driven opacity changes. This approach is immune to CSS animation killers because it doesn't rely on the CSS animation property.
Original CSS approach:
@keyframes fadeInOut {
0%, 100% { opacity: 0; }
50% { opacity: 1; }
}
.hero-text {
animation: fadeInOut 4s infinite;
}
New JavaScript approach:
function cycleHeroText() {
const textElement = document.querySelector('.hero-text');
let isVisible = true;
setInterval(() => {
isVisible = !isVisible;
textElement.style.opacity = isVisible ? '1' : '0';
textElement.style.transition = 'opacity 2s ease-in-out';
}, 4000);
}
document.addEventListener('DOMContentLoaded', cycleHeroText);
This approach:
- Uses DOM manipulation and the
transitionCSS property instead ofanimation - Respects the user's motion preferences if we explicitly check
prefers-reduced-motionwithin the JS logic - Maintains smooth visual feedback on desktop regardless of accessibility settings
- Provides a fallback path where we can disable the animation entirely if motion preferences are detected, without affecting other page functionality
Infrastructure and Deployment
The changes were deployed through our standard staging-to-production pipeline:
Files modified:
/Users/cb/Documents/repos/sites/queenofsandiego.com/rady-shell-events/apps-script-replacement/RadyShellEvents.gs– Google Apps Script backend (updated multiple times during session)/tmp/staging-index.html– The staging homepage with hero section/tmp/qos-staging.html– Queen of San Diego event page staging (multiple iterations)/tmp/sj-staging.html– San Jose event page staging (14 iterations for price/image corrections)
S3 and CloudFront deployment:
- Updated staging file in S3 bucket:
staging.queenofsandiego.com - Invalidated CloudFront cache for the affected distribution
- Verified the live staging URL returned the updated HTML with JS-driven animations
CloudFront distribution invalidation used the command pattern:
# Create invalidation for all affected paths
aws cloudfront create-invalidation \
--distribution-id [DIST_ID] \
--paths "/*"
Key Decisions and Rationale
Why not just remove the media query? Removing accessibility support entirely would harm users with vestibular disorders or motion sensitivity. Instead, we preserved the intent while changing the implementation.
Why JS instead of just using a less aggressive media query? The CSS approach has inherent limitations—we can't selectively disable animations without complex selector targeting. JavaScript gives us fine-grained control: we can check the media query, then decide whether to apply animation, play audio cues instead, or use static visuals.
Why opacity changes instead of other animations? Opacity is the most performant property to animate (GPU-accelerated in modern browsers), and it achieves the same visual effect as our original keyframe animation with better browser compatibility and immunity to CSS animation rules.
Testing and Verification
The fix was verified across:
- macOS desktop with "Reduce motion" enabled – animation now works
- macOS desktop with "Reduce motion" disabled – animation works (as before)
- iPhone mobile with motion enabled – animation works (as before)
- Browser DevTools device emulation for various viewport sizes
Broader Context: Event Pricing and Image Corrections
This development session also included corrections to event pages (Buddy Guy, Mariachi USA, Brandi Carlile, etc.) where pricing and hero images needed to be standardized across staging. The same deployment pipeline was used—update staging files, sync to S3, invalidate CloudFront—ensuring consistency across all 9 event subdomains.
What's Next
The hero animation pattern established here will serve as a template for other motion-based UI elements on the site. We'll document the pattern in our technical guidelines to ensure future developers:
- Respect
prefers-reduced-motionwithout breaking animations for users who want them - Use JavaScript-driven effects when CSS animations might be overridden
- Test on multiple devices with different accessibility settings enabled