protofunc()

Wai-Aria Widget-Entwicklung mit Accessibility Probe/Inspect am Beispiel einer custom Select-Drop-Down-Box

Tags: accessibility, javascript, jquery

Einleitung

Die Entwicklung von Aria-Widgets ist letztendlich keine triviale Sache. Zum einen müssen alle für das Widget wichtigen Aria-Attribute vorhanden sein, die Verschachtelung und letztendlich auch das – durch den Entwickler zu implementierende – Verhalten stimmen. Fehler in der HTML-Struktur oder dem Verhalten können häufig größeren Schaden anrichten als helfen. Bei der Entwicklung von zugänglichen Javascript-Komponenten erhält daher das Testen mit verschiedenen Screenreader-/Browser-Kombinationen eine hohe Bedeutung. Dieses Tutorial soll helfen zu zeigen, wie einfach man “bessere” Wai-Aria-Widgets schreibt bzw. wie man bereits vorhandene Widgets beurteilen kann, um den Entwickler auf Fehler aufmerksam zu machen.

Dies möchte ich am Beispiel einer normalen Ausklappliste demonstrieren, doch vorab hier eine Demo sowie ein Screencast, welches das fertige Aria-Widget mit verschiedenen Screenreader/Browser-Kombinationen zeigt.

Gute Resourcen

Die zentrale Anlaufstelle für gute Inhalte zum Thema WAI-Aria stellt Codetalks von Mozilla zur Verfügung. Dort lassen sich zahlreiche Aria-Beispiele/Test Cases, Artikel, FAQs, Tutorial, Blogs, Tools rund um das Thema Aria finden.

Einiges sei hier extra erwähnt.

  • Die entscheidenden Passagen des Aria Best Practices Dokuments sollten immer vor der Entwicklung eines Widgets gelesen werden. Das Dokument enthält sowohl allgemeine Informationen (z.B. Tastaturbenutzung/Fokus-Management) sowie konkrete Informationen zu entsprechenden Widgets (in unserem Fall der combobox).
  • Scripte sollten immer mit mehr als einem Screenreader und mehr als einem Browser getestet werden. Todd Kloots hat hierzu im YUI-Blog einige Informationen zum Installieren und Konfigurieren von Screenreadern für Entwickler zusammengestellt. Marco Zehe gibt weitere Informationen zum Testen mit NVDA.
  • Gute Tools machen uns das Entwicklen deutlich einfacher. Neben Tools wie Firebug/Dragonfly sind dies für die Aria-Entwicklung insbesondere die Firefox Accessibility Extension sowie eines der im Titel dieses Tutroials genannten Accessibility-Inspect Tools (Inspect bzw. Accessibility Probe).
  • Eine mögliche Anlaufstelle für Fragen sowie Diskussionen über eigene Lösungen/Ansätze stellt die Free ARIA Community dar.

Die Accessibility-Infomationen des select-Elements (im Firefox)

Die Aria-Rolle combobox beschreibt sowohl das Widget Ausklappliste (Dropdown-Liste), bei dem der Benutzer zwischen vorgegeben Werten wählen kann, als auch das Widget “kombiniertes Eingabefeld”, bei dem der Nutzer zusätzlich freie Texteingaben machen kann. Wir werden in diesem Tutorial das Inspect-Tool von Microsoft dafür nutzen, um die richtige HTML-Struktur inklusive der Aria-Attrbiute für eine einfache Ausklappliste zu ermitteln und zu testen.

Wenn wir ein Select-Element mit folgender HTML-Struktur schreiben,

<label for="select-element" id="label">Bezeichner</label>
<select id="select-element">
	<option>Option A</option>
	<option>Option B</option>
</select>

… erhalten wir folgende Accessibility-Informationen:

Das select-Element besitzt einen Namen (das label), einen aktuellen Wert, eine Rolle (Kombinationsfeld), verschiedene default-Zustände und eine unsichtbare Liste, welche ebenfalls mit dem label-Element verbunden ist.

Sofern wir die unsichtbare Liste weglassen, erreichen wir mit folgender HTML-Struktur die Weitergabe ähnlicher Accessibility-Informationen im Firefox:

<span id="label">Bezeichner</span>
<div role="combobox" aria-valuetext="Option A" aria-labelledby="label" tabindex="0">
	Option A
</div>

Die obige HTML-Struktur berücksichtigt jedoch nicht die fatale Kleinigkeit, daß der Internet Explorer 8 die valuetext-Eigenschaft nicht unterstützt. Letztendlich kann man Microsoft zumindest bei der combobox keinen Fehler vorwerfen, denn die Aria-Spezifikation für comboboxen sieht dies selbst nicht vor. Bei der Spezifikation hatte man anscheinend vor allem das Widget kombiniertes Eingabefeld im Kopf und die Realisierung der einfachen Drop-Down-Liste vergessen (reine Behauptung/Vermutung). Das Aria Best Practices Dokument geht dann zwar auf Ausklapplisten ein (verkürzte HTML-Struktur: [role=combobox][tabindex=-1] > [role=textbox][aria-readonly=true][tabindex=0]), aber dies würde zu einer HTML-Struktur führen, die in Screenreadern nicht funktioniert.

Mit Bugs gegen Bugs

Ein Problem bei der Entwicklung von Aria-Widgets ist die Tatsache, daß semantische Überreste von Elementen übrig bleiben, wenn man die Rolle mit Aria ändert. Ein typisches Beispiel hierfür ist ein Anchor-Element mit einem href-Attribut.

<a href="#bla" role="button">Button-Text</a>

Bei dieser Grundstruktur wird zwar aus einem Link ein Schalter, aber dieser Schalter besitzt neben dem Namen “Button-Text” noch zusätzlich den Wert der href-DOM-Eigenschaft als “Zugänglichkeitswert” (nicht zu Verwechseln mit dem Wert des href-Attributs). Aus diesem Grund sollte man, wenn man einen Link mit einer anderen Rolle belegt, das href-Attribut entfernen. Dies führt gleichzeitig dazu, daß

  1. der Link ohne tabindex-Attribut nicht mehr fokusierbar ist
  2. die Pseudoklasse :focus nicht in allen Browsern funktioniert (im IE6 funktioniert auch :hover nicht mehr)
  3. ein click-Event auf dem Link kein geräteunabhängiges Event mehr darstellt

Nun ist für uns die href-DOM-Eigenschaft nicht wirklich brauchbar, da wir diesen Wert nicht in der Hand haben. Das selbe Problem besteht jedoch ebenfalls bei Texteingabefeldern und läßt sich zur Lösung unseres Problems mißbrauchen.

Folgende HTML-Struktur ergibt sowohl im Firefox als auch im Internet Explorer die gewünschten Zugänglichkeitsinformationen:

<span id="label">Bezeichner</span>
<input role="combobox" value="Option A" aria-labelledby="label" tabindex="0" />

Die Ausklappliste selbst

Zusammen mit der Ausklappliste würde die HTML-Struktur wie folgt aussehen:

<span id="label">Bezeichner</span>
<input role="combobox" value="Option A" aria-labelledby="label" tabindex="0" />
<!-- weiteres HTML dazwischen | listbox wird ans Ende des Dokuments hinzugefügt -->
<ul role="listbox" aria-labelledby="label">
	<li role="option" tabindex="-1">Option A</li>
	<li role="option" tabindex="-1">Option B</li>
</ul>

Ändert der User mit der Maus oder mit den hoch/runter-Pfeiltasten die Optionen, wird dies über das zusätzliche Wai-Aria-Attribut activedescendant deutlich gemacht:

<span id="label">Bezeichner</span>
<input role="combobox" value="Option B" aria-labelledby="label" aria-activedescendant="option-2" tabindex="0" />
<ul role="listbox" aria-labelledby="label">
	<li role="option" id="option-1" tabindex="-1">Option A</li>
	<li role="option" id="option-2" tabindex="-1">Option B</li>
</ul>

Focus-Management: activedescendant vs. tabindex + focus

Grundsätzlich sollte man, statt der activedesendant-Methode, den Fokus mit tabindex und der focus-Methode ändern. Die focus-Methode funktioniert in recht vielen Browser-/Screenreader-Kombinationen, so daß Widgets, welche den focus auf diese Weise managen, selbst in Screenreader/Browser-Kombinationen funktionieren können, die kein Wai-Aria unterstüzten.

In unserem Fall, ist dieser Weg allerdings wenig sinnvoll, da wir die Liste ans Ende unseres Dokuments hinzufügen und dadurch aus der logischen Tabreihenfolge nehmen. Dem aufmerksamen Beobachter wird außerdem auffallen, daß wir den einzelnen Optionen dennoch ein tabindex-Attribut gegeben haben, obwohl wir die activedescendant-Methode nutzen und das Aria Best Practices Dokument in diesem Fall ein tabindex-Attribut für unnötig hält. Dies hat den Hintergrund, daß das tabindex-Attribut jedes Element nicht nur fokusierbar macht, sondern auch die Eigenschaft der Fokusierbarkeit (Markierbarkeit) an die jeweilige Zugänglichkeitsschnittstelle weiterleitet (Die Rolle option tut dies, wie fast alle Rollen, nicht implizit!). Fehlt dieses Attribut wird diese nicht weitergeleitet, was in einigen Screenreadern negative Folgen haben kann.

Wird die Liste reduziert, ist dieses Attribut wieder restlos zu entfernen.

Obgleich die Aria-Spezifikation noch weitere notwenige Aria-Attribute vorsieht (aria-expanded an der listbox) und weitere sinnvoll erscheinen (aria-checked an der jeweiligen Option, aria-selected=true|false an den Optionen), ist die oben gezeigte Aria-Struktur alles was wir für eine gut funktionierende Ausklappliste brauchen.

Endgültige HTML-Struktur

Mit dem Texteingabefeld als Workaround haben wir uns ein zusätzliches Problem geschaffen. Wird dieses fokusiert erscheint in der Regel ein Curosr und der Nutzer kann Text frei eingeben. Um dieses Verhalten rückgängig zu machen, müßen wir sowohl durch HTML-Struktur als auch unser Script einige Dinge berücksichtigen. Nachfolgend eine HTML-Struktur, welches einen Teil des ungewünschten Verhaltens rückgängig macht. Den Rest erledigen wir in unserem Script.

<span id="label">Bezeichner</span>
<div class="select" tabindex="-1">
	<input role="combobox" value="Option A" aria-valuetext="Option A" readonly="readonly" aria-readonly="false" aria-labelledby="label" tabindex="0" />
</div>
<ul role="listbox" aria-labelledby="label" id="datalist">
	<li role="option" id="option-1" tabindex="-1">Option A</li>
	<li role="option" id="option-2" tabindex="-1">Option B</li>
</ul>

Außerdem wollen wir die native Selectbox

  1. gegen eine schön stylbare/animierbare Selectbox tauschen
  2. in ihren Zugänglichkeitsinformationen nicht nur funktional, sondern möglichst originalgetreu nachbauen

und da brauchen wir bestimmt noch ein bißchen mehr HTML, so daß wir bei folgender Struktur landen:

<span id="label">Bezeichner</span>
<div class="select" tabindex="-1">
	<input role="combobox" value="Option A" aria-valuetext="Option A" readonly="readonly" aria-readonly="false" aria-expanded="false" aria-labelledby="label" tabindex="0" />
</div>
<div role="listbox" aria-labelledby="label" id="datalist">
	<div role="presentation">
		<ul role="presentation">
			<li role="option" id="option-1" tabindex="-1"><span role="presentation">Option A</span></li>
			<li role="option" id="option-2" tabindex="-1"><span role="presentation">Option B</span></li>
		</ul>
	</div>
</div>

Wann ist die Liste ausgeklappt/sichtbar?

Grundsätzlich klappt eine Auswahllliste für den User sichtbar erst bei gedrückter alt-Taste auf. Bei normaler Tastaturnutzung sieht es dagegen so aus, als würde sich lediglich der Wert des Select-Elements ändern. Wie ein Test mit dem Inspect-Tool zeigt, täuscht dieser Eindruck. Sobald der Wert mit den Pfeiltasten geändert wird, befindet sich der User automatisch auf der jeweiligen Option. Damit dies klappt, muß die Liste sichtbat gemacht (display: block; visibility: visible;) und das activedescendant-Attribut gesetzt werden. Es bleibt dem Autoren überlassen, ob er die Liste zu diesem Zeitpunkt außerhalb des Viewports plaziert oder nicht (In meiner Demo habe ich hierauf verzichtet).

Das Setzen des activedescendant-Attributs

Das activedescendant-Attribut sollte, ebenso wie das Setzen des Fokus mit der Fokus-Methode, immer mit einem timeout geschehen (Ein delay von 0 ms reicht). Wird das activedescendant-Attribut ohne delay auf ein Element gesetzt, welches zuvor noch versteckt war, kann der Screenreader dieses Element nicht finden und liest nichts vor. (Dies gilt nur für (neue) DOM-Elemente, nicht für die Änderung von Attributen oder Textknoten.)

Fazit

Es gibt viele Dinge, welche die Aria-Entwicklung erschweren: Browser-Bugs, Screenreader-Bugs, Merkwürdigkeiten in den Spezifikationen und letztendlich mangelndes Know-How. Geeignete Tools können uns helfen, diese Probleme schneller in den Griff zu kriegen. Ein Testen mit Screenreadern muß die Entwicklung begleiten.

Eine abschließende Frage an alle Entwickler, die mich immer wieder beschäftigt: Mit welchen Screenreader-/Browser-Kombinationen (inkl. Version) testet ihr bzw. bei welchen sollte – Eurer Meinung nach – die Funktionsfähigkeit sichergestellt werden (und sagt jetzt bitte nicht bei allen)?

Written September 23, 2009 by
protofunc