Spring til indhold

Kontrastforhold

hvor langt to farver er fra hinanden, som et tal

Hvad det er

Et kontrastforhold er det tal mellem 1:1 og 21:1, som WCAG bruger til at afgøre, om tekst, ikoner og andre synlige dele af en side skiller sig nok ud fra det, de står på. Tallet sammenligner to farvers luminans, altså deres lyshed, og ser helt bort fra farvetonen. I designsprog hedder det også farvekontrast eller luminanskontrast, og i engelske auditrapporter står der contrast ratio pr. farvepar. Det er den samme beregning under alle navnene.

Formlen er kort. Find den relative luminans for den lyseste farve, L1, og for den mørkeste, L2, og divider (L1 + 0,05) med (L2 + 0,05). Relativ luminans går fra 0 for det mørkeste sort til 1 for det lyseste hvide. For en sRGB-farve omregnes hver kanal først fra sin gammakodede værdi til lineært lys, og bagefter vægtes de tre kanaler: 0,2126 for rød, 0,7152 for grøn og 0,0722 for blå. To ens farver giver 1:1, og sort på hvidt giver 21:1.

Det er vægtene, der snyder øjet. Ren grøn på sort måler omkring 15,3:1, mens ren blå på sort når cirka 2,4:1, selv om de to ser lige kraftige ud. Ren rød på hvidt lander på 3,998:1. Mellemgrå #767676 på hvidt måler 4,54:1, og #777777, ét trin lysere, måler 4,48:1. Hele regnestykket fylder et dusin linjer:

// Kontrastforhold mellem to sRGB-farver, som WCAG 2.2 definerer det
const linear = (c) => {
    const s = c / 255;
    return s <= 0.04045 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4;
};
const luminance = (rgb) => {
    const [r, g, b] = rgb.map(linear);
    return 0.2126 * r + 0.7152 * g + 0.0722 * b;
};
const contrast = (a, b) => {
    const [hi, lo] = [luminance(a), luminance(b)].sort((x, y) => y - x);
    return (hi + 0.05) / (lo + 0.05);
};
contrast([118, 118, 118], [255, 255, 255]); // 4.54...

I WCAG 2.2 er forholdet en definition, og fire succeskriterier sætter tærskler på det:

  • 1.4.3 Contrast (Minimum), niveau AA: mindst 4,5:1 for tekst og billeder af tekst og mindst 3:1 for stor tekst. Tekst i en inaktiv kontrol, ren dekoration og logoer er undtaget.
  • 1.4.6 Contrast (Enhanced), niveau AAA: mindst 7:1, og 4,5:1 for stor tekst.
  • 1.4.11 Non-text Contrast, niveau AA: mindst 3:1 mod de tilstødende farver for det, der viser en kontrol og dens tilstand, og for de dele af en grafik, der skal til for at forstå den.
  • 2.4.13 Focus Appearance, niveau AAA: et område af fokusindikatoren, mindst lige så stort som en 2 CSS-pixel tyk ramme om kontrollen, skifter med mindst 3:1 mellem fokuseret og ufokuseret tilstand.

Stor tekst betyder mindst 18 punkt, eller 14 punkt med fed skrift. Omregnet til CSS-pixel er det 24px, eller omkring 18,7px med fed.

Hvorfor det betyder noget

Tærsklerne bygger på målt synstab, og det hjælper at kende baggrunden, når nogen vil bøje dem. WCAG’s Understanding-dokument tager udgangspunkt i 3:1, det minimum en ANSI-standard satte for seere med normalt syn. Et syn på 20/40 følger med et tab i kontrastfølsomhed på omkring 1,5, så 3 × 1,5 giver 4,5:1, og samme dokument nævner, at 20/40 ofte angives som typisk synsstyrke omkring 80-årsalderen. AAA-tallet 7:1 kompenserer for et syn omkring 20/80. Fordi forholdet kun regner med luminans, holder det for de fleste former for nedsat farvesyn, hvor en regel bygget på farvetone ville falde igennem. Den samme margin hjælper alle, der læser på en telefon i skarpt sollys.

Den beslutning, et kontrastforhold afgør, er derfor, hvilke farver et brand må bruge til hvilket job, og den er langt billigere at træffe i paletten end på den færdige side. En brandfarve, der måler 3,2:1 på hvidt, har stadig opgaver: store overskrifter, ikoner og kanten om et formularfelt, som kræver 3:1, baggrunde bag mørk tekst, dekoration og selve logoet. Brødtekst, links i en brødtekst og pladsholdertekst kræver derimod 4,5:1, så dem må farven overlade til andre. At afgøre det én gang i designtokens koster en eftermiddag. At opdage det efter lanceringen betyder, at hver komponent, der har lånt farven, skal farves om, og et mørkt tema fordobler antallet af par.

Kontrast er også et af de få WCAG-krav, en maskine kan efterprøve præcist, når den kender begge farver. Derfor dukker det op som første fund i de fleste automatiske audits og som første spørgsmål på mange indkøbstjeklister, og niveau AA er det niveau, tilgængelighedsregler og kontrakter oftest henviser til. At gå efter AAA’s 7:1 overalt er et valg, der skal vejes: WCAG fraråder selv at kræve AAA som politik for hele websteder, men 7:1 er et fornuftigt mål for lang læsetekst, hvor paletten giver plads til det.

Sådan ser det ud i praksis

To sider på dette site viser forholdet ført som et dokumenteret tal frem for et skøn med øjet.

Da sitets farvesystem blev lukket, fik alle 28 farver deres målte kontrast noteret, målt på den flade, hvor farven bruges, mens de elleve brandværdier kun findes ét sted, som tokens. Linteren kender fem fejltyper, og en af dem er et noteret forhold, der ikke passer. Til systemet hører en fast regel, som blandt andet forbyder at skrue et dokumenteret forhold ned for designets skyld, og den, der ønsker endnu en farve ud over de 28, må lægge dens kontrastforhold frem som en del af begrundelsen. Siden fastsætter også en anden grænse: Ligger to farver under 0,02 fra hinanden i et perceptuelt ensartet farverum, behandles de som samme farve skrevet på to måder. Det tal skiller paletposter ad, og det er et andet mål end kontrast.

Det svære tilfælde var tekst over et fotografi. Siden om, hvordan hele sitet blev bragt op på WCAG 2.2 niveau AA, fortæller, at en automatisk regelmotor markerer den slags kontrast som noget, den ikke kan bedømme, og at sitet indtil da havde støttet sig til et ræsonnement frem for en måling. Da der blev målt, lå kontrasten under AA i 49 af 64 par af side og tema, og årsagen var to flader, designet aldrig havde gjort krav på. Den kontrol, der afslørede dem, er siden gjort til en bundgrænse: Hvert par skal nu klare AA mod den væg, buildet genererer, og ét par under grænsen er nok til, at buildet fejler.

Hvor det går galt

Tekst over et fotografi består i værktøjet og fejler på skærmen. WCAG måler tekst mod dens angivne baggrund og antager hvid, hvis der mangler en. En overskrift på et hero-billede har en erklæret baggrundsfarve, som ikke er den, der ligger under bogstaverne, så et tjekværktøj enten måler det forkerte par eller sender elementet videre til manuel vurdering. Mål mod de lyseste og mørkeste pixels, teksten kan stå på, i hvert udsnit og ved hver bredde, eller læg en fast flade eller et mørkt slør under teksten, så farven bag den er kendt.

Et token måler 4,6:1, og siden viser 3,9:1. Opacitet, rgba()-tekst, et gennemsigtigt overlag eller en blend mode ændrer den farve, der når skærmen, så forholdet noteret for tokenet beskriver en farve, ingen ser. Beregn forholdet for det sammensatte par, eller hold alfa helt væk fra tekst.

En audit melder 4,49:1 som bestået. Et forhold vist med én decimal gør 4,49 til 4,5. Understanding-dokumentet siger udtrykkeligt, at tallene ikke afrundes: 4,499:1 opfylder ikke 4,5:1. Gem mindst to decimaler, og sammenlign den rå værdi med tærsklen.

Det lyse tema er tjekket, og det mørke går i drift på tillid. Hvert tema og hver tilstand, en farve kan have, er sit eget par: hover, fokus, besøgt, fejl og pladsholdertekst, som tæller som tekst og kræver 4,5:1. En deaktiveret kontrol er undtaget. Generér listen over par ud fra tokens, pr. tema og pr. tilstand, og tjek listen frem for et skærmbillede.

Tynd skrift består på papiret og blegner på skærmen. Forholdet regnes ud fra de angivne farver, men kantudjævning tegner hårfine snit svagere end deres CSS-farve, og Understanding-dokumentet nævner netop dette tilfælde: sådan tekst kan bestå på papiret og alligevel læses med langt lavere kontrast. Hæv forholdet for lette snit, eller vælg en tungere vægt af skrifttypen.

Læs mere

Ordliste