WCAG 2.4.11: Fokus niezasłonięty (Minimum)

WCAG 2.4.11: Fokus niezasłonięty (Minimum)

Kryterium sukcesu WCAG 2.4.11, wprowadzone w wersji 2.1, ma kluczowe znaczenie dla użytkowników nawigujących za pomocą klawiatury. Jego celem jest zapewnienie, że kiedy element interfejsu użytkownika otrzymuje fokus klawiatury, jego wskaźnik fokusu jest zawsze w pełni widoczny i nie jest zasłonięty przez żadną inną treść renderowaną przez autora strony.

Czym jest kryterium 2.4.11 Fokus niezasłonięty (Minimum)?

To kryterium wymaga, aby wskaźnik wizualny (np. ramka, zmiana tła), który pojawia się, gdy element interaktywny (taki jak link, przycisk, pole formularza) otrzymuje fokus za pomocą klawiatury, był w pełni widoczny dla użytkownika. Oznacza to, że żadna część tego elementu, w szczególności jego wskaźnik fokusu, nie może być całkowicie zakryta przez inne elementy strony, takie jak:

  • Stałe nagłówki (sticky headers)
  • Stałe stopki (sticky footers)
  • Paski boczne, które mogą przewijać się wraz ze stroną
  • Wyskakujące okna, modale lub dymki podpowiedzi, które nie przejmują fokusu
  • Inne dynamicznie pojawiające się treści

Kryterium to nie dotyczy przypadków, gdy fokus jest zasłonięty przez elementy kontrolne przeglądarki lub systemu operacyjnego, ale wyłącznie przez treści stworzone przez autora strony.

Dlaczego to jest ważne? (Wpływ na dostępność)

Widoczny fokus jest fundamentalny dla wielu grup użytkowników. Bez niego nawigacja po stronie staje się niezwykle trudna, a często niemożliwa. Brak widocznego fokusu może prowadzić do frustracji, błędów i niemożności wykonania zamierzonych działań.

Kryterium 2.4.11 ma szczególne znaczenie dla:

  • Użytkowników nawigujących klawiaturą: Osoby, które nie mogą używać myszy (np. z powodu niepełnosprawności ruchowej) lub preferują nawigację klawiaturą, polegają na widocznym fokusie, aby wiedzieć, gdzie aktualnie się znajdują na stronie i jaki element zostanie aktywowany.
  • Osób z niepełnosprawnościami ruchowymi: Mogą mieć trudności z precyzyjnym sterowaniem kursorem myszy i polegają na nawigacji tabulatorem.
  • Osób słabowidzących: Potrzebują wyraźnego wskaźnika, aby śledzić swoją pozycję na stronie.
  • Osób z niepełnosprawnościami poznawczymi: Widoczny i stabilny fokus pomaga w utrzymaniu orientacji i zrozumieniu interakcji.

Kiedy fokus jest zasłonięty, użytkownik traci punkt odniesienia, nie wie, który element jest aktywny, co uniemożliwia efektywne korzystanie z serwisu.

Wymagania kryterium sukcesu 2.4.11

Oficjalne brzmienie kryterium sukcesu 2.4.11 (Fokus niezasłonięty (Minimum)) jest następujące:

Gdy komponent interfejsu użytkownika otrzymuje fokus klawiatury, komponent ten nie jest całkowicie zasłonięty przez treść renderowaną przez autora.

Kluczowe aspekty tego wymagania to:

  • „Gdy komponent interfejsu użytkownika otrzymuje fokus klawiatury”: Odnosi się to do momentu, w którym użytkownik tabuluje do elementu, a przeglądarka lub skrypt nadaje mu fokus.
  • „komponent ten nie jest całkowicie zasłonięty”: Oznacza to, że żadna część skupionego elementu, a w szczególności jego wskaźnik fokusu, nie może być niewidoczna z powodu innej treści. Częściowe zasłonięcie może być dopuszczalne, o ile wskaźnik fokusu jest wciąż wyraźnie widoczny. Jednak zaleca się unikanie jakiegokolwiek zasłaniania.
  • „przez treść renderowaną przez autora”: Podkreśla, że odpowiedzialność spoczywa na twórcy strony. Zasłonięcie przez elementy interfejsu przeglądarki (np. paski narzędzi) lub systemu operacyjnego nie wchodzi w zakres tego kryterium.

Kryterium to ma poziom zgodności AA.

Praktyczne wskazówki dotyczące zgodności

Aby zapewnić zgodność z WCAG 2.4.11, należy wziąć pod uwagę następujące wytyczne:

  • Unikaj nakładania się elementów: Projektuj układy strony w taki sposób, aby stałe nagłówki, stopki, paski boczne lub wyskakujące okienka nie mogły zasłonić elementów interaktywnych, gdy te otrzymają fokus.
  • Użyj scroll-margin (lub scroll-padding): W przypadku „lepkich” nagłówków lub stopek, które mogą zasłaniać treść po przewinięciu do elementu z kotwicy (np. linki w spisie treści), zastosuj właściwość CSS scroll-margin-top (lub scroll-margin-bottom) na elementach docelowych. Zapewnia to dodatkowy margines u góry (lub u dołu) obszaru przewijania, skutecznie „odpychając” treść od lepkiego elementu i odsłaniając fokus.
  • Zarządzanie fokusem w JavaScript: Gdy używasz JavaScriptu do dynamicznego wyświetlania lub ukrywania treści (np. modale, rozwijane menu, alerty), upewnij się, że fokus klawiatury jest odpowiednio zarządzany. W przypadku modali fokus powinien być przeniesiony do modala, a po jego zamknięciu – przywrócony do elementu, który go wywołał, lub do logicznego, widocznego miejsca na stronie.
  • Testowanie nawigacji klawiaturą: Regularnie testuj całą stronę, nawigując wyłącznie za pomocą klawiatury (klawisz Tab, Shift+Tab, Enter, Spacja). Obserwuj, czy fokus jest zawsze widoczny, i czy nie jest zasłaniany przez inne elementy.
  • Zapewnij wystarczający kontrast dla wskaźnika fokusu: Chociaż nie jest to bezpośrednio część kryterium 2.4.11, dobrze widoczny wskaźnik fokusu (z dobrym kontrastem i odpowiednią grubością) jest niezbędny, aby użytkownicy mogli go dostrzec. WCAG 2.4.7 (Fokus widoczny) oraz 1.4.11 (Kontrast elementów nietekstowych) dotyczą tych kwestii.

Przykłady implementacji

Przykład niepoprawny: Sticky header zasłaniający fokus

W tym przykładzie stały nagłówek (sticky header) może zasłaniać linki lub elementy kotwicy, gdy strona jest przewijana, a fokus ląduje na elemencie znajdującym się bezpośrednio pod nagłówkiem.

HTML

<header class="sticky-header">
    <nav>
        <a href="#sekcja1">Sekcja 1</a>
        <a href="#sekcja2">Sekcja 2</a>
    </nav>
</header>

<main>
    <div style="height: 800px;"></div> <!-- Dużo treści, aby umożliwić przewijanie -->
    <section id="sekcja1">
        <h2>Pierwsza Sekcja</h2>
        <p>To jest treść pierwszej sekcji.</p>
        <a href="#" class="focusable-element">Przycisk w Sekcji 1</a>
    </section>
    <div style="height: 800px;"></div>
    <section id="sekcja2">
        <h2>Druga Sekcja</h2>
        <p>To jest treść drugiej sekcji.</p>
        <button class="focusable-element">Przycisk w Sekcji 2</button>
    </section>
</main>

CSS

.sticky-header {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    background-color: #333;
    color: white;
    padding: 15px;
    box-shadow: 0 2px 5px rgba(0,0,0,0.2);
    z-index: 1000;
    height: 60px; /* Wysokość nagłówka */
}

.sticky-header a {
    color: white;
    margin-right: 20px;
}

section {
    padding-top: 20px;
    margin-top: 60px; /* Dodatkowy margines, żeby treść nie zaczynała się pod nagłówkiem */
    min-height: 300px;
    border: 1px solid #ccc;
    margin-bottom: 20px;
}

.focusable-element:focus {
    outline: 3px solid red; /* Wyraźny wskaźnik fokusu */
}

W tym scenariuszu, jeśli użytkownik kliknie link <a href="#sekcja1">Sekcja 1</a> w nagłówku, przeglądarka przewinie stronę do elementu #sekcja1. Ze względu na stały nagłówek, nagłówek <h2>Pierwsza Sekcja</h2> i ewentualnie początek tekstu <p>, wraz ze wskaźnikiem fokusu, mogą być całkowicie zasłonięte przez .sticky-header.

Przykład poprawny: Sticky header z użyciem scroll-margin

Aby rozwiązać problem z zasłanianiem fokusu przez stały nagłówek, można użyć właściwości CSS scroll-margin-top. Ta właściwość określa odległość, jaką przeglądarka powinna zachować od górnej krawędzi elementu docelowego podczas przewijania do niego.

HTML

<header class="sticky-header">
    <nav>
        <a href="#sekcja1">Sekcja 1</a>
        <a href="#sekcja2">Sekcja 2</a>
    </nav>
</header>

<main>
    <div style="height: 800px;"></div> <!-- Dużo treści, aby umożliwić przewijanie -->
    <section id="sekcja1">
        <h2>Pierwsza Sekcja</h2>
        <p>To jest treść pierwszej sekcji.</p>
        <a href="#" class="focusable-element">Przycisk w Sekcji 1</a>
    </section>
    <div style="height: 800px;"></div>
    <section id="sekcja2">
        <h2>Druga Sekcja</h2>
        <p>To jest treść drugiej sekcji.</p>
        <button class="focusable-element">Przycisk w Sekcji 2</button>
    </section>
</main>

CSS

.sticky-header {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    background-color: #333;
    color: white;
    padding: 15px;
    box-shadow: 0 2px 5px rgba(0,0,0,0.2);
    z-index: 1000;
    height: 60px; /* Wysokość nagłówka */
}

.sticky-header a {
    color: white;
    margin-right: 20px;
}

section {
    padding-top: 20px;
    margin-top: 60px;
    min-height: 300px;
    border: 1px solid #ccc;
    margin-bottom: 20px;
    /* Kluczowa zmiana: scroll-margin-top dla sekcji, które mogą być celem linków kotwicowych */
    scroll-margin-top: 70px; /* Wysokość nagłówka + kilka pikseli marginesu */
}

.focusable-element:focus {
    outline: 3px solid green; /* Wyraźny, zielony wskaźnik fokusu */
}

Dzięki dodaniu scroll-margin-top: 70px; do elementów <section>, przeglądarka podczas przewijania do tych sekcji pozostawi 70 pikseli wolnego miejsca u góry, co zapobiegnie ich zasłonięciu przez 60-pikselowy nagłówek. W ten sposób fokus na elemencie docelowym będzie zawsze w pełni widoczny.

Przykład niepoprawny: Modal / Pop-up zasłaniający fokus

W tym przykładzie, po otwarciu modala, fokus pozostaje na elemencie znajdującym się za modalem, który jest częściowo lub całkowicie zasłonięty.

HTML

<button id="openModalBtn">Otwórz Modal</button>
<a href="#" class="focusable-behind-modal">Link za modalem</a>

<div id="myModal" class="modal">
    <div class="modal-content">
        <span class="close-button">&times;</span>
        <p>To jest zawartość modala.</p>
        <button>Przycisk w modalach</button>
    </div>
</div>

CSS

.modal {
    display: none; /* Ukryty domyślnie */
    position: fixed;
    z-index: 1001; /* Wyżej niż inne elementy */
    left: 0;
    top: 0;
    width: 100%;
    height: 100%;
    overflow: auto;
    background-color: rgba(0,0,0,0.4); /* Półprzezroczyste tło */
}

.modal-content {
    background-color: #fefefe;
    margin: 15% auto;
    padding: 20px;
    border: 1px solid #888;
    width: 80%;
    max-width: 500px;
    position: relative;
}

.close-button {
    color: #aaa;
    float: right;
    font-size: 28px;
    font-weight: bold;
    cursor: pointer;
}

.focusable-behind-modal:focus {
    outline: 3px solid red;
}

JavaScript

document.getElementById('openModalBtn').addEventListener('click', function() {
    document.getElementById('myModal').style.display = 'block';
    // NIE PRZENOSIMY FOKUSU do modala, co jest błędem
});

document.querySelector('.close-button').addEventListener('click', function() {
    document.getElementById('myModal').style.display = 'none';
});

Jeśli użytkownik nawiguje klawiaturą, otwiera modal, ale fokus pozostaje na elemencie znajdującym się za modalem (np. .focusable-behind-modal), który jest zasłonięty przez półprzezroczyste tło modala lub sam modal, wówczas kryterium 2.4.11 jest naruszone. Użytkownik nie wie, gdzie znajduje się fokus.

Przykład poprawny: Modal z zarządzaniem fokusem

Aby zachować zgodność z WCAG 2.4.11, po otwarciu modala należy przenieść fokus do pierwszego interaktywnego elementu wewnątrz modala, a także zastosować „pułapkę fokusu” (focus trap), aby fokus nie mógł opuścić modala, dopóki ten jest otwarty. Po zamknięciu modala fokus powinien wrócić do elementu, który go wywołał.

HTML

<button id="openModalBtn">Otwórz Modal</button>
<a href="#" class="focusable-behind-modal">Link za modalem (nie powinien być fokusowalny, gdy modal jest aktywny)</a>

<div id="myModal" class="modal" role="dialog" aria-modal="true" aria-labelledby="modalTitle" tabindex="-1">
    <div class="modal-content">
        <h2 id="modalTitle">Tytuł Modala</h2>
        <p>To jest zawartość modala. Możesz z nim teraz interakcji.</p>
        <input type="text" placeholder="Wprowadź tekst">
        <button id="submitModalBtn">Wyślij</button>
        <button id="closeModalBtn">Anuluj</button>
    </div>
</div>

CSS

/* Takie same style modala jak wcześniej */
.modal {
    display: none;
    position: fixed;
    z-index: 1001;
    left: 0;
    top: 0;
    width: 100%;
    height: 100%;
    overflow: auto;
    background-color: rgba(0,0,0,0.4);
    /* Ważne: ukryj elementy pod spodem dla czytników ekranu, gdy modal jest otwarty */
}

.modal-content {
    background-color: #fefefe;
    margin: 15% auto;
    padding: 20px;
    border: 1px solid #888;
    width: 80%;
    max-width: 500px;
    position: relative;
    outline: none; /* Usunięcie domyślnego focus outline z modala, fokus przeniesiony wewnętrznie */
}

.modal-content button, .modal-content input {
    margin: 10px;
}

.modal-content button:focus, .modal-content input:focus {
    outline: 3px solid blue;
}

JavaScript

const openModalBtn = document.getElementById('openModalBtn');
const myModal = document.getElementById('myModal');
const closeModalBtn = document.getElementById('closeModalBtn');
const focusableElements = 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])';
let previouslyFocusedElement;

function openModal() {
    previouslyFocusedElement = document.activeElement; // Zapamiętaj element, który otworzył modal
    myModal.style.display = 'block';
    // Przenieś fokus na pierwszy interaktywny element w modalach lub na sam modal
    const focusableContent = myModal.querySelectorAll(focusableElements);
    if (focusableContent.length > 0) {
        focusableContent[0].focus();
    } else {
        myModal.focus(); // Ustaw focus na modal (dla tabindex="-1" na modal)
    }
    document.body.classList.add('modal-open'); // Zapobiega przewijaniu tła
    // Opcjonalnie: ukryj treść tła dla czytników ekranu
    document.querySelector('main').setAttribute('aria-hidden', 'true');
}

function closeModal() {
    myModal.style.display = 'none';
    document.body.classList.remove('modal-open');
    if (previouslyFocusedElement) {
        previouslyFocusedElement.focus(); // Przywróć fokus
    }
    document.querySelector('main').removeAttribute('aria-hidden');
}

// Funkcja do "pułapki fokusu" (focus trap)
function trapFocus(e) {
    if (myModal.style.display === 'block') { // Tylko gdy modal jest otwarty
        const focusable = myModal.querySelectorAll(focusableElements);
        const firstFocusable = focusable[0];
        const lastFocusable = focusable[focusable.length - 1];

        if (e.key === 'Tab') {
            if (e.shiftKey) { // Shift + Tab
                if (document.activeElement === firstFocusable || document.activeElement === myModal) {
                    lastFocusable.focus();
                    e.preventDefault();
                }
            } else { // Tab
                if (document.activeElement === lastFocusable) {
                    firstFocusable.focus();
                    e.preventDefault();
                }
            }
        }
    }
}

openModalBtn.addEventListener('click', openModal);
closeModalBtn.addEventListener('click', closeModal);
myModal.addEventListener('keydown', function(e) {
    if (e.key === 'Escape') {
        closeModal();
    }
    trapFocus(e);
});

// Dodatkowo, aby zapobiec przewijaniu tła, gdy modal jest otwarty
// (można to zrobić poprzez CSS na body.modal-open { overflow: hidden; })
// lub bardziej zaawansowane rozwiązania.

W tym poprawionym przykładzie, po otwarciu modala, fokus jest natychmiast przenoszony do pierwszego interaktywnego elementu w jego wnętrzu. Implementacja „pułapki fokusu” zapobiega wydostawaniu się fokusu poza modal, a po jego zamknięciu fokus jest przywracany do elementu, który go wywołał. Dodatkowo, użycie atrybutów role="dialog" i aria-modal="true" oraz aria-labelledby poprawia dostępność dla czytników ekranu.

Najlepsze praktyki i typowe pułapki

Najlepsze praktyki:

  • Zawsze zapewnij widoczny wskaźnik fokusu: Upewnij się, że domyślny wskaźnik fokusu przeglądarki jest widoczny i wyraźny, lub zastąp go niestandardowym stylem, który jest równie lub bardziej widoczny.
  • Testuj nawigację klawiaturą od początku do końca: Przejdź przez całą stronę, używając tylko klawiszy Tab, Shift+Tab i Enter. Upewnij się, że każdy interaktywny element może otrzymać fokus i że fokus jest zawsze widoczny.
  • Używaj scroll-margin dla elementów docelowych: W przypadku stałych nagłówków, stopek lub pasków bocznych, które mogą zasłaniać treść, zastosuj scroll-margin na elementach, do których prowadzą linki kotwicowe, aby zapewnić prawidłowe przewijanie i widoczność fokusu.
  • Poprawne zarządzanie fokusem w JS: Kiedy treści są dynamicznie ukrywane lub wyświetlane (np. menu, modale, akordeony), upewnij się, że fokus jest przenoszony do odpowiedniego, widocznego elementu.
  • Projektuj responsywnie z myślą o fokusie: Upewnij się, że na różnych rozmiarach ekranu i orientacjach (mobilne, tablet, desktop) widoczność fokusu jest zachowana i nie jest on zasłaniany przez elementy, które mogą zmieniać swoje położenie.

Typowe pułapki:

  • Usuwanie domyślnego wskaźnika fokusu: Użycie outline: none; w CSS bez zapewnienia alternatywnego, widocznego wskaźnika fokusu jest jednym z najczęstszych błędów i naruszeń WCAG.
  • Niewłaściwe zarządzanie fokusem w JS: Otwieranie modala lub menu bez przeniesienia fokusu do niego, lub nieprzywracanie fokusu do poprzedniego miejsca po jego zamknięciu.
  • Stałe nagłówki/stopki bez scroll-margin: Klasyczny problem, w którym treść pod nagłówkiem/stopką zostaje zasłonięta po przewinięciu do kotwicy.
  • Zbyt mały lub niewidoczny wskaźnik fokusu: Nawet jeśli fokus jest technicznie obecny, ale jest zbyt słabo widoczny (np. niski kontrast, cienka linia, która zlewa się z tłem), to i tak jest to problem użyteczności i może prowadzić do niezgodności z innymi kryteriami WCAG (np. 2.4.7 i 1.4.11).
  • Brak testów klawiaturą: Poleganie wyłącznie na testach myszką i wizualnych przeglądach.

Podsumowanie

Kryterium WCAG 2.4.11 „Fokus niezasłonięty (Minimum)” jest niezbędne dla zapewnienia dostępności i użyteczności stron internetowych dla wszystkich użytkowników, a w szczególności dla tych, którzy polegają na nawigacji klawiaturą. Zapewnienie, że wskaźnik fokusu jest zawsze w pełni widoczny, pozwala użytkownikom na świadome i efektywne poruszanie się po treściach i interakcję z nimi. Implementacja wskazówek, takich jak użycie scroll-margin i prawidłowe zarządzanie fokusem w JavaScript, jest kluczowa dla spełnienia tego wymogu i stworzenia bardziej inkluzywnego doświadczenia użytkownika.

Przegląd prywatności

Ta strona korzysta z ciasteczek, aby zapewnić Ci najlepszą możliwą obsługę. Informacje o ciasteczkach są przechowywane w przeglądarce i wykonują funkcje takie jak rozpoznawanie Cię po powrocie na naszą stronę internetową i pomaganie naszemu zespołowi w zrozumieniu, które sekcje witryny są dla Ciebie najbardziej interesujące i przydatne.