Dostępność
Czym jest dostępność?
Pojęcie dostępności stron internetowych (określanej również a11y od ang. accessibility) zostało zaprojektowane i stworzone z myślą o internecie przystępnym dla wszystkich. Wspieranie dostępności jest niezbędne, aby umożliwić technologiom asystującym poprawną interpretację stron.
React w pełni wspiera budowanie dostępnych dla wszystkich stron internetowych, często z wykorzystaniem standardowych technik HTML.
Standard oraz wytyczne
WCAG
Web Content Accessibility Guidelines dostarcza zbiór wytycznych, jak tworzyć poprawne oraz dostępne dla wszystkich strony internetowe.
Poniższe listy kontrolne WCAG zawierają przegląd:
- Lista kontrolna WCAG stworzona przez Wuhcag
- Lista kontrolna WCAG stworzona przez WebAIM
- Lista kontrolna projektu A11Y
WAI-ARIA
Dokument Web Accessibility Initiative - Accessible Rich Internet Applications zawiera listę technik pomagających w budowaniu w pełni dostępnych aplikacji javascriptowych.
Warto zaznaczyć, że wszystkie atrybuty HTML aria-*
są w pełni wspierane przez JSX. Mimo że większość tagów oraz atrybutów DOM w Reakcie zapisujemy w notacji camelCase, to te związane z dostępnością powinny być zapisywane z wykorzystaniem myślników (znanych również jako kebab-case, lisp-case itp.), ponieważ są one traktowane jak zwykłe atrybuty HTML.
<input
type="text"
aria-label={etykieta} aria-required="true" onChange={obserwatorZdarzenia}
value={wartoscPola}
name="imie"
/>
Semantyczny HTML
Semantyczny HTML jest podstawą dostępności aplikacji webowych. Wykorzystując różne elementy HTML, które wzmacniają znaczenie informacji na naszych stronach, bardzo często możemy stworzyć w pełni dostępną stronę bez dodatkowego nakładu pracy.
Czasem łamiemy zasady semantycznego HTML, kiedy dodajemy dodatkowy element div
do naszego kodu JSX, aby uruchomić aplikację. Dzieje się tak zwłaszcza kiedy pracujemy z listami (<ol>
, <ul>
czy <dl>
) oraz tabelami <table>
.
W takim przypadkach powinniśmy korzystać z fragmentów reactowych, które pozwalają na grupowanie elementów.
Przykład:
import React, { Fragment } from 'react';
function ListItem({ item }) {
return (
<Fragment> <dt>{item.term}</dt>
<dd>{item.description}</dd>
</Fragment> );
}
function Glossary(props) {
return (
<dl>
{props.items.map(item => (
<ListItem item={item} key={item.id} />
))}
</dl>
);
}
Możesz mapować kolekcje elementów do tablicy fragmentów, zupełnie jak w przypadku innych typów elementów:
function Glossary(props) {
return (
<dl>
{props.items.map(item => (
// Fragmenty zawsze powinny mieć ustawioną wartość `key` podczas mapowania kolekcji
<Fragment key={item.id}> <dt>{item.term}</dt>
<dd>{item.description}</dd>
</Fragment> ))}
</dl>
);
}
Jeśli nie chcesz przekazywać żadnych dodatkowych właściwości do Fragmentu, wówczas możesz użyć skróconej składni. Upewnij się, że wspomniany zapis jest wspierany przez używane przez ciebie środowisko.
function ListItem({ item }) {
return (
<> <dt>{item.term}</dt>
<dd>{item.description}</dd>
</> );
}
Więcej informacji znajdziesz w dokumentacji fragmentów.
Dostępne formularze
Nadawanie etykiet
Każdy element kontrolujący formularz, taki jak input
czy textarea
, powinien być etykietowany w przystępny sposób. Etykieta powinna dobrze opisywać pole tekstowe i być widoczna dla czytników ekranowych.
Poniższe zasoby opisują, jak robić to poprawnie:
- W3C - etykietowanie elementów
- WebAim - etykietowanie elementów
- Grupa Paciello wyjaśnia przystępność nazw
Chociaż tych standardowych praktyk HTML-owych można używać bezpośrednio w Reakcie, zauważ, że atrybut for
jest w składni JSX zapisywany jako htmlFor
:
<label htmlFor="imiePoleTekstowe">Imię:</label><input id="imiePoleTekstowe" type="text" name="imie"/>
Powiadamianie użytkownika o błędach
W sytuacji zgłoszenia błędu, komunikaty muszą być zrozumiałe dla wszystkich użytkowników. Poniższe linki pokazują, jak wyświetlić błędy w sposób zrozumiały dla czytników ekranowych.
Kontrola fokusa
Upewnij się, że twoją aplikację internetową można w pełni obsługiwać za pomocą samej klawiatury:
Fokus klawiaturowy a kontur
Fokus klawiaturowy odnosi się do bieżącego elementu w DOM, który został wybrany poprzez zdarzenia wywołane przez klawiaturę. Zazwyczaj oznaczany jest za pomocą konturu, podobnego do tego na obrazku poniżej:
Jeśli decydujesz się na usunięcie konturu, np. ustawiając właściwość CSS outline: 0
, nie zapomnij zastąpić go inną implementacją konturu.
Przejście do treści
Zapewnij mechanizm umożliwiający użytkownikom pominięcie sekcji nawigacji na stronie, ponieważ ułatwia on i przyspiesza nawigowanie z wykorzystaniem klawiatury.
Łącza typu “Przejdź do treści” lub “Pomiń nawigację” to specjalne, ukryte linki nawigacyjne, które stają się widoczne tylko wtedy, gdy użytkownicy klawiatury wchodzą w interakcję ze stroną. Są bardzo łatwe w implementacji z wykorzystaniem wewnętrznych kotwic oraz odrobiny stylowania:
Używaj również elementów i punktów orientacyjnych, takich jak <main>
i <aside>
, aby rozgraniczyć sekcje strony. Dzięki nim technologie wspierające pozwalają użytkownikowi na szybkie przemieszczanie się między sekcjami.
Przeczytaj więcej o wykorzystaniu tych elementów w celu zwiększenia dostępności:
Programowe zarządzanie fokusem
Nasze aplikacje Reactowe nieustannie modyfikują HTML DOM w czasie działania, co chwilami prowadzi do utraty konturu aktywnego elementu lub ustawienia go na nieoczekiwany element. Aby to naprawić, musimy ręcznie ustawić fokus we właściwym miejscu. Przykładowo, jeśli użytkownik zamknie okno modalne, fokus mógłby zostać przeniesiony na przycisk, który to okno otworzył.
Dokumentacja MDN opisuje dokładniej, w jaki sposób możemy tworzyć widżety javascriptowe z obsługą klawiatury.
W Reakcie, aby ustawić fokus, możemy posłużyć się mechanizmem referencji do elementu DOM.
Aby móc skorzystać z tego mechanizmu, w kodzie JSX wybranego komponentu tworzymy referencję ref
do elementu :
class CustomTextInput extends React.Component {
constructor(props) {
super(props);
// Stwórz referencję do elementu DOM this.textInput= React.createRef(); }
render() {
// Użyj funkcji zwrotnej `ref`, aby zapisać referencję do pola tekstowego // we właściwości instancji komponentu (na przykład this.textInput). return (
<input
type="text"
ref={this.textInput} />
);
}
}
Wówczas, w razie potrzeby, możemy przenieść fokus na inny element naszego komponentu:
focus() {
// Jawne przeniesienie fokusa na pole tekstowe przy użyciu natywnego interfejsu DOM
// Uwaga: korzystamy z "current", aby uzyskać dostęp do węzła DOM
this.textInput.current.focus();
}
Czasami komponent nadrzędny musi ustawić fokus na element komponentu podrzędnego. Możemy to zrobić poprzez przesłanie referencji “w górę” do komponentu nadrzędnego za pomocą specjalnej właściwości nadanej komponentowi podrzędnemu.
function CustomTextInput(props) {
return (
<div>
<input ref={props.inputRef} /> </div>
);
}
class Parent extends React.Component {
constructor(props) {
super(props);
this.inputElement = React.createRef(); }
render() {
return (
<CustomTextInput inputRef={this.inputElement} /> );
}
}
// Teraz możesz ręcznie ustawiać fokus, kiedy to potrzebne.
this.inputElement.current.focus();
Kiedy używasz HOC-a do rozszerzenia komponentów, zaleca się przekazanie referencji do opakowanego komponentu przy użyciu funkcji forwardRef
, która wbudowana jest w Reacta. Jeśli wybrany HOC z którejś zewnętrznej biblioteki nie implementuje takiego przekierowania, można użyć powyższego wzorca jako wyjście awaryjne.
Doskonałym przykładem zarządzania fokusem jest biblioteka react-aria-modal. Jest to stosunkowo rzadki przykład w pełni dostępnego okna modalnego. Nie tylko ustawia początkowy fokus na przycisku zamykającym okno (uniemożliwiając tym samym użytkownikowi klawiatury przypadkowe aktywowanie akcji akceptującej) i zatrzymuje fokus klawiaturowy wewnątrz okna, lecz dodatkowo po zamknięciu przywraca fokus z powrotem na element, który zainicjował otwarcie okna.
Uwaga:
Chociaż jest to bardzo ważna technika zapewniająca dostępność, należy stosować jej z umiarem. Użyj jej, aby skorygować naturalną “drogę” fokusa w aplikacji, ale nie próbuj przewidzieć, jak użytkownicy będą chcieli korzystać z aplikacji i nie wymuszaj własnej “drogi”.
Zdarzenia myszy i wskaźnika
Upewnij się, że wszystkie funkcje dostępne dla korzystających z myszy lub wskaźnika są również osiągalne za pomocą samej klawiatury. Poleganie na samych urządzeniach wskazujących prowadzi często do sytuacji, w których użytkownicy klawiatury nie mogą w ogóle korzystać z aplikacji.
Aby to zilustrować, spójrzmy na przykład zepsutej dostępności spowodowanej obsługą wyłącznie zdarzenia kliknięcia. Dotyczy to sytuacji, w której użytkownik może zamknąć otwarty “dymek” poprzez kliknięcie gdzieś poza nim.
Zazwyczaj jest to implementowane poprzez nasłuchiwanie zdarzenia click
w obiekcie window
, które zamyka dymek:
class OuterClickExample extends React.Component {
constructor(props) {
super(props);
this.state = { isOpen: false };
this.toggleContainer = React.createRef();
this.onClickHandler = this.onClickHandler.bind(this);
this.onClickOutsideHandler = this.onClickOutsideHandler.bind(this);
}
componentDidMount() { window.addEventListener('click', this.onClickOutsideHandler); }
componentWillUnmount() {
window.removeEventListener('click', this.onClickOutsideHandler);
}
onClickHandler() {
this.setState(currentState => ({
isOpen: !currentState.isOpen
}));
}
onClickOutsideHandler(event) { if (this.state.isOpen && !this.toggleContainer.current.contains(event.target)) { this.setState({ isOpen: false }); } }
render() {
return (
<div ref={this.toggleContainer}>
<button onClick={this.onClickHandler}>Wybierz opcję</button>
{this.state.isOpen && (
<ul>
<li>Opcja 1</li>
<li>Opcja 2</li>
<li>Opcja 3</li>
</ul>
)}
</div>
);
}
}
Powyższy przykład działa poprawnie dla użytkowników korzystających ze wskaźników, takich jak np. mysz. Jednakże, obsługiwanie za pomocą samej klawiatury prowadzi do problemu przy przechodzeniu do następnego elementu listy za pomocą tabulatora. Dzieje się tak, ponieważ obiekt window
nigdy nie otrzymuje zdarzeniaclick
. Może to doprowadzić do uniemożliwienia użytkownikom korzystania z aplikacji.
Ta sama funkcjonalność może zostać uzyskana poprzez użycie odpowiednich procedur obsługi zdarzeń, takich jak onBlur
ionFocus
:
class BlurExample extends React.Component {
constructor(props) {
super(props);
this.state = { isOpen: false };
this.timeOutId = null;
this.onClickHandler = this.onClickHandler.bind(this);
this.onBlurHandler = this.onBlurHandler.bind(this);
this.onFocusHandler = this.onFocusHandler.bind(this);
}
onClickHandler() {
this.setState(currentState => ({
isOpen: !currentState.isOpen
}));
}
// Zamykamy dymek w następnym cyklu za pomocą funkcji setTimeout. // Jest to konieczne, ponieważ musimy najpierw sprawdzić, // czy inny potomek elementu otrzymał fokus, jako że // zdarzenie onBlur wywołuje się przed ustawieniem fokusa // na innym elemencie. onBlurHandler() { this.timeOutId = setTimeout(() => { this.setState({ isOpen: false }); }); }
// Jeśli potomek otrzyma fokus, nie zamykaj dymku. onFocusHandler() { clearTimeout(this.timeOutId); }
render() { // React wspiera nas w przesyłaniu fokusa // do rodzica.
return ( <div onBlur={this.onBlurHandler} onFocus={this.onFocusHandler}>
<button onClick={this.onClickHandler}
aria-haspopup="true"
aria-expanded={this.state.isOpen}>
Wybierz opcję
</button>
{this.state.isOpen && (
<ul>
<li>Opcja 1</li>
<li>Opcja 2</li>
<li>Opcja 3</li>
</ul>
)}
</div>
);
}
}
Ten kod udostępnia funkcje użytkownikom zarówno urządzeń wskaźnikowych, jak i klawiatury. Zwróć także uwagę na właściwości aria- *
dodane w celu obsłużenia czytników ekranu. Dla uproszczenia kodu nie zostały zaimplementowane zdarzenia klawiaturowe pozwalające na interakcję z dymkiem za pomocą klawiszy strzałek.
Jest to tylko jeden przykład z wielu przypadków, w których poleganie jedynie na zdarzeniach wskaźnika i myszy możemy uniemożliwić poruszanie się po aplikacji użytkownikom korzystającym z samej klawiatury. Każdorazowe testowanie aplikacji za pomocą klawiatury pozwala na sprawne wyszukiwanie problemów, którym można zaradzić poprzez dodanie obsługi zdarzeń klawiaturowych.
Bardziej złożone widżety
Bardziej złożone scenariusze niekoniecznie muszą być mniej dostępne dla użytkowników. Dostępność najłatwiej osiągnąć poprzez trzymanie się jak najbliżej wzorców znanych z natywnego HTML-a. Nawet najbardziej złożony widżet może być przyjazny dla użytkownika.
Wymagamy tutaj znajomości standardu ARIA, m.in. ról oraz stanów i właściwości. Są to “skrzynki narzędziowe” wypełnione atrybutami HTML, które są w pełni obsługiwane przez JSX i umożliwiają nam tworzenie w pełni dostępnych, wysoce funkcjonalnych komponentów reactowych.
Każdy typ widżetu ma określone wzorce i zarówno użytkownicy, jak i przeglądarki oczekują, że będzie działał w określony sposób.
- Dobre praktyki WAI-ARIA - Wzorce projektowe i widżety
- Heydon Pickering - ARIA w praktyce
- Inclusive Components
Inne punkty do rozważenia
Ustawianie języka
Jawnie wskaż ludzki język tekstów zamieszczonych na stronie, ponieważ oprogramowanie czytnika ekranu używa go do wyboru prawidłowych ustawień głosu:
Ustawienie tytułu dokumentu
Ustaw znacznik <title>
dokumentu tak, by poprawnie opisywał zawartość strony. Ułatwia to użytkownikowi zrozumienie bieżącego kontekstu strony.
W Reakcie tytuł dokumentu możemy ustawić za pomocą reactowego komponentu tytułu dokumentu.
Kontrast kolorów
Upewnij się, że wszystkie teksty na twojej stronie mają wystarczający kontrast kolorów, aby pozostały maksymalnie czytelne dla użytkowników o słabym wzroku:
- WCAG - Dlaczego wymagane jest zachowanie kontrastu kolorów
- Wszystko o kontraście kolorów i dlaczego warto się nad nim zastanowić
- A11yProject - Czym jest kontrast kolorów
Ręczne obliczanie odpowiednich kombinacji kolorów dla wszystkich przypadków na swojej stronie internetowej może być nudne. Zamiast tego możesz użyć Colorable do przeliczenia całej palety kolorów dla zachowania dobrej dostępności aplikacji.
Jeśli chcesz rozszerzyć możliwości testowania kontrastu, możesz użyć następujących narzędzi:
Narzędzia do tworzenia oraz testowania
Istnieje wiele narzędzi, których możemy użyć, aby pomóc sobie przy tworzeniu przystępnych aplikacji internetowych.
Klawiatura
Zdecydowanie najłatwiejszą i jedną z najważniejszych kontroli jest sprawdzenie, czy poruszanie się po całej stronie jest możliwe z wykorzystaniem wyłącznie klawiatury. Instrukcja sprawdzenia aplikacji:
- Odłącz mysz od komputera.
- Używaj wyłącznie klawiszy
Tab
orazShift + Tab
do przeglądania strony. - Używaj klawisza
Enter
do aktywowania elementów. - W razie potrzeby używaj klawiszy strzałek do interakcji z niektórymi elementami, takimi jak menu i listy rozwijane.
Pomoc przy tworzeniu
Część testów dostępności możemy wykonać bezpośrednio w naszym kodzie JSX. Często kontrole dostępności dla ról, stanów i właściwości ARIA są wbudowane w środowisko IDE obsługujące JSX. Dodatkowo, mamy do dyspozycji również inne narzędzia:
eslint-plugin-jsx-a11y
Wtyczka [eslint-plugin-jsx-a11y] (https://github.com/evcohen/eslint-plugin-jsx-a11y) dla narzędzia ESLint informuje o problemach z dostępnością w twoim kodzie JSX. Wiele środowisk IDE umożliwia integrację ostrzeżeń o zgłaszanych problemach z dostępnością bezpośrednio z narzędziami do analizy kodu i oknami edytorów.
Create React App ma tę wtyczkę domyślnie zainstalowaną z aktywnymi niektórymi regułami. Jeśli chcesz włączyć dodatkowe reguły dotyczące dostępności, możesz utworzyć plik .eslintrc
w katalogu głównym swojego projektu z następującą zawartością:
{
"extends": ["react-app", "plugin:jsx-a11y/recommended"],
"plugins": ["jsx-a11y"]
}
Testowanie dostępności w przeglądarce
Istnieje wiele narzędzi, które umożliwiają przeprowadzanie kontroli dostępności na stronach internetowych bezpośrednio w przeglądarce. Używaj ich w połączeniu z innymi narzędziami wymienionymi tutaj, aby jak najlepiej przygotować swój kod HTML.
aXe, aXe-core oraz react-axe
Deque Systems oferuje aXe-core do automatycznych i kompleksowych testów dostępności aplikacji. Moduł ten obejmuje integracje z Selenium.
The Accessibility Engine (lub inaczej aXe) jest rozszerzeniem przeglądarkowego inspektora dostępności, zbudowanym na bazie aXe-core
.
Możesz również użyć modułu @axe-core/react, aby zgłosić luki dotyczące dostępności bezpośrednio do konsoli, podczas rozwoju aplikacji i debugowania.
WebAIM WAVE
Web Accessibility Evaluation Tool jest kolejną wtyczką pomagającą w testowaniu dostępności.
Inspektory dostępności i Accessibility Tree
The Accessibility Tree jest podzbiorem drzewa DOM. Zawiera wszystkie dostępne dla technologii wspomagających obiekty, odpowiadające każdemu elementowi modelu DOM.
W niektórych przeglądarkach możemy łatwo wyświetlić informacje o dostępności dla każdego elementu w drzewie DOM:
- Korzystanie z Inspektora dostępności w Firefoksie
- Korzystanie z Inspektora dostępności w Chromie
- Korzystanie z Inspektora dostępności w OS X Safari
Czytniki ekranowe
Testowanie za pomocą czytnika ekranu powinno stanowić część testów dostępności. Należy pamiętać, że kombinacje przeglądarka/czytnik ekranu mają bardzo duże znaczenie. Zaleca się testowanie aplikacji w przeglądarce rekomendowanej do wybranego czytnika ekranu.
Często używane czytniki ekranu
NVDA w Firefoxie
NonVisual Desktop Access (lub NVDA) to czytnik ekranu systemu Windows o otwartym kodzie źródłowym, który jest szeroko stosowany.
Zapoznaj się z następującymi poradnikami opisującymi, jak najlepiej wykorzystać NVDA:
VoiceOver w Safari
VoiceOver to zintegrowany czytnik ekranu na urządzeniach Apple.
Zapoznaj się z następującymi przewodnikami dotyczącymi aktywacji i korzystania z VoiceOver:
- WebAIM - Korzystanie z VoiceOver do oceny dostępności stron internetowych
- Deque - VoiceOver dla OS X: Skróty klawiszowe
- Deque - VoiceOver dla iOS: Skróty
JAWS w Internet Explorer
Job Access With Speech (lub JAWS) jest popularnym czytnikiem ekranu w systemie Windows.
Zapoznaj się z następującymi poradnikami, jak najlepiej korzystać z JAWS:
- WebAIM - Korzystanie z JAWS do oceny dostępności stron internetowych
- Deque - JAWS: Skróty klawiszowe
Inne czytniki ekranowe
ChromeVox w Google Chrome
[ChromeVox] (https://www.chromevox.com/) jest zintegrowanym czytnikiem ekranu na Chromebookach i jest dostępny [jako rozszerzenie] (https://chrome.google.com/webstore/detail/chromevox/kgejglhpjiefppelpmljglcjbhoiplfn?hl=pl) dla Google Chrome.
Zapoznaj się z następującymi poradnikami opisującymi, jak najlepiej korzystać z ChromeVox: