Kurs HTML i CSS · Moduł 10: Dostępność (a11y)

Kontrast kolorów i typografia

7 min czytania
W tej lekcji8

Otwierasz stronę muzeum na telefonie w pełnym słońcu nad Nilem i widzisz jasnoszary tekst na białym tle, którego nie da się przeczytać. Osoba słabowidząca widzi ją tak zawsze. W Starożytnym Egipcie hieroglify malowane były jaskrawymi kolorami na kontrastowym tle - nie dla ozdoby, lecz po to, by można je było odczytać nawet w ciemnych komorach świątyń. Dziś ta sama zasada obowiązuje w web designie: treści muszą być czytelne dla każdego.

Współczynnik kontrastu

Współczynnik kontrastu porównuje jasność dwóch kolorów i przyjmuje wartości od 1:1 (ten sam kolor) do 21:1 (czarny na białym). WCAG definiuje minimalne wymagania dotyczące kontrastu między tekstem a tłem:

PoziomNormalny tekstDuży tekst (24px lub ok. 18,7px bold)
AA4.5:13:1
AAA7:14.5:1

Duży tekst to według WCAG co najmniej 18 punktów (ok. 24px) albo 14 punktów pogrubionych. Dla zwykłego tekstu na poziomie AA zapamiętaj jedną liczbę: 4.5:1.

Przykłady kontrastu

Zobacz trzy pary kolorów - dobrą, złą i poprawioną. Wartości z komentarzy sprawdzisz w dowolnym narzędziu do kontrastu:

1/* DOBRZE - wysoki kontrast */
2.good-contrast {
3    color: #1a1a2e;            /* Ciemny tekst */
4    background-color: #f5f1e3; /* Jasne tło */
5    /* Kontrast: ~15:1 - spełnia AAA */
6}
7
8/* ŹLE - zbyt niski kontrast */
9.bad-contrast {
10    color: #999999;            /* Szary tekst */
11    background-color: #cccccc; /* Szare tło */
12    /* Kontrast: ~1.8:1 - nie spełnia nawet AA */
13}
14
15/* POPRAWIONE */
16.fixed-contrast {
17    color: #595959;            /* Ciemniejszy szary */
18    background-color: #ffffff; /* Białe tło */
19    /* Kontrast: ~7:1 - spełnia AA, a nawet AAA */
20}

Poprawka nie zmieniła charakteru strony - wystarczyło przyciemnić tekst i rozjaśnić tło. Kontrast zależy od obu kolorów, więc zawsze sprawdzaj parę.

Nie polegaj wyłącznie na kolorze

Kolor nie może być jedynym sposobem przekazywania informacji - osoby z daltonizmem mogą nie rozróżniać niektórych kolorów, zwłaszcza czerwieni i zieleni. Porównaj pole z błędem oznaczone samym kolorem i takie, które mówi to na trzy sposoby:

1<!-- ŹLE - tylko kolor wskazuje błąd -->
2<style>
3    .error-field { border-color: red; }
4</style>
5<input type="email" class="error-field">
6
7<!-- DOBRZE - kolor + ikona + tekst -->
8<style>
9    .error-field {
10        border: 2px solid #d32f2f;
11        background-image: url('error-icon.svg');
12        background-repeat: no-repeat;
13        background-position: right 8px center;
14    }
15</style>
16<input type="email" class="error-field"
17       aria-invalid="true"
18       aria-describedby="email-err">
19<p id="email-err" class="error-msg">
20    <span aria-hidden="true">&#10060;</span>
21    Podaj poprawny adres e-mail
22</p>

Czerwona ramka zostaje, ale dochodzą do niej ikona i tekst połączony z polem przez aria-describedby. Krzyżyk ma aria-hidden="true", bo czytnik i tak odczyta komunikat obok.

Kolor nie dociera też do czytnika ekranowego: gdy status biletów zmienia się z zielonego na czerwony, osoba niewidoma niczego nie zauważy - dlatego zmianę ogłasza region aria-live, który znasz z lekcji o ARIA:

1<div aria-live="polite" id="ticket-status">
2    Zostały 3 bilety na dziś
3</div>

Wartość polite czeka, aż czytnik skończy bieżące zdanie, assertive przerywa go natychmiast, a off (domyślna, gdy atrybutu brak) niczego nie ogłasza. role="alert" działa jak skrót od aria-live="assertive" - rezerwuj go dla pilnych komunikatów.

Typografia dostępna

Rozmiar tekstu

Rozmiar tekstu ustawiaj w jednostkach względnych. rem liczy się od rozmiaru czcionki elementu html, który domyślnie wynosi 16px i zależy od ustawień przeglądarki użytkownika:

1/* Używaj jednostek relatywnych */
2body {
3    font-size: 100%; /* = 16px domyślnie */
4}
5
6h1 { font-size: 2rem; }      /* 32px */
7h2 { font-size: 1.5rem; }    /* 24px */
8p  { font-size: 1rem; }      /* 16px */
9small { font-size: 0.875rem; } /* 14px - minimum! */
10
11/* NIGDY nie blokuj powiększania tekstu */
12/* ŹLE */
13html { font-size: 16px !important; }
14/* DOBRZE */
15html { font-size: 100%; }

Gdy ktoś ustawi w przeglądarce większą czcionkę, strona w rem urośnie razem z nią, a sztywne 16px !important zignoruje jego wybór. WCAG nie podaje minimalnego rozmiaru czcionki, ale 16px dla treści to rozsądny standard.

Interlinia i odstępy

Kryterium WCAG 1.4.12 (poziom AA) wymaga, by treść nie ucinała się ani nie nakładała, gdy użytkownik zwiększy odstępy do poniższych wartości. Najprościej projektować z takim zapasem od razu:

1/* Wartości z kryterium WCAG 1.4.12: */
2p {
3    line-height: 1.5;        /* Interlinia: 1.5x rozmiar fontu */
4    letter-spacing: 0.12em;  /* Odstępy między literami */
5    word-spacing: 0.16em;    /* Odstępy między słowami */
6}
7
8/* Odstęp między akapitami >= 2x rozmiar fontu */
9p + p {
10    margin-top: 2em;
11}

To próg odporności, a nie sztywny przepis - w ćwiczeniach spotkasz też wyraźny, choć mniejszy odstęp margin-top: 1.5em.

Szerokość tekstu

Bardzo długie linie męczą wzrok, bo trudno trafić na początek następnego wiersza. Jednostka ch to szerokość znaku "0" w bieżącej czcionce:

1/* Optymalna szerokość linii: 50-80 znaków */
2.article-content {
3    max-width: 70ch; /* 70 znaków */
4    margin: 0 auto;
5}

Kolumna mieści więc około 70 znaków, a margin: 0 auto ją centruje. WCAG na poziomie AAA (kryterium 1.4.8) mówi o maksymalnie 80 znakach w linii.

Narzędzia do sprawdzania kontrastu

Specyfikacja CSS opisuje funkcje, które same dobierają kontrastowy kolor: eksperymentalną color-contrast() i prostszą contrast-color(), ale ich wsparcie jest wciąż ograniczone. Kontrast sprawdzisz więc w selektorze kolorów Chrome DevTools albo w WebAIM Contrast Checker. Kolory najłatwiej trzymać w zmiennych CSS, które media queries podmieniają dla różnych trybów:

1/* Użyj zmiennych CSS dla spójności */
2:root {
3    --text-primary: #1a1a2e;
4    --text-secondary: #4a4a5a;
5    --bg-primary: #ffffff;
6    --bg-secondary: #f5f1e3;
7    --accent: #8b6914;
8    --error: #d32f2f;
9    --success: #2e7d32;
10}
11
12/* Tryb wysokiego kontrastu */
13@media (prefers-contrast: more) {
14    :root {
15        --text-primary: #000000;
16        --text-secondary: #1a1a1a;
17        --bg-primary: #ffffff;
18        --accent: #6b4c00;
19    }
20}
21
22/* Tryb ciemny */
23@media (prefers-color-scheme: dark) {
24    :root {
25        --text-primary: #e0e0e0;
26        --text-secondary: #b0b0b0;
27        --bg-primary: #1a1a2e;
28        --bg-secondary: #2d2d44;
29    }
30}

Tryb ciemny nie wymaga przepisywania selektorów - zmieniają się tylko wartości w :root. Każdą nową parę kolorów sprawdź jednak ponownie, bo jasny tekst na ciemnym tle też potrzebuje 4.5:1. Starsze materiały podają wartość high, ale przeglądarki rozpoznają prefers-contrast: more.

Prefers-contrast i forced-colors

Dwa kolejne media queries reagują na ustawienia systemu: prefers-contrast wykrywa prośbę o większy kontrast, a forced-colors tryb, w którym system narzuca własną paletę, jak motywy kontrastu w Windows:

1/* Tryb wysokiego kontrastu */
2@media (prefers-contrast: more) {
3    button {
4        border: 3px solid currentColor;
5    }
6}
7
8/* Tryb wymuszonych kolorów (np. Windows High Contrast) */
9@media (forced-colors: active) {
10    .card {
11        border: 2px solid ButtonText;
12    }
13
14    a {
15        color: LinkText;
16    }
17
18    /* Dekoracyjne tła są usuwane - dodaj obramowania */
19    .highlight {
20        forced-color-adjust: none;
21        /* LUB zachowaj oryginalne kolory */
22    }
23}

W tym trybie przeglądarka nadpisuje kolory tekstu, tła i ramek systemową paletą, więc element wyróżniony samym tłem może zniknąć - obramowanie przetrwa. ButtonText i LinkText to kolory systemowe, a forced-color-adjust: none wyłącza wymuszanie - używaj go rzadko.

Kontrast obwódki focusu

Kontrast dotyczy nie tylko tekstu. Kryterium WCAG 1.4.11 (poziom AA) wymaga co najmniej 3:1 dla elementów interfejsu i ich stanów, także obwódki :focus-visible z lekcji o nawigacji klawiaturowej. Złoto #d4af37 świetnie widać na ciemnym granacie (ok. 8:1), ale na białym tle ma tylko ok. 2,1:1, więc tam wybierz ciemniejszy odcień.

Checklist kontrastu i typografii

  1. Kontrast tekstu do tła: minimum 4.5:1 (AA) lub 7:1 (AAA)
  2. Rozmiar fontu: minimum 16px (1rem) dla body
  3. Interlinia: minimum 1.5
  4. Szerokość linii: 50-80 znaków
  5. Nie polegaj wyłącznie na kolorze
  6. Wspieraj prefers-contrast i prefers-color-scheme
  7. Używaj jednostek relatywnych (rem, em)

Tak jak starożytni Egipcjanie dbali o czytelność hieroglifów malowanych na ścianach świątyń, tak my musimy zadbać o czytelność tekstu na naszych stronach - aby każdy mógł odczytać nasz przekaz, niezależnie od warunków. W następnej lekcji sprawdzisz to wszystko narzędziami.

Praktyka

Porównaj w edytorze poniżej dobre i złe pary kolorów, a potem zmień je i sprawdź kontrast w DevTools:

Kod do tej lekcji: index.html
1<!DOCTYPE html>
2<html lang="pl">
3<head>
4    <meta charset="UTF-8">
5    <meta name="viewport" content="width=device-width, initial-scale=1.0">
6    <title>Kontrast kolorow i typografia</title>
7    <link rel="stylesheet" href="style.css">
8</head>
9<body>
10    <h1>Kontrast i typografia</h1>
11
12    <section>
13        <h2>Przyklady kontrastu</h2>
14
15        <div class="contrast-box good-contrast">
16            <h3>Dobry kontrast (12:1)</h3>
17            <p>Ciemny tekst na jasnym tle - czytelny dla kazdego.</p>
18        </div>
19
20        <div class="contrast-box bad-contrast">
21            <h3>Zly kontrast (1.8:1)</h3>
22            <p>Szary tekst na szarym tle - prawie nieczytelny!</p>
23        </div>
24
25        <div class="contrast-box fixed-contrast">
26            <h3>Poprawiony kontrast (5.9:1)</h3>
27            <p>Ciemniejszy szary na bialym tle - spelnia WCAG AA.</p>
28        </div>
29    </section>
30
31    <section>
32        <h2>Nie polegaj tylko na kolorze</h2>
33
34        <div class="example-bad">
35            <h3>Zle: Tylko kolor wskazuje blad</h3>
36            <input type="email" class="error-only-color" value="niepoprawny">
37        </div>
38
39        <div class="example-good">
40            <h3>Dobrze: Kolor + ikona + tekst</h3>
41            <input type="email" class="error-with-icon"
42                   value="niepoprawny"
43                   aria-invalid="true"
44                   aria-describedby="email-err">
45            <p id="email-err" class="error-msg">
46                &#10060; Podaj poprawny adres e-mail
47            </p>
48        </div>
49    </section>
50
51    <section>
52        <h2>Typografia dostepna</h2>
53        <article class="readable-text">
54            <h3>Optymalnie sformatowany tekst</h3>
55            <p>Ten tekst ma poprawna interlinire (1.6), rozmiar fontu (1rem = 16px)
56               i optymalna szerokosc linii (max 70 znakow). Wszystkie te parametry
57               sa wazne dla czytelnosci i spelniaja wymagania WCAG.</p>
58            <p>Drugi akapit ma odpowiedni odstep od pierwszego (2em),
59               co ulatwia czytanie dluzszych tekstow.</p>
60        </article>
61    </section>
62
63    <section>
64        <h2>Zmienne CSS i motywy</h2>
65        <div class="theme-demo">
66            <p>Ten tekst uzywa zmiennych CSS (Custom Properties) i automatycznie
67               dostosowuje sie do preferencji uzytkownika: tryb ciemny,
68               wysoki kontrast.</p>
69        </div>
70    </section>
71</body>
72</html>

Pamiętaj: hieroglif namalowany złotem na piasku jest piękny, ale nikt go nie odczyta - kontrast to farba, dzięki której przekaz widzi każdy pielgrzym.

Widzisz błąd w tej lekcji?

Sprawdź się

Odpowiedz na pytania z tej lekcji. Wybierz odpowiedź, a od razu zobaczysz, czy jest poprawna.

  1. 1. Jaki minimalny kontrast wymaga WCAG 2.1 poziom AA dla NORMALNEGO tekstu?

  2. 2. Jaki jest minimalny współczynnik kontrastu tekstu do tła wymagany przez WCAG AA dla normalnego tekstu?

To 2 z 3 pytań do tej lekcji. Pozostałe rozwiążesz w grze.

Zadania praktyczne w grze

  • Edytor kodu

    Popraw kontrast kolorów (zmień jasny tekst na ciemniejszy), dodaj ikony obok kolorowych statusów, i stwórz zmienne CSS z dark mode.

  • Edytor kodu

    Popraw typografie: zmień font-size na 100%, ustaw line-height na 1.5, max-width na 70ch, dodaj letter-spacing, word-spacing i margin-top dla akapitów.

  • Klikanie w kolejności

    Ułóż poprawną składnię CSS media query dla trybu ciemnego:

  • Układanie w pionie

    Ułóż poprawną składnię CSS dla widocznego focusa:

  • Klikanie w kolejności

    Ułóż poprawną składnię elementu div z regionem aria-live:

  • Edytor kodu

    Dodaj aria-live="polite" do regionu statusu biletów, role="alert" do pilnych powiadomień, i aria-live="assertive" do licznika odliczania.

  • Układanie w pionie

    Ułóż wartości aria-live od najniższego priorytetu do najwyższego:

Przydatne artykuły