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.
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.
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.
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.
| Clause | Type of ICT | What it requires |
|---|---|---|
| 9.2.3.1 | Web pages | Web content must satisfy WCAG success criterion 2.3.1. |
| 10.2.3.1 | Non-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.1 | Non-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
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.
@keyframes blink {
0%, 100% { background: #ffffff; }
50% { background: #000000; }
}
.alert { animation: blink 0.2s infinite; } /* 5 flashes per second */.alert { background: #fff3cd; border: 2px solid #8a6d00; }
/* A static, high-contrast style draws attention without flashing */Slow, smooth changes are not flashes. A fade that takes a second or more is safe.
.notice { animation: flash 0.25s steps(1) infinite; }.notice { animation: fade 2s ease-in-out infinite alternate; }
@keyframes fade {
from { opacity: 1; }
to { opacity: 0.6; }
}A timer that toggles the page between light and dark many times per second is a flashing source.
setInterval(() => document.body.classList.toggle("invert"), 100);// 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);Remove non-essential animation for users who ask for it. This does not replace meeting the criterion, but it lowers risk.
.banner { animation: pulse 0.3s infinite; }.banner { animation: pulse 2s ease-in-out infinite; }
@media (prefers-reduced-motion: reduce) {
.banner { animation: none; }
}Check clips with strobe effects, camera flashes, explosions and rapid cuts before publishing. A looping GIF is analysed while it loops.
<img src="strobe-promo.gif" alt="Promotion"><video src="promo.mp4" muted playsinline controls
aria-label="Promotion"></video>
<!-- Checked with a flash analysis tool before publishing -->Ads, embeds and widgets are part of your page. Test them, and choose providers that do not use flashing effects.
<iframe src="https://ads.example/banner" title="Ad"></iframe> <!-- never tested --><!-- Contract and test: no flashing creatives; remove the placement if one appears -->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.
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 }
}
}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)
)
}steps() timing, and timers that toggle classes or colours.