WCAG 2.4.12: Fokus niezasłonięty (rozszerzony)
Kryterium sukcesu WCAG 2.1 2.4.12 „Fokus niezasłonięty (rozszerzony)” to kluczowy element zapewniający dostępność dla użytkowników nawigujących za pomocą klawiatury. Jest to kryterium na poziomie AA, które rozszerza wymagania WCAG 2.0 2.4.7 „Fokus widoczny”, dodając bardziej rygorystyczne wymagania dotyczące widoczności wskaźnika fokusu.
Wprowadzenie do kryterium sukcesu 2.4.12: Fokus niezasłonięty (rozszerzony)
To kryterium wymaga, aby w momencie, gdy komponent interfejsu użytkownika (np. przycisk, link, pole formularza) otrzyma fokus klawiatury, żadna część wskaźnika fokusu nie była zasłonięta przez inną treść na stronie. Oznacza to, że cały wskaźnik (np. obramowanie, cień, zmiana tła) musi być w pełni widoczny dla użytkownika. Jest to szczególnie ważne w przypadku elementów, które mogą być przesłonięte przez stałe nagłówki, stopki, boczne paski nawigacyjne lub inne dynamiczne elementy, takie jak wyskakujące okienka czy dymki pomocy.
Dlaczego to kryterium jest ważne? (Wpływ na dostępność)
- Użytkownicy klawiatury: Osoby, które nie mogą lub nie chcą używać myszy, polegają wyłącznie na klawiaturze do nawigacji. Widoczny i niezasłonięty wskaźnik fokusu jest dla nich jedynym sposobem na zrozumienie, gdzie aktualnie znajduje się ich „kursor” i z którym elementem interfejsu będą wchodzić w interakcję. Brak widocznego fokusu czyni stronę praktycznie nieużywalną.
- Osoby z zaburzeniami poznawczymi: Ci użytkownicy często potrzebują jasnych i spójnych wizualnych sygnałów. Niezasłonięty fokus pomaga im utrzymać orientację na stronie, śledzić postępy i zmniejsza obciążenie poznawcze związane z domyślaniem się, który element jest aktywny.
- Osoby słabowidzące: Dla wielu osób słabowidzących, które mogą powiększać ekran lub używać specjalnych stylów, wskaźnik fokusu musi być wyraźny i niezastawiony, aby mogli go łatwo dostrzec i śledzić.
- Zwiększona użyteczność dla wszystkich: Dobre zarządzanie fokusem poprawia ogólną użyteczność strony dla każdego. Jasno widoczny fokus minimalizuje błędy, przyspiesza nawigację i sprawia, że interakcja jest bardziej intuicyjna i mniej frustrująca.
Wymagania kryterium sukcesu 2.4.12
Oficjalne brzmienie kryterium sukcesu 2.4.12: Fokus niezasłonięty (rozszerzony) to:
Kiedy komponent interfejsu użytkownika otrzymuje fokus klawiatury, żadna część wskaźnika fokusu nie jest zasłonięta przez treść autora.
Kluczowe aspekty tego wymagania:
- „Komponent interfejsu użytkownika”: Odnosi się do każdego elementu, który może otrzymać fokus klawiatury (linki, przyciski, pola formularzy, elementy sterujące widżetami).
- „Wskaźnik fokusu”: Wizualna reprezentacja wskazująca, że dany element jest aktualnie aktywny i gotowy do interakcji (np. obramowanie, podkreślenie, cień, zmiana koloru tła lub tekstu).
- „Nie jest zasłonięta przez treść autora”: Oznacza to, że żaden inny element stworzony przez dewelopera (np. stały nagłówek/stopka, boczne paski, modale, dymki narzędziowe, okna dialogowe) nie może fizycznie przysłaniać lub zakrywać wskaźnika fokusu. Nawet częściowe zasłonięcie jest niezgodne z tym kryterium.
Należy pamiętać, że to kryterium wymaga, aby wskaźnik fokusu był całkowicie widoczny, co jest wzmocnieniem dla „Fokusu widocznego” (2.4.7), które wymaga jedynie, aby fokus był widoczny, ale niekoniecznie całkowicie niezasłonięty.
Praktyczne wskazówki dotyczące zgodności
1. Projektowanie z myślą o widoczności fokusu
- Unikaj nakładania się elementów: Planuj układ strony tak, aby elementy stałe (takie jak nagłówki i stopki z
position: fixed
lubposition: sticky
) nie zachodziły na obszar, w którym mógłby pojawić się wskaźnik fokusu dla elementów interaktywnych. Użyjpadding
na elemenciebody
lub na głównym kontenerze treści, aby stworzyć przestrzeń. - Odpowiednie użycie
z-index
: Upewnij się, że wskaźnik fokusu (np. niestandardowe obramowanie lub cień) ma odpowiednio wysokiz-index
, aby pojawiał się ponad innymi elementami, które mogłyby go zasłonić. Pamiętaj o kontekstach nakładania.
2. Dynamiczne zarządzanie fokusem i przewijaniem (JavaScript)
- Automatyczne przewijanie: Jeśli element z fokusem jest zasłonięty przez stały nagłówek/stopkę lub znajduje się poza widocznym obszarem, użyj JavaScriptu, aby przewinąć stronę tak, aby element i jego wskaźnik fokusu były w pełni widoczne.
- Przesunięcie obszaru widocznego: Przy przewijaniu, możesz zastosować offset (przesunięcie), aby fokusowany element nie był bezpośrednio pod stałym nagłówkiem, ale nieco niżej, gwarantując jego pełną widoczność.
3. Elementy stałe i dynamiczne nakładki
- Modale i dymki narzędziowe: Kiedy pojawia się modal lub dymek narzędziowy, upewnij się, że fokus jest przenoszony do elementu wewnątrz tej nakładki, a wskaźnik fokusu dla tego elementu jest w pełni widoczny wewnątrz modala, a nie zasłonięty przez krawędzie modala. Po zamknięciu modala, fokus powinien wrócić do elementu, który go aktywował.
- Sticky/fixed headers/footers: Regularnie testuj nawigację klawiaturą, aby upewnić się, że żaden element interaktywny, po otrzymaniu fokusu, nie jest zasłonięty przez te stałe elementy.
4. Testowanie
- Nawigacja klawiaturą: Najskuteczniejszą metodą testowania jest nawigacja po całej stronie wyłącznie za pomocą klawiszy
Tab
,Shift + Tab
,Enter
iSpacji
. - Różne rozdzielczości: Testuj stronę na różnych rozmiarach ekranu i poziomach powiększenia (zarówno systemowego, jak i przeglądarkowego), aby upewnić się, że responsywne układy nie wprowadzają problemów z widocznością fokusu.
Przykłady implementacji
Przykład prawidłowy: Fokus zawsze widoczny
W tym przykładzie, stały nagłówek i stopka są obsługiwane tak, aby nie zasłaniały fokusu, a niestandardowy styl fokusu jest dobrze widoczny.
HTML
<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WCAG 2.4.12 Fokus niezasłonięty (Prawidłowy)</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<header class="fixed-header">
<nav>
<a href="#main-content" class="skip-link">Przejdź do głównej treści</a>
<a href="#">Start</a>
<a href="#">Usługi</a>
<a href="#">Kontakt</a>
<button>Zaloguj</button>
</nav>
</header>
<main id="main-content">
<h1>Główna treść strony</h1>
<p>Poniżej znajduje się długa lista linków, aby zademonstrować przewijanie.</p>
<ul>
<li><a href="#">Link 1</a></li>
<li><a href="#">Link 2</a></li>
<li><a href="#">Link 3</a></li>
<li><a href="#">Link 4</a></li>
<li><a href="#">Link 5</a></li>
<!-- Dodaj więcej linków, aby strona była długa -->
<li><a href="#">Link 6</a></li>
<li><a href="#">Link 7</a></li>
<li><a href="#">Link 8</a></li>
<li><a href="#">Link 9</a></li>
<li><a href="#">Link 10</a></li>
<li><a href="#">Link 11</a></li>
<li><a href="#">Link 12</a></li>
<li><a href="#">Link 13</a></li>
<li><a href="#">Link 14</a></li>
<li><a href="#">Link 15</a></li>
<li><a href="#">Link 16</a></li>
<li><a href="#">Link 17</a></li>
<li><a href="#">Link 18</a></li>
<li><a href="#">Link 19</a></li>
<li><a href="#">Link 20</a></li>
<li><a href="#">Link 21</a></li>
<li><a href="#">Link 22</a></li>
<li><a href="#">Link 23</a></li>
<li><a href="#">Link 24</a></li>
<li><a href="#">Link 25</a></li>
<li><a href="#">Link 26</a></li>
<li><a href="#">Link 27</a></li>
<li><a href="#">Link 28</a></li>
<li><a href="#">Link 29</a></li>
<li><a href="#">Link 30</a></li>
<li><a href="#">Link 31</a></li>
<li><a href="#">Link 32</a></li>
<li><a href="#">Link 33</a></li>
<li><a href="#">Link 34</a></li>
<li><a href="#">Link 35</a></li>
<li><a href="#">Link 36</a></li>
<li><a href="#">Link 37</a></li>
<li><a href="#">Link 38</a></li>
<li><a href="#">Link 39</a></li>
<li><a href="#">Link 40</a></li>
</ul>
</main>
<footer class="fixed-footer">
<p>© 2023 Dostępna Strona. Wszystkie prawa zastrzeżone.</p>
<a href="#">Polityka Prywatności</a>
</footer>
<script src="script.js"></script>
</body>
</html>
CSS (style.css)
body {
margin: 0;
font-family: sans-serif;
/* Zapewnia przestrzeń dla fixed header/footer */
padding-top: 60px; /* Wysokość nagłówka */
padding-bottom: 40px; /* Wysokość stopki */
}
.fixed-header {
position: fixed;
top: 0;
left: 0;
width: 100%;
background-color: #333;
color: white;
padding: 10px 20px;
box-sizing: border-box;
z-index: 1000; /* Upewnij się, że nagłówek jest zawsze na wierzchu */
height: 60px;
}
.fixed-header nav {
display: flex;
justify-content: flex-start;
align-items: center;
gap: 15px;
}
.fixed-header a, .fixed-header button {
color: white;
text-decoration: none;
padding: 8px 12px;
border: 1px solid transparent;
background-color: transparent;
cursor: pointer;
font-size: 1rem;
}
.fixed-header a:focus, .fixed-header button:focus,
main a:focus, footer a:focus {
outline: 3px solid yellow; /* Wyraźny wskaźnik fokusu */
outline-offset: 2px; /* Odsuwa outline od elementu, aby nie był zasłonięty */
border-color: yellow; /* Dodatkowy efekt dla lepszej widoczności */
box-shadow: 0 0 0 4px rgba(255, 255, 0, 0.5); /* Dodatkowy cień dla wyrazistości */
z-index: 9999; /* Upewnia, że focus jest na wierzchu wszystkiego, co może go zasłonić */
position: relative; /* Potrzebne do z-index */
}
.fixed-footer {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
background-color: #333;
color: white;
padding: 10px 20px;
box-sizing: border-box;
text-align: center;
z-index: 1000;
height: 40px;
display: flex;
justify-content: center;
align-items: center;
gap: 20px;
}
.fixed-footer p {
margin: 0;
}
main {
padding: 20px;
}
main ul {
list-style: none;
padding: 0;
}
main li {
margin-bottom: 10px;
}
main a {
display: inline-block;
padding: 5px 10px;
border: 1px solid #ccc;
text-decoration: none;
color: #333;
background-color: #f0f0f0;
}
.skip-link {
position: absolute;
top: -40px;
left: 0;
background: #000;
color: white;
padding: 8px;
z-index: 1001; /* Wyżej niż nagłówek */
}
.skip-link:focus {
top: 0;
}
JavaScript (script.js)
document.addEventListener('focusin', function(event) {
const focusedElement = event.target;
const headerHeight = document.querySelector('.fixed-header').offsetHeight;
const footerHeight = document.querySelector('.fixed-footer').offsetHeight;
if (focusedElement) {
const rect = focusedElement.getBoundingClientRect();
// Sprawdź, czy element jest zasłonięty przez nagłówek
if (rect.top < headerHeight) {
window.scrollBy({
top: rect.top - headerHeight - 10, // Przewiń tak, aby był widoczny poniżej nagłówka + niewielki margines
behavior: 'smooth'
});
}
// Sprawdź, czy element jest zasłonięty przez stopkę
else if (rect.bottom > (window.innerHeight - footerHeight)) {
window.scrollBy({
top: rect.bottom - (window.innerHeight - footerHeight) + 10, // Przewiń tak, aby był widoczny powyżej stopki + niewielki margines
behavior: 'smooth'
});
}
}
});
Przykład nieprawidłowy: Fokus zasłonięty
W tym przykładzie, stały nagłówek i stopka nie pozostawiają miejsca dla treści, co prowadzi do zasłaniania wskaźnika fokusu.
HTML
<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WCAG 2.4.12 Fokus niezasłonięty (Nieprawidłowy)</title>
<link rel="stylesheet" href="incorrect-style.css">
</head>
<body>
<header class="fixed-header">
<nav>
<a href="#">Start</a>
<a href="#">O nas</a>
<button>Zaloguj</button>
</nav>
</header>
<main>
<h1>Główna treść strony</h1>
<p>Ta strona ma stałe nagłówki i stopki, które mogą zasłaniać fokus.</p>
<!-- Wiele linków do przewinięcia -->
<ul>
<li><a href="#">Link A</a></li>
<li><a href="#">Link B</a></li>
<li><a href="#">Link C</a></li>
<li><a href="#">Link D</a></li>
<li><a href="#">Link E</a></li>
<li><a href="#">Link F</a></li>
<li><a href="#">Link G</a></li>
<li><a href="#">Link H</a></li>
<li><a href="#">Link I</a></li>
<li><a href="#">Link J</a></li>
<li><a href="#">Link K</a></li>
<li><a href="#">Link L</a></li>
<li><a href="#">Link M</a></li>
<li><a href="#">Link N</a></li>
<li><a href="#">Link O</a></li>
<li><a href="#">Link P</a></li>
<li><a href="#">Link Q</a></li>
<li><a href="#">Link R</a></li>
<li><a href="#">Link S</a></li>
<li><a href="#">Link T</a></li>
</ul>
</main>
<footer class="fixed-footer">
<p>Stopka strony.</p>
</footer>
</body>
</html>
CSS (incorrect-style.css)
body {
margin: 0;
font-family: sans-serif;
/* Brak padding-top/bottom, co powoduje nakładanie się */
}
.fixed-header {
position: fixed;
top: 0;
left: 0;
width: 100%;
background-color: #f8d7da; /* Czerwony, aby podkreślić problem */
color: #721c24;
padding: 10px 20px;
box-sizing: border-box;
z-index: 1000;
height: 60px;
border-bottom: 2px solid #721c24;
}
.fixed-header nav {
display: flex;
justify-content: flex-start;
align-items: center;
gap: 15px;
}
.fixed-header a, .fixed-header button {
color: #721c24;
text-decoration: none;
padding: 8px 12px;
border: 1px solid transparent;
background-color: transparent;
cursor: pointer;
font-size: 1rem;
}
/* Używamy domyślnego focus outline, który będzie zasłonięty */
a:focus, button:focus {
outline: 2px solid blue; /* Domyślny, ale zostanie zasłonięty */
outline-offset: 2px;
}
main {
padding: 20px;
/* Treść zaczyna się od góry strony, pod fixed header */
/* Co może powodować zasłanianie linków pod nagłówkiem */
}
main ul {
list-style: none;
padding: 0;
margin-top: 200px; /* Dodatkowe miejsce by niektóre linki znalazły się pod nagłówkiem */
margin-bottom: 200px; /* Dodatkowe miejsce by niektóre linki znalazły się nad stopką */
}
main li {
margin-bottom: 10px;
}
main a {
display: inline-block;
padding: 5px 10px;
border: 1px solid #721c24;
text-decoration: none;
color: #721c24;
background-color: #fce8e9;
}
.fixed-footer {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
background-color: #f8d7da;
color: #721c24;
padding: 10px 20px;
box-sizing: border-box;
text-align: center;
z-index: 1000;
height: 40px;
border-top: 2px solid #721c24;
}
Najlepsze praktyki i częste pułapki
- Zawsze definiuj widoczny styl fokusu: Nawet jeśli nie masz elementów stałych, upewnij się, że styl
:focus
jest wyraźny i ma wystarczający kontrast. Nigdy nie usuwajoutline
(np.outline: none;
) bez zapewnienia alternatywnego, widocznego wskaźnika fokusu. - Rozważ
outline-offset
: Użycieoutline-offset
może pomóc przesunąć wskaźnik fokusu na zewnątrz elementu, co zwiększa szansę, że nie zostanie on zasłonięty przez sam element lub jego otoczenie. - Testowanie w przeglądarce i systemie: Różne przeglądarki i systemy operacyjne mogą renderować domyślne wskaźniki fokusu inaczej. Zawsze testuj.
- Pułapki:
- Stałe nagłówki/stopki (
position: fixed
/sticky
): Najczęstsza przyczyna problemów. Zawsze zapewnij bufor przestrzeni. - Dynamicznie pojawiające się elementy: Takie jak dymki narzędziowe, modale, powiadomienia, które mogą pojawić się nad elementem z fokusem. Upewnij się, że ich wygląd nie koliduje z wskaźnikiem fokusu.
- Użycie
overflow: hidden
na kontenerach: Może to ukryć część wskaźnika fokusu, jeśli jest on wyświetlany poza granicami elementu. - Niski kontrast: Wskaźnik fokusu musi być wyraźnie widoczny na tle elementu i jego otoczenia.
- Stałe nagłówki/stopki (
Podsumowanie
Kryterium sukcesu WCAG 2.1 2.4.12 „Fokus niezasłonięty (rozszerzony)” jest niezwykle ważne dla zapewnienia dostępności i użyteczności stron internetowych, zwłaszcza dla użytkowników nawigujących klawiaturą. Zapewnienie, że wskaźnik fokusu jest zawsze w pełni widoczny i niezasłonięty, eliminuje frustrację i umożliwia wszystkim użytkownikom skuteczną interakcję z treścią. Projektanci i programiści powinni aktywnie uwzględniać to kryterium już na etapie projektowania, a następnie dokładnie testować jego implementację, aby zapewnić pełną zgodność.