Atrakcyjny i funkcjonalny focus ♿️
Kilka porad dotyczących jednej z podstawowych i często zaniedbywanych funkcjonalności w kontekście a11y.
Istota stanu zaznaczenia 🥱
Element interfejsu może przyjmować różne stany i w zależności od nich zmieniać swój wygląd. Najbardziej powszechny przykład to :hover
na przyciskach albo linkach. Czyli zmiana stanu (co za tym idzie wyglądu, np. koloru) po najechaniu myszką. Taki zabieg poza walorami estetycznymi ma przede wszystkim być dla użytkownika pewną formą wskazówki. "Ten element można kliknąć, więc jeśli teraz kliknę przycisk myszy, to pewnie zostanie wykonana jakaś akcja albo zostanę gdzieś przeniesiony. 🤔" Taki scenariusz jest tak oczywisty, że nikomu nie trzeba tego za bardzo tłumaczyć.
Aczkolwiek :hover
to nie jest jedyny dostępny stan przycisku (a ściślej niejedyna pseudoklasa CSS odwołująca się do określonego stanu). Jest ich znacznie więcej i każda z nich ma mniejsze lub większe znaczenie. Z moich obserwacji wynika jednak, że stan :focus
, który jest krytycznie ważny z punktu widzenia a11y (accessibility), bywa czasami całkowicie pomijany w stylowaniu. Samo zostawienie go w spokoju nie jest jeszcze najgorszą możliwością, bo przeglądarki dodają do najważniejszych elementów dodatkowe, domyślne stylowanie w przypadku zaznaczenia. Jednak niestety czasami można spotkać się z czymś takim:
Kod tego typu całkowicie usuwa stan zaznaczenia ze wszystkich elementów, co pod względem accessibility jest katastrofą. Sam temat accessibility, czyli dostępność UI dla osób z niepełnosprawnościami jest wciąż relatywnie niszowy, wymaga pewnej dodatkowej wiedzy i poświęcenia dodatkowego czasu na implementację rzeczy, które nie dotyczą większości użytkowników. Poza tym zabiegi takie często wymuszają pewne ograniczenia w designie i dlatego mogą skutkować gorszym efektem wizualnym produktu.
Jednak zadbanie o podstawowe kwestie dostępności nie jest szczególnie trudne ani pracochłonne, a może wielu osobom ułatwić, albo wręcz umożliwić korzystanie z naszego interfejsu. Jedną z takich podstaw jest :focus
, którego w prosty sposób możemy atrakcyjnie ostylować.
Pseudoklasa :focus
dotyczy w zasadzie elementów, z którymi użytkownik może wchodzić w interakcje (focusable elements). Są to więc wszelkiego rodzaju przyciski, linki czy elementy formularza.
Dla osób, które wspomagają się czytnikami ekranowymi albo nawigują się po stronie wyłącznie za pomocą klawiatury, jest podstawowym narzędziem służącym do poruszania się po stronach. Jego złe obsłużenie przez developera w kodzie można porównać do sytuacji, kiedy np. użytkownik korzystający z myszy nie jest w stanie kliknąć danego przycisku, bo jakiś inny kawałek interfejsu, w wyniku błędu, częściowo na niego najeżdża.
Prosty mixin na ratunek ⛑
Przeglądarki dodają swoje domyślne stylowanie, jednak zazwyczaj nie pasuje ono do brandingu czy style guide'ów naszej strony lub aplikacji. Zwłaszcza że każda przeglądarka dodaje trochę inne stylowanie. Aby to ustandaryzować pomiędzy przeglądarkami i systemami operacyjnymi powinniśmy dodać swoje customowe style. Można takie style dodać globalnie, ale zdecydowanie lepszym rozwiązaniem jest dostosowanie stylowania w zależności od elementu i kontekstu, w jakim się on pojawia. Idealnie sprawdzi się do tego Scss-owy @mixin
, który pozwoli nam na jego wielokrotne wykorzystywanie.
Takie rozwiązanie pozwala nam na sporą customizację (używając zmiennych) przy eleganckim zachowaniu zasady DRY (Don't repeat yourself).
Problem z kliknięciem 🖱
Jednym z powodów, dla których stan :focus
jest całkowicie usuwany, jest problem, który polega na tym, że w większości przeglądarek jest on aktywowany dodatkowo w momencie pojedynczego kliknięcia myszą. Kiedy użytkownik posługujący się myszką klika przyciski albo linki w nawigacji i w momencie kliknięcia przycisku pojawia się obramowanie, jest to sytuacja w większości przypadków niepożądana, zazwyczaj nie wygląda to zbyt atrakcyjnie i z tego powodu często style dla :focus
są całkowicie usuwane. A to błąd! Ponieważ problem ten bez większego wysiłku można rozwiązać na zasadzie: jednocześnie mieć ciastko i zjeść ciastko! 🍪
Zanim przejdziemy do rozwiązania warto zrozumieć działanie pseudoklas :focus
oraz :active
, ponieważ ich działanie wbrew pozorom nie jest tak trywialne, jakby się mogło wydawać. Dlatego należy zapamiętać te 4 zasady:
- Domyślnie stan
:active
oraz:focus
są wyłączone. - Kiedy użytkownik nawiguje się za pomocą klawiatury (klikając klawisz Tab) po focusable elements (
<input>
,<button>
,<textarea>
) to otrzymują one stan:focus
, bez stanu:active
. - Kiedy użytkownik klika po non-focusable elements (
<div>
,<p>
,<span>
) to otrzymują one stan:active
, bez stanu:focus
. - Kiedy użytkownik klika po focusable elements (
<input>
,<button>
,<textarea>
) to otrzymują one jednocześnie stan:focus
oraz:active
.
W przypadku przycisków jest to sytuacja zazwyczaj niepożądana, ponieważ wystarczy nam :active
, a :focus
jest całkowicie zbędny. Z pomocą przychodzi nam kolejna pseudoklasa :focus-visible
.
Dzięki wprowadzonemu w specyfikacji Selectors Level 4 pseudoselektorowi :focus-visible
możemy przypisać style do elementu, który jest "sfokusowany" oraz w przypadku, co do którego (w zależności od kontekstu) przeglądarka uzna, że powinna je zastosować. Hmmm, ale... to znaczy kiedy?
W gruncie rzeczy to właśnie wtedy, kiedy użytkownik porusza się po interfejsie za pomocą klawiatury oraz gdy element wymaga do działania klawiatury (np. <input>
). Czyli: <button>
po kliknięciu nie będzie otrzymywał dodatkowych stylów, otrzyma je, gdy użytkownik "złapie go" za pomocą klawiatury. Jednak już element <input>
otrzyma dodatkowe style, zarówno po kliknięciu, jak i w przypadku nawigacji przy pomocy klawiatury.
Jednak zachowanie to może zmienić użytkownik (np. ze względu na problemy ze wzrokiem) i z poziomu ustawień przeglądarki ustawić, żeby :focus-visible
był aktywowany zawsze, tak samo, jak ma to miejsce w przypadku zwykłej pseudoklasy :focus
. Więcej info w specyfikacji W3C.
Jest jedna istotna kwestia w kontekście Safari. Przeglądarka ta na chwilę obecną nie wspiera pseudoklasy :focus-visible
, ale również nie "fokusuje" klikniętego przycisku oraz linku. Biorąc pod uwagę te dwa fakty, to posługując się pseudoselektorem :not
można stworzyć rozwiązanie, które będzie prawidłowo działało na wszystkich współczesnych przeglądarkach internetowych! 😌
Stosowanie takich selektorów dla każdego elementu z osobna może negatywnie wpływać na czytelność kodu, dlatego zastosowanie tutaj mixina jest według mnie bardzo dobrym rozwiązaniem.
🗓 Update 12.2021
Pseudoklasa
:focus-visible
została już zaimplementowana w Safari Technology Preview (R122). Zatem w niedalekiej przyszłości nie będzie już potrzeby stosowania przekomplikowanego rozwiązania. 🎉
🗓 Update 03.2022
Pseudoklasa
:focus-visible
została zaimplenentowana w Safari 15.4, jest teraz oznaczona jako Baseline 2022 i jest dostępna we wszystkich współczesnych przeglądarkach. 🎉
Atrakcyjniejszy focus ✨
Jeśli już wiemy, że nie musimy całkowicie pozbywać się focusa w celu poprawy estetyki UI, to warto też zastanowić się nad tym, co zrobić, aby ten focus mógł wyglądać nieco lepiej? Przede wszystkim warto skorzystać z właściwości outline-offset
, która pozwala odsunąć outline od elementu, dodając trochę bezcennego światła. Zastosowanie tylko tej jednej rzeczy może diametralnie poprawić wrażenia estetyczne. Do tego warto stosować pasujący do elementu i kontekstu kolor oraz korzystać z kropkowanego dotted
lub kreskowanego dashed
obramowania.
A dzięki naszemu mixinowi możemy to zrobić bardzo prosty i customizowalny sposób!
Podsumowanie 📋
Jeśli kiedyś zdarzyło Ci się, czy to na prośbę klienta, czy z własnej inicjatywy całkowicie usunąć stylowanie dla stanu :focus
to znając pseudoklase :focus-visible
oraz właściwość outline-offset
nie musisz tego już więcej robić!
Te kilka dodatkowych linijek kodu może okazać się naprawdę niezwykle przydatnych dla większości osób z niepełnosprawnościami, które używają alternatywnych sposobów nawigacji po interfejsie.