Dla administratorów: pełny przebieg z dokładnymi liczbami, poleceniami i konfiguracją. Wersje: stan na listopad 2026.
W skrócie
| Element | Szczegóły |
| Środowisko | WordPress 7.1.4, Elementor 4.3.5, własny motyw (Tailwind CSS 4.3.3); stara wersja: motyw z generatora, NextGEN Gallery 4.5.3 z simplelightbox, Cookie Notice 3.1.17 (stan na 2026-11) |
| Narzędzia | axe-core 4.14.0 (reguły WCAG 2.0–2.2 A/AA), Lighthouse 12.8.2 (mobile), przejście klawiaturą (Tab/Shift+Tab), scenariusze menu i lightboxa, formularz, odstępy tekstu, tryb wymuszonych kolorów, drzewo dostępności |
| Objaw | stara: link-name, meta-viewport, brak main i H1, brak skip linku; po redesignie axe 0 / Lighthouse 100, ale fokus wychodzący z panelu menu, podmenu bez Esc, błędy formularza tylko natywne, status bez ogłoszenia |
| Rozwiązanie | inert + przeniesienie fokusu w panelu menu, Esc dla podmenu, walidacja z aria-invalid/aria-describedby i podsumowaniem błędów, role="status" z fokusem po load, gwiazdka aria-hidden, reguła podkreślenia poza @layer |
| Efekt | axe 0 na 9 stronach × 2 szerokości i w 3 stanach interaktywnych; Lighthouse 100 (5 stron × 2); przejście klawiaturą bez barier |
Kontekst
Trzy wersje tej samej treści w laboratorium: stara (motyw z generatora), po redesignie mobile-first (osobne case study) i po
poprawkach dostępności. Redesign przeszedł już bramkę jakości, która dała część wymagań WCAG (kontrast tekstu i kontrolek,
:focus-visible, prefers-reduced-motion, cele dotyku ≥ 48 px), więc audyt „przed” na wersji po redesignie zaczynał
od czystego wyniku narzędzi.
Diagnoza
1. axe-core na 9 stronach × 390 i 1280 px; osobno stany interaktywne: formularz po nieudanym wysłaniu, otwarte menu
na telefonie, otwarty lightbox. Zapisane także wyniki incomplete (do ręcznej oceny).
2. Lighthouse 12.8.2, profil mobilny, 5 stron × 2 przebiegi.
3. Przejście klawiaturą (desktop 1280 px i telefon 390 px z klawiaturą): Tab od początku do końca strony; przy każdym
zatrzymaniu: czy fokus jest widoczny, czy element jest w oknie i niczym niezasłonięty, czy fokus nie krąży w kółko. Scenariusze: skip
link (czy widoczny po fokusie, dokąd prowadzi), menu (Enter, następny Tab, wyjście fokusu z panelu, Esc, powrót fokusu),
lightbox (Enter, fokus w oknie, strzałki, Tab w oknie, Esc, powrót fokusu).
4. Dodatki: Shift+Tab od końca strony (2.4.11 pod przyklejonym nagłówkiem),
podmenu desktopu z fokusem + Esc (1.4.13), style odstępów tekstu z kryterium 1.4.12 (line-height: 1.5, letter-spacing:
0.12em, word-spacing: 0.16em, akapity 2em) i wykrywanie uciętego tekstu w kontenerach overflow: hidden,
formularz: pusty, z błędnym e-mailem i poprawny (aria-invalid, opis z aria-describedby, validationMessage, fokus,
regiony role="status"/alert), drzewo dostępności formularza i strony z otwartym menu, podgląd w trybie wymuszonych kolorów.
Wyniki „przed”
Stara wersja:
| Kontrola | Wynik |
| axe (9 stron × 2 szerokości) | 2 reguły na każdej stronie: link-name (1–12 węzłów: linki-obrazki bez alt, a.art-menu-btn bez tekstu) i meta-viewport (user-scalable=no) |
axe incomplete | color-contrast 152 (telefon) i 195 (desktop) węzłów: „Element's background color could not be determined due to a background image”; bypass: „No valid skip link found” |
| Kontrast policzony ręcznie | linki #5ebfd9 na białym 2,11:1 |
| Lighthouse dostępność | 89–90 |
| Drzewo dostępności | punkty orientacyjne navigation, banner, contentinfo, brak main; strona główna bez H1 (tylko h3) |
| Klawiatura, desktop | brak skip linku (pierwszy Tab: „Strona główna”), 5 zatrzymań bez widocznego fokusu (pozycje menu) |
| Klawiatura, telefon | pierwszy Tab na a.art-menu-btn (href="#", bez tekstu); Esc nie zamyka menu, fokus nie wraca |
| Lightbox (simplelightbox) | fokus nie przechodzi do okna (zostaje na miniaturze), Tab chodzi po stronie pod spodem, w oknie image-alt |
| Shift+Tab | link „Kontakt” w stopce zasłonięty banerem cookies (fixed) |
| Odstępy tekstu 1.4.12 | treść 585 px (strona główna) i 936 px (kontakt) w oknie 390 px, ucięta przez overflow: hidden |
| Formularz | brak |
Po redesignie (przed poprawkami dostępności):
| Kontrola | Wynik |
| axe (9 stron × 2 szerokości i 3 stany) | 0 naruszeń; incomplete: 5 węzłów kontrastu |
| Lighthouse dostępność | 100 na 5 stronach |
| Fokus | widoczny na każdym zatrzymaniu (desktop 19–29, telefon 12–22 zatrzymań na stronę), bez cykli |
| Skip link | widoczny po fokusie, następny Tab w main |
Lightbox (<dialog> + showModal()) | fokus w oknie, strzałki działają, Tab zostaje w oknie, Esc zamyka, fokus wraca na miniaturę |
| Shift+Tab | 0 elementów zasłoniętych przez przyklejony nagłówek (scroll-padding-top: 72px) |
| Menu na telefonie | po Enter następny Tab: „Zapytaj o termin” (poza menu); fokus trafia na link telefonu zasłonięty panelem |
| Podmenu na desktopie | widoczne przy fokusie, Esc go nie chowa |
| Formularz pusty | brak aria-invalid i opisów; validationMessage: „Please fill out this field.” (język przeglądarki); fokus na pierwszym polu |
| Po wysłaniu | /kontakt/?wyslano=1#formularz, fokus na body, brak regionu statusu |
| Nazwy pól | textbox "Imię i nazwisko *", checkbox "Zgadzam się … *", brak objaśnienia gwiazdki |
Problemy i poprawki (z kryteriami WCAG 2.2)
| # | Problem | Kryterium | Wersja | Poprawka |
| 1 | Linki-obrazki i przycisk menu bez nazwy dostępnej | 1.1.1, 2.4.4, 4.1.2 | stara | redesign: alt z biblioteki mediów w każdym zdjęciu, przycisk <button> z tekstem „Menu” |
| 2 | user-scalable=no blokuje powiększanie | 1.4.4 | stara | redesign: width=device-width, initial-scale=1 |
| 3 | Brak linku do treści, brak main, brak H1 | 2.4.1, 1.3.1 | stara | redesign: skip link do #content, <main>, jedna H1 na stronę |
| 4 | Kontrast linków 2,11:1 | 1.4.3 | stara | redesign (bramka jakości): #146b80, 6,1:1 |
| 5 | Fokus niewidoczny na pozycjach menu | 2.4.7 | stara | redesign: :focus-visible { outline: 3px solid #0c3640; outline-offset: 2px } |
| 6 | Treść ucinana przy odstępach tekstu z 1.4.12 | 1.4.12 | stara | redesign: układ bez stałych szerokości i overflow: hidden |
| 7 | Baner cookies zasłania fokus | 2.4.11 | stara | redesign: bez banera (brak cookies) |
| 8 | Fokus wychodzi z otwartego panelu menu pod spód, strona pod panelem w drzewie dostępności | 2.4.3, 2.4.11, 1.3.2 | redesign | fokus na pierwszej pozycji, inert na tle, Esc + powrót fokusu |
| 9 | Podmenu pokazane fokusem nie chowa się po Esc | 1.4.13 | redesign | klasa is-dismissed do opuszczenia pozycji |
| 10 | Błędy formularza tylko w dymku przeglądarki | 3.3.1, 3.3.3 | redesign | komunikat przy polu, aria-invalid, aria-describedby, podsumowanie z linkami i fokusem |
| 11 | Komunikat po wysłaniu nieogłaszany | 4.1.3 | redesign | role="status", fokus na komunikacie po load |
| 12 | „” w nazwach pól, brak objaśnienia | 3.3.2 | redesign | gwiazdka ukryta przed czytnikiem (aria-hidden="true"), „Pola oznaczone gwiazdką () są wymagane.” |
| 13 | Podkreślenie linków w treści usuwane przez CSS Elementora | 1.4.1 | redesign | reguła poza @layer o wyższej specyficzności |
Kryteria sprawdzone i spełnione już po redesignie: 1.4.10 (reflow: 0 px przewijania w poziomie od 320 do 1920 px),
1.4.11 (obramowanie pól 4,6:1, pierścień fokusu 12,6:1), 2.5.8 (wszystkie cele ≥ 44 px), 1.3.5 (autocomplete="name",
email, tel), 3.1.1 (lang="pl-PL"), 3.2.6 (kontakt w stopce każdej strony), 2.5.7 (przewijanie zdjęć gestem ma
przyciski), ruch: prefers-reduced-motion: reduce wyłącza animacje i płynne przewijanie.
Przyczyna
Narzędzia automatyczne sprawdzają stan dokumentu w chwili uruchomienia: atrybuty, nazwy, kontrast policzalny z CSS.
Bariery po redesignie dotyczyły zachowania: kolejności fokusu po interakcji, tego, co dzieje się po naciśnięciu Esc,
co trafia do drzewa dostępności po wysłaniu formularza. axe uruchomiony w stanie „menu otwarte” widzi poprawne
aria-expanded i poprawne linki, ale nie naciska Tab. Stara wersja miała problemy innego rodzaju: generator motywu
nie dawał nazw elementom, a treść wpisywana w edytorze klasycznym nie miała tekstów alternatywnych.
Co nie było oczywiste
- axe 0 i Lighthouse 100 przy pięciu barierach. Po redesignie axe nie zgłosił nic na 9 stronach w dwóch szerokościach,
także w stanach: formularz z błędami, otwarte menu, otwarty lightbox. Wszystkie bariery (fokus pod panelem, Esc w podmenu,
błędy formularza, brak ogłoszenia statusu, gwiazdka w nazwach) wyszły w przejściu klawiaturą i w drzewie dostępności.
- Kontrast na tle z obrazkiem trafia do „incomplete”, nie do błędów. Stara strona ma tło z teksturą (
page.png), więc
axe oznaczył 152–195 węzłów jako „Element's background color could not be determined due to a background image”
i Lighthouse dał 89–90 bez kary za kontrast; policzony ręcznie kontrast linków to 2,11:1.
- Kolejność DOM po otwarciu panelu.
<nav id="menu"> stoi w kodzie przed <button aria-controls="menu">, więc po
otwarciu kolejny Tab idzie w przód, do main zasłoniętego panelem (position: fixed; inset: 60px 0 0). Samo
aria-expanded tego nie naprawia; potrzebne było przeniesienie fokusu i inert na tle. Po poprawce strona za panelem
znika też z drzewa dostępności, a fokus po ostatnim elemencie paska przechodzi do interfejsu przeglądarki.
- Fokus na komunikacie po przeładowaniu znika przez kotwicę. Skrypt z
defer ustawiał fokus na role="status" od
razu, ale przewinięcie do #formularz po załadowaniu kasowało go (document.activeElement = body). Fokus ustawiony
w load + requestAnimationFrame zostaje: activeElement = div.form-status. Komunikat obecny w HTML od początku nie
jest ogłaszany przez region role="status" (ogłaszane są zmiany), dlatego fokus jest konieczny.
- Dymki walidacji przeglądarki mówią językiem przeglądarki, nie strony. W testowej przeglądarce ustawionej na angielski
formularz polskiej strony mówił „Please fill out this field.” i „Please include an '@' in the email address.”, po jednym
błędzie naraz, bez śladu w DOM. Własne komunikaty (
novalidate + te same teksty w PHP i JS) są po polsku i zostają przy polach.
- CSS Elementora wyłącza podkreślenia i wygrywa z motywem. Elementor ma
.elementor a{box-shadow:none;text-decoration:none}
bez warstw kaskady, a style motywu Tailwind są w @layer components. Reguła spoza warstwy wygrywa z każdą regułą w
warstwie, niezależnie od specyficzności, więc text-decoration: underline motywu nie działał (getComputedStyle →
none). Poprawka: reguła poza @layer z selektorem .elementor .text-link (wyższa specyficzność niż .elementor a).
- Wbudowany
<dialog> z showModal() przechodzi bez poprawek. Fokus w oknie, Tab zostaje w środku, Esc zamyka,
fokus wraca do miniatury (opener.focus() w zdarzeniu close). Stary lightbox z wtyczki galerii (simplelightbox)
zostawiał fokus na stronie pod spodem.
Rozwiązanie
Menu na telefonie (assets/js/app.js):
const tlo = () => [...doc.querySelectorAll('main, .footer, .skip-link, .to-top')];
const setOpen = (open, oddajFokus) => {
toggle.setAttribute('aria-expanded', String(open));
nav.classList.toggle('is-open', open);
tlo().forEach((el) => { el.inert = open; });
if (open) nav.querySelector('a')?.focus();
else if (oddajFokus) toggle.focus();
};
doc.addEventListener('keydown', (e) => { if (e.key === 'Escape' && toggle.getAttribute('aria-expanded') === 'true') setOpen(false, true); });
Podmenu, Esc (1.4.13): li.classList.add('is-dismissed') po Esc, usunięcie przy focusout poza pozycję albo
mouseleave; CSS: .nav__menu li.is-dismissed > .sub-menu { visibility: hidden; }.
Formularz (inc/form.php, ten sam tekst błędu w PHP i JS):
<p class="form-hint">Pola oznaczone gwiazdką (*) są wymagane.</p>
<div class="form-status" role="status" tabindex="-1">…</div>
<div class="error-summary" tabindex="-1" hidden>…lista linków do pól…</div>
<form novalidate …>
<label for="f-email">E-mail <span aria-hidden="true">*</span></label>
<input type="email" id="f-email" name="email" required autocomplete="email"
aria-describedby="f-email-blad" aria-invalid="true">
<p class="field__error" id="f-email-blad">Wpisz adres e-mail w formacie nazwa@domena.pl.</p>
Przy wysyłce z błędami: komunikat przy każdym polu, podsumowanie „Formularz zawiera błędy (3)” z linkami do pól,
fokus na podsumowaniu; komunikat znika, gdy pole jest poprawne (zdarzenie input, bez komunikatu przy każdym znaku).
Bez JS: te same komunikaty z serwera (transient 10 min), wartości wracają do pól.
Podkreślenie linków poza warstwą (src/app.css, na końcu, poza @layer):
.elementor .text-link,
.elementor .error-summary a,
.elementor .elementor-widget-text-editor p a:not(.contact-link) { text-decoration: underline; text-underline-offset: 3px; }
Wyniki „po”
| Kontrola | Stara | Po redesignie | Po poprawkach |
| axe, 9 stron × 2 szerokości | 2 reguły na 9/9 stronach | 0 | 0 |
| axe, stany: formularz z błędami / menu / lightbox | nie dotyczy / 2 reguły / 3 reguły | 0 / 0 / 0 | 0 / 0 / 0 |
| Lighthouse dostępność (5 stron) | 89–90 | 100 | 100 |
| Zatrzymania bez widocznego fokusu | 5 na stronę (desktop) | 0 | 0 |
| Skip link | brak | jest | jest |
| Menu: następny Tab po otwarciu | w menu, Esc nie zamyka | poza menu, pod panelem | „Realizacje” (w menu), tło inert, Esc + powrót fokusu |
| Podmenu + Esc | nie dotyczy | nie chowa | chowa |
| Pusty formularz | brak formularza | dymek przeglądarki, 0 pól z aria-invalid | podsumowanie z fokusem, 4 pola z aria-invalid i opisem |
| Komunikat po wysłaniu | nie dotyczy | fokus na body | fokus na role="status" |
| Shift+Tab, fokus zasłonięty | 4–5 (baner) | 0 | 0 |
| Odstępy tekstu 1.4.12 | treść ucięta (585–936 px w 390 px) | bez ucięcia | bez ucięcia |
| Wymuszone kolory | nie sprawdzano | nie sprawdzano | fokus i ramki widoczne (zrzut) |
Bramka anty-slop (detektor impeccable) | 1–10 znalezisk na stronę | 0 | 0 |
Rezultaty
| Etykieta | Wartość |
| Naruszenia axe na 9 stronach | 2 reguły na każdej stronie (stara), 0 po poprawkach |
| Lighthouse, dostępność | 89–90 (stara), 100 po poprawkach (5 stron × 2 przebiegi) |
| Bariery znalezione ręcznie przy axe = 0 | 5 po redesignie, 0 po poprawkach |
Pola z komunikatem błędu powiązanym przez aria-describedby | 0 z 4 przed, 4 z 4 po |
| Problemy przypisane do kryteriów WCAG 2.2 | 13 (7 w starej wersji, 6 po redesignie), wszystkie poprawione |
Wnioski
- Raport z narzędzia to punkt wyjścia. W tym projekcie wszystkie bariery w nowej wersji przeszły przez axe i Lighthouse.
- Najtańsza kontrola z największym zwrotem: jedno przejście każdej strony samą klawiaturą i sprawdzenie formularza z błędami.
- Kreatory stron i ich CSS potrafią po cichu cofnąć poprawki; trzeba sprawdzać wynik w przeglądarce, nie w kodzie motywu.
- Przed deklaracją zgodności warto dodać odsłuch z NVDA lub VoiceOver i test z użytkownikiem czytnika; tu tego nie robiliśmy.