Content that flashes more than three times a second can trigger seizures. WCAG 2.3.1 sets the limit and the exceptions for video, animation and alerts.
Most criteria are about usability. This one is about harm: flashing light can trigger seizures in people with photosensitive epilepsy, and the effect can be severe. Success Criterion 2.3.1 says web pages must not contain anything that flashes more than three times in any one-second period, unless the flashing is below the general flash and red flash thresholds.
The thresholds are defined in the standard. A general flash is a pair of opposing changes in relative luminance of 10% or more of the maximum (1.0), where the darker state is below 0.80. A red flash is a pair of transitions involving saturated red. Content also passes if the combined area of the flashing is small: no more than 0.006 steradians, which is 25% of a 10-degree visual field. The standard approximates that whole field as a 341 by 256 pixel rectangle on a 1024 by 768 screen, so the flashing area must stay below about a quarter of that rectangle.
Fine, balanced patterns such as checkerboards with very small squares do not break the thresholds. The requirement applies to all content on the page, including embedded video and third-party ads.
WCAG 2.3.1 applies to all content on the page, including embedded video and ads. The intent behind a blinking alert is usually good. The fix is to draw attention without luminance flicker: bold colors, icons, position and a clear message.
A map marker for a delayed bus can announce itself briefly and then stop. Here the marker changes state six times in 2.4 seconds, which is three flashes in 2.4 seconds, far below three per second.
setInterval(() => marker.classList.toggle("on"), 150); // about 3.3 flashes/s foreverlet n = 0;
const t = setInterval(() => {
marker.classList.toggle("on");
if (++n >= 6) clearInterval(t); // stops after about 2.4 seconds
}, 400);Hard black-to-white wipes are a classic source of fast flashes. Fade in once instead.
@keyframes strobe { 0% { background: #000; } 50% { background: #fff; } }
.splash { animation: strobe 0.12s 12; }@keyframes fade-in { from { opacity: 0; } to { opacity: 1; } }
.splash { animation: fade-in 0.6s ease-out 1; }The same limit applies to app screens, including loading indicators and status effects. SwiftUI:
let timer = Timer.publish(every: 0.1, on: .main, in: .common).autoconnect()
// ...
.onReceive(timer) { _ in invert.toggle() } // full-screen flicker(urgent ? Color.red : Color.clear)
.animation(.easeInOut(duration: 0.8), value: urgent) // one change, no loopJetpack Compose:
val c by rememberInfiniteTransition().animateColor(
initialValue = Color.White, targetValue = Color.Red,
animationSpec = infiniteRepeatable(tween(100), RepeatMode.Reverse)
)val c by animateColorAsState(
targetValue = if (urgent) Color(0xFFB00020) else Color.White,
animationSpec = tween(800)
) // one smooth change, no repeatAsk the people who supply video and ads whether they have checked their content, and re-check after each content update.
Ship it when nothing on the page flashes more than three times in any second, or what does is clearly below the general and red flash thresholds, including content from vendors.
See vendors and third-party content for handling embedded media.