14 lutego Ryan Florence napisał na Twitterze, że przyciski w React Native Web (RNW) są dostępne:

Both true:

  1. Most devs should just use a <button>, not <a>, <div>, <span>, etc.

  2. React Native Web’s div buttons are better buttons than <button>

    • Still keyboard and AT accessible
    • Better touch event handling
    • Populate e.relatedTarget unlike <button>
    • Easier to style

I think a lot of HTML/CSS experts are being overly critical of React because they see the output of the new https://twitter.com and think they know there are fundamental flaws when they see the div soup. That div soup is accessible.

<h2/>
<div role="heading" aria-level="2"/>

<button/>
<div {...allTheRightAttributesAndEventHandlers}/>

These are identical as far as accessibility is concerned when implemented correctly.

If you’re critical of this, you don’t actually care about a11y, you care about your niche

So yeah … just use a button, or a RNW button, but not your own div button.

[Obydwa stwierdzenia są prawdziwe:

  1. Większość devów powinna używać <button> zamiast <a>, <div>, <span> itd.
  2. Przycisk z React Native Web oparty o div jest lepszy niż <button>:
    1. Wciąż dostępny z poziomu klawiatury i technologii asystującej.
    2. Lepsza obsługa dotyku.
    3. Zawiera e.relatedTarget w przeciwieństwie do <button>.
    4. Łatwiejszy do stylowania.

Myślę, że wielu ekspertów HTML/CSS jest zbytnio krytycznych względem Reacta, ponieważ widzą oni kod nowego Twittera i myślą, że wiedzą, żę są tam podstawowe błędy, gdy widzą divową zupę.

Ta divowa zupa jest dostępna.

<h2/>
<div role="heading" aria-level="2"/>

<button/>
<div {...allTheRightAttributesAndEventHandlers}/

Te kody są identyczne, jeśli bierzemy pod uwagę poprawnie zaimplementowaną dostępność.

Jeśli jesteś krytyczny wobec tego, tak naprawdę nie dbasz o dostępność, ale o własną niszę.

Więc tak… po prostu użyj przycisku albo przycisku z RNW, ale nie swojego własnego przycisku na div.]

Takie podejście jest nie tyle niewłaściwe, co szkodliwe. Postaram się pokrótce przybliżyć, czemu tak uważam.

Informacje o natywnym przycisku są niepełne

Przyciski po kliknięciu mają własność event.relatedTarget:

Jeśli ponawigujemy po przykładzie klawiaturą (Tab oraz Shift+Tab), zauważymy, że wyświetla się poprawny identyfikator pola, z którego przeszliśmy do przycisku. Co prawda Ryan uściśla następnie, że chodzi o Firefoksa i Safari, ale wciąż nie pokrywa się to z moimi testami. Na moim macOS 10.14.3 zarówno w Firefoksie, jak i w Safari powyższy kod działa – wbrew informacji na MDN . Być może zmienił się sposób obsługi focusowania na poziomie całego systemu. To równocześnie oznaczałoby, że problem nieprzekazywania event.relatedTarget przez przyciski jest już rozwiązany.

Inna rzecz, że po raz kolejny dostrzec tu można problem “makizacji” developmentu i zamykania go w bańce. Fakt, że coś nie działa w dwóch przeglądarkach na macOS (lub inaczej: zachowuje się zgodnie z tym, jak zachowują się przyciski w tym systemie), nie oznacza równocześnie, że jest to problem globalny. Chrome, mający 61% rynku, tego problemu nie posiada, a sam jeden prezentuje już większość rynku. Oczywiście, użytkowników, którzy mogą natrafić na ten błąd, wciąż jest sporo, ale tutaj trzeba sobie zadać pytanie, czy warto jest rezygnować z natywnego button na rzecz własnego rozwiązania, by zadowolić ok. 15% użytkowników kosztem reszty? W tym wypadku wydaje mi się, że sensowniejszym pomysłem byłoby znalezienie alternatywnego sposobu rozwiązania problemu lub stworzenie jakiegoś obejścia dla przeglądarek na macOS.

Kolejną niepełną informacją jest ta, że przyciski w RNW są łatwiejsze do stylowania. Niemniej ta informacja nie wydaje się być aktualna. Obecnie usunięcie wszystkich stylów z przycisku sprowadza się do użycia w CSS własności all z wartością unset. A jeśli musimy wspierać IE i Edge’a lub natrafimy na (wciąż, niestety, istniejące) bugi z all: unset, istnieją tradycyjne sposoby.

Niepełna jest także informacja o lepszej obsłudze dotyku. Ryan nie podaje szczegółów i nie wiem do końca, o co chodzi. Zwłaszcza, że przecież natywne przyciski również działają normalnie ze zdarzeniami dotykowymi.

Mam także pewną wątpliwość co do źródła danych. Ryan przy omawianiu zgodności ARIA-owych zamienników z czytnikami ekranowymi powołuje się na statystyki obsługi ARIA przez czytniki ekranowe. Widzę z nimi jeden podstawowy problem: nie ma w nich ani jednego czytnika głosowego dla Linuksa czy Androida. Być może okazałoby się wówczas, że wsparcie dla divowych nagłówków wcale nie jest takie świetne.

Dostępność to nie tylko czytniki ekranowe

Gdy weźmie się pod uwagę wyłącznie czytniki ekranowe, można dojść do wniosku, że faktycznie – div[role=heading][aria-level=2] jest równoznaczne z h2. Niemniej dostępność nie kończy się na czytnikach ekranowych – dostępność dotyczy absolutnie każdego. I tak jak błędem dostępności jest fakt, że czytnik ekranowy nie potraktuje naszego nagłówka jako nagłówka, tak samo błędem dostępności jest fakt, że dodatek do przeglądarki widomego użytkownika nie będzie mógł stworzyć dla niego spisu treści, bo zamiast nagłówków użyliśmy pełno div.

I choć dodatek do przeglądarki opierający się na znacznikach HTML, by budować spis treści, nie jest specjalnie popularnym przypadkiem, to o wiele częściej można znaleźć przypadki związane z doborem odpowiednich stylów dla osób niedowidzących. Chyba najprostszym przykładem może być wykorzystanie przez takich użytkowników własnych stylów CSS. Niestosowanie semantycznych znaczników lub używanie stylów inline (co zresztą RNW też robi, sądząc po demo) może znacząco wpłynąć na komfort przeglądania strony przez takich użytkowników, a w skrajnym wypadku im to uniemożliwić całkowicie.

Problem ten dotyczy nie tylko niestandardowych arkuszy stylów użytkownika, ale także wbudowanego w Windows trybu wysokiego kontrastu. Usuwa on bowiem wszystkie style nadane elementom przez developera i na podstawie domyślnych arkuszy stylów przeglądarki oraz semantyki poszczególnych elementów wyświetla wszystkie elementy. W tym momencie przyciski oparte na div przestaną pełnić swoją funkcję. Dzieje się tak, ponieważ w ich przypadku dostępność jest ściśle połączona z ich prezentacją – tych dwóch aspektów nie da się rozdzielić.

Brak ramki i innych wyznaczników, że jest to przycisk; został sam tekst.
Przycisk z RNW w trybie wysokiego kontrastu
Ramka wyraźnie wskazuje, w którym miejscu znajduje się przycisk.
Natywny przycisk w trybie wysokiego kontrastu

Przycisk na div przestaje być przyciskiem w chwili, gdy przestaje wyglądać. Z kolei button ma przypisaną “przyciskowość” niejako do własnej tożsamości. Tak jak wilk przebrany za owcę nigdy nie stanie się prawdziwą owcą, tak przycisk na div nigdy nie stanie się tym samym co button. W wielu przypadkach może się sprawdzić, ale w wielu – spektakularnie się wyglebi.

Dodatkowo fakt, że część stylów jest umieszczana bezpośrednio w znaczniku, w tym te dotyczące animacji, może sprawiać problemy, gdy pojawi się potrzeba wyłączenia animacji. W przypadku całkowitego rozdziału elementu od prezentacji taka zmiana jest bardzo prosta do wprowadzenia.

Divowy przycisk nie działa bez JS

I zanim ktoś zdąży powiedzieć, że “przecież nikt nie wyłącza dzisiaj JS”: JS może nie zadziałać. Ba, wystarczy zadyszka naszego CDN-a, żeby zachowanie dla naszego przycisku wczytało się kilka sekund za późno. Przez ten czas użytkownik będzie wrzucony do doliny dziwów i będzie się zastanawiał, czemu przyciski wyglądające jak przyciski nic nie robią. W przypadku zastosowania button i podejścia Progressive Enhancement można tak pokombinować z SSR, żeby użytkownik dostał podstawową wersję produktu zanim JS się doczyta i był w stanie np. złożyć zamówienie w sklepie. A skoro da się zrobić nowoczesną stronę WWW, która odpala się na pierwszej przeglądarce internetowej w historii, to da się naprawdę dużo.

Divowy przycisk łamie pierwszą zasadę ARIA

Pierwsza zasada ARIA brzmi: nie używaj ARIA, jeśli istnieje odpowiedni element HTML, którego możesz użyć. Ta zasada nie wzięła się znikąd i mam nadzieję, że powyższe punkty to pokazują. Nie ma sensu wymyślać na nowo koła – zwłaszcza, że jest to koło z napędem odrzutowym i silnikiem jądrowym, które, źle skonstruowane, grozi zniszczeniem całej planety. Skoro nikt nie wpadłby na pomysł budowania takiego cuda w garażu, skoro geniusze z NASA zrobili to w tajnym laboratorium 50 metrów pod ziemią, nie widzę powodu, dla którego ktoś miałby implementować od podstaw przycisk.

Fakt, że divowy przycisk działa dobrze w czytnikach ekranowych, oparty jest na założeniu, że implementacja jest prawidłowa. Problem polega na tym, że to jest ten typ błędów, który wychodzi dopiero w praniu. Przeglądarki miały na to kilkanaście lat, RNW – zdecydowanie mniej. Dodatkowo jest dość niszową technologią, co zmniejsza ryzyko wystąpienia błędów. Tym samym na divowy przycisk czaić się mogą najróżniejsze przypadki brzegowe i inne monstra. Albo po prostu zmieni się standard ARIA lub zachowanie przycisków w przeglądarkach i divowy przycisk przestanie przystawać do rzeczywistości. A źle działający przycisk jest w stanie czasami wyrządzić więcej szkody niż całkowicie niedziałający.

HTML to nie tylko dostępność

Stwierdzić, że div[role=button] to to samo co button, to jak stwierdzić, że w sumie Słowacki to to samo co Mickiewicz… No nie, po prostu nie. Jeśli na gruncie czytników ekranowych jest to to samo, tak na gruncie semantyki są to całkowicie różne elementy. Fakt, że obecnie coraz większa część Sieci jest divową zupą, wynika z faktu, że HTML stał się formatem kompilacji. To samo w sobie nie jest złe, ale równocześnie pokazuje problem: współczesne aplikacje nie mają semantycznego HTML-a, bo ich twórcy nie muszą go znać. W końcu z punktu widzenia developera RNW przycisk to po prostu <Button />. Nikogo nie interesuje, że do przeglądarki trafia ostatecznie taki potwór:

<div role="button" data-focusable="true" tabindex="0" class="rn-1oszu61 rn-1iymjk7 rn-s2skl2 rn-l5bh9y rn-101sy47 rn-1efd50x rn-14skgim rn-rull8r rn-mm0ijv rn-13yce4e rn-fnigne rn-ndvcnb rn-gxnn5r rn-deolkf rn-1loqt21 rn-6koalj rn-1qe8dj5 rn-1mlwlqe rn-eqz5dr rn-1mnahxq rn-61z16t rn-p1pxzi rn-11wrixw rn-ifefl9 rn-bcqeeo rn-wk8lta rn-9aemit rn-1mdbw0j rn-gy4na3 rn-bnwqim rn-1otgn73 rn-eafdt9 rn-1i6wzkk rn-lrvibr rn-1lgpqti" style="background-color: rgb(23, 191, 99);">
    <div dir="auto" class="rn-13yce4e rn-fnigne rn-ndvcnb rn-gxnn5r rn-deolkf rn-jwli3a rn-1471scf rn-14xgk7a rn-1b43r93 rn-o11vmf rn-ebii48 rn-majxgm rn-t9a87b rn-1mnahxq rn-61z16t rn-p1pxzi rn-11wrixw rn-tskmnb rn-1pyaxff rn-xd6kpl rn-1m04atk rn-q4m81j rn-bauka4 rn-tsynxw rn-q42fyq rn-qvutc0">Press me</div>
</div>

Nikogo, oprócz użytkowników końcowych, którzy – oprócz wszelkich innych opisanych problemów – muszą najzwyczajniej w świecie ściągnąć ten nadmiarowy kod HTML (albo wygenerować u siebie w przeglądarce, co również nie należy do najwydajniejszych rzeczy pod słońcem). W świecie, w którym walczy się o każdy bajt, wycinając wszelkie niepotrzebne znaki z HTML-a, taka rozrzutność jest po prostu niezrozumiała.

Niemniej to tylko fragment problemu. Wspomniane już utrudnienia w audytowaniu takiego kodu również są tylko wycinkiem. Nie można bowiem zapomnieć, że w obecnym świecie HTML jest uniwersalnym językiem wymiany informacji – popularniejszym nawet od JSON. To język, na bazie którego zbudowano WWW. To język, na bazie którego powoli powstaje Semantyczna Sieć. W końcu to język, który traktuje się po macoszemu i odrzuca w pełni jego semantykę… Przez lata wypracowano rozwiązania pokroju RDF, RDFa, mikrodanych, Schema.org, mikroformatów, JSON-LD i wielu innych, by teraz porzucić to wszystko na rzecz divowej zupy, niemającej jakiejkolwiek semantycznej wartości. Parsowanie tego typu dokumentów i próba wyciągnięcia z niej znaczącej treści jest niemal niemożliwa. A to oznacza, że z HTML-a pozostaje tylko T – tekst odarty z własnego znaczenia.

I dlatego twierdzenie, że obrona semantycznego HTML-a jest obroną pewnej niszy, jest z gruntu fałszywe. Obrona semantycznego HTML-a to obrona podstawowych wartości, na jakich zbudowano Sieć. A niszą w tym zestawieniu pozostaje RNW.