WCAG 2.3.1 · Level A · EN 301 549 9.2.3.1

Three Flashes or Below Threshold

Requires that nothing on a page flashes more than three times in any one-second period, or stays below the general and red flash thresholds, to protect people with photosensitive epilepsy.

In brief

Flashing content can trigger seizures in people with photosensitive epilepsy. A seizure is a medical emergency, and unlike most accessibility barriers, this one is not an inconvenience. It can hurt people. A strobing banner, a flashing video clip or a rapid animation can be enough.

This criterion requires that web pages do not contain anything that flashes more than three times in any one-second period, or that the flash is below the general flash and red flash thresholds. The safest rule is simple: do not flash more than three times per second, whatever the size of the flashing area.

What does it cover?

  • The simple rule: No more than three flashes in any one-second period. A flash is a pair of opposing changes in brightness, a rise followed by a fall or the other way round.
  • The threshold alternative: Content that flashes more often can still pass if it stays below the general flash and red flash thresholds. In practice this is checked with a tool, not by eye.
  • General flash: A pair of opposing changes in relative luminance of 10% or more of the maximum, where the darker state has a relative luminance below 0.80.
  • Red flash: Any pair of opposing transitions involving a saturated red.
  • Area: Flashing is allowed if the combined flashing area is no more than 0.006 steradians within any 10-degree field of view, roughly 341 by 256 pixels on a 1024 by 768 screen at typical viewing distance.
  • Everything on the page counts: Video, animated GIFs, CSS animation, script-driven effects, advertisements and third-party content all fall under it, even when they are not what the page is about.
  • Out of scope: Content that moves or blinks without flashing is 2.2.2. A stricter Level AAA version without the threshold alternative is 2.3.2.

Who does it affect?

People with photosensitive epilepsy, for whom a flash or a strobing pattern can trigger a seizure. Some people with migraines and vestibular disorders are also affected. Users who sit close to the screen or enlarge content are at greater risk, because the flashing then fills more of their field of view.

In the EAA and EN 301 549

The European Accessibility Act (Directive (EU) 2019/882) applies to in-scope products and services placed on the market after 28 June 2025. Recital 47 names four principles for websites and mobile applications: perceivable, operable, understandable and robust. Content that is safe to look at is part of "operable".

The technical counterpart is the standard EN 301 549, which incorporates WCAG 2.3.1 by reference and turns it into separate clauses for each kind of ICT.

Where WCAG 2.3.1 appears in EN 301 549
ClauseType of ICTWhat it requires
9.2.3.1Web pagesWeb content must satisfy WCAG success criterion 2.3.1.
10.2.3.1Non-web documents (PDF, Office files)Documents that contain flashing content must meet the same rule where the standard applies the criterion to them.
11.2.3.1Non-web software (native mobile apps)Software must not contain anything that flashes more than three times in any one-second period, or must stay below the flash thresholds.

Whether and how the criterion applies to non-web documents is stated in clause 10 of the standard. Clause numbers follow the "9. + WCAG number" pattern of EN 301 549. In V3.2.1 the software clause is divided into further subclauses for open and closed functionality. Check the exact numbers in the standard when you cite them.

Standard version: EN 301 549 V4.1.1 (September 2026) is based on WCAG 2.2 and is the first version prepared specifically for the EAA. It has not yet been cited in the Official Journal of the European Union, so for now it is best treated as a voluntary technical route to the EAA's requirements. Once cited, it will give a presumption of conformity. The text of criterion 2.3.1 is the same in WCAG 2.1 and 2.2.

A note on EAA scope: Article 2(4) of the Directive excludes certain content from the requirements, for example office file formats published before 28 June 2025 and archives that are not updated after that date. Content you publish or change after that date is not covered by these exclusions.

Directive (EU) 2019/882, EUR-Lex, opens in a new tab

Code examples for the web

1. A CSS animation that blinks quickly

A one-second animation with four or more bright-dark cycles breaks the rule. Keep the number of flashes per second at three or fewer, or better, do not flash at all.

Incorrect
@keyframes blink {
  0%, 100% { background: #ffffff; }
  50%      { background: #000000; }
}
.alert { animation: blink 0.2s infinite; } /* 5 flashes per second */
Correct
.alert { background: #fff3cd; border: 2px solid #8a6d00; }
/* A static, high-contrast style draws attention without flashing */

2. A gentle fade instead of a hard flash

Slow, smooth changes are not flashes. A fade that takes a second or more is safe.

Incorrect
.notice { animation: flash 0.25s steps(1) infinite; }
Correct
.notice { animation: fade 2s ease-in-out infinite alternate; }
@keyframes fade {
  from { opacity: 1; }
  to   { opacity: 0.6; }
}

3. Script-driven flashing

A timer that toggles the page between light and dark many times per second is a flashing source.

Incorrect
setInterval(() => document.body.classList.toggle("invert"), 100);
Correct
// Do not toggle the whole page on a fast timer.
// If a visual cue is needed, show it once:
element.classList.add("highlight");
setTimeout(() => element.classList.remove("highlight"), 1500);

4. Respect reduced motion

Remove non-essential animation for users who ask for it. This does not replace meeting the criterion, but it lowers risk.

Incorrect
.banner { animation: pulse 0.3s infinite; }
Correct
.banner { animation: pulse 2s ease-in-out infinite; }
@media (prefers-reduced-motion: reduce) {
  .banner { animation: none; }
}

5. Video and GIF content

Check clips with strobe effects, camera flashes, explosions and rapid cuts before publishing. A looping GIF is analysed while it loops.

Incorrect
<img src="strobe-promo.gif" alt="Promotion">
Correct
<video src="promo.mp4" muted playsinline controls
       aria-label="Promotion"></video>
<!-- Checked with a flash analysis tool before publishing -->

6. Third-party content

Ads, embeds and widgets are part of your page. Test them, and choose providers that do not use flashing effects.

Incorrect
<iframe src="https://ads.example/banner" title="Ad"></iframe> <!-- never tested -->
Correct
<!-- Contract and test: no flashing creatives; remove the placement if one appears -->

Code examples for mobile

In native apps, animations and video can flash as well. Keep animation slow, respect the system Reduce Motion setting, and test video content before shipping it.

iOS (SwiftUI)

import SwiftUI

struct AlertBadge: View {
    @Environment(\.accessibilityReduceMotion) private var reduceMotion
    @State private var dim = false

    var body: some View {
        Circle()
            .fill(.red)
            .frame(width: 16, height: 16)
            // Avoid: .animation(.linear(duration: 0.1).repeatForever(), value: dim)
            // A slow pulse, off when the user asks to reduce motion
            .opacity(dim ? 0.6 : 1)
            .animation(reduceMotion ? nil :
                .easeInOut(duration: 2).repeatForever(autoreverses: true), value: dim)
            .onAppear { dim = true }
    }
}

Android (Jetpack Compose)

import android.provider.Settings
import androidx.compose.animation.core.*
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp

@Composable
fun AlertBadge() {
    val context = LocalContext.current
    val animationsOn = Settings.Global.getFloat(
        context.contentResolver, Settings.Global.ANIMATOR_DURATION_SCALE, 1f
    ) != 0f

    val transition = rememberInfiniteTransition(label = "pulse")
    // Avoid: tween(durationMillis = 100). Use a slow pulse instead.
    val alpha by transition.animateFloat(
        initialValue = 1f, targetValue = 0.6f,
        animationSpec = infiniteRepeatable(tween(2000), RepeatMode.Reverse),
        label = "alpha"
    )
    Box(
        modifier = Modifier
            .size(16.dp)
            .alpha(if (animationsOn) alpha else 1f)
            .background(Color.Red, CircleShape)
    )
}

How to test

  • Count the flashes: Watch every animation, video, GIF and script effect. Does anything alternate between bright and dark more than three times in one second?
  • Slow it down: Record the screen and step through the frames, or use the browser animation tools to count cycles per second.
  • Use an analysis tool: Run video and animated content through a photosensitive epilepsy analysis tool (see technique G15). It tests both the general flash and red flash thresholds.
  • Check at full size: Test at the largest size the content can be viewed, for example full-screen video, and at normal browser zoom.
  • Check the code: Look for animations with very short durations, steps() timing, and timers that toggle classes or colours.
  • Check third-party content: Open pages with ads, embeds and widgets, and watch them for a while.
  • Check mobile: Review in-app animations, transitions and video for strobing, and make sure Reduce Motion switches non-essential animation off.
  • Use a checker as a starting point: A static scanner cannot see a flash rate. A person, or a flash analysis tool, has to look at the actual output.

Common mistakes

  • Fast blinking alerts: A 0.2-second blink cycle is five flashes per second.
  • Strobe effects in promotional video: Camera flashes and rapid cuts count too.
  • Looping GIFs that flicker: The loop repeats the problem endlessly.
  • Full-screen colour inversion on a timer: It is a large-area flash, which is the most dangerous kind.
  • Untested third-party ads: They are your responsibility on your page.
  • Saturated red flashing: Red has its own, stricter threshold.
  • Ignoring Reduce Motion: Users who set it expect non-essential animation to stop.
  • Assuming a small flash is safe: Users enlarge content and sit close to screens, so area matters less than it seems.

Sources

Standard version: EN 301 549 V4.1.1 (2026-09), WCAG 2.2. This page is for informational purposes only and is not legal advice. For binding requirements, refer to the official texts of the European Accessibility Act (Directive (EU) 2019/882) and EN 301 549.

Corpowid is recognized by Gartner

Corpowid has been recognized by Gartner, a leading global research and advisory firm, for our innovation and performance in digital accessibility. These badges reflect our commitment to creating inclusive, AI-powered web experiences.

Have questions about Corpowid?

Let’s connect.

We will get back to you as soon as possible.