|

CSS 42 💻 prefers-reduced-data and prefers- reduced-motion media features

These two media features prioritize user comfort and performance. One respects users who want less data usage, the other respects users who are sensitive to motion.


Overview of Features

FeatureDescriptionValues
prefers-reduced-dataUser wants lighter data usageno-preference, reduce
prefers-reduced-motionUser wants less animation/motionno-preference, reduce

1. prefers-reduced-data

The prefers-reduced-data media feature detects if the user has requested a lightweight alternative to save data.

@media (prefers-reduced-data: reduce) {
    .hero {
        background-image: none;
    }
}

Values

ValueDescription
no-preferenceUser has not expressed a preference (default)
reduceUser prefers lighter data usage

When to Use

  • Avoid large background images on reduced data
  • Skip autoplay videos and heavy media
  • Use simpler fonts (system fonts instead of web fonts)
  • Reduce image quality or use placeholders
  • Defer non-essential resources

⚠️ Important Note

prefers-reduced-data is an experimental technology with limited browser support. Always provide a working fallback.

How users enable it:

  • Chrome (Android): Settings → Privacy → Lite mode
  • Safari: Low Data Mode in iOS settings
  • Not yet widely supported in desktop browsers

2. prefers-reduced-motion

The prefers-reduced-motion media feature detects if the user has requested reduced motion — essential for users with vestibular disorders, motion sensitivity, or epilepsy.

@media (prefers-reduced-motion: reduce) {
    .container h1,
    .container p {
        animation: none !important;
        transition: none !important;
    }
}

Values

ValueDescription
no-preferenceUser has not expressed a preference (default)
reduceUser prefers less motion

When to Use

  • Disable animations (fade, slide, bounce)
  • Remove transitions (hover, focus)
  • Stop auto-playing carousels and videos
  • Avoid parallax scrolling
  • Reduce or remove loading spinners

How Users Enable It

  • Windows: Settings → Ease of Access → Display → Show animations
  • macOS: System Preferences → Accessibility → Display → Reduce motion
  • iOS: Settings → Accessibility → Motion → Reduce Motion
  • Android: Settings → Accessibility → Remove animations

Complete Example

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>prefers-reduced-data and prefers-reduced-motion</title>
    <style>
        *, *::before, *::after {
            box-sizing: border-box;
        }

        :root {
            --bg-primary: #ffffff;
            --bg-secondary: #f8f9fa;
            --bg-tertiary: #e9ecef;
            --text-primary: #212529;
            --text-secondary: #6c757d;
            --accent: #007bff;
            --border: #dee2e6;
            --shadow: rgba(0, 0, 0, 0.1);
        }

        @media (prefers-color-scheme: dark) {
            :root {
                --bg-primary: #1a1a1a;
                --bg-secondary: #0d0d0d;
                --bg-tertiary: #2d2d2d;
                --text-primary: #f8f9fa;
                --text-secondary: #adb5bd;
                --accent: #4dabf7;
                --border: #444;
                --shadow: rgba(0, 0, 0, 0.5);
            }
        }

        body {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            padding: 20px;
            background: var(--bg-secondary);
            color: var(--text-primary);
            max-width: 1200px;
            margin: 0 auto;
            line-height: 1.6;
            transition: background 0.3s, color 0.3s;
        }

        h1 {
            color: var(--accent);
            border-bottom: 3px solid var(--accent);
            padding-bottom: 10px;
        }

        h2 {
            color: var(--accent);
            border-left: 4px solid var(--accent);
            padding-left: 15px;
            margin-top: 30px;
        }

        h3 {
            color: var(--text-primary);
            margin-top: 20px;
        }

        section {
            background: var(--bg-primary);
            padding: 20px;
            border-radius: 8px;
            box-shadow: 0 2px 10px var(--shadow);
            margin: 20px 0;
            transition: all 0.3s;
        }

        .code-block {
            background: #1e1e1e;
            color: #d4d4d4;
            padding: 15px;
            border-radius: 8px;
            overflow-x: auto;
            font-family: 'Courier New', monospace;
            font-size: 0.9rem;
            line-height: 1.8;
            margin: 10px 0;
        }

        .reference-table {
            width: 100%;
            border-collapse: collapse;
            margin: 15px 0;
        }

        .reference-table th,
        .reference-table td {
            padding: 10px;
            border: 1px solid var(--border);
            text-align: left;
        }

        .reference-table th {
            background: var(--accent);
            color: white;
        }

        .reference-table tr:nth-child(even) {
            background: var(--bg-tertiary);
        }

        .note {
            font-size: 0.9rem;
            color: var(--text-secondary);
            margin-top: 5px;
        }

        .highlight {
            background: #ffc107;
            color: #333;
            padding: 2px 6px;
            border-radius: 4px;
            font-weight: bold;
        }

        /* ====== MOTION DEMOS ====== */
        .container h1,
        .container p {
            animation: fadeIn 2s ease-in;
        }

        @keyframes fadeIn {
            from {
                opacity: 0;
                transform: translateY(20px);
            }
            to {
                opacity: 1;
                transform: translateY(0);
            }
        }

        @keyframes pulse {
            0%, 100% {
                transform: scale(1);
                box-shadow: 0 0 0 0 rgba(0, 123, 255, 0.4);
            }
            50% {
                transform: scale(1.05);
                box-shadow: 0 0 0 15px rgba(0, 123, 255, 0);
            }
        }

        @keyframes spin {
            from { transform: rotate(0deg); }
            to { transform: rotate(360deg); }
        }

        @keyframes slide {
            0% { transform: translateX(0); }
            50% { transform: translateX(100px); }
            100% { transform: translateX(0); }
        }

        .motion-demo {
            padding: 20px;
            border-radius: 8px;
            background: var(--bg-tertiary);
            border: 2px solid var(--border);
            text-align: center;
            margin: 15px 0;
            min-height: 120px;
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            gap: 15px;
        }

        .animated-box {
            width: 80px;
            height: 80px;
            background: var(--accent);
            border-radius: 12px;
            display: flex;
            align-items: center;
            justify-content: center;
            color: white;
            font-size: 2rem;
        }

        .animated-box.pulse {
            animation: pulse 2s infinite;
        }

        .animated-box.spin {
            animation: spin 3s linear infinite;
        }

        .animated-box.slide {
            animation: slide 2s ease-in-out infinite;
        }

        /* Transitions */
        .transition-demo {
            padding: 20px;
            border-radius: 8px;
            background: var(--bg-tertiary);
            border: 2px solid var(--border);
            margin: 15px 0;
            cursor: pointer;
            transition: all 0.4s ease;
            text-align: center;
            font-weight: bold;
        }

        .transition-demo:hover {
            background: var(--accent);
            color: white;
            transform: scale(1.02);
            box-shadow: 0 4px 20px var(--shadow);
        }

        /* ====== REDUCED MOTION ====== */
        @media (prefers-reduced-motion: reduce) {
            .container h1,
            .container p {
                animation: none !important;
                transition: none !important;
            }

            .animated-box {
                animation: none !important;
            }

            .animated-box::after {
                content: " (motion reduced)";
                font-size: 0.7rem;
                font-weight: normal;
                display: block;
            }

            .transition-demo {
                transition: none !important;
            }

            .transition-demo:hover {
                transform: none;
            }

            * {
                animation-duration: 0.01ms !important;
                animation-iteration-count: 1 !important;
                transition-duration: 0.01ms !important;
                scroll-behavior: auto !important;
            }
        }

        @media (prefers-reduced-motion: no-preference) {
            .container h1,
            .container p {
                animation: fadeIn 2s ease-in;
            }
        }

        /* ====== REDUCED DATA ====== */
        .hero-section {
            padding: 40px;
            border-radius: 12px;
            text-align: center;
            margin: 15px 0;
            color: white;
            font-weight: bold;
            font-size: 1.5rem;
            /* Heavy background image */
            background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="400" height="200"><defs><linearGradient id="g" x1="0%" y1="0%" x2="100%" y2="0%"><stop offset="0%" style="stop-color:%23007bff"/><stop offset="100%" style="stop-color:%236c5ce7"/></linearGradient></defs><rect width="400" height="200" fill="url(%23g)"/><circle cx="100" cy="100" r="40" fill="rgba(255,255,255,0.1)"/><circle cx="300" cy="50" r="60" fill="rgba(255,255,255,0.1)"/></svg>');
            background-size: cover;
            background-position: center;
            transition: all 0.3s;
        }

        @media (prefers-reduced-data: reduce) {
            .hero-section {
                /* Remove heavy background image */
                background-image: none;
                background: var(--accent);
                font-size: 1.2rem;
                padding: 30px;
            }

            .hero-section::after {
                content: " (data saver mode)";
                font-size: 0.8rem;
                font-weight: normal;
                display: block;
                margin-top: 5px;
            }

            /* Hide non-essential images */
            .decorative-image {
                display: none;
            }

            /* Use system fonts */
            body {
                font-family: system-ui, -apple-system, sans-serif;
            }
        }

        /* ====== DETECTION CARDS ====== */
        .detection-grid {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
            gap: 15px;
            margin: 20px 0;
        }

        .detection-card {
            padding: 15px;
            border-radius: 8px;
            text-align: center;
            border: 2px solid var(--border);
            background: var(--bg-tertiary);
            transition: all 0.3s;
        }

        .detection-card .label {
            font-size: 0.85rem;
            color: var(--text-secondary);
            display: block;
            margin-bottom: 5px;
        }

        .detection-card .value {
            font-size: 1.1rem;
            font-weight: bold;
            color: var(--accent);
        }

        .detection-card.active {
            border-color: var(--accent);
            background: var(--bg-primary);
        }

        /* ====== LIVE INDICATOR ====== */
        .live-indicator {
            position: fixed;
            bottom: 20px;
            right: 20px;
            background: var(--accent);
            color: white;
            padding: 12px 20px;
            border-radius: 50px;
            font-weight: bold;
            font-size: 0.85rem;
            box-shadow: 0 4px 15px var(--shadow);
            z-index: 1000;
            transition: all 0.3s;
            display: flex;
            flex-direction: column;
            gap: 3px;
        }

        .live-indicator .detail {
            font-size: 0.75rem;
            opacity: 0.9;
        }

        /* ====== TIPS ====== */
        .tip-box {
            padding: 15px;
            border-radius: 8px;
            margin: 10px 0;
            border-left: 4px solid var(--accent);
            background: var(--bg-tertiary);
        }

        .tip-box.success { border-left-color: #28a745; }
        .tip-box.warning { border-left-color: #ffc107; }
        .tip-box.danger { border-left-color: #dc3545; }

        /* ====== PRACTICAL: ACCESSIBLE ANIMATION ====== */
        .loading-spinner {
            width: 50px;
            height: 50px;
            border: 5px solid var(--bg-tertiary);
            border-top: 5px solid var(--accent);
            border-radius: 50%;
            animation: spin 1s linear infinite;
            margin: 20px auto;
        }

        @media (prefers-reduced-motion: reduce) {
            .loading-spinner {
                animation: none;
                border-top-color: var(--accent);
                /* Show a static alternative */
            }
            .loading-spinner::after {
                content: "Loading...";
                display: block;
                text-align: center;
                font-size: 0.8rem;
                margin-top: 10px;
                color: var(--text-secondary);
            }
        }

        /* ====== PRACTICAL: DATA SAVER IMAGES ====== */
        .image-gallery {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
            gap: 15px;
            margin: 15px 0;
        }

        .image-gallery .gallery-item {
            height: 150px;
            border-radius: 8px;
            display: flex;
            align-items: center;
            justify-content: center;
            color: white;
            font-weight: bold;
            font-size: 1.5rem;
            background: linear-gradient(135deg, var(--accent), #6c5ce7);
            transition: all 0.3s;
        }

        /* High-quality images (default) */
        .image-gallery .gallery-item::after {
            content: "🖼️ HD";
            font-size: 0.8rem;
            position: absolute;
        }

        @media (prefers-reduced-data: reduce) {
            .image-gallery .gallery-item {
                /* Use simple placeholder */
                background: var(--bg-tertiary);
                border: 2px dashed var(--border);
                color: var(--text-secondary);
                font-size: 1rem;
            }
            .image-gallery .gallery-item::after {
                content: "📷 (data saver)";
                font-size: 0.8rem;
            }
        }
    </style>
</head>
<body>

    <h1>prefers-reduced-data and prefers-reduced-motion</h1>

    <!-- ====== 1. REDUCED MOTION DEMO ====== -->
    <section class="container">
        <h2>1. prefers-reduced-motion</h2>
        <p>Detects if the user has requested <strong>reduced motion</strong> for accessibility.</p>

        <h3>Animated Elements</h3>
        <div class="motion-demo">
            <div class="animated-box pulse">💓</div>
            <div class="animated-box spin">🔄</div>
            <div class="animated-box slide">➡️</div>
            <p class="note">These animations are disabled when <code>prefers-reduced-motion: reduce</code> is active.</p>
        </div>

        <h3>Transition Demo</h3>
        <div class="transition-demo">
            Hover over me to see the transition
        </div>
        <p class="note">Transitions are also disabled for users who prefer reduced motion.</p>

        <div class="code-block">
            /* Disable animations and transitions */
            @media (prefers-reduced-motion: reduce) {
                .container h1,
                .container p {
                    animation: none !important;
                    transition: none !important;
                }

                * {
                    animation-duration: 0.01ms !important;
                    animation-iteration-count: 1 !important;
                    transition-duration: 0.01ms !important;
                    scroll-behavior: auto !important;
                }
            }

            /* Enable animations only when no preference */
            @media (prefers-reduced-motion: no-preference) {
                .container h1,
                .container p {
                    animation: fadeIn 2s ease-in;
                }
            }
        </div>
    </section>

    <!-- ====== 2. REDUCED DATA DEMO ====== -->
    <section>
        <h2>2. prefers-reduced-data</h2>
        <p>Detects if the user prefers <strong>lighter data usage</strong>.</p>

        <div class="hero-section">
            🎨 Hero Section with Background Image
        </div>
        <p class="note">On reduced data, the heavy background image is replaced with a solid color.</p>

        <h3>Image Gallery</h3>
        <div class="image-gallery">
            <div class="gallery-item"></div>
            <div class="gallery-item"></div>
            <div class="gallery-item"></div>
            <div class="gallery-item"></div>
        </div>
        <p class="note">On reduced data, high-quality images become simple placeholders.</p>

        <div class="code-block">
            /* Default: heavy background image */
            .hero-section {
                background-image: url('large-image.jpg');
                background-size: cover;
            }

            /* Reduced data: remove heavy images */
            @media (prefers-reduced-data: reduce) {
                .hero-section {
                    background-image: none;
                    background: var(--accent);
                }

                .decorative-image {
                    display: none;
                }

                body {
                    font-family: system-ui, sans-serif;
                }
            }
        </div>

        <div class="tip-box warning">
            <strong>⚠️ Experimental Feature:</strong>
            <code>prefers-reduced-data</code> has limited browser support. Always provide a working fallback.
        </div>
    </section>

    <!-- ====== 3. DETECTION SUMMARY ====== -->
    <section>
        <h2>3. Your Preferences</h2>
        <p>Below is a live detection of your preferences.</p>

        <div class="detection-grid">
            <div class="detection-card" id="motion-no-pref">
                <span class="label">prefers-reduced-motion: no-preference</span>
                <span class="value" id="motion-no-pref-value">Detecting...</span>
            </div>
            <div class="detection-card" id="motion-reduce">
                <span class="label">prefers-reduced-motion: reduce</span>
                <span class="value" id="motion-reduce-value">Detecting...</span>
            </div>
            <div class="detection-card" id="data-no-pref">
                <span class="label">prefers-reduced-data: no-preference</span>
                <span class="value" id="data-no-pref-value">Detecting...</span>
            </div>
            <div class="detection-card" id="data-reduce">
                <span class="label">prefers-reduced-data: reduce</span>
                <span class="value" id="data-reduce-value">Detecting...</span>
            </div>
        </div>

        <div class="code-block">
            /* Detect motion preference */
            @media (prefers-reduced-motion: no-preference) { /* Animations OK */ }
            @media (prefers-reduced-motion: reduce) { /* Reduce motion */ }

            /* Detect data preference */
            @media (prefers-reduced-data: no-preference) { /* Normal data */ }
            @media (prefers-reduced-data: reduce) { /* Lightweight content */ }
        </div>
    </section>

    <!-- ====== 4. PRACTICAL: ACCESSIBLE SPINNER ====== -->
    <section>
        <h2>4. Practical Example: Accessible Loading Spinner</h2>
        <p>A spinner that respects reduced motion preferences.</p>

        <div class="loading-spinner"></div>

        <div class="code-block">
            /* Default: spinning animation */
            .loading-spinner {
                animation: spin 1s linear infinite;
            }

            /* Reduced motion: static alternative */
            @media (prefers-reduced-motion: reduce) {
                .loading-spinner {
                    animation: none;
                }
                .loading-spinner::after {
                    content: "Loading...";
                }
            }
        </div>
    </section>

    <!-- ====== 5. REFERENCE TABLES ====== -->
    <section>
        <h2>5. Reference Tables</h2>

        <h3>prefers-reduced-motion Values</h3>
        <table class="reference-table">
            <tr>
                <th>Value</th>
                <th>Description</th>
            </tr>
            <tr>
                <td><code>no-preference</code></td>
                <td>No preference expressed (default)</td>
            </tr>
            <tr>
                <td><code>reduce</code></td>
                <td>User prefers reduced motion</td>
            </tr>
        </table>

        <h3>prefers-reduced-data Values</h3>
        <table class="reference-table">
            <tr>
                <th>Value</th>
                <th>Description</th>
            </tr>
            <tr>
                <td><code>no-preference</code></td>
                <td>No preference expressed (default)</td>
            </tr>
            <tr>
                <td><code>reduce</code></td>
                <td>User prefers lighter data usage</td>
            </tr>
        </table>

        <h3>When to Reduce Motion</h3>
        <table class="reference-table">
            <tr>
                <th>Element</th>
                <th>Default</th>
                <th>Reduced Motion</th>
            </tr>
            <tr>
                <td>Fade animations</td>
                <td>Enabled</td>
                <td>Disabled</td>
            </tr>
            <tr>
                <td>Slide transitions</td>
                <td>Enabled</td>
                <td>Disabled</td>
            </tr>
            <tr>
                <td>Parallax scrolling</td>
                <td>Enabled</td>
                <td>Disabled</td>
            </tr>
            <tr>
                <td>Auto-playing carousels</td>
                <td>Enabled</td>
                <td>Paused/disabled</td>
            </tr>
            <tr>
                <td>Loading spinners</td>
                <td>Animated</td>
                <td>Static</td>
            </tr>
        </table>

        <h3>When to Reduce Data</h3>
        <table class="reference-table">
            <tr>
                <th>Element</th>
                <th>Default</th>
                <th>Reduced Data</th>
            </tr>
            <tr>
                <td>Background images</td>
                <td>High quality</td>
                <td>None or simplified</td>
            </tr>
            <tr>
                <td>Web fonts</td>
                <td>Custom fonts</td>
                <td>System fonts</td>
            </tr>
            <tr>
                <td>Auto-play videos</td>
                <td>Enabled</td>
                <td>Disabled</td>
            </tr>
            <tr>
                <td>Decorative images</td>
                <td>Visible</td>
                <td>Hidden</td>
            </tr>
            <tr>
                <td>High-res images</td>
                <td>Full quality</td>
                <td>Placeholders</td>
            </tr>
        </table>
    </section>

    <!-- ====== 6. BEST PRACTICES ====== -->
    <section>
        <h2>6. Best Practices</h2>

        <div style="background: #d4edda; padding: 20px; border-radius: 8px; border-left: 4px solid #28a745;">
            <h3 style="margin-top: 0; color: #155724;">✅ Do This:</h3>
            <ul>
                <li>Respect <code>prefers-reduced-motion: reduce</code> for all animations</li>
                <li>Provide static alternatives for animated content</li>
                <li>Use <code>prefers-reduced-data: reduce</code> to skip heavy media</li>
                <li>Test with reduced motion enabled in your OS</li>
                <li>Provide working fallbacks (since <code>prefers-reduced-data</code> is experimental)</li>
                <li>Consider the user's comfort over visual flair</li>
            </ul>
        </div>

        <div style="background: #f8d7da; padding: 20px; border-radius: 8px; border-left: 4px solid #dc3545; margin-top: 15px;">
            <h3 style="margin-top: 0; color: #721c24;">❌ Don't Do This:</h3>
            <ul>
                <li>Don't ignore reduced motion — it's an accessibility requirement</li>
                <li>Don't use animations that can't be disabled</li>
                <li>Don't rely solely on <code>prefers-reduced-data</code> (limited support)</li>
                <li>Don't force heavy media on users with limited data</li>
                <li>Don't forget to test with actual users who have motion sensitivity</li>
            </ul>
        </div>
    </section>

    <!-- ====== LIVE INDICATOR ====== -->
    <div class="live-indicator" id="live-indicator">
        <span id="indicator-motion">Motion: —</span>
        <span class="detail" id="indicator-data">Data: —</span>
    </div>

    <!-- ====== SCRIPT FOR DETECTION ====== -->
    <script>
        function updateIndicators() {
            // Motion detection
            const motionNoPref = document.getElementById('motion-no-pref');
            const motionReduce = document.getElementById('motion-reduce');

            const isMotionNoPref = window.matchMedia('(prefers-reduced-motion: no-preference)').matches;
            const isMotionReduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;

            function setCard(card, isActive, activeText, inactiveText) {
                const value = card.querySelector('.value');
                if (isActive) {
                    value.textContent = activeText;
                    card.classList.add('active');
                } else {
                    value.textContent = inactiveText;
                    card.classList.remove('active');
                }
            }

            setCard(motionNoPref, isMotionNoPref, 'Active ✅', 'Not active');
            setCard(motionReduce, isMotionReduce, 'Active ✅', 'Not active');

            // Data detection
            const dataNoPref = document.getElementById('data-no-pref');
            const dataReduce = document.getElementById('data-reduce');

            const isDataNoPref = window.matchMedia('(prefers-reduced-data: no-preference)').matches;
            const isDataReduce = window.matchMedia('(prefers-reduced-data: reduce)').matches;

            setCard(dataNoPref, isDataNoPref, 'Active ✅', 'Not active');
            setCard(dataReduce, isDataReduce, 'Active ✅', 'Not active');

            // Live indicator
            const indicatorMotion = document.getElementById('indicator-motion');
            const indicatorData = document.getElementById('indicator-data');
            const indicator = document.getElementById('live-indicator');

            indicatorMotion.textContent = isMotionReduce ? 'Motion: Reduced 🚫' : 'Motion: Full ✨';
            indicatorData.textContent = isDataReduce ? 'Data: Reduced 📉' : 'Data: Normal 📶';

            // Color the indicator
            if (isMotionReduce || isDataReduce) {
                indicator.style.background = '#28a745';
                indicator.style.color = 'white';
            } else {
                indicator.style.background = '#007bff';
                indicator.style.color = 'white';
            }
        }

        window.addEventListener('load', updateIndicators);

        // Listen for changes
        window.matchMedia('(prefers-reduced-motion: reduce)').addEventListener('change', updateIndicators);
        window.matchMedia('(prefers-reduced-data: reduce)').addEventListener('change', updateIndicators);

        updateIndicators();
    </script>

</body>
</html>

Quick Reference

FeatureDescriptionValues
prefers-reduced-motionUser wants less animationno-preference, reduce
prefers-reduced-dataUser wants lighter data usageno-preference, reduce

prefers-reduced-motion Values

ValueDescription
no-preferenceNo preference expressed (default)
reduceUser prefers reduced motion

prefers-reduced-data Values

ValueDescription
no-preferenceNo preference expressed (default)
reduceUser prefers lighter data usage

When to Reduce Motion

ElementDefaultReduced Motion
Fade animationsEnabledDisabled
Slide transitionsEnabledDisabled
Parallax scrollingEnabledDisabled
Auto-playing carouselsEnabledPaused/disabled
Loading spinnersAnimatedStatic

When to Reduce Data

ElementDefaultReduced Data
Background imagesHigh qualityNone or simplified
Web fontsCustom fontsSystem fonts
Auto-play videosEnabledDisabled
Decorative imagesVisibleHidden
High-res imagesFull qualityPlaceholders

Best Practices

Do This:

/* Disable animations for reduced motion */
@media (prefers-reduced-motion: reduce) {
    * {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
        scroll-behavior: auto !important;
    }
}

/* Enable animations only when no preference */
@media (prefers-reduced-motion: no-preference) {
    .fade-in {
        animation: fadeIn 2s ease-in;
    }
}

/* Reduce data usage */
@media (prefers-reduced-data: reduce) {
    .hero {
        background-image: none;
        background: #007bff;
    }
    .decorative-image {
        display: none;
    }
}

/* Provide static alternatives */
@media (prefers-reduced-motion: reduce) {
    .spinner {
        animation: none;
    }
    .spinner::after {
        content: "Loading...";
    }
}

Don’t Do This:

/* Don't ignore reduced motion */
.animation {
    animation: bounce 2s infinite;
    /* No reduced motion support — accessibility issue! */
}

/* Don't rely solely on prefers-reduced-data */
@media (prefers-reduced-data: reduce) {
    /* Limited browser support — always provide fallbacks */
}

/* Don't force heavy media on data-saver users */
.hero {
    background-image: url('huge-image.jpg');
    /* No alternative for reduced data */
}

Pro Tip: prefers-reduced-motion is essential for accessibility. Users with vestibular disorders, motion sensitivity, or epilepsy can be physically harmed by excessive animation. Always provide static alternatives — a loading spinner can become “Loading…” text, and a fade-in can simply appear. prefers-reduced-data is still experimental with limited support, so use it as an enhancement, not a requirement. Always provide working fallbacks, and test with actual users who have these preferences enabled!


Stop using slow, ad-bloated tool sites! 🤮

🔎 Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)

⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free

🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!