Blog/Webfejlesztés
Akadálymentes Vue-komponensek: a WCAG 2.2 minták, amelyeket a tesztek nem látnak
WCAG 2.2 minták komponens szinten Vue-hoz és Nuxthoz: fókusz modálisokban és Teleportnál, élő régiók, kombinált mezők, az axe-core hiányai és egy tesztlétra.
Balázs Csorba··12 perc olvasás
- Vue accessibility
- WCAG 2.2
- Focus management
- Live regions
- Component testing

A lényeg röviden
- A WCAG 2.2 által újként jelölt kilenc kritérium közül öt közvetlenül a komponenskódot érinti: a fókusz nincs eltakarva, a húzómozdulatok, a célterület mérete, a redundáns adatbevitel és az akadálymentes hitelesítés.
- Az axe-core átlagosan a WCAG-problémák 57%-át találja meg automatikusan, a WCAG 2.2-es szabálya pedig alapból ki van kapcsolva, ezért a zöld scan nem bizonyíték a billentyűzetes vagy a képernyőolvasós minőségre.
- A fókuszkezelés a komponensbe tartozik: a natív dialógus inert háttérrel az olcsóbb alap, az egyoldalas útvonalváltásokhoz pedig kifejezett fókuszáthelyezés kell.
- Az élő régióknak létezniük kell, mielőtt változnak, és csak egyszerű szöveget tartalmazhatnak; a streamelt MI-kimenetet mondatnyi részekben érdemes bejelenteni, nem tokenenként.
- Lépcsőzetesen teszteljen: lintből és komponens-scanekből ARIA-pillanatképekig és kézi képernyőolvasós ellenőrzésig, a kész-definíciót pedig írja le, hogy a review érvényesíthesse.
A Vue-alkalmazások akadálymentességi hibáinak többsége komponenshiba. Egy dialógus, amely nem adja vissza a fókuszt, egy üzenet, amelyet a képernyőolvasó sosem olvas fel, egy kombinált mező, amely nem reagál a nyílbillentyűkre, egy 16 pixel széles ikongomb: mindegyiket egyszer írják meg egy közös komponensben, aztán minden oldalon kiszállítják, ahol használják. Ezért a komponens a megfelelő egység az akadálymentesség munkájához, és ide érdemes leírni a szabályokat.
Ez a cikk a WCAG 2.2 mérnöki felét adja Vue-hoz és Nuxthoz. Megmutatja, mely sikerkritériumok érintik a komponenskódot, elmagyarázza, mit lát és mit nem lát az axe-core, végigvezet a fókuszkezelésen, az aszinkron élő régiókon és az egyedi kombinált mezőkön, végül egy tesztlétrát és egy kész-definíciós ellenőrzőlistát ad, amelyet beilleszthet egy pull request sablonba. Azt nem dönti el, hogy egy oldal megfelel-e; a jogi keretet lásd az európai akadálymentességi törvényről B2B webshopoknak szóló cikkben.
Mely WCAG 2.2 kritériumok érintik a komponenskódot?
A kilenc kritérium közül, amelyet a WCAG 2.2 újként jelöl, öt közvetlenül komponenskódot érint: 2.4.11 Fókusz nincs eltakarva, 2.5.7 Húzómozdulatok, 2.5.8 Célterület mérete (minimum), 3.3.7 Redundáns adatbevitel és 3.3.8 Akadálymentes hitelesítés (minimum). A régebbi kritériumok, amelyek a legtöbbször buknak, továbbra is a 2.4.7 Fókusz látható, valamint az 1.4.3 és 1.4.11 kontraszt-kritériumok. Ezek a 2.2-ben nem változtak, de minden egyedi felületben ott vannak.
Mindegyik kritérium egy konkrét Vue-elemre vonatkozik. A fókusz nincs eltakarva jellemzően azt jelenti, hogy egy rögzített fejléc, egy sütijelző vagy egy lebegő csevegőablak eltakarja a fókuszált sort vagy gombot. A célterület mérete a csak ikonos bezáró gombokra és eszköztárakra vonatkozik, ha a kattintható felület kisebb 24 × 24 CSS-pixelnél. A húzómozdulatok a rendezhető listákat és csúszkákat érintik, amelyekhez egy mutatóval használható alternatíva, például gombok kellenek. A redundáns adatbevitel a többlépcsős űrlapokat érinti, amelyek újra kérdeznek egy címet, amelyet a felhasználó két lépéssel korábban már megadott. Az akadálymentes hitelesítés a bejelentkező képernyőket és az egyszer használatos kódos mezőket érinti, amelyek tiltják a beillesztést vagy a jelszókezelőket.
A táblázat a munkatérkép: soronként egy kritérium, az a Vue-elem, ahol megjelenik, hogy az axe-core-nak van-e rá szabálya, és a legolcsóbb javítás.
| Kritérium | Vue-elem | axe-szabály most | Legolcsóbb javítás |
|---|---|---|---|
| 2.4.7 Fókusz látható (AA) | egyedi vezérlők fókuszstílusai | nincs | a natív keretet meg kell tartani; cserélni, nem egyszerűen elvenni |
| 2.4.11 Fókusz nincs eltakarva (AA) | rögzített fejléc, sütijelző, csevegőablak | nincs | scroll-margin a fókuszcélokon |
| 2.5.7 Húzómozdulatok (AA) | rendezhető listák, csúszkák | nincs | elemeket mozgató gombok |
| 2.5.8 Célterület mérete (AA) | csak ikonos bezáró és eszköztár-gombok | target-size, alapból kikapcsolva | 24 × 24 px-es kattintható felület |
| 3.3.7 Redundáns adatbevitel (A) | többlépcsős pénztár- és címűrlapok | nincs | előző lépésekből előtöltés |
| 3.3.8 Akadálymentes hitelesítés (AA) | bejelentkezési, kódos és jelszómezők | nincs | beillesztés és jelszókezelők engedélyezése |
| 1.4.3 és 1.4.11 kontraszt | design tokenek, keretek, fókuszkeretek | csak szöveg (color-contrast) | minden tokenpárt minden állapotban ellenőrizni |
| 4.1.2 Név, szerep, érték | egyedi kombinált mezők, ikongombok | részben | először a natív elemet használni |
Mit fed le valójában az axe-core?
Az automatikus scannerek a WCAG-problémák hasznos részét megtalálják, de ritkán azokat, amelyek a billentyűzetet vagy a képernyőolvasót használó felhasználót leginkább akadályozzák. Az axe-core szerint átlagosan a WCAG-problémák 57%-át találja meg automatikusan. A WCAG 2.2-es részében egyetlen szabály van, a target-size, és ez alapból ki van kapcsolva.
Az axe-core README egyértelműen megnevezi a határt: „With axe-core, you can find on average 57% of WCAG issues automatically.” A szabályleírások megerősítik a WCAG 2.2 rést: az egyetlen 2.2-es szabály a target-size, és az ki van kapcsolva, amíg a WCAG 2.2 el nem terjed. Egyetlen szabály sem ellenőrzi a fókusz nincs eltakarva, a redundáns adatbevitel vagy az akadálymentes hitelesítés kritériumát.
A népességi adatok szerint a rés mérhető. A WebAIM Million 2026 felmérése egymillió főoldalt vizsgált, és átlagosan 56,1 észlelt hibát talált oldalanként, ami 10,1%-os emelkedés az 51-es 2025-ös értékhez képest. A Vue.js-szel készült főoldalakon átlagosan 64,6, a Nuxt.js-oldalakon 54,7 észlelt hiba volt. A WebAIM kimondja, hogy nem minden megfelelési hiba észlelhető automatikusan, ezért ezeket a számokat az eszközök által látottként olvassa, nem pedig egy oldal ítéleteként.
Hogyan kell viselkednie a fókusznak modálisban, Teleportnál és útvonalváltásnál?
A modálisnak megnyitáskor a fókuszt be kell vinnie, a Tab és a Shift+Tab billentyűt benne kell tartania, Escape-re be kell záródnia, és vissza kell adnia a fókuszt annak az elemnek, amely megnyitotta. Egy kézzel írt fókuszcsapda mind a négy feladatot elláthatja, de a natív dialógus-elem, amelynek a többi oldala inert, kevesebb kóddal elvégzi a munka nagyobb részét.
A kézzel írt csapdák jellemzően három helyen buknak el. Csak a csatoláskor gyűjtik össze a fókuszálható elemeket, ezért a később renderelt mezőt kihagyják. Arra a konténerre szűkítenek, amelyben a komponenst írták, de a Teleport a dialógus DOM-csomópontját a dokumentum egy másik részébe mozgatja, így a csapda nem találja meg. Olyan gombra állítják vissza a fókuszt, amelyet egy újrarenderelés már eltávolított, így a fókusz a body-ra esik vissza.
A olcsóbb alap a natív dialógus. A showModal() hívás a elemet a felső rétegbe helyezi, a dokumentum többi részét inertté teszi, és Escape-re bezárul. A mai motorok a bezáráskor a fókuszt is visszaadják a megnyitó elemnek, de a visszaadás részletei eltérnek a megvalósítások között, ezért írjon hozzá tesztet minden szállított böngészőben. Ahol a natív dialógus nem illik, az inert attribútum ugyanezt a hatást adja a saját overlay mögötti oldalon.
Útvonalváltások a Vue Routerben és a Nuxtban
Az egyoldalas navigáció nem tölti újra a dokumentumot, ezért a böngésző sem helyezi át a fókuszt, és nem jelenti be az új oldalt. A Vue akadálymentességi útmutatója ezt egy átugró hivatkozással kezeli, amely az első fókuszálható elem, és egy route.path-ra figyelő watcherrel, amely minden útvonalváltás után áthelyezi a fókuszt. Az itt használt őrszem-minta egy fókuszálható címsort helyez a fő terület elejére, amelyet az elrendezés navigáció után fókuszál. Egy címsor vagy őrszem fókuszálása jobb, mint a body fókuszálása, mert a body nem alkalmas arra, hogy a képernyőolvasó onnan kezdjen olvasni.
<!-- Layout sketch: move focus to the page heading after each route change -->
<script setup lang="ts">
const route = useRoute()
const sentinel = ref<HTMLElement | null>(null)
watch(() => route.path, async () => {
await nextTick() // let the new page render first
sentinel.value?.focus()
})
</script>
<template>
<main>
<h1 ref="sentinel" tabindex="-1"><slot name="title" /></h1>
<slot />
</main>
</template>A fókuszáthelyezést csak akkor kösse bejelentéshez, ha a fókuszált címsor nem mutatja már meg a felhasználónak, hol jár. Hogy egy megváltozott dokumentumcím önmagában bejelentésre kerül-e, a képernyőolvasótól és a böngészőtől függ, ezért ezt a kombinációt tesztelje, ne feltételezze.
Hogyan viselkednek az aszinkron frissítések és az élő régiók?
Az élő régió csak akkor jelent be változást, ha a régió a változás előtt már létezett. A konténert az oldallal együtt renderelje, és utána frissítse a szövegét. Alapértelmezésben használjon polite szintet, assertive-et csak olyan hibákra, amelyek blokkolják a felhasználót, és soha ne tegyen vezérlőt a régióba, mert az élő régiók egyszerű szövegként kerülnek bejelentésre.
Az MDN útmutatója az élő régiókról ugyanezekre a szabályokra épül: a régiót előbb létre kell hozni, az első tartalmi frissítést egy későbbi feladatra kell halasztani, és az assertive szintet a sürgős üzenetekre kell tartogatni. Figyelmeztet is, hogy az élő régiók általában egyszerű szövegként kerülnek bejelentésre, így a frissített tartalom linkjei, gombjai és egyéb szemantikája elveszhet.
Ezen múlnak a hibaüzenetes toastok. Ha egy komponens a toastot az élő régiójával együtt csatolja, a régió és az üzenet ugyanabban a DOM-frissítésben jelenik meg, és sok képernyőolvasó nem jelenti be az üzenetet. A megoldás egy az oldallal együtt renderelt régió, amely jelen van, de üres, és ebbe írja a komponens a toast szövegét. Ugyanez igaz a beviteli ellenőrzés összesítőire és az útvonalváltás utáni mentési visszaigazolásokra.
A streamelt kimenet a legnehezebb eset. Ha az LLM-válasz minden darabja egy udvarias régióba kerül, a képernyőolvasó szavanként olvassa fel a választ, és folyton megszakítja magát. A látható szöveg frissüljön a streamelés közben, az összefoglalót vagy a kész választ pedig mondatoknál vagy a befejezésnél jelentse be. A streamelt csevegők akadálymentességéről szóló cikk a csevegőspecifikus részleteket tárgyalja.
Érdemes-e egyedi legördülőt vagy kombinált mezőt építeni?
Ha a design engedi, mindig natív select-et használjon. Egyedi kombinált mezőt csak akkor építsen, ha a termék olyasmit igényel, amire egy natív vezérlő nem képes, és akkor vállalja a teljes billentyűzetes szerződést a WAI-ARIA Authoring Practices Guide (APG) alapján.
Az APG egyértelműen megnevezi az árat: „Unlike native HTML form elements, browsers do not provide keyboard support for graphical user interface (GUI) components that are made accessible with ARIA; authors have to provide the keyboard support in their code.” Egy kombinált mezőnél ez azt jelenti: a nyílbillentyűk végigviszik az opciókat, az Enter kiválaszt, az Escape bezár, a gépelés szűri a listát, és a fókusz ott marad, ahol a minta előírja. Az APG a kombinált mezőt, a listboxot, a dialógust, a rácsot, a körhintát és a harmonikát külön mintaként kezeli, külön szerződéssel, és a csapatok gyakran összekeverik őket.
Két megvalósítási döntés számít. Az aria-activedescendant a DOM-fókuszt a szöveges mezőn tartja, és azonosítóval mutat a kiemelt opcióra; ez a szűrhető kombinált mezőknek felel meg. A roving tabindex valódi fókuszt mozgat az elemek között; ez az eszköztáraknak, menüknek és rácsoknak való. Azt nézze, hová gépel a felhasználó: a szöveges mező az activedescendantot indokolja, a vezérlősor a roving tabindexet.
Az egyedi vezérlő állandó karbantartást jelent. Minden keretrendszer-frissítés, minden új opciótípus és minden designváltozás megtörheti a szerződést, a komponenstesztnek pedig a billentyűzetet is le kell fednie, nem csak a kirenderelt jelölést. Ha egy stílusozott burkolóval ellátott natív select teljesíti a követelményt, az szinte mindig olcsóbb komponens.
Mit hagynak továbbra is önre a headless könyvtárak?
A headless komponenskönyvtárak egy minta ARIA-bekötését, billentyűzetkiosztását és fókuszhatókörét adják. Nem adják meg az akadálymentes neveket, a színkontrasztot, a hibaüzeneteket vagy az ikongombjaik célterületének méretét. A Reka UI 2026 októberében az egyik Vue-os lehetőség, és a dokumentációja egyértelműen leírja, mit fed le.
A Reka UI a Radix Vue v2-es továbbfejlesztéseként mutatkozik be, és azt állítja, hogy komponensei a WAI-ARIA tervezési mintákhoz igazodnak, és kezelik az olyan részleteket, mint az aria-attribútumok, a billentyűzetes navigáció és a fókuszkezelés. A szerződés többi része a terméket birtokló csapatnál marad. Az alábbi lista azt mutatja, mi marad az ön feladata bármilyen headless megoldásnál:
- Akadálymentes nevek: a kosarat bezáró ikongombnak olyan név kell, mint „Kosár bezárása”; egy könyvtár nem tudja kitalálni, mit jelent egy puszta ikon.
- Kontraszt a saját designban: a könyvtár nem szállít színeket, ezért minden tokenpárt ellenőrizni kell világos és sötét módban, minden állapotban.
- Hibaüzenetek: a hibaszövegnek az
aria-describedbyrévén kell kapcsolódnia a mezőhöz, és meg kell mondania, mit tegyen a felhasználó. - Útvonalváltási bejelentések: az egyoldalas alkalmazás oldalváltásai az ön feladatai, ahogy fentebb leírtuk.
- Célterület mérete: egy headless gomb akkora, amekkorát a CSS ad neki.
Hogyan teszteli mindezt?
Rétegekben teszteljen, a legolcsóbbtól: linteljük a sablonokat, minden komponenst egységtesztben vizsgáljunk, a valós folyamatokat végig scanneljük, az akadálymentességi fát ARIA-pillanatképekkel ellenőrizzük, és kézi ellenőrzéssel zárjuk billentyűzettel és képernyőolvasóval. Minden fok olyan hibatípusokat talál meg, amelyeket az előtte lévők nem, a legfelsőhöz pedig továbbra is ember kell.
A lint azért jön először, mert a legolcsóbb hibák azok, amelyeket a sablon láthatóvá tesz. Az eslint-plugin-vuejs-accessibility a szokásos sablonhibákat jelöli meg, például a hiányzó alternatív szöveget és a hibás ARIA-attribútumokat. Azt nem tudja eldönteni, hogy egy alternatív szöveg értelmes-e, ezért szűrőként működik, nem ítéletként.
Az egységtesztek minden komponensre külön futtatják az axe-core-t, így a hiba egyetlen komponensre mutat.
// Vitest: scan the rendered dialog, not the whole page
import axe from 'axe-core'
it('confirm dialog has no detectable violations', async () => {
const { container } = render(ConfirmDialog, { props: { open: true } })
const results = await axe.run(container)
expect(results.violations).toEqual([])
})A végponttól végpontig tartó fok a @axe-core/playwright csomagot futtatja valós folyamatokon: megnyitja a dialógust, hibával elküldi az űrlapot, majd a felhasználó által ténylegesen elért állapotot scanneli. A Playwright akadálymentességi útmutatója ezt a kombinációt javasolja, és egyértelműen kimondja, hogy sok probléma csak kézi teszteléssel derül ki. A Playwrightban van toMatchAriaSnapshot is, a 1.49-es verziótól, amely az akadálymentességi fát ellenőrzi, így a csendben nevét vesztő gombot egy olyan teszt is megbukik, amelyet egy vizuális pillanatkép elnézne.
// Playwright: scan the state a user reaches, not the empty page
import AxeBuilder from '@axe-core/playwright'
await page.getByRole('button', { name: 'Delete order' }).click()
const results = await new AxeBuilder({ page }).include('[role="dialog"]').analyze()
expect(results.violations).toEqual([])A legfelső fok egy ember képernyőolvasóval. A WebAIM Screen Reader User Survey #11, amelyet 2026 júliusában és augusztusában 1780 válasszal készítettek, a JAWS-t nevezi meg elsődleges asztali képernyőolvasóként a válaszadók 55,0%-ánál, az NVDA-t 32,9%-nál, a VoiceOver-t 6,5%-nál. Alapként az NVDA-t teszteljen Windowson, a VoiceOver-t macOS-en és iOS-en, és vegye hozzá a JAWS-t, ha a felhasználók használják. Először csak billentyűzettel járja végig a teljes folyamatot, mert a legtöbb fókuszhiba még azelőtt látszik, hogy bármilyen képernyőolvasó futna. A második kört egy ügynöki munkamenetből futtatom a Playwright MCP szerverrel, így az ügynök megnyitja a dialógust, végigtabolja és lefuttatja a scant, de a bejelentéseket továbbra is ember hallgatja meg.
A fejlesztéshez a @nuxt/a11y a Nuxt DevToolsban futtatja az axe-core-t útvonalfüggő vizsgálattal, és a nuxt generate során jelentéseket készíthet. A legújabb kiadása még az 1.0.0-alpha.1, ezért fejlesztési segédeszközként használja, a CI-kaput pedig a fenti fokokon tartsa. Ugyanaz a fegyelem érvényes, mint a Nuxt Core Web Vitals és teljesítménykeretek esetében: a CI-ben ellenőrzött szám igaz marad, az egyszer ellenőrzött nem.
Mi tartozik egy komponens kész-definíciójába?
Egy komponens akkor kész, ha a neve, szerepe, billentyűzetes viselkedése, fókuszkezelése, kontrasztja, célterületének mérete és élő régiójának viselkedése külön-külön ellenőrizve és a review-ban rögzítve van. Az alábbi lista úgy készült, hogy beleférjen egy pull request sablonba vagy egy review-skillbe, és minden pontja a tesztlétra egy fokának felel meg.
- Név és szerep: minden vezérlőnek van akadálymentes neve, és a szerepe egy natív elemnek vagy az általa megvalósított APG-mintának felel meg.
- Billentyűzet: minden művelet a minta által előírt billentyűkkel működik, és a dialógusokon kívül nincs fókuszcsapda.
- Fókusz: a fókuszjelzés világos és sötét témában is látszik, görgetéskor nem takarja el rögzített elem, és bezárás vagy navigáció után visszatér az indítóhoz.
- Kontraszt: a szöveg eléri a 4,5:1-et, a szükséges vizuális állapotok a 3:1-et, világos és sötét témában.
- Célterület: a mutatóval kattintható célok legalább 24 × 24 CSS-pixelesek, vagy olyan távolságra vannak, hogy a kivétel érvényesüljön.
- Élő régiók: a régió a frissítés előtt már létezik, udvarias, hacsak az üzenet nem blokkolja a felhasználót, és csak egyszerű szöveget tartalmaz.
- Űrlapok: a hibák a mezőikhez kapcsolódnak, és nem csak színnel jelzik őket; az ismétlődő adatok előre ki vannak töltve; a bejelentkező mezők engedik a beillesztést és a jelszókezelőket.
- Húzás: minden húzási műveletnek van egy mutatóval használható alternatívája.
- Mozgás: a jelentéssel bíró átmenetek tiszteletben tartják a
prefers-reduced-motionbeállítást. - Jobbról balra: a fókuszjelzés és az ikonok iránya jobbról balra elrendezésben is működik.
Ha a lista le van írva, egy review-ügynök az emberi diff-olvasás előtt ellenőrizheti velük az új komponenseket, a képernyőolvasós ellenőrzés pedig marad embernél. Körülbelül húsz ügynök-skillem van a ~/.agents/skills mappában, és ez az ellenőrzőlista természetes jelölt egyhez. Ha Vue- vagy Nuxt-komponenskönyvtárat épít vagy vizsgál, és ezt a CI-be szeretné kötni, nézze meg a Vue- és Nuxt-munkámat.
Források
- W3C: Web Content Accessibility Guidelines (WCAG) 2.2
- axe-core README (Deque)
- axe-core rule descriptions
- WebAIM: The WebAIM Million 2026
- WebAIM: Screen Reader User Survey #11
- Vue: accessibility guide
- WAI-ARIA Authoring Practices Guide
- MDN: ARIA live regions
- Playwright: accessibility testing
- Playwright: locator assertions (toMatchAriaSnapshot)
- nuxt/a11y on GitHub
- Reka UI: introduction
Gyakori kérdések
Az axe-core akadálymentessé teszi a Vue-alkalmazást?
Nem. Az axe-core saját megadása szerint átlagosan a WCAG-problémák 57%-át találja meg automatikusan, a WCAG 2.2-es szabálya pedig alapból ki van kapcsolva. A billentyűzetes, a fókusz- és a képernyőolvasós viselkedést továbbra is kézzel kell tesztelni.
Mely WCAG 2.2 kritériumok érintik leginkább a Vue-komponenseket?
A fókusz nincs eltakarva, a húzómozdulatok, a célterület mérete (minimum), a redundáns adatbevitel és az akadálymentes hitelesítés (minimum) közvetlenül a komponenskódot érintik. A fókusz látható és a kontraszt-kritériumok régebbiek, de egyedi stílusozásnál ugyanolyan gyakran buknak.
Hogyan kezeljem a fókuszt a Teleportot használó modálisban?
Használjon natív dialógust, amelyet showModal() nyit meg, a hátteret szükség esetén tegye inertté, és teszteljen fókuszvisszaadást bezáráskor. Az eredeti szülőjén belül kereső egyedi csapda nem találja meg a teleportált dialógust.
Miért nem olvassa fel a képernyőolvasó az üzeneteimet?
Gyakran az élő régiót ugyanabban a frissítésben hozták létre, mint az üzenetet. Először a régiót rendelje az oldallal együtt, legyen jelen, de üres, az üzenetet pedig utána írja bele.
Egyedi kombinált mezőt vagy natív select-et használjak?
Natív select-et, hacsak a termék olyan viselkedést nem igényel, amelyet az nem tud nyújtani. Az egyedi kombinált mező azt jelenti, hogy ön birtokolja az APG billentyűzetes szerződést, és minden frissítésnél karban kell tartania.