Felugró ablakok akadálymentesítése

| oaron | Webes akadálymentesség

A felugró ablakok – más néven modális párbeszédablakok vagy dialógusok – gyakori elemei a modern webalkalmazásoknak. Gondoljunk például egy bejelentkező ablakra vagy törlés megerősítésére szolgáló modális dialógusra. Ezek az ablakok a fő tartalom fölött jelennek meg, és amíg be nem zárjuk őket, a háttérben lévő oldal nem elérhető. Az ilyen megoldások akadálymentesítése kritikus fontosságú, mert ha rosszul valósítják meg őket, a modális ablakok használata rendkívül frusztráló lehet a képernyőolvasót használók vagy billentyűzettel navigáló felhasználók számára.

Ebben a cikkben áttekintjük, mire kell odafigyelni egy felugró ablak fejlesztésekor, hogy az mindenki számára használható legyen. Szó lesz a fókusz kezeléséről, a háttértartalom „inaktiválásáról”, a megfelelő ARIA attribútumok beállításáról és a teljes körű billentyűzetes vezérlés megvalósításáról. Végül kitérünk a gyakori hibákra is, amelyeket érdemes elkerülni.

Fókusz átadása a felugró ablaknak

Amikor egy modális ablak megnyílik, a fókuszt azonnal át kell helyezni ebbe az ablakba. Ez tipikusan azt jelenti, hogy a fókusz egyből a modálon belüli első interaktív elemre (pl. első űrlapmező vagy gomb) kerül. Ennek hiányában gyakori hiba, hogy a fókusz a háttértartalmon marad, és a felhasználó tovább tud lépkedni a mögöttes weboldal elemein, miközben a modál nyitva van.

Ilyenkor a képernyőolvasót használó felhasználó esetleg észre sem veszi az új ablakot, a billentyűzetes navigáció pedig „kiszökik” a modálból. A megoldás: amikor a modált megnyitjuk, programozottan vigyük át a fókuszt a modál első releváns elemére, és jegyezzük meg azt az elemet, amelyről a fókusz érkezett.

Például JavaScriptben elmenthetjük a document.activeElement értékét (ez lesz a modált megnyitó gomb), majd a modál tartalmának első fókuszálható elemére hívunk .focus()-t. Amikor a modált bezárjuk, állítsuk vissza a fókuszt erre az eredeti elemre, hogy a felhasználó ott folytathassa, ahol abbahagyta.

Az alábbi kódrészlet bemutatja ezt a folyamatot:

// Megnyitáskor:
const inditoElem = document.activeElement;
modal.style.display = 'block';       // modál megjelenítése (vagy dialog.showModal())
modal.querySelector('input, button, a').focus();  // fókusz az első interaktív elemre

// Bezáráskor:
modal.style.display = 'none';        // modál elrejtése (vagy dialog.close())
inditoElem.focus();                  // fókusz visszaadása az indító gombra

A fenti kód feltételezi, hogy a modal változó hivatkozik a modál ablakelemére, és hogy van benne fókuszálható elem. A lényeg, hogy a fókusz útját pontosan kontrolláljuk: nyitáskor a modálra, záráskor a kiinduló pontra tegyük.

Ezzel biztosítjuk, hogy a képernyőolvasók azonnal felolvassák a párbeszédablak tartalmát, és a csak billentyűzetet használó user sem tud véletlenül a háttérben maradt elemek között navigálni.

Fókusz elhelyezése a megfelelő elemre

Nem mindegy, hova kerül a fókusz a modálon belül. Gyakori megoldás, hogy a dialógus első interaktív vezérlőjére (pl. beviteli mezőre vagy gombra) fókuszálunk – ez sok esetben megfelelő. Viszont előfordulhat, hogy a modális ablak elején fontos magyarázó szöveg vagy címsor található, amit a felhasználónak érdemes először hallania.

Ha ilyenkor a fókusz rögtön egy űrlapmezőre kerül, a képernyőolvasó esetleg nem olvassa fel az instrukciókat. Megoldás erre a helyzetre: tegyük fókuszálhatóvá a bevezető tartalmat (például a címsort vagy az első bekezdést) egy tabindex="-1" attribútummal, és nyitáskor erre tegyük a fókuszt. Ebben az esetben ez tab billentyűvel nem lesz elérhető, viszont kaphat programozottan fókuszt.

Fontos, hogy ne magát a teljes modális ablakot tegyük fókuszálhatóvá (pl. ne adjunk tabindex-et a külső <div role="dialog"> elemnek). Helyette mindig egy belső, értelmes kezdő elemre (címsor, bekezdés vagy űrlapmező) állítsuk a fókuszt.

A háttértartalom blokkolása és a képernyőolvasók kezelése

Amíg a modális ablak nyitva van, a háttérben lévő weboldal tartalmát „inertté” kell tenni, hogy ne legyen elérhető a felhasználó számára. Ez két szempontból fontos:

  • Interakció tiltása: az egérrel ne lehessen a háttér tartalmára kattintani, és billentyűzettel se lehessen oda fókuszt navigálni. Ezt általában egy fél-átlátszó fedőréteg (backdrop) megjelenítésével, valamint a háttérelemek pointer-events: none és scroll letiltásával érjük el.
  • Képernyőolvasó navigáció tiltása: a kisegítő technológiák ne „lássák” a háttértartalmat, amíg a modál nyitva van. Ha ez elmarad, a képernyőolvasó felhasználó esetleg ki tud navigálni a modál tartalmából és tovább olvasni a háttérben, ami nagyon zavaró.

Modern megközelítésként használhatjuk az aria-modal="true" attribútumot a modális ablak konténerén. Ez jelzi a képernyőolvasóknak, hogy a dialógus modális, de ennyi sajnos nem elég. Nekünk kell JavaScripttel gondoskodni arról, hogy tényleg ne lehessen a háttérbe fókuszt küldeni vagy görgetni.

Alternatív vagy kiegészítő megoldásként használhatjuk a nemrég bevezetett inert HTML attribútumot is. Az inert egy globális attribútum, amelynek hatására az adott elem (és annak minden gyermeke) fókuszálhatatlanná és interakcióra képtelenné válik, és a képernyőolvasók számára is kihagyásra kerül.

Célszerű a modál megnyitásakor a fő tartalmi konténerre (pl. <main>) beállítani az inert attribútumot, és ugyanígy aria-hidden="true" értéket adni neki a kompatibilitás kedvéért. Például:

// Háttér tartalom letiltása
document.body.style.overflow = 'hidden';              // görgetés letiltása
document.querySelector('main').setAttribute('aria-hidden', 'true');
document.querySelector('main').setAttribute('inert', '');

// ... (modál megjelenítése, stb.)

// Háttér tartalom visszaengedése (modál bezárásakor)
document.body.style.overflow = '';
document.querySelector('main').removeAttribute('aria-hidden');
document.querySelector('main').removeAttribute('inert');

A fenti kód biztosítja, hogy amíg a modál nyitva van, a háttérben lévő tartalom sehová se „szökjön ki”: nem görgethető, nem fókuszálható, a képernyőolvasó pedig kihagyja azt a virtuális kurzor navigálásakor.

Gyakori hiba: A modál megnyitásakor nem tiltják le a háttértartalmat. Ilyenkor előfordulhat, hogy a háttér továbbra is görgethető vagy interaktív marad, a fókusz pedig kiszökhet oda.

Ennek az ellenkezője is előfordul: a fejlesztő a HTML-ben eleve aria-hidden="true" attribútummal rejt el minden modált (hogy csak akkor olvassa a képernyőolvasó, ha meg van nyitva), de elfelejti megnyitáskor ezt eltávolítani. Ilyenkor a modális ablak látszólag megjelenik, de a képernyőolvasó továbbra is rejtettnek tekinti, így egyáltalán nem fogja felolvasni. Ügyeljünk tehát, hogy a modál megnyitásakor mindig tegyük a megfelelő állapotúvá az ARIA attribútumokat.

Megfelelő ARIA szerepkörök és attribútumok

A modális ablak HTML struktúráját úgy kell kialakítani, hogy a kisegítő technológiák egyértelműen felismerjék és felolvassák azt. Ebben segítenek az ARIA (Accessible Rich Internet Applications) szerepkörök és attribútumok. Néhány fontos szabály:

  • Használjunk ARIA szerepkört a konténeren: tipikusan role="dialog" a helyes választás egy általános párbeszédablakhoz, vagy role="alertdialog" ha sürgős megerősítést igénylő, kritikus figyelmeztetésről van szó. E szerepkörök jelzik a képernyőolvasónak, hogy egy különálló párbeszédablak következik, ami a lap többi részétől elkülönül.
  • Tegyük a dialógust modálissá: az előző fejezetben tárgyalt aria-modal="true" attribútumot mindig állítsuk be a modális dialógusra (legyen az <div role="dialog"> vagy akár a natív <dialog> elem). E nélkül a segédeszköz nem feltétlen tudja, hogy a párbeszédablak idejére a többi tartalom nem releváns.
  • Adjunk a modálnak nevet és leírást: minden modális ablaknak legyen címkéje, amit a képernyőolvasó felolvas. Ezt elérhetjük, ha a modál tartalmában van egy címsor (pl. <h2>Modal címe</h2>), és a konténer aria-labelledby="..." attribútuma erre a címsorra mutat. Ha nincs vizuális címsor, használhatunk aria-label="Valamilyen rövid cím" attribútumot is. Emellett, ha a dialógusban van a felhasználó számára fontos magyarázó szöveg, érdemes azt egy elembe foglalva aria-describedby="..." attribútummal a dialógushoz kötni. Így a képernyőolvasó a címet és a leírást is be fogja olvasni, amikor a modál megnyílik.

Példa ARIA-val ellátott dialógusra:

<div class="modal-overlay" role="presentation"></div>
<!-- A modál fő tartalma -->
<div class="modal" role="dialog" aria-modal="true" aria-labelledby="modal-cim" aria-describedby="modal-leiras">
  <h2 id="modal-cim" tabindex="-1">Bejelentkezés</h2>
  <p id="modal-leiras">Kérjük adja meg a bejelentkezési adatait.</p>
  <!-- ... egyéb tartalom, űrlapmezők ... -->
  <button>Bezárás</button>
</div>

A fenti kódban a <div role="dialog"> elem jelenti a felugró ablakot, az aria-modal="true" jelzi, hogy modális. A aria-labelledby a cím elemhez kapcsolja a dialógust, míg az aria-describedby a leíráshoz.

Természetesen mindez önmagában nem elég – szükséges a JavaScriptes viselkedés (fókuszkezelés, háttér letiltása) is, amit korábban tárgyaltunk. Viszont ezek az attribútumok gondoskodnak róla, hogy egy képernyőolvasó pontosan közölje a felhasználóval, hogy egy párbeszédablak nyílt meg, mi a címe, és mi a tartalma.

Gyakori hibák ARIA-val: Fejlesztői figyelmetlenség miatt sokszor kimaradnak ezek az attribútumok. Például előfordul, hogy nincs role="dialog" megadva, így a képernyőolvasó nem különbözteti meg a modált a többi tartalomtól. Hasonlóan gyakori hiba a hiányzó aria-modal, vagy hogy nincs címkéje a modálnak (se aria-label, se aria-labelledby). Ezek mind azt eredményezhetik, hogy a felugró ablak akadálymentes szempontból „láthatatlan” vagy érthetetlen marad a rászoruló felhasználóknak.

Teljes körű billentyűzetes kezelés

Egy akadálymentes felugró ablaknak egér nélkül, kizárólag billentyűzettel is használhatónak kell lennie. Az alábbiakra kell odafigyelni:

Tab-bal való navigáció a modálon belül

A felhasználó a Tab billentyűvel tud lépkedni a fókuszálható elemek között (linkek, gombok, űrlapelemek). A modális ablakban ezt egy fókuszcsapdával kell megoldanunk: ha a felhasználó az utolsó elemen nyom Tab-ot, a fókuszt vissza kell ugrasztani az első elemre. Ugyanígy, ha az első elemen van és Shift+Tab-ot nyom, akkor az utolsóról folytatódjon a fókusz ciklus. Így a fókusz körbe-körbe jár a modál elemei között, és nem tud kiszökni onnan a háttérbe.

Az alábbi kódrészlet mutat egy egyszerű fókuszcsapda megvalósítást:

function trapFocus(modalElement) {
  const focusable = modalElement.querySelectorAll('a[href], button:not([disabled]), input, select, textarea, [tabindex]:not([tabindex="-1"])');
  const firstEl = focusable[0];
  const lastEl = focusable[focusable.length - 1];

  modalElement.addEventListener('keydown', (e) => {
    if (e.key === 'Tab') {
      if (e.shiftKey && document.activeElement === firstEl) {
        e.preventDefault();
        lastEl.focus();
      } else if (!e.shiftKey && document.activeElement === lastEl) {
        e.preventDefault();
        firstEl.focus();
      }
    }
  });

  firstEl.focus(); // Fókusz első elemre a csapda aktiválásakor
}

A fenti kódban lekérdezzük a modálon belüli összes fókuszálható elemet, majd a keydown eseményt figyelve lekezeljük a Tab és Shift+Tab eseteket. Ha a fókusz elhagyná a modált, visszaugrasztjuk a lista elejére vagy végére. Fontos a firstEl.focus() hívás is a végén: ezzel aktiváljuk a fókuszcsapdát, és biztosítjuk, hogy a modál megnyitásakor rögtön a megfelelő elemre kerüljön a fókusz.

Kilépés biztosítása (Escape)

Minden modális ablakot gyorsan és egyszerűen be lehessen zárni billentyűzetről is. Ennek szabványos módja az Esc (Escape) billentyű támogatása. Ha a felhasználó megnyomja az Esc-et, azonnal zárjuk be a modált (mintha a „Bezárás” gombra kattintott volna). Ezt a fenti fókuszcsapda kódba így lehet beilleszteni:

modalElement.addEventListener('keydown', (e) => {
  if (e.key === 'Escape') {
    closeModal(); // definiált bezáró függvény
  }
  // ... Tab billentyű kezelése ...
});

Győződjünk meg róla, hogy a closeModal() függvény visszaadja a fókuszt a megfelelő elemnek (lásd korábbi fókuszkezelés részt).

Látható és elérhető bezáró gomb

Az Escape mellett legyen vizuálisan is egy bezárás gomb a modál sarkában (általában egy „X” ikon vagy „Bezárás” feliratú gomb). Ezt a gombot megfelelően kell kialakítani, hogy fókuszálható legyen (<button> elem alkalmazása javasolt), és legyen rajta érthető címke. Ha csak ikon van rajta, adjunk hozzá aria-label="Bezárás" attribútumot, hogy a képernyőolvasó is jelezze a funkcióját. Például:

<button class="modal-close" aria-label="Bezárás">&#x2715;</button>

Természetesen ez a gomb is a closeModal() függvényt hívja meg kattintáskor, ugyanúgy, mint az Esc leütése.

Egyéb bezárási lehetőségek

Sok felhasználó számít rá, hogy a modált a háttérre kattintva is be lehet zárni (pl. rákattint a sötét háttérrétegre). Ezt is megvalósíthatjuk, de figyeljünk rá, hogy csak akkor zárjuk a modált, ha valóban a háttérre kattintottak, és nem a modál tartalmára. Ezt általában úgy oldjuk meg, hogy figyeljük a click eseményt a teljes modál overlay-en, és ha az esemény célja (event.target) maga az overlay (vagy nincs benne a modált tartalmazó elemben), akkor zárjuk az ablakot.

Összefoglalva: a modális ablakban teljes értékűen kell működnie a billentyűzetnek. A Tab és Shift+Tab körbejár, az Esc zár, a fókusz sehol nem vész el, és a felhasználó semmi olyan műveletre nincs kényszerítve, amihez egér kellene.

Natív dialog elem használata

A HTML5 tartalmaz egy <dialog> elemet, amelyet kifejezetten dialógusokhoz terveztek. Sok fejlesztő nem használja, pedig megfelelő támogatással bír és számos beépített funkcióval segít (pl. dialog.showModal() automatikusan kezeli a fókuszt bizonyos mértékig).

A <dialog> implicit módon role="dialog" szerepkörű, és a showModal() hívás során a böngésző tipikusan kezeli a modális viselkedés alapjait (pl. fókusz körülzárása). Persze a teljes akadálymentességhez itt is szükség lehet kiegészítő kódra (pl. egyedi stílus, egyedi fókuszkezelés vagy Esc gomb lekezelése), de ez jó alapot ad.

Bővebb információ a Dialog használatáról: MDN Web Docs – Dialog

Gyakori hibák összefoglalása és ellenőrzőlista

Végül tekintsük át pontokba szedve a leggyakoribb hibákat, amelyek modális párbeszédablakok akadálymentesítésénél előfordulnak – és amelyeket fentebb már részletesen kifejtettünk. Fejlesztőként ezeket érdemes elkerülni:

  • Fókusz kezelés elmulasztása: a modál megnyitásakor a fókusz a háttéren marad, így a képernyőolvasó nem az új ablakot olvassa fel, a billentyűzetes navigáció pedig „kiszökik” a modálon kívülre. Ugyanilyen probléma, ha bezárás után nem adjuk vissza a fókuszt a korábbi aktiváló elemnek – a felhasználó ilyenkor elveszti a kontextust.
  • Hiányzó vagy hibás ARIA attribútumok: nincs role="dialog"/alertdialog szerepkör, elmarad az aria-modal="true", vagy nincs címke (aria-label/aria-labelledby) a modálon. Ilyenkor a kisegítő technológiák nem tudják megfelelően felismerni és azonosítani a felugró ablakot.
  • Rosszul beállított aria-hidden: tipikus hiba, hogy a háttértartalmat nem rejtik el a képernyőolvasó elől (ilyenkor a modal kinyílik, de a felhasználó a rejtett tartalmat is eléri). A másik véglet, hogy a modált magát is rejtve hagyják (pl. eleve aria-hidden="true" marad rajta), így az teljesen láthatatlan marad a kisegítő technológiák számára.
  • Fókuszcsapda hiánya: a felhasználó Tab-bal ki tud lépni a modál ablakból a weblap más részeire.
  • Escape billentyű figyelmen kívül hagyása: a felhasználó nem tudja gyorsan bezárni a modált billentyűzetről, mert az Esc gomb nem működik.
  • Háttértartalom nincs blokkolva: a modál megnyílik, de a mögötte lévő oldal továbbra is görgethető vagy interaktív, esetleg a képernyőolvasóval is elérhető.
  • Nem megfelelő vizuális fókuszjelzés: bár nem kifejezetten csak akadálymentességi hiba, de gyakori probléma, hogy a modálon belül a fókuszált elemnek nincs jól látható körvonala vagy kiemelése.

Záró gondolatok

Egy akadálymentes modális ablak kialakítása odafigyelést és részletekbe menő tervezést igényel. Összegezve a legfontosabbakat: használjunk szemantikus HTML-t és ARIA-t a struktúrához, ügyeljünk a fókusz helyes kezelésére (megnyitáskor a modálra, bezáráskor vissza), implementáljunk teljes billentyűzet-navigációt (Tab körbe, Esc zár, fókuszcsapda), és tegyük a háttértartalmat elérhetetlenné a művelet idejére.

Mindezt alaposan teszteljük különböző képernyőolvasókkal (NVDA, JAWS, VoiceOver stb.), különböző böngészőkben, valamint billentyűzettel navigálva is.

Egy teljes működő példa az alábbi oldalon kipróbálható: Modal dialog példa – W3C

Megosztás

Iratkozz fel hírlevelünkre!

Amennyiben szeretnél első kézből értesülni az új bejegyzésekről, iratkozz fel hírlevelünkre!

Vélemény, hozzászólás?

Az e-mail címet nem tesszük közzé. A kötelező mezőket * karakterrel jelöltük

Ez a weboldal sütiket használ a böngészési élmény javítása és a webhely megfelelő működésének biztosítása érdekében. A webhely használatának folytatásával elismeri és elfogadja a sütik használatát.

Összes elfogadása Csak a szükségesek elfogadása