ARIA i webbutiken: fel roller, attribut och listor
Uppdaterad
Så rättar du ARIA-fel i webbutiken: roller och attribut från teman och appar, nästlade knappar samt listor och tabeller med fel struktur. Med kodexempel.
Det här är teknisk vägledning och inte juridisk rådgivning. För tolkningsfrågor om Lag (2023:254) om vissa produkters och tjänsters tillgänglighet, vänd dig till Post- och telestyrelsen (PTS).
Teman, appar och kassatillägg lägger ofta till ARIA: extra attribut som talar om för skärmläsaren vad ett element är och gör. Då räcker en felstavning eller en roll på fel element för att skärmläsaren ska läsa upp något annat än det som syns. Här går vi igenom felen, vilka kriterier de hör till och hur de rättas.
Kodexemplen är konstruerade för att visa mönstren. De är inte hämtade från någon riktig butik, något tema eller någon app, och vi säger ingenting om hur enskilda produkter är byggda.
Vad ARIA är, och den första regeln
ARIA (WAI-ARIA) är en uppsättning roller och attribut som W3C har definierat. En roll, till exempel role="button", talar om vad ett element är. Ett attribut, till exempel aria-expanded="true", talar om i vilket tillstånd det är. ARIA ändrar bara vad skärmläsaren säger, inte hur elementet fungerar. Ett element med role="button" får inget tangentbordsstöd av det.
Därför är den första regeln i W3C:s dokument Using ARIA att du inte ska använda ARIA när ett vanligt HTML-element redan gör jobbet, och ARIA Authoring Practices Guide sammanfattar det som att ingen ARIA är bättre än dålig ARIA. En button är redan en knapp, en ul en lista och en details med summary ett fällbart avsnitt. Många av felen nedan försvinner om du byter ut ARIA mot rätt HTML-element.
Felen hör till två kriterier i WCAG: 4.1.2 Namn, roll, värde och 1.3.1 Information och relationer. Två av reglerna, om mätare och förloppsindikatorer, pekar på 1.1.1 Innehåll som inte är text.
Roller som inte finns eller inte passar elementet
Regeln aria-roles slår till när role har ett värde som inte finns i ARIA. Det är oftast en felstavning eller ett påhittat namn:
<div role="buton">Lägg i varukorgen</div>
<span role="product-card">...</span>
Skärmläsaren känner inte igen någon av rollerna och behandlar elementen som vanlig text. Rätta genom att använda en riktig knapp, och ta bort roller som du själv har hittat på:
<button type="button">Lägg i varukorgen</button>
<article>...</article>
Regeln aria-deprecated-role gäller roller som ARIA 1.2 har markerat som föråldrade, till exempel directory. Ersätt dem med en vanlig lista eller de element som specifikationen anger. Rollen kan fortfarande fungera i vissa läsare, men den ska inte nyskrivas.
Attribut som inte finns, inte är tillåtna eller har fel värde
Här ligger flest regler, och de skiljer sig åt på ett sätt som är värt att känna till.
aria-valid-attr hittar attribut som börjar på aria- men inte finns. Det vanligaste är en felstavning:
<input type="text" aria-labeledby="postnr-etikett">
Rätt stavning är aria-labelledby, med två l. Med fel stavning ignoreras attributet helt och fältet får inget namn.
aria-valid-attr-value gäller värdet. Attribut som aria-describedby och aria-labelledby ska peka på ett id som finns på sidan. Pekar de på ett id som saknas, till exempel för att ett tillägg har byggt om kassan, läses ingenting upp:
<input id="epost" aria-describedby="epost-hjalp">
Här finns inget element med id="epost-hjalp". Samma regel kontrollerar att tillståndsattribut har tillåtna värden, till exempel att aria-expanded är true eller false och inte "yes".
aria-allowed-attr hittar attribut som finns men inte får användas på just den rollen. Ett exempel är aria-checked på en vanlig knapp:
<button type="button" aria-checked="true">Fri frakt</button>
En knapp som kan vara intryckt använder aria-pressed. Attributet aria-checked hör till kryssrutor, radioknappar och liknande roller. Läs i ARIA 1.2 vilka attribut rollen stöder, eller byt till ett element som redan har rätt egenskap, som en input av typen checkbox. I Sök och filter i webbutiken visar vi hur en favoritknapp i produktlistan får både ett namn och aria-pressed.
aria-prohibited-attr är en särskild variant: vissa roller får inte ha ett namn alls. Det typiska felet är aria-label på ett div- eller span-element utan roll, till exempel en kampanjbanner:
<span aria-label="Fri frakt över 499 kr">Fri frakt över 499 kr</span>
Ett element utan roll har inget namn att sätta, så attributet gör ingen nytta och kan ge olika resultat i olika skärmläsare. Ta bort aria-label och låt den synliga texten vara det som läses, eller ge elementet en roll som tillåter namn om det verkligen behövs.
aria-conditional-attr gäller attribut som bara är tillåtna under vissa villkor. Ett exempel är aria-checked på en riktig kryssruta: värdet får inte säga emot vad rutan faktiskt är, och på en inbyggd kryssruta behövs det oftast inte alls. Ta bort attributet och låt elementets eget tillstånd gälla.
aria-braille-equivalent gäller aria-braillelabel och aria-brailleroledescription, två nyare attribut (ARIA 1.3, arbetsutkast) som styr vad som visas på en punktskriftsrad. De får bara användas om elementet också har ett vanligt namn (aria-label) respektive en vanlig rollbeskrivning (aria-roledescription). Är du inte säker på att du behöver dem, ta bort dem.
Hela sidan dold för skärmläsare
Regeln aria-hidden-body slår till när aria-hidden="true" ligger på body-elementet. Det döljer hela sidan för skärmläsare. Det händer när ett skript som ska dölja sidan bakom en öppen ruta sätter attributet på fel element och sedan inte tar bort det. Sätt aria-hidden på syskonen till rutan, inte på body, eller använd dialog med showModal(), som sköter det själv. Läs gärna om dialoger i guiden om tangentbord och synligt fokus.
Roller som saknar obligatoriska attribut, barn eller förälder
Vissa roller kräver mer än bara namnet.
aria-required-attr: rollen kräver ett visst attribut. Ett checkbox-element byggt av ett div behöver aria-checked, och ett heading som inte är en h-tagg behöver aria-level:
<div role="checkbox">Spara mina uppgifter</div>
<div role="heading">Leverans</div>
Det enklaste är att använda input type="checkbox" och h2, som inte behöver några extra attribut. Behåller du rollerna, lägg till aria-checked="false" respektive aria-level="2".
aria-required-children: vissa roller ska innehålla bestämda roller. En role="list" ska innehålla role="listitem", en tablist ska innehålla tab, och en menu ska innehålla menuitem. Ett produktgalleri där listan är markerad men barnen är vanliga div-element ser ut så här:
<div role="list">
<div class="kort">Tröja</div>
<div class="kort">Byxor</div>
</div>
Skärmläsaren får en lista utan poster. Använd ul och li, eller lägg role="listitem" på varje kort.
aria-required-parent är motsatsen: en roll som kräver en förälder med en viss roll. En role="listitem" ska ligga i en lista, en role="tab" i en tablist och en role="menuitem" i en meny eller en grupp. Hittar skärmläsaren posten utan sin förälder vet den inte hur den ska presentera den.
Interaktiva element inuti varandra
Regeln nested-interactive gäller roller vars innehåll skärmläsare behandlar som en enda sak, till exempel knapp och kryssruta. Ligger det en länk eller en annan knapp inuti, går det inte att nå den innersta med skärmläsare, och tangentbordsanvändare kan hamna på två fokuspunkter för samma yta:
<div role="button" tabindex="0">
Rabattkod: <a href="/villkor">se villkor</a>
</div>
Länken ligger i ett element som skärmläsaren presenterar som en knapp, och den går inte att nå som en länk. Det vanligaste mönstret i en butik är ett helt produktkort som är klickbart och som också innehåller en egen "Lägg i varukorgen". Rätta genom att ha ett klickbart område och placera de andra knapparna bredvid det, inte inuti. Observera att en button inuti en länk (eller tvärtom) dessutom är ogiltig HTML enligt specifikationen, även om det inte är det som regeln mäter.
Listor och tabeller med fel struktur
Dessa regler hör till 1.3.1: strukturen i koden ska stämma med strukturen på sidan.
- list: en ul eller ol får bara ha li, script eller template som direkta barn. Ett div-element direkt i en lista bryter regeln, vilket ofta händer när ett tema lägger en omslagsruta runt varje produkt.
- listitem: ett li ska ligga i en ul eller ol. Ligger det löst, till exempel för att ett tillägg har bytt ut ul mot div, läses det inte som en listpost.
- definition-list: en dl får bara innehålla dt- och dd-element (eller grupper av dem i ett div-element). Används dl för att göra en produktspecifikation, kontrollera att inget annat hamnar där.
- dlitem: dt och dd ska ligga i en dl.
- td-headers-attr: attributet headers på en tabellcell ska bara peka på andra celler i samma tabell, och id:na måste finnas. En storlekstabell som har byggts om och där id:na har försvunnit ger den här regeln.
- th-has-data-cells: varje rubrikcell (th) i en datatabell ska höra till minst en datacell. En tom rubrik i en tabell med bara layout, eller en rubrikcell som inte har något under sig, bryter regeln. Använd inte tabeller för layout.
<ul class="produkter">
<div class="rad"><li>Tröja</li></div>
</ul>
Rättat:
<ul class="produkter">
<li>Tröja</li>
</ul>
Mätare, förloppsindikatorer och sammanfattningar utan namn
Tre regler handlar om att ett element saknar ett namn som en skärmläsare kan läsa.
- aria-meter-name och aria-progressbar-name: ett element med role="meter" eller role="progressbar" ska ha ett namn, via aria-label, aria-labelledby eller title. Ett typexempel är en mätare för hur nära kunden är fri frakt: utan namn får användaren veta att något är 60 procent men inte vad som mäts.
<div role="progressbar" aria-valuenow="60" aria-valuemin="0" aria-valuemax="100"
aria-label="Hur nära du är fri frakt"></div>
- summary-name: ett summary-element, som är rubriken på ett fällbart avsnitt, ska ha text som går att läsa. Det blir tomt när texten bara är en ikon utan alternativtext.
De två första reglerna pekar på kriteriet 1.1.1. Läs gärna guiden om alt-texter för att se hur man tänker kring namn på bilder och ikoner.
Så hittar du felen
- Sök efter role= och aria- i webbläsarens inspektör, i kassan och på produktsidan. Fråga för varje träff om ett vanligt HTML-element hade räckt.
- Pröva med en skärmläsare. Går det att nå och förstå varje knapp, lista och mätare?
- Kör ett verktyg som bygger på axe-core (öppen källkod) i webbläsaren. Det hittar fel i koden men kan inte avgöra om ett namn är begripligt.
- Rätta först det som hindrar ett köp (kassan, knappen "Lägg i varukorgen"), sedan resten.
Reglerna i den här guiden körs i AccessAudits månadsgranskning (abonnemanget), som använder axe-core 4.11 i en riktig webbläsare. Det gratis testet på butik.nightwave.se/tillganglighet läser bara källkoden på upp till fem sidor och hittar en del av det som beskrivs här, till exempel dubbla id som får en ARIA-koppling att peka fel, men inte ARIA-rollerna och attributen i sig. Ingen automatisk granskning ersätter en människas bedömning.
Börja här
Sök igenom kassan och produktsidan efter role= och aria-, och ta bort allt som ett vanligt HTML-element kan ersätta. Det som blir kvar är det som behöver granskas. I checklistan finns kontrollerna du kan göra själv. Vill du ha löpande kontroll finns ett abonnemang för 1 990 kr per månad exklusive moms för upp till 30 sidor. Det är teknisk granskning mot WCAG, inte juridisk rådgivning.
Källor
- Understanding 4.1.2 Name, Role, Value, W3C
- Understanding 1.3.1 Info and Relationships, W3C
- Accessible Rich Internet Applications (WAI-ARIA) 1.2, W3C
- Accessible Rich Internet Applications (WAI-ARIA) 1.3, W3C Working Draft
- Using ARIA, W3C
- ARIA Authoring Practices Guide: Read Me First, W3C
- Deque University, axe-regler (axe-core 4.11), ett avsnitt per regel: aria-roles, aria-deprecated-role, aria-valid-attr, aria-valid-attr-value, aria-allowed-attr, aria-prohibited-attr, aria-conditional-attr, aria-braille-equivalent, aria-hidden-body, aria-required-attr, aria-required-children, aria-required-parent, nested-interactive, list, listitem, definition-list, dlitem, td-headers-attr, th-has-data-cells, aria-meter-name, aria-progressbar-name, summary-name