Zum Artikel springen

Messung Erschienen Lesezeit 9 Min.

Warum axe Kontrastfehler auf Text meldet, den du lesen kannst

Dieselben 144 Scans, sechs Minuten auseinander, meldeten erst 707 schwerwiegende Kontrastknoten und dann 618 — und die zweite Zahl ist die kleinere Lüge. axe kennt keine Zeit: Es blendet deinen Text selbst ein, und alles, was bei exakt null Deckkraft steht, lässt es durch. Beide Fehler stehen Knoten für Knoten in den gespeicherten Läufen dieses Repositorys.

Kurz gesagt

axe misst das Frame, das es zufällig erwischt. Es multipliziert die Textfarbe mit der Deckkraft jedes Vorfahren und legt das Ergebnis auf das, was dahinter liegt — eine Überschrift ein Prozent in eine Einblendung hinein ist also tatsächlich #eaf2f0 auf #edf5f3, also 1.02:1, während dieselbe Überschrift eingeschwungen fast schwarz ist und sich gut liest. Ein Durchlauf über diese Seite ohne reduzierte Bewegung, 144 Scans über 72 Dokumente in zwei Breiten, meldete 707 schwerwiegende Farbkontrast-Knoten auf 60 Routen; sechs Minuten später unter prefers-reduced-motion: reduce wiederholt, fielen fünf ganze Seitenfamilien auf exakt null. Dieselbe geänderte Bedingung legte einen echten Defekt frei, den der erste Lauf verborgen hatte, denn alles, was bei exakt opacity: 0 steht, gibt axe als bestanden zurück.

Was du mitnimmst

  1. Die Scan-Bedingung ist eine Messvariable: Dieselben 144 Scans ergaben 707 schwerwiegende Kontrastknoten auf 60 Routen ohne reduzierte Bewegung und 618 auf 36 mit ihr, sechs Minuten auseinander.
  2. axe multipliziert den Alphawert des Vordergrunds mit der Deckkraft jedes Vorfahren und legt das Ergebnis auf den Hintergrund, also misst eine Überschrift ein Prozent in eine Einblendung hinein 1.02:1 — es irrt sich nicht in den Pixeln, es kennt nur keine Zeit.
  3. Dieselbe Blindheit wirkt auch in die andere Richtung: Ein Element bei exakt null Deckkraft gilt als verborgen und kommt als bestanden zurück, was 420 der 612 Knoten eines echten 4.36:1-Fehlers verdeckt hat.
  4. Vergleiche die beiden Bedingungen, statt über einen einzelnen Befund zu streiten — was beide Läufe übersteht, ist eine Frage der Farbe, und was nur in einem auftaucht, eine Frage des Zeitpunkts.
  5. Veröffentliche eine Zahl, die sich bewegen kann: fünf Verstöße mit Element, Verhältnis, Datum, Toolversion und Scan-Bedingung daneben sind eine Messung, null ist eine Behauptung.

axe-core 4.11.0 · axe.js:24321-24345

Warum meldet axe Kontrastfehler auf Text, den du lesen kannst?

Die Regel tut genau das, was sie ankündigt. axe-core läuft vom Textknoten aus durch jeden übergeordneten Stacking Context nach oben, multipliziert den Alphawert der Vordergrundfarbe mit der Deckkraft jedes einzelnen, legt das Ergebnis auf das, was dahinter gemalt ist, und legt das am Ende auf Weiß. Eine Überschrift ein Prozent in eine 800 ms lange Einblendung hinein ist deshalb nicht fast schwarz auf Papier — sie ist #eaf2f0 auf #edf5f3, und dieses Paar misst 1.02:1 gegen eine Anforderung von 4.5:1.

Dieses Komposit ist die Seiten-Tinte bei ungefähr ein bis zwei Prozent Deckkraft, und man kann es ohne Leuchtdichteformel nachrechnen: #eaf2f0 unterscheidet sich vom Papier, auf dem es liegt, um drei in Rot, Grün und Blau. Fast die ganze Tinte ist vom Compositing weggewaschen worden. axe irrt sich nicht in diesen Pixeln. Es irrt sich im Moment, und das ist ein anderer Fehler und braucht eine andere Lösung.

Zwei weitere Details der Regel werden wichtig, sobald eine Zahl nahe an der Grenze landet. axe schneidet das Verhältnis ab, statt zu runden — abgerundet aus Verhältnis mal hundert, geteilt durch hundert — und der Bestehenstest verlangt echt größer als die Anforderung, ein Paar mit exakt 4.50:1 fällt also durch. Beides kommt in Abschnitt 06 zurück, wo ein echter Defekt vierzehn Hundertstel unter der Grenze lag.

Die zwei Stellen in axe-core 4.11.0, an denen ein Moment zur Messung wird. Aus der gepinnten Kopie in node_modules/axe-core/axe.js gelesen und gegen den Tag v4.11.0 im Upstream geprüft.
// lib/commons/color/get-foreground-color.js — der Fehlalarm
function calculateBlendedForegroundColor(fgColor, context, stacks) {
  while (context) {
    if (context.opacity === 1 && context.ancestor) {
      context = context.ancestor; continue;
    }
    fgColor.alpha *= context.opacity;            // die Tinte wird blasser
    fgColor = flattenColors(fgColor, bgColor);   // dann auf den Hintergrund gelegt
    context = context.ancestor;
  }
  return fgColor;
}
 
// lib/checks/color/color-contrast-evaluate.js — der übersehene Fehler
if (!isVisibleOnScreen(node)) {
  this.data({ messageKey: 'hidden' });
  return true;                                 // ein scharfes Reveal gilt als BESTANDEN
}
 
// lib/commons/dom/visibility-methods.js
function opacityHidden(vNode) {
  return vNode.getComputedStylePropertyValue('opacity') === '0';
}

Der zweite Block ist die Hälfte, die in dieser Geschichte meist fehlt, und er ist die gefährlichere. Ein Fehlalarm kommt mit Selektor, Schweregrad und Knotenzahl, also streitet jemand darüber. Ein übersehener Fehler kommt als gar nichts.

two stored runs · 6 min 24 s apart

Was hat die geänderte Scan-Bedingung wirklich verändert?

Zwei Durchläufe über diese Seite liegen in tests/.golden-runs/, sechs Minuten und vierundzwanzig Sekunden auseinander, am 22. August 2026. Beide scannten 144 Seiten: 72 Dokumente bei 1440x900 und noch einmal bei 390x844. Beide spielten axe-core 4.11.0 aus node_modules ein statt von einem CDN, begrenzt auf die Tags wcag2a, wcag2aa, wcag21a und wcag21aa. Der einzige Unterschied zwischen ihnen ist eine Playwright-Context-Option: reducedMotion auf reduce statt auf no-preference.

Der erste Lauf meldete 707 schwerwiegende Farbkontrast-Knoten auf 60 Routen — 419 in Desktop-Breite, 288 im Handy-Viewport — in 117 Befundgruppen, jede davon dieselbe Regel im selben Schweregrad. Der zweite meldete 618 auf 36 Routen. Neunzig Knoten weniger klingt nach einem Rundungsfehler. Familie für Familie ist es nichts dergleichen.

Dieselben 144 Scans, sechs Minuten auseinander, geändert wurde nur die Scan-Bedingung. Die Knotenzahlen sind exakt und stammen aus den zwei gespeicherten Läufen; diese Läufe sind gitignored, also sind sie lokale Belege aus erster Hand und nichts, was eine Leserin aus dem öffentlichen Repository ziehen kann.
Seitenfamilie Routen Ohne reduce Unter reduce
Startseite 3 6 0
Projektübersicht 3 60 0
Disziplinenübersicht 3 33 0
Disziplin-Detailseite 12 292 0
Kontakt 3 118 0
Fallstudien 33 192 612
404 3 6 6

Fünf Familien gingen von Hunderten schwerwiegender Knoten auf exakt null. Das sind 509 Knoten reines Rauschen — Text, den ein Mensch einwandfrei liest, mitten im Reveal von einem Scanner mit Stoppuhr erwischt. Eine Familie ging in die andere Richtung und stieg um 420. Eine bewegte sich überhaupt nicht.

Die Kontrolle, die dieses Argument braucht, steckt im selben Lauf. Von den 62 eigenen Routen, die das Manifest damals führte, fand der Durchlauf ohne reduce auf 60 Verstöße; die zwei, zu denen er nichts zu sagen hatte, waren Impressum und Datenschutz — die einzigen zwei Dokumente der Seite ohne Reveal-System beim Eintritt. Die zehn migrierten Demoseiten, zusammen zwanzig Scans, steuerten aus demselben Grund null der 707 bei: Auf ihnen bewegt sich nichts.

Die Fallstudien-Familie zählte damals 33 Routen statt der 30, die sie heute zählt, weil sie zehn Fallstudien in drei Sprachen plus eine eigenständige Erklärseite umfasste, die inzwischen stillgelegt und umgeleitet ist.

Dieselbe Frage — wie viel eines gemeldeten Unterschieds der Seite gehört und wie viel dem Harness — hat auch eine Antwort in Pixeln: das Rauschniveau eines Canvas unter Visual Regression.

axe.js:26148 · 11317 · runs A and B

Die andere Hälfte: axe lässt Text bei null Deckkraft durch

Die Fallstudien-Familie meldete 192 Knoten ohne reduzierte Bewegung und 612 mit ihr. Diese Richtung ist die überraschende, und der Mechanismus sind zwei Zeilen des Werkzeugs. Der Kontrast-Check beginnt mit der Frage, ob der Knoten auf dem Bildschirm sichtbar ist; das Deckkraft-Prädikat antwortet nein für jedes Element, dessen berechnete Deckkraft exakt null ist; und der Check gibt true zurück, also bestanden.

Jedes Reveal auf dieser Seite wird über einen Startzustand mit null Deckkraft scharfgestellt, der nur dann in die Kaskade geschrieben wird, während ein Observer das Element bereits hält. Unter no-preference waren die meisten dieser Elemente auf einer Fallstudie während des Scan-Durchlaufs nie in den Viewport gekommen, standen also bei exakt null — verborgen, übersprungen, als bestanden gezählt. Unter reduce wird der Startzustand gar nicht erst geschrieben, also wurden alle 612 Knoten ausgewertet, und alle 612 fielen durch.

Der Kommentar im Kopf des Harness in diesem Repository dreht das falsch herum, in einem Satz einer sonst ungewöhnlich sorgfältigen Passage: Er sagt, ein Reveal unterhalb des Falzes stehe aus Sicht eines Scanners für immer scharf da und falle auf jeder Seite durch, auf der es vorkommt. Es passiert das Gegenteil. Ein scharfes Reveal wird stillschweigend durchgelassen, und die zwei gespeicherten Läufe belegen es — die Familie, deren Reveals meistens nie ausgelöst wurden, lieferte die kleinste Zahl, nicht die größte.

Deshalb ist die verbreitete Formulierung — axe liefere Fehlalarme bei Animationen — eine halbe Wahrheit, und die fehlende Hälfte ist die teure. Eine Regel ohne Zeitbegriff irrt sich in beide Richtungen zugleich: Sie erfindet Fehler auf Text, der gerade hereinkommt, und entschuldigt Fehler auf Text, der noch gar nicht da ist.

derived · linear fade · 800 ms reveal

Wie viel einer Einblendung ist garantiert ein Fehler?

Der Übergangspunkt lässt sich für jedes Paar ausrechnen, und für dein eigenes lohnt es sich, weil er dir sagt, welcher Anteil jeder Transition auf deiner Seite nicht scanbar ist. Lege die Tinte bei jedem Alphawert auf das Papier, wende die WCAG-Formel für relative Leuchtdichte an und schneide so ab, wie axe abschneidet.

Für die Tokens dieser Seite wird die Grenze 4.5:1 bei einer Deckkraft von 0.5521 überschritten und die Grenze 3:1 für große Schrift bei 0.4271. Unterhalb dieser Punkte ist jedes Frame ein gemeldeter schwerwiegender Verstoß gegen SC 1.4.3; oberhalb besteht dasselbe Element, ohne dass sich auf der Seite irgendetwas geändert hätte außer der Wartezeit des Scanners.

Deckkraft 0.0125 1.02:1
Deckkraft 0.30 2.05:1
Deckkraft 0.50 3.76:1
Deckkraft 0.5521 — der Übergang 4.50:1
Deckkraft 0.70 7.71:1
eingeschwungen, Deckkraft 1 20.36:1
Der gemeldete Kontrast der Seiten-Tinte auf dem Seiten-Papier über eine Einblendung hinweg.Abgeleitet statt gemessen: #040504 bei jedem Alphawert auf #edf5f3 gelegt, abgeschnitten wie axe abschneidet. Die Balken sind maßstäblich gegen die eingeschwungenen 20.36:1.

Bei einem 800 ms langen Reveal läge eine lineare Einblendung rund 442 ms unter der Grenze für normalen Text und rund 342 ms unter der für große Schrift. Nimm diese Millisekunden als Größenordnung und nicht mehr — jedes Reveal hier ist geeast, die echte Zeit unter der Grenze unterscheidet sich also von Abschnitt zu Abschnitt. Die Deckkraft-Übergänge sind exakt, die Dauern sind eine Näherung.

Das Harness macht das Rauschen dann schon durch seinen Aufbau unvermeidlich. Es scrollt jedes Dokument in Schritten von Viewport-Höhe, 60 ms auseinander, kehrt nach oben zurück, wartet 120 ms und spielt axe ein. Ein Element, das im letzten Schritt in den Blick kam, ist rund 180 ms in eine 800 ms lange Einblendung hinein, wenn axe anfängt, seinen Baum zu bauen — deutlich vor dem Übergang bei 55 Prozent, und allein deshalb als schwerwiegender Fehler gemeldet.

Der reinste Fall von allen ist das Label des Seiten-Loaders. Eingeschwungen ist es #040504 auf #21f1a8: 13.81:1, mehr als das Dreifache dessen, was die Regel verlangt. Es blendet nach 900 ms Halten über 300 ms aus, und der Lauf ohne reduce hat es genau dort erwischt — sechs Knoten, drei Startseiten, nur Desktop. Unter reduce steht es in jedem gespeicherten Lauf auf null. An der Seite hat sich nichts geändert. Nur das Frame.

three guarantees · one gate that checks

Versteckt ein Scan unter reduzierter Bewegung nicht einfach das Problem?

Das würde er, wenn die Seite unter reduzierter Bewegung die animierte Seite wäre, eingefroren in ihrem ersten Frame. Das ist die Variante dieser Technik, der man misstrauen sollte: die Einstellung anschalten, die Zahl fallen sehen, die kleinere veröffentlichen und über die Bedingung schweigen. Die Frage, die beide trennt, lautet, ob der eingeschwungene Zustand garantiert ist und ob etwas Unabhängiges das nachprüft.

Auf dieser Seite ist er auf drei Wegen garantiert, die nicht voneinander abhängen, und ein zweites Gate behauptet das Ergebnis nicht, sondern prüft es.

  1. Der Startzustand wird nie scharfgestellt Die Runtime schreibt das Attribut, das ein Reveal verbirgt, nur dann, wenn reduzierte Bewegung aus ist. Unter reduce gelangt der verborgene Startzustand überhaupt nicht in die Kaskade, es bleibt also nichts mitten in der Transition übrig, das einfrieren könnte.
  2. Jedes Ziel wird von vornherein eingeblendet Der Reveal-Installer läuft mit settleImmediately und wendet das Endframe jeder Animation sofort an. Der Motion-Vertrag nennt das end-state, und es ist die vorgeschriebene Antwort für jeden Effekt, dessen Ergebnis der Inhalt selbst ist.
  3. Das Stylesheet erzwingt die eingeschwungene Lage ohnehin Jedes der fünf Abschnitts-Stylesheets, die das Muster tragen, setzt in einer Media Query für reduzierte Bewegung zusätzlich volle Deckkraft und keinen Transform — für den Fall, dass die Einstellung mitten in der Sitzung umgelegt wird.
  4. Die Reset-Ebene zieht die Dauern zusammen base/reset.css setzt animation-duration und transition-duration unter dieser Einstellung auf 0.01ms, und zwar gezielt im Reset-Layer, damit nichts weiter unten es überspezifizieren kann.
  5. Ein zweites Gate belegt, dass nichts verborgen ist Das ältere Accessibility-Gate läuft über jeden animierten Hook, jedes Split-Text-Element und jede erzeugte Zeile auf der Seite unter reduzierter Bewegung und prüft, dass keins auf verborgener Sichtbarkeit, null Deckkraft oder beschnitten steht. Die Behauptung lautet: Die Zahl verborgener Inhalte ist null.
Was die reduce-Bedingung auf dieser Seite garantiert, und die Prüfung, die sie ehrlich hält.

Es gibt auch ein Argument aus der Norm, und es ist das stärkere. Die Ausnahme Incidental zu SC 1.4.3 nimmt Text aus, der für niemanden sichtbar ist, und eine Überschrift bei einem Prozent Deckkraft ist für niemanden sichtbar. Den eingeschwungenen Zustand zu messen ist kein Ausweichen vor der Anforderung; es ist der Zustand, um den es der Anforderung geht, und der Befund war nie einer, den die Norm verlangt hat.

Das Accessibility-Dokument dieser Seite zieht in die andere Richtung, und die Spannung gehört benannt statt geglättet. Verpflichtung C-4 sagt, eine Farbe, die animiert, müsse ihre Grenze an jedem abgetasteten Punkt der Transition halten und nicht nur an den Endpunkten — wörtlich genommen macht das die 1.02:1 zu einem echten Fehler. Unterschieden wird in Wahrheit zwischen einem Zustand, in dem eine Leserin sich befindet, etwa einer scrollgetriebenen Farbüberblendung, die alle 10 px gemessen wird, und einem Zustand, den niemand jemals als Text wahrnimmt.

Reduzierte Bewegung ist lange vor einem Testschalter eine inhaltliche Entscheidung, und die härteste Variante davon ist eine Animation, die sich mit der Einstellung nicht einfach abschalten lässt.

project-detail.css:37-46 · not-found.css:53

Was die Korrektur überstanden hat, und was das Beheben gekostet hat

Bei drei Befunden in diesen Läufen ging es um Farbe und nicht um den Zeitpunkt, und erkennen ließ sich das nur, indem man die beiden Bedingungen verglich. Einer davon war groß, echt und 420 Knoten größer, als der erste Lauf gesagt hatte.

Der Fallstudien-Akzent ist der Zuschnitt des Seitengrüns, der auf Papier lesbar bleibt, wo er 4.82:1 misst. Die Fallstudienseite druckt ihn auf ihre eigene Kartenfläche, #e2eae7, und gegen diesen Grund misst dasselbe Paar 4.36:1 — vierzehn Hundertstel unter der Grenze, über 612 Knoten und jedes Fallstudien-Dokument hinweg. Es ist genau die Art Fehler, die ein Designer nie sieht und ein Scanner immer.

Die drei Befunde, die einen Wechsel der Scan-Bedingung überstanden haben. Die mittlere Zeile ist die Kontrolle: eine statische Farbe auf einer Seite, auf der sich nichts bewegt, lieferte unter beiden Bedingungen dieselben sechs Knoten.
Befund Paar und Ort Grenze Gemessen, dann behoben auf
Fallstudien-Akzent als Fließtext #0c7a55 auf der Karte #e2eae7, 612 Knoten, jedes Fallstudien-Dokument 4.5:1 4.36:1, dann 5.55:1
404-Wasserzeichen die Seiten-Tinte bei 10 Prozent über dem Papier, 6 Knoten auf 3 Routen 3:1 bei 70px 1.25:1, dann 3.12:1
Zertifikats-Überschrift, Akzenthälfte #21f1a8 auf #edf5f3, 5 Knoten auf 3 Routen 3:1 1.33:1, nicht behoben

Die Korrektur an der Fallstudie ist eine Zeile: fünfzehn Prozent der seiteneigenen Tinte in den Akzent gemischt, was ihn auf 5.55:1 auf der Karte und 6.13:1 auf Papier bringt und nichts ändert, das irgendjemand wahrnehmen kann. Das 404-Wasserzeichen ging von 10 Prozent Tinte auf 44 Prozent, das sind 3.12:1 gegen die Grenze 3:1, die bei 70 Pixeln gilt, und es bleibt der Überschrift daneben klar untergeordnet.

Beide waren innerhalb von acht Minuten nach dem Lauf behoben, der sie gefunden hat. Der nächste gespeicherte Durchlauf, sieben Minuten und achtundfünfzig Sekunden später, meldete null, und die Zahl blieb dann über neun aufeinanderfolgende Läufe an zwei Tagen bei null, bis eine Entscheidung sie auf fünf setzte.

Die 404 verdient noch einen Satz, weil sie die sauberste Kontrolle von allen ist. Auf dieser Seite bewegt sich nichts, sie meldete also unter beiden Bedingungen sechs Knoten und wurde nicht dadurch gefunden, dass irgendetwas geändert wurde. Nur die wahre Größe des Fallstudien-Defekts hing an der Bedingung — 192 gemeldet, 612 echt.

Die Dokumente, über die sich diese 612 Knoten verteilten, sind die Fallstudien selbst — zehn Builds, jeder mit seinen eigenen Messwerten.

published-measurements.json:33, 56-62

Warum sagt die veröffentlichte Zahl fünf und nicht null?

Die Überschrift des Zertifikatsabschnitts setzt ihre zweite Hälfte im seiteneigenen Akzent, #21f1a8, auf dem hellen Grund, auf dem dieser Abschnitt aufgebaut ist. Das misst 1.33:1, wo 3:1 gilt. Das Token, das es genau für diesen Fall gibt — der Akzent so weit abgedunkelt, dass er auf Papier lesbar ist — misst 3.23:1, und löst man die Kontrastgleichung für diesen Grund auf, deckelt sie den Akzent bei #0aa16c, was von dem Token, das ohnehin schon ausgeliefert wurde, nicht zu unterscheiden ist.

Dem Inhaber wurde die Messung zuerst gezeigt, und er wollte den Akzent trotzdem. Also geht er als Entscheidung mit ihrer Zahl daneben online und nicht als Feinjustierung: fünf Knoten, ein Element, drei Routen, zwei Viewports. Von einer Ausnahme ist er auch nicht gedeckt — die Logotype-Klausel deckt ein Logo oder einen Markennamen, und eine Abschnittsüberschrift in einer Markenfarbe ist Text wie jeder andere.

  • 144 Scans 72 Dokumente in zwei Breiten, WCAG 2.1 A und AA
  • 5 Verstöße ein Element, drei Routen, zwei Viewports
  • 4.11.0 axe-core-Version aus node_modules eingespielt, nie von einem CDN
  • 0 Screenreader-Durchgänge hier hat nie ein manueller Durchgang stattgefunden
Der veröffentlichte Eintrag, so wie contracts/published-measurements.json ihn festhält. Jedes Feld wird bei jedem Prüflauf gegen die sichtbare Fassung in drei Sprachen neu abgeglichen.

Die Ausnahme, die das Gate passieren lässt, ist eine Zeile, die eine Regel auf einem Selektor benennt. Jeder andere Knoten derselben Regel irgendwo auf der Seite lässt den Build weiterhin scheitern. Und sie verrottet mit Absicht: Wird diese Überschrift jemals behoben, passt die Zeile nicht mehr, und der Lauf schlägt fehl, bis jemand sie löscht — denn eine Ausnahme, die niemand braucht, ist eine Lüge im Repository.

Der Matcher hat eine scharfe Kante, die man klar aussprechen sollte. Er prüft nur den ersten fehlschlagenden Knoten jeder Befundgruppe, wer also eine Gruppe entschuldigt, entschuldigt jeden Knoten darin. Was diese Grobheit begrenzt, ist die Zahl: Das Gate gibt die Knotensumme aller Entschuldigungen aus, die Seite veröffentlicht sie, und die Summe ist fünf. Dahinter ist kein Platz, in dem sich noch etwas verstecken könnte.

Diese Rechnung zu bezahlen brauchte drei Stellen und eine vierte, die sie zusammenhält — den veröffentlichten Eintrag, die sichtbare Fassung auf Englisch, Deutsch und Arabisch und die eine Ausnahme im Harness, dazu ein Fakten-Gate, das prüft, ob Scan-Zahl, Dokumentzahl, Toolversion und Verstoßzahl im Text noch zum Eintrag passen. Eine Zahl, die irgendwo steht, wo kein Gate hinreicht, wird schal, und eine in diesem Repository ist es schon.

Die Seite, die Leserinnen diese Zahl nennt, direkt neben dem Tastaturweg, den sie bewusst ausschließt, ist die vier Disziplinen.

144 scans · 0 keystrokes

Was ein grüner Scan trotzdem nicht sagt

Ein Scanner liest Markup. Er kann keine Tabulatortaste drücken, er kann dir nicht sagen, dass eine Überschrift falsch ist, und er kann nicht wissen, dass ein Bedienelement in der Praxis unerreichbar ist. Was diese Seite veröffentlicht, ist eine Zahl automatisiert gefundener Verstöße, und sie ist nie das Wort barrierefrei.

Die Regel, um die es hier geht, hat auf der anderen Seite derselben Seite einen passenden blinden Fleck. Deques eigene Liste der Grenzen sagt, colour-contrast melde nichts zu Text, der von einem anderen Element verdeckt wird — und jede Zeile des Reveals dieser Seite ist von einem deckenden Balken bedeckt, der wegwischt. Die sechshundert Millisekunden, die dieser Balken auf dem Schirm steht, ist der Text buchstäblich unsichtbar, und axe verbucht ein Bestanden; die achthundert Millisekunden, die eine Einblendung läuft, ist der Text für einen Menschen bei sechzig Prozent lesbar, und axe verbucht einen schwerwiegenden Fehler. Beide Fehler kommen aus derselben fehlenden Idee.

Fahre den Durchlauf zweimal
Einmal unter prefers-reduced-motion: reduce und einmal ohne, dann vergleiche beide. Was beide Läufe übersteht, ist eine Frage der Farbe. Was nur in einem auftaucht, eine Frage des Zeitpunkts.
Prüfe, worauf deine Reveals stehen
Exakt null Deckkraft wird als verborgen durchgelassen. Ein Scan, der deine Reveals nie auslöst, sagt dir nicht, dass sie in Ordnung sind — er sagt dir überhaupt nichts über sie.
Rechne deinen eigenen Übergangspunkt aus
Lege deine Tinte auf dein Papier und finde den Alphawert, bei dem sie 4.5:1 überschreitet. Dieser Anteil ist der Teil jeder Transition auf deiner Seite, den ein Scanner nicht ehrlich lesen kann.
Behebe einen Befund nie, indem du die Animation änderst
Wenn die eingeschwungenen Farben bestehen, geht es bei dem Befund um die Stoppuhr des Scanners. Eine Dauer zu kürzen, damit ein Bericht grün wird, verschiebt den Defekt, statt ihn zu entfernen.
Veröffentliche die Knotenzahl von allem, was du entschuldigst
Eine benannte Ausnahme mit einer Zahl daneben kann nicht heimlich wachsen, und eine, die nicht mehr passt, sollte den Build scheitern lassen, statt unbemerkt in der Datei zu liegen.
Wie du ein Scan-Artefakt von einem Defekt unterscheidest, ohne darüber zu streiten.

Zwei weitere Grenzen gehören in denselben Atemzug. Eine Route, die nicht lädt, liefert keine Befunde, und liegt sie außerhalb des gewerteten Satzes, lässt sie den Lauf auch nicht scheitern — ein gespeicherter Durchlauf scannte 143 Seiten statt 144, weil eine Demo-Route nach dreißig Sekunden in einen Timeout lief. Und eine veröffentlichte Zahl ist ein Beleg über einen Build an einem Datum: Der Routensatz hier hat sich seit dem 24. August 2026 bereits geändert, und genau deshalb sagt der Eintrag selbst, dass vor dem Deployment neu gemessen werden muss.

Beim Kontrast ist die alte Seite das nützliche Gegenbeispiel. Ihr axe-Lauf über 40 Routen in zwei Viewports lieferte 379 Verstoßknoten, alle Farbkontrast, aus nur vier verschiedenen Farbpaaren zwischen 1.26:1 und 1.47:1 — derselbe Akzent auf vier fast weißen Gründen. Überhaupt kein Rauschen mitten im Reveal, weil jenes Harness die Seite in Schritten von 600 Pixeln las und vor dem Scannen 800 ms wartete, und weil ihre Eintritte fertig waren. Dasselbe Werkzeug, dieselbe Regel, völlig anderes Rauschen. Die Scan-Prozedur ist eine Variable, und sie gehört neben die Zahl, die sie erzeugt hat.

Wenn ein Bericht über deinen eigenen Build Zahlen enthält, die niemand erklären kann, ist das meist ein Problem der Bedingung und nicht der Farbe — sprich es durch.

Fragen

Ist ein Kontrastbefund mitten in einer Animation ein Bug in axe?

Nein, und wer ihn so nennt, verfehlt den Punkt. axe hat das Frame korrekt gemessen: Eine Überschrift ein Prozent in eine Einblendung hinein ist wirklich #eaf2f0 auf #edf5f3, und das sind wirklich 1.02:1. Das Werkzeug kennt keine Zeit, es kann also nicht wissen, dass das Frame flüchtig ist. Deques eigener Issue-Tracker hält 2019 einen Maintainer fest, der genau das sagt — außer Inhalte außerhalb des sichtbaren Viewports gar nicht zu testen, sei da wenig zu machen. Die Lösung liegt darin, wie du den Scan fährst, und nicht in der Regel.

Verlangt WCAG überhaupt Kontrast auf Text, der gerade einblendet?

Nein. Die Ausnahme Incidental zu SC 1.4.3 nimmt Text aus, der für niemanden sichtbar ist, und Text mitten in einer Einblendung bei einem Prozent Deckkraft ist für niemanden sichtbar. Den eingeschwungenen Zustand zu messen ist kein Ausweichen vor der Norm; es ist der Zustand, um den es der Norm geht. Die Ausnahme, die nicht greift, ist die zum Logotype, denn sie deckt ein Logo oder einen Markennamen — eine Abschnittsüberschrift in einem Markenakzent ist vollwertiger Text.

Versteckt ein Scan unter prefers-reduced-motion nicht einfach das Problem?

Das würde er, wenn die Seite unter reduzierter Bewegung die eingefrorene animierte Seite wäre. Auf dieser Seite ist sie das nicht, und ein eigenes Gate belegt es: Das ältere Accessibility-Harness läuft über jeden animierten Hook, jedes Split-Text-Element und jede erzeugte Zeile auf der Seite unter reduzierter Bewegung und prüft, dass keins verborgen, bei null Deckkraft oder beschnitten ist. Unter reduce schreibt die Runtime das Attribut, das den verborgenen Startzustand scharfstellt, nie, es ist also nichts da, was einfrieren könnte.

Woher weiß ich, dass eine niedrige Zahl nicht einfach heißt, dass der Scan nicht hingesehen hat?

Meistens weißt du es nicht, und das ist die unangenehme Hälfte daran. Die Läufe hier zeigen es genau so: ohne reduce meldeten die Fallstudienseiten 192 Kontrastknoten, mit reduce 612. Die 420 fehlenden standen bei exakt null Deckkraft, was axe als verborgen behandelt und durchlässt. Wenn ein Scan die Zustände nicht erreicht, die deine Besucher sehen, ist eine kleinere Zahl keine bessere Nachricht — sie ist weniger Nachricht.

Was hat die Scan-Bedingung nicht verändert?

Das 404-Wasserzeichen. Es meldete sechs Knoten ohne reduzierte Bewegung und sechs mit ihr, weil es eine statische Farbe auf einer Seite ist, auf der sich nichts bewegt. Diese Unveränderlichkeit ist der sauberste verfügbare Test dafür, ob ein Befund echt ist: Fahre den Durchlauf unter beiden Bedingungen und vergleiche sie. Was beide übersteht, ist eine Frage der Farbe, und was nur in einem auftaucht, eine Frage des Zeitpunkts.

Quellen

In diesem Repository gemessen

  • contracts/published-measurements.json Der Accessibility-Block: Werkzeug, Version, Befehl, Datum, Norm, Dokument- und Scan-Zahlen, die Verstoßzahl, die Scan-Bedingung und eine ausdrückliche Liste dessen, was nicht gemessen wurde.
  • tools/golden/site-a11y.mjs Der Durchlauf selbst — zwei Viewports, die eine Zeile, die die Bewegungseinstellung setzt, der Scroll-Durchgang, die eingespielte axe-Kopie, die Grenze, die er durchsetzt, und die eine benannte Ausnahme, die den Lauf scheitern lässt, sobald sie nicht mehr passt.
  • src/styles/sections/project-detail.css Der Akzent, gemessen mit 4.82:1 auf Papier und 4.36:1 auf der Fallstudien-Karte, und die eine Zeile Mischung, die ihn auf 5.55:1 und 6.13:1 bringt.
  • src/styles/sections/not-found.css Das 404-Wasserzeichen, gemessen mit 1.25:1 und auf 3.12:1 bei 44 Prozent Tinte gesetzt, auf der einen Seite, auf der sich überhaupt nichts bewegt.
  • src/styles/sections/home-certificate.css Die Zertifikats-Überschrift bei 1.33:1, das Token mit 3.23:1, das sie ersetzt hat, die Obergrenze, die die Kontrastgleichung auf diesem Grund setzt, und die Entscheidung, festgehalten mit ihrem Preis.

Geprüft gegen

Alaa Abbod

Geschrieben von

Alaa Abbod

Creative Developer — Herne, Deutschland

Designer und Entwickler, der barrierefreie Websites, Mobile Apps, Onlineshops und visuelle Identitäten als eine Arbeit baut, von Hand. Diese Seite erscheint in Englisch, Deutsch und Arabisch aus einer Quelle, und daher kommen die meisten dieser Fragen.

Bitte dreh dein Gerät,
Diese Seite ist hochkant gebaut.