RSS Amplifier

Dostępnik · Aug 17, 2026

Dostępnik o sklepowym koszyku

0
Sign in to vote or save

Jacek Zadrożny · Dostępnik

Ostatnio pracowałem z firmą developerską nad sklepem internetowym. W rekomendacjach z audytu napisałem w punkcie o koszyku, że powinien być zgodny z kryterium 4.1.3 Komunikaty o stanie. Pracowaliśmy na wspólnym arkuszu w Google, więc zaraz obok tej mojej rekomendacji znalazło się pytanie, że przecież koszyk jest cały czas widoczny, więc o co chodzi?

No i właśnie. “Wszyscy widzą, że się dodało”. Tylko że nie wszyscy patrzą na koszyk. Niektórzy słuchają. A żeby było jeszcze śmieszniej - dopisałem mój komentarz w kolejnej kolumnie. Tam wyjaśniłem, o co chodzi i jakie techniki zastosować. Tylko że mój rozmówca... nie widział mojego wyjaśnienia, bo na ekranie się nie zmieściło. Pomyślałem sobie, że to może być ciekawy materiał do Dostępnika i ani słowa w nim o sztucznej inteligencji:)

Kryterium 4.1.3 domaga się, żeby komunikaty o stanie dało się programowo określić — przez atrybut role lub właściwości — tak, aby technologie wspomagające mogły je przedstawić użytkownikowi bez konieczności skupienia na elemencie. No bo przecież fokus jest teraz na przycisku “Dodaj do koszyka”, a nie na koszyku.

Rozłóżmy to na czynniki, bo każde słowo tu jest na wagę złota:

  • Komunikat o stanie — WCAG definiuje to jako zmianę treści, która niesie informację o wyniku działania (”Dodano do koszyka”), stanie aplikacji (”Oczekiwanie na płatność”), postępie procesu (”Przesyłanie 45%”) albo wystąpieniu błędu (”Kupon nieprawidłowy”).

  • Programowo określone — musi być oznaczone w kodzie (rolą lub właściwością), a nie tylko narysowane. Sama cyferka obok wózka to nie status; <span role=”status”> z tekstem to już status.

  • Bez skupienia — czytnik nie ma czekać, aż użytkownik do tego przeskoczy. Ma to powiedzieć od razu, z własnej woli.

Z jednej strony to kryterium mówi technologiom wspomagającym “teraz powiedz coś użytkownikowi”. A z drugiej — co równie ważne — mówi im, czego nie mają mówić: nic, co nie jest komunikatem o stanie. Ale to zostawiam na koniec.

Nie każda zmiana na stronie to komunikat o stanie. I to jest kluczowa precyzja, której brakuje większości audytów. Dwie cechy decydują:

  1. Komunikat niesie informację o wyniku działania, stanie aplikacji, postępie procesu albo wystąpieniu błędu.

  2. Komunikat nie zmienia kontekstu — czyli nie zabiera fokusu.

Zobacz na konkrecie. Wyniki wyszukiwania w sklepie to nie komunikat o stanie — to treść, która zastępuje poprzednią. Ale “Wyświetlono 18 wyników” albo “Brak wyników” to już komunikat. Pasek postępu ładowania archiwum zamówień — komunikat. “Twoje zamówienie zostało złożone” — komunikat.

I ważny wyjątek: jeśli coś zabiera fokus, zmienia kontekst — np. modal z błędem przenosi tam kursor — to nie jest komunikat o stanie i 4.1.3 go nie dotyczy. Takie zdarzenie i tak jest ogłaszane przez technologie wspomagające. Częsty błąd: audytor przyczepia się do każdego błędu w dialogu, a ten jest poza zakresem. Ale strzeż się drugiej strony — jeżeli schowasz błąd w diviku i nie dasz mu fokusu, łapiesz się na 4.1.3.

Zostawmy definicje. Jak to technicznie zrobić? Masz w HTML i ARIA kilka sprawnych narzędzi. Ich wspólnym mianownikiem jest region na żywo (live region) — taką nazwą ARIA określa obszar strony, którego zmianami przeglądarka sama zainteresuje czytnik ekranu lub inną technologię asystującą.

Tu jest sedno całej tej zabawy: to nie jest ani proste “dodaj tag”, ani czarna magia. To dobranie roli do sytuacji, a potem pilnowanie kilku atrybutów, które tę rolę dopieszczają. Pójdę od ról, przez ich właściwości, po zasady łączenia.

Dokładnie trzy role są regionami na żywo i to one wykonują pracę pod 4.1.3. Czwarta — progressbar — sprawa, na którą trzeba uważać, więc znalazła się niżej, osobno.

  • role=”status” — region na żywo dla komunikatów uprzejmych: wyników działania, stanu aplikacji. “2 produkty w koszyku”, “Zapisano zmiany”, “5 wyników wyszukiwania”. Nie przerywa czytania — komunikuje w ciszy, gdy czytnik skończy wątek. Specyfikacja przypisuje mu niejawnie aria-live=”polite” i aria-atomic=”true” (tak, właśnie true — a nie na odwrót, jak się czasem mylnie podaje w sieci). Praktyka bywa kapryśna: technika ARIA22 wprost zaleca dopisywać aria-atomic=”true” ręcznie, bo nie wszystkie środowiska traktują domyślną wartość tak samo. Dlatego w kodzie poniżej widzisz go jawnie.

  • role=”alert” — dla komunikatów pilnych, przerywających: błąd, ostrzeżenie, nieoczekiwana blokada. “Hasło nieprawidłowe, zostały Ci dwie próby”, “Produkt wyczerpany”. To wyspecjalizowana postać `status`, przetwarzana jako atomowy region na żywo: niesie niejawnie `aria-live=”assertive”` (przerywa czytanie i mówi od razu) oraz `aria-atomic=”true”`. Ostrożnie z użyciem — o tym za chwilę.

  • role=”log” — region na żywo, który kolejkuje przyrosty informacji, zamiast zastępować poprzednie. To rola dla kanału czatu, logów, listy zdarzeń. Nowa linia dokleja się, a nie czyta w miejscu starej.

Trzy role, trzy pozycje. Status jest cichy, alert jest głośny, log jest uporządkowany. Wybierasz temperaturę do sytuacji.

Tu wielu autorów popełnia błąd i liczę, że Ty go nie popełnisz. role=”progressbar” to nie jest region na żywo. Owszem, jest blisko spokrewniony — w specyfikacji jako pojęcie powiązane widnieje “status” — ale z definicji nie ogłasza zmian automatycznie. Jedynymi wartościami domyślnymi, które niesie, są aria-valuemin=”0” i aria-valuemax=”100”; nie ma tam żadnego aria-live. Sam progressbar to przede wszystkim widget, który mówi o stanie wartości (aria-valuenow), a nie o zmianie.

Kiedy już chcesz o postępie powiadamiać użytkownika czytnika (a w e-commerce to konieczność przy ładowaniu zamówień, płatności, przesyłaniach), nie zamieniaj paska w rozgadany region na żywo i nie aktualizuj go co każdy procent. Częste zmiany wartości w żywym regionie potrafią zatkać i zablokować syntezator mowy. Do komunikowania postępu pod 4.1.3 właściwym narzędziem jest role=”status” z komunikatem etapowym — czytaj “50 proc.”, “75 proc.”, “gotowe” — a nie migający licznik. Sam progressbar zostaw jako komponent wizualny albo steruj jego ogłaszaniem precyzyjnie skryptem (sam aria-valuenow, okazjonalna aktualizacja), bez ubierania w aria-live.

Same role to nie wszystko. Cztery atrybuty decydują o tym, jak region na żywo zachowuje się:

  • aria-live — ustawia temperaturę regionu wprost, gdy nie chcesz używać gotowej roli albo chcesz ją nadpisać. Wartość polite (czekaj) lub assertive (przerywaj). Uwaga: role=”status” i `role=”alert”` już niosą swoją wartość aria-live, więc pisanie go ręcznie przy nich to najczęściej redundancja dla starszych czytników.

  • aria-atomic — mówi, czy czytnik ma czytać cały region, czy tylko zmieniony fragment. Wartość true znaczy “przeczytaj całość”. Kluczowe przy licznikach, gdzie zmienia się tylko cyferka — samo `aria-atomic=”true”` sprawia, że czytnik powie “3 produkty w koszyku”, a nie samo “trzy”.

  • aria-relevant — mówi, jakie zmiany mają być ogłaszane w regionie. Wartość zapisujesz jako listę: additions (dodany węzeł), removals (usunięty), text (zmiana tekstu) albo pojedyncze all (co do zasady: wszystko). Domyślnie, gdy nie podasz atrybutu, region reaguje na additions text — czyli na dodany tekst i węzły, a usunięcia ignoruje. I dobrze: wartości removals i all należy stosować oszczędnie (sama specyfikacja wręcz to zaleca) — czytnik ogłasza usunięcie tylko wtedy, gdy to istotna zmiana stanu (np. ktoś opuszcza pokój czatu), a przy zwykłych komunikatach o stanie bywa to po prostu szum. Poza tym ogłaszanie usunięć to tylko sugestia dla technologii wspomagających — nie są zobowiązane ich respektować.

  • aria-busy — gdy region jest w trakcie aktualizacji partiami i czytnik nie ma czytać po kawałku. Ustawiasz aria-busy=”true” przed serią zmian, false po. Czytnik wstrzymuje się, aż skończysz i ogłasza całość we właściwym momencie.

Masz to wszystko, ale jak to poskładać? Trzy reguły, które działają w 95% przypadków:

  1. Rola przed atrybutem. Jeżeli istnieje rola pasująca do sytuacji (status, alert, log) — użyj jej zamiast surowego aria-live. Rola niesie sens i domyślne zachowanie; aria-live to wyjście awaryjne. Ręczne aria-live=”polite” bez roli bywa na dłuższą metę mniej czytelne dla zespołu.

  2. aria-atomic to najczęściej Twój przyjaciel. Przy komunikatach, które w całości niosą sens (licznik, potwierdzenie, błąd) — dodawaj aria-atomic=”true”, nawet przy role=”status”. Owszem, specyfikacja przypisuje status domyślną wartość true, ale technika ARIA22 wprost radzi zapisywać ją jawnie, bo nie wszystkie środowiska traktują domyślną wartość jednakowo. Dlatego w przykładzie niżej widzisz go jawnie — to jest redundandne, ale świadoma decyzja. Z kolei aria-live przy status/alert już by został wpisany wbrew zaleceniu — nie rób tego.

  3. Nie mieszaj temperatór w jednym regionie. Jeden region na żywo = jeden tryb. Pilne i uprzejme komunikaty trzymaj w osobnych regionach, inaczej czytnik nie będzie wiedział, kiedy przerywać, a kiedy czekać.

Dobra wiadomość jest taka, że masz cztery narzędzia. Zła — że samo wklejenie role=”status” nic nie da, jeżeli zrobisz to w złym momencie albo oznaczysz złą część.

Po pierwsze, rola musi wisieć tam od początku, zanim jakiekolwiek treści do niej wpadną. role=”status” na kontenerze dodanym do strony razem z komunikatem nie zadziała — czytnik płynnie porusza się po drzewie i doczyta go dopiero przy kolejnym przejściu, które może nie nastąpić od razu. Kontener ma żyć w DOM-ie przed zmianą; zmienia się tylko jego treść.

Po drugie, nie komunikuj samej cyferki. Nawet gdyby mój wczorajszy koszyk dostał aria-live, komunikat “3” to za mało. Czytnik powie “trzy”. Trzy co? Buty? Pary? Kilometry? Jeżeli cały ciąg “3 produkty w koszyku” nie jest oznaczony jako jedna całość, zwłaszcza gdy zmienia się tylko cyfra, użytkownik usłyszy lakoniczne “trzy”. Understanding — objaśnienie kryterium — ma na to bezpośredni przykład z koszyka, jakby ktoś pisał o twoim sklepie.

Typowy polski koszyk to coś takiego:

<span class=”cart-count”>3</span>

<span>produkty</span>

Cyferka, słówko, koniec. Niewinne z wyglądu, ale nieczytelne dla czytnika: rozbite na dwa elementy, z których zmienia się tylko jeden. Poprawnie to mniej więcej tak:

<span role=”status” aria-atomic=”true”>

<span class=”sr-only”>Koszyk: </span>

<span class=”cart-count”>3 produkty</span>

</span>

Kontener z role=”status” wisi w kodzie od początku. Treść w środku — “3 produkty” plus ukryte “Koszyk: “ dla kontekstu — zmienia się w całości. aria-atomic=”true” wymusza przeczytanie całego ciągu. Czytnik nie ma już szans powiedzieć tylko “trzy”. A aria-live? Nie wpisujesz go tutaj — role=”status” domyślnie niesie polite, jawny zapis byłby tu redundancją.

Dwa przykłady z innych typów komunikatów:

<!-- pilny błąd walidacji -->

<p role=”alert”>Kod rabatowy NIEZAWODKA nie istnieje. Sprawdź literówkę.</p>

<!-- postęp procesu składania zamówienia: etapowy komunikat, bez paska -->

<p role=”status”>Przesyłanie zamówienia: 45%</p>

Trzy role? Nie — w drugim przykładzie celowo znów status. Postęp procesu pod 4.1.3 podajesz jako uprzejmy, etapowy komunikat, a nie jako rozgadany progressbar. Jedyny wspólny mianownik tych trzech sytuacji: czytnik ogłasza je bez proszenia o skupienie.

A teraz to, o czym wspomniałem na początku — i czego nikt nie lubi mówić głośno. 4.1.3 to jedno z tych kryteriów, które można spełnić tak, że użytkownik będzie miał gorzej, niż gdyby strona w ogóle nie była “dostosowana”.

role=”alert” i aria-live=”assertive” to broń, nie flaga świąteczna. Jeżeli oznaczysz alertem każdą drobnostkę — “Dodano do koszyka”, “Zapisano”, “Kliknięto” — czytnik przerywa czytanie bez przerwy. Użytkownik z czytnikiem słucha wtedy serii komunikatów, które nie pozwalają mu dokończyć ani jednego zdania na stronie. To jest gorsze niż cisza: cisza da się ogarnąć, przerwany wątek już nie.

Zasada jest prosta: uprzejmość to domyślny tryb, pilność to wyjątek. “Dodano do koszyka” nie ma prawa przecinać czytanego zdania o właściwościach materiału — to status. “Hasło nieprawidłowe, zostały Ci dwie próby” — to jest usprawiedliwione przerwanie, alert. Jak w przyzwoitej rozmowie: ktoś krzyczy, kiedy się dzieje coś naprawdę ważnego albo groźnego, a nie na każde skinienie.

Druga pułapka to dobry pomysł w złym miejscu. role=”alert” na regionie żyjącym od początku, do którego wpadają treści równolegle z tym, co użytkownik wpisuje — w konfiguratorze, w kreatorze zamówienia, edytorze tekstu — potrafi przerywać użytkownikowi w trakcie pisania. Każde pojawienie się nowej wartości w polu obok wstrzymuje mu oddech w połowie zdania. Tu z pomocą przychodzą ci wiedza z sekcji o aria-busy i aria-relevant: sterujesz kiedy i co ma być ogłoszone.

I trzecia, najgorsza: naddostępność. Niektóre zespoły, chcąc pokazać dobrą wolę, obwieszczają alertem absolutnie wszystkie przejściowe efekty — suwaki, wskaźniki, animacje, ticker kursów. Czytnik zamienia się w teleprompter, który mówi non stop. Taki “dostępny” sklep jest mniej dostępny niż sklep, w którym po prostu zostałoby domyślne ustawienie.

Dobra reguła, którą warto zapisać zespołowi na ścianie: 4.1.3 nie każe Ci wymyślać nowych komunikatów. Nie zmusza, żebyś dodał potwierdzenie tam, gdzie projekt go nie zakłada. Ono tylko każe, żeby te komunikaty, które już są, zostały odczytane — i to w sposób, który użytkownikowi pomaga, nie przeszkadza. Mniej znaczy tu naprawdę więcej.

A gdzie w tym wszystkim zwykła walidacja formularzy? Tu przyda się precyzja, bo ten temat bywa mylony częściej niż którykolwiek inny. Status message z 4.1.3 to nie jest synonim “błędu walidacji”. Kryteria z wytycznej 3.3 — 3.3.1 Identyfikacja błędu i 3.3.3 Sugestia usuwania błędu — to one rządzą tym, jak pola formularza mają być oznaczone i jak użytkownik ma się dowiedzieć, co i gdzie poszło nie tak (oznaczenia błędów, aria-describedby, sugestie poprawnych wartości, fokus w pierwsze pole z błędem). 4.1.3 wnosi co innego: ogłoszenie zmiany, która następuje bez przeniesienia fokusu i bez odświeżenia strony. Dlatego:

Gdy błąd urodzi się w bezpośrednim sąsiedztwie pola — np. użytkownik zgubi literówkę w kodzie rabatowym i natychmiast dostaje czerwoną ramkę z podpisem — to sprawa 3.3.1/3.3.3, zwłaszcza jeśli fokus ląduje w polu z błędem. Nie doklejasz tu od razu role=”alert”.

Gdy komunikat o wyniku dokumentuje się na stronie bez fokusu i bez kontekstu — np. kupon został użyty, liczba pozycji wzrosła, zamówienie przyjęte — to jest status message i tu pracuje 4.1.3. „Invalid entry” w objaśnieniu kryterium to właśnie ten drugi przypadek: ogłoszenie, które przychodzi do czytnika samo.

Nie chodzi więc o to, żeby wpisywać 4.1.3 w rubrykę “walidacja” — tylko żeby każde ogłoszenie zmian zachodzących poza fokusem było podane czytnikowi w odpowiedni sposób. A gdy komunikat łączy oba światy (błąd i nagłą zmianę), mapujesz go zgodnie z tym, co faktycznie się dzieje na ekranie i gdzie ląduje fokus.

Wróćmy do sedna. 4.1.3 dotyczy nie tylko koszyka. To esencja całego e-commerce:

  • komunikat o dodaniu do koszyka,

  • licznik produktów,

  • zmiana stanu “Dostępny / Wyprzedany”,

  • błąd kodu rabatowego,

  • przeliczanie wysyłki po zmianie adresu,

  • postęp płatności,

  • potwierdzenie złożenia zamówienia,

  • “produkt niedostępny w tej ilości”.

Jeden programista sam tego nie ogarnie. Ten temat flirtuje z product managementem i designem: jeżeli nie wiesz, które zdarzenia są komunikatami o stanie, a które nie, zrób z zespołem mapowanie wszystkich zdarzeń dynamicznych w aplikacji i przypisz każdemu jedną z kategorii: “status message”, “alert”, “treść/modal” albo “nie komunikować w ogóle”. To spokojnie robota na warsztat przy każdej większej przebudowie sklepu — a przy okazji mocno ogranicza ryzyko nadużyć z poprzedniej sekcji, bo świadomie decydujecie, co i jak ma być ogłaszane.

I właśnie to — ta wspólna decyzja, a nie sam tag — jest prawdziwą różnicą, którą robi 4.1.3. Wtedy rozmówca z firmy developerskiej na pytanie “co to znaczy być zgodnym z tym kryterium” będzie mógł odpowiedzieć bez zawahania: “wiemy, które komunikaty mamy, i wiemy, jak je ogłaszać, żeby pomagały”.

Na moim kanale YouTube zaczynają się pojawiać kolejne materiały. Już wylądowało 19 odcinków podcastu Salonik u Jacka. To były świetne rozmowy o dostępności z ludźmi na bardzo wiele tematów. Pojawił się też pierwszy film z AutomaticA11y 2026, ten który zrobił ogromne wrażenie, jak mogę wnioskować z ewaluacji. To prezentacja Natalii Pacygi na temat wykorzystania AI przez Głuchych. Pojawią się też filmy z poprzednich edycji, a to dzięki Januszowi Olszyńskiemu i Ani Czapnik z Fundacji Kinematograf.

Indie też egzekwują dostępność. SEBI, indyjski regulator rynku kapitałowego, przesunął termin audytów i napraw dostępności na 31 października 2026. Ciekawy przypadek tego samego kierunku, co u nas: kończy się epoka “standardu, który można odkładać”.

Formularze pod lupą. Deque rozkłada na czynniki, jak oznaczać pola wymagane w formularzach dostępnych — od required, przez komunikaty o błędach, po kontekst dla czytników. Szczegółowe jak podręcznik, idealne pod motyw dzisiejszego numeru.

Modal nie zawsze musi zamykać się po kliknięciu w tło. Adrian Roselli odpala solidną dyskusję: klik w zewnętrze okna dialogowego bywa pułapką, zwłaszcza dla klawiatury. A że dialogi zamykające po kliknięciu w tło to specjalność sklepów internetowych — warto przeczytać, zanim następny “wyskakujący” komunikat o wyłączności promocji wyląduje na produkcji.

☕ Dawno się nie przymilałem o kawę, więc przypomnę. Możesz postawić mi wirtualną kawę, którą zamierzam zamienić na tę prawdziwą. Oczywiście tylko wtedy, gdy podoba Ci się Dostępnik i uznajesz moją pracę za wartościową. Z góry też za to dziękuję.

I to by było na tyle. Do listy płonących krajów sprzed tygodnia dokładam Belgię i Chorwację. Być może za chwilę Niemcy, Holandia i kto tam wie, kto jeszcze. Mam nadzieję, że to lato zbliża się do końca. Tymczasem nadal cieszę się AI i Hermesem, który jest coraz mądrzejszy i lepiej mnie zna. Pomaga mi w codziennej pracy, więc pewnie zacznę pisać też wyłącznie o nim, ale to w innym miejscu. W sobotę jedziemy całą rodziną do Zakopanego do teatru Witkacego. Od przyjazdu pociągu do spektaklu mamy prawie 4 godziny czekania. Jeżeli masz pomysł, gdzie możemy odpocząć, to daj proszę znać. Trzymaj się i ciśnij na dostępność, bo dzięki niej świat jest choć trochę bardziej znośny.

Read the original on dostepnik.substack.com

Comments

Nothing yet. Say the first thing.

    Sign in to join the conversation.