12. Februar 2010
Skiplinks gehören zu den grundlegenden Navigationshilfen, welche die Zugänglichkeit komplexer Webseiten verbessern können, indem sie es dem Nutzer erlauben, wichtige Elemente einer Webseite schnell und ohne Umwege zu erreichen, z.B. die Hauptnavigation, den Inhaltsbereich oder ein Formular. Aufgrund meiner Erfahrungen bei der Arbeit an und mit YAML gibt’s hier nun einen kleinen Best Practice Beitrag zum Thema.
Anforderungen
Zunächst wären noch einmal die Anforderungen an Skiplinks zu definieren. Die WCAG 2.0 gibt unter G1 Testkriterien vor:
- Stelle sicher, dass Skiplinks die ersten focussierbaren Elemente der Webseite darstellen.
- Stelle sicher, dass die Linkbeschreibung eine verständliche Beschreibung des Sprungzieles enthält.
- Stelle sicher, dass der Skiplink entweder immer oder zumindest dann sichtbar ist, wenn der Tastaturfocus auf ihm liegt.
- Stelle sicher, dass beim Aktivieren eines Skiplinks der visuelle Focus im Viewport des Browsers auf das Ziel gesetzt wird.
- Stelle sicher, dass nach dem Aktivieren des Skiplinks der Tastaturfocus auf das Zielelement gesetzt wurde.
Die WCAG ist unnachgiebig, was diese Kritierien betrifft: Alle 5 Testkriterien müssen erfüllt werden.
Die Grundlagen: Markup und Styling
Skiplinks sollten grundsätzlich vor dem eigentlichen Inhalt der Webseite eingebaut werden, um dem Testkriterium 1 (erstes focussierbares Element) gerecht zu werden. Eine einfache Kontrollmöglichkeit für diese Regel besteht darin, sich die Webseite mit deaktivierten Stylesheets anzuschauen. Die Skiplinks sollten dabei an erster Stelle stehen.

Die technische Umsetzung per HTML & CSS ist nicht weiter kompliziert. Das HTML-Grundgerüst bildet ein einfacher Hyperlink, als Ziel kann jedes beliebige HTML-Element der Webseite dienen, soweit es über eine ID eindeutig identifizierbar ist. In der Regel wird man hier auf z. B. den DIV-Container verweisen, welcher die Hauptnavigation oder die Hauptinhaltsbereich der Webseite beinhaltet.
Skiplinks sind nichts Neues mehr, deshalb will ich den technischen Teil an dieser Stelle kurz halten. Tomas Caspers hat vor einiger Zeit auf Einfach für Alle ein sehr eingängiges Tutorial verfasst, welches ich jedem Einsteiger in die Materie ans Herz legen möchte. Auch die Diskussion in den Kommentaren ist bei diesem Beitrag lesenswert. Der wichtigste Punkt, Thomas spricht es an, betrifft die gewählte Technik für das zwischenzeitliche Ausblenden der Skiplinks. Hier darf keinesfalls die CSS-Eigenschaft display:none verwendet werden, denn hierdurch ist der Link für Screenreader nicht mehr erreichbar – und somit nutzlos. Das Aus- und Einblenden von Skiplinks geschieht daher in der Regel durch eine absolute Positionierung, womit die auszublendenden Elemente einfach seitlich aus dem Viewport verschoben (versteckt) werden und bei Focussierung per Tastatur wieder in den sichtbaren Teil zurückgeholt werden.
Mehr Gestaltungsfreiheit
Wie anfangs beschrieben, sollten Skiplinks generell vor den eigentlichen Seiteninhalten im Quelltext stehen. Das Verstecken der Skiplinks ist nie ein Problem – wohl aber die Eingliederung ins Layout, sobald die Skiplinks sichtbar werden. Einerseits muss im Layout Platz (Leerraum) für die Skiplinks eingeplant werden, zum anderen müssen Sie für den Nutzer bei Aktivierung auch gut erkennbar (Kontrast, Schriftgröße) sein. In grafiklastigen Layouts kann dies recht aufwändig werden.
Aus diesem Grund liefere ich seit einiger Zeit (v3.2) bei meinem CSS-Framework YAML eine etwas aufwändigere Darstellungsvariante mit, die in allen Layout-Beispielen implementiert ist. Hierbei werden die Skiplinks nicht in das Layout integriert, sondern bei Focussierung in Form eines farbigen Balkens über dem Layout eingeblendet. Auf diese Weise eröffnen sich viel größere Freiheiten bei der Darstellung – ohne dass das eigentlich Layout in irgendeiner Form beeinflusst wird.

Soweit, so gut. Im Prinzip wäre ich hiermit am Ende des Beitrags, wenn es da nicht neuerdings ein ausgesprochen hässliches Problem mit einigen modernen Webbrowsern gäbe.
Folge dem weißen Kaninchen Tastaturfocus
In den aktuellen Versionen der Webkit-basierten Browser (Google Chrome, Apple Safari) sowie im aktuellen Internet Explorer 8 unter Windows 7 (unter Vista tritt das Problem im IE8 offenbar nicht auf) wird bei Aktivierung eines Skiplinks zwar der visuelle Focus auf das Zielelement ausgerichtet (WCAG 2.0, Testkriterium 4 erfüllt), der Tastaturfocus verbleibt jedoch auch nach Aktivierung auf dem Skiplink, er wird nicht verschoben. Damit ist das Testkriterium 5 nicht erfüllt, denn beim nächsten Druck auf die Tabulatortaste springt der Focus (viuseller Focus und Tastaturfocus) auf das focussierbare Element, welches dem zuvor aktivierten Skiplink im Quelltext folgt. Damit wird der Sinn der Skiplinks vollständig ausgehebelt, denn man befindet sich praktisch wieder bei (oder direkt hinter) den Skiplinks.
Wen trifft das Problem eigentlich?
Ich habe gestern Marco Zehe um Hilfe gebeten, einige Browser/Screenreader-Kombinationen durchzutesten. Marco arbeitet in der Regel mit NVDA und hat im Internet Explorer 8 unter Windows 7 keinerlei Probleme, NVDA greift hier offenbar korrigierend ein. Auch im Safari/Mac trat das Problem in Verbindung mit VoiceOver nicht auf – Google Chrome und Safari/Win sind hingegen nach Marcos Meinung "gar nicht accessible!". Eine Nutzung scheidet für Ihn daher generell aus – das Focusproblem ist damit nicht seines.
Anders sieht die Situation bei JAWS aus: Hier konnte Marco das Problem in Verbindung mit dem Internet Explorer 8 verifizieren. Freundlicherweise ist JAWS der mit Abstand am meisten genutzte Screenreader und auch der IE hat eine gewisse Verbreitung – das Focusproblem ist also überaus real und bedarf einer Lösung.
Workaround – Runde 1
Ich wurde seinerzeit durch einen Blogbeitrag von Paul Redcliffe auf einen cleveren JavaScript-Workaround aufmerksam (Demo). Der Workaround besteht darin, dass er den Skiplinks dynamisch einen Click-Event zuweist, um bei Aktivierung den Tastaturfocus per JavaScript zu setzen. Hier der zugehörige JavaScript-Code ...
var is_webkit = navigator.userAgent.toLowerCase().indexOf('webkit') > -1;
var is_opera = navigator.userAgent.toLowerCase().indexOf('opera') > -1;
if(is_webkit || is_opera) {
var target = document.getElementById('skiptarget');
target.href="#skiptarget";
target.innerText="Start of main content";
target.setAttribute("tabindex" , "0");
document.getElementById('skiplink').setAttribute("onclick" , "document.getElementById('skiptarget').focus();");
}
Grundsätzlich funktioniert der Workaround, allerdings gefallen mir einige Details nicht, weshalb ich für YAML einen eigenen Fix geschrieben habe.
- Die Variablen werden im globalen Scope definiert und kein Namespacing verwendet. Das mag im konkreten Fall nicht weiter stören, dennoch empfinde ich es als unsauber.
- Das Script ist monoton auf einen einzigen Skiplink mit der ID skiplink ausgelegt (getElementById) und nicht flexibel erweiterbar. Das ist unzureichend, da zwei oder drei Skiplinks (z.B.: Navigation, Inhalt, Kontaktformular ...) keine Seltenheit sind.
- Den Zielelementen wird innerhalb des Skripts das Attribut
tabindex="0"zugewiesen. Das ist aus meiner Sicht keine gute Lösung, denn so wird das Zielelement permanent in die normale Tabreihenfolge aufgenommen, obwohl es bei Focussierung per Tastatur keinerlei Funktion bietet – im Gegensatz zu focussierbaren Links, dessen URL der Nutzer bei Aktivierung folgt. - Eine weiter Unschönheit ergibt sich durch
tabindex="0"in Verbindung mit Apples Safari, denn dieser hebt das focussierte Element generell durch eine leuchtend blaue Umrandung peroutlinehervor. So angenehm diese Darstellung bei focussierten Links oder Formularelementen ist, so störend oder verwirrend kann die plötzliche Hervorhebung eines einfachen DIV-Containers im Layout sein.
Workaround – Runde 2
Letztlich konnte ich das Phänomen im aktuellen Opera nicht nachvollziehen und konzentriere mich daher nur auf Webkit-Browser und den Internet Explorer. Der nachfolgende Code ist (bis auf minimale Abweichungen) Bestandteil der aktuellen Version 3.2.1 von YAML und ist unter yaml/core/js/yaml-focusfix.js zu finden.
/**
* "Yet Another Multicolumn Layout" - (X)HTML/CSS Framework
*
* (en) Workaround for Webkit browsers to fix focus problems when using skiplinks
* (de) Workaround für Webkit browsers, um den Focus zu korrigieren, bei Verwendung von Skiplinks
*
* @copyright Copyright 2005-2010, Dirk Jesse
* @package yaml
*/
var YAML_focusFix = {
init: function() {
var skipClass = 'skip';
var userAgent = navigator.userAgent.toLowerCase();
var is_webkit = userAgent.indexOf('webkit') > -1;
var is_ie = userAgent.indexOf('msie') > -1;
var i = 0;
var links, skiplinks = [];
if (is_webkit || is_ie) {
// find skiplinks in modern browsers ...
if ( document.getElementsByClassName !== undefined) {
skiplinks = document.getElementsByClassName(skipClass);
for (i=0; i<skiplinks.length; i++) {
this.setTabIndex(skiplinks[ i ]);
}
} else {
// find skiplinks in older browsers ...
links = document.getElementsByTagName('a');
for (i=0; i<links.length; i++) {
var s = links[ i ].getAttribute('href');
var c = links[ i ].getAttribute('class');
if (s.length > 1 && c.indexOf(skipClass) != -1 && s.substr(0, 1) == '#' ) {
this.setTabIndex(links[ i ]);
}
}
}
}
},
setTabIndex: function( skiplink ){
var target = skiplink.href.substr(skiplink.href.indexOf('#')+1);
var targetElement = document.getElementById(target);
if (targetElement !== null) {
// make element accessible for .focus() method
targetElement.setAttribute("tabindex", "-1");
skiplink.setAttribute("onclick", "document.getElementById('"+target+"').focus();");
}
}
};
YAML_focusFix.init();
Welche Änderungen habe ich nun im Detail vorgenommen? Das Script springt nun sowohl bei der Webkit-Engine, als auch beim Internet Explorer an und kann mehrere Skiplinks verarbeiten, indem es alle Links mit der Klasse .skip auswertet und entsprechend mit dem Workaround versieht. In YAML-basierte Layouts kann man es daher ohne weitere Konfiguration einbinden.
Beim Internet Explorer wurde bewusst auf einen Versionstest verzichtet, denn wie in dem weiter oben verlinkten Tutorial von Tomas Caspers zu lesen, haben die älteren IE-Versionen ein ähnliches Problem, sobald das Zielelement das proprietäre Merkmal hasLayout nicht besitzt. Da ich per JavaScript schlecht ins Layout eingreifen kann, um hasLayout zu triggern, ist es einfacher, das Script auch in den älteren IE’s pauschal durchlaufen zu lassen. Und letzten Endes setzt das Script auf dem Zielelement das Attribut tabindex="-1". Der Wert -1 erlaubt das dynamische Setzen des Tastaturfocus per JavaScript, fügt das Element aber nicht in die Tabreihenfolge ein. Damit werden die oben beschriebenen, unschönen Nebeneffekte von tabindex="0" vermieden.
Fazit
Ich muss gestehen, ich bin von der aktuellen Situation alles andere als begeistert. Nicht einmal ein so einfaches Konzept wie Skiplinks lässt sich problemfrei crossbrowser implementieren – auch nicht in den sonst so hoch gelobten, 100% ACID 3 unterstützenden Vertretern Chrome oder Safari. Erschwerend hinzu kommt, dass man – zumindest soviel ich im Moment weiß – um JavaScript nicht herumkommt.
Nun kann ich mir einerseits auf die Schulter klopfen, wie schlau ich doch bin, weil ich das Problem in den Griff bekommen habe. Doch das kann nicht darüber hinwegtäuschen, dass die Implementation von Skiplinks im normalen Webdesign-Alltag eigentlich nicht mehr als eine Fingerübung sein sollte und kein für Einsteiger fast unüberwindlicher Stolperstein (wer bringt als Einsteiger schon JS- und DOM-Kenntnisse mit).
Freitag, 12.02.10 (15:20 Uhr)
Guter Artikel. Aber zwei Anmerkungen hätte ich dann doch noch.
1. Meinre Ansicht nach gibt es (auf einer Seite) nicht die Skiplinks, sondern den Skiplink (Singular). Ansonsten wird der Sinn des Skiplinks ausgehebelt. Der Sinn ist ja, ellenlange Navigationslisten schnell überspringen zu können, um direkt zum Inhalt zu gelangen. Wenn nun der Skiplink auch wieder eine ellenlange Liste von Skiplinks ist, muss man dann nicht einen Skiplink Skiplink einbauen?
2. Der erste fokussierbare Element heisst nicht, das erste Element überhaupt. Ich habe eine Weile hin und her überlegt, und bin zu dem Schluss gekommen, dass der Skiplink am Besten direkt nach dem Seitentitel plaziert wird. Man könnte das auch umgekehrt machen, aber ich finde, der Titel ist wesentlicher Bestandteil des Inhalts. Daher halte ich eine Positionierung nach dem Titel für sinnvoller.
Freitag, 12.02.10 (15:31 Uhr)
@Siegfried
Es ist durchaus üblich, mehr als nur einen einzigen Skiplink zu setzen, im Beitrag wurden auch ensprechende Beispiele genannt. Insofern ist die Mehrzahl auch richtig gewählt.
Was die Position betrifft, schreibe ich bewusst “das erste focussierbare Element”. Ich bin mir nicht sicher, was Du mit “Titel” meinst? Das <title> Element steht im <head> der Seite und sollte bereits grundlegende Informationen zur Seite enthalten. Die Skiplinks hingegen stehen im <body> und damit grundsätzlich dahinter. Oder was meinst Du?
Freitag, 12.02.10 (15:36 Uhr)
Kleine Korrektur: Ich habe das Verhalten mit Safari und VoiceOver unter Mac OS getestet, nicht mit Safari und NVDA unter Windows. Wie Chrome ist auch Safari unter Windows nicht accessible. Die Webkit-Engine unter Windows muss erst richtig MSAA lernen. Daher: NVDA und IE 8, VoiceOver und Safari auf Mac, und NVDA mit Firefox, wie auch JAWS mit IE 8 und JAWS mit Firefox. Das waren meine Testkombis. :)
Freitag, 12.02.10 (15:41 Uhr)
Danke Marco, für den Hinweis. Ich habe die ungenaue Passage gerade berichtigt.
Freitag, 12.02.10 (15:51 Uhr)
Mit Seitentitel meine ich hier die erste H1 Überschrift. Sorry für den nicht eindeutigen Ausdruck.
Und nein, ich denke nach wie vor, dass ein einzige Skiplink sinnvoller ist. In der Regel ist es doch so, dass nach H1 und Skiplink zunächst die Navigation kommt, dann der Inhalt, dann die Randbemerkungen (oder was auch immer in dem Container rechts steht), dann der Footer. Der Inhalt folgt also i.d.R. im Quelltext nach der Navigation. zur Navigation muss man also nicht per Skiplink springen, sondern kann ganz normal voranschreiten.
Übrigens, wenn die Seite eingeleitet wird von H1, H2, H3 u.s.w., also von Titel und Untertiteln, dann folgt der Skiplink logischerweise nach den Untertiteln. Abr es bleibt das erste fokussierbare Element.
Und noch ein Gedanke am Rand. der Skiplink ist ja nur deswegen nötig, weil aufgrund von Schwächen im CSS der Navigationscontainer vor dem Inhalt stehen muss. Wäre das nicht nötig, könnte man sich den Skiplink sparen. Vielleicht gibt es für XHTML eine solche Möglichkeit. Man könnte den Quelltext so abfassen, wie es für eine audible Version notwendig wäre, und könnte das vor der visuellen Darstellung zunächst durch XSL umsortieren lassen, dann per CSS visuell aufbereiten. Dann könnte man sich den Skiplink sparen. Geht aber natürlich nur mit XHTML.
Freitag, 12.02.10 (16:39 Uhr)
@Siegfried
Es gibt keine Vorschrift oder Regel nach welcher die Reihenfolge von Inhalten im Quelltext festgelegt ist. Die einzelnen Elemente können sonstwo im Quelltext stehen - für die Anordnung am Bildschirm ist CSS zuständig.
Nochmal Nein: Es gibt keine solche CSS-Schwäche. Selbstverständlich kann man per CSS auch eine Navigation vom Ende des Quelltextes auf der Webseite nach oben holen. Zudem definieren sich Skiplinks nicht exklusiv darüber, Navigationselemente zu überspringen.
Freitag, 12.02.10 (17:23 Uhr)
Wenn dem so ist, dann ist ein Skiplink überflüssig.
Freitag, 12.02.10 (17:31 Uhr)
Oder gleich UI Automation. Wäre mal interessant zu erfahren, ob da nicht doch wieder wie so oft irgendwas in der alten Schnittstelle hängenbleibt (soll bei MSAA ja des öfteren vorkommen).
Samstag, 13.02.10 (14:07 Uhr)
Dumme Frage: Kann man das JavaScript nicht noch weiter vereinfachen? Ist z.B. die Browserabfrage überhaupt nötig? Hätte es Nachteile, wenn man einfach allen Browsern diesen Fix spendieren würde?
Anstatt eines umständlichen Selektor-Ansatzes würde ich eher Event-Delegation verwenden. Also zentral bei document.body auf click-Events hören und bei Klicks auf Skiplinks den Fix anwenden.
Außerdem funktionieren getAttribute(‘class’) und setAttribute(‘onclick’, ‘..’) m.W. nur im IE8 und nur im standardkonformen Modus. Für alle müsste es .className bzw. .onclick = function () {..} heißen. Mit dieser Closure spart man sich auch den doppelten getElementById-Aufruf und man spart sich, dass zur Laufzeit nochmal Code geparst wird (das ist quasi ein eval()).
Samstag, 13.02.10 (14:24 Uhr)
Hab das mal beispielhaft umgesetzt:
http://gist.github.com/303421
Samstag, 13.02.10 (15:43 Uhr)
@Molily
Gute Frage: Firefox braucht diesen Fix zumindest nicht. Ältere IE’s auch nur, wenn hasLayout nicht da ist (was zumindest bei YAML überwiegend gesichert ist). Ich wollte lediglich unnötige Codeausführungen vermeiden.
Und Nachteile? Vermutlich nicht, so wie Deine Änderungen aussehen :-). Zwar könnte man mit der Browserabfrage die Ausführung im FF stoppen, das Laden kann man aber eh nicht unterbinden. In jedem Fall Danke für die Codeüberarbeitungen, die übernehme ich gern.
Sonntag, 14.02.10 (11:38 Uhr)
@Molily
Nachtrag Ich habe Deinen Code getestet: Im IE8/Win7 scheint es Probleme zu geben, der Focus wird zwar im IE7-Modus, aber nicht mehr im Standardmodus gesetzt. Mein Ansatz funktioniert auch im IE7 und im Standardmodus des IE8. Im IE7-Modus reagieren beide Lösungen gleich.
Lass uns mal skypen oder telefonieren.
Dirk
Samstag, 06.03.10 (17:06 Uhr)
Manueller Trackback:
Februar 2010 im Kontext
[...] Sollen Skiplinks auch im Safari und Chrome wie vorgesehen funktionieren, lässt sich der Einsatz von Javascript nicht vermeiden. Dirk Jesse stellt aus meiner Sicht die bisher flexibelste Lösung zur Diskussion [...]
Freitag, 12.03.10 (03:47 Uhr)
Hallo Dirk,
an der JavaScript Lösung für die WebKit basierten Browser habe ich mich vor einiger Zeit auch versucht.
HIer meine Lösung
Samstag, 13.03.10 (13:37 Uhr)
@Fritz,
deine Lösung basiert auf fest im Markup verankerten Inline-Events (“onclick”). Sowas würde ich heutzutage niemandem mehr empfehlen.
Der Vorteil der hier vorstellten Lösung ist, dass der Anwender keinen zusätzlichen Markup schreiben muss und zudem nicht nur Webkit-Browser sondern auch der IE8 unterstützt wird. Der Vorschlag von @molily macht die Lösung noch einfacher und robuster.
Samstag, 13.03.10 (14:32 Uhr)
Hallo Dirk,
auf dieser Seite hier scheinst du die beschriebenen Lösung nicht einzusetzen? Zumindest zeigen meine aktuellen WebKit Browser unter Win das falsche Verhalten.
Samstag, 13.03.10 (15:30 Uhr)
@Fritz
Das stimmt. Meine private Webseite betrachte allerdings ich auch nicht als Referenzimplementation aller meiner Entwicklungen. Meine Homepage erhält dann ein YAML-Update, wenn ich Zeit dafür finde. Ich hoffe inständig, dass diese Einstellung bei meinen Lesern keinen negativen Einfluss auf die Qualität des hier vorgestellten Skriptes hat.
Wie Du im Beitrag lesen kannst, ist die hier vorgestellte Lösung seit einiger Zeit bereits in YAML und allen mitgelieferten Beispielen integriert. Wieso schaust Du also nicht einfach mal auf der YAML-Homepage vorbei und überzeugst Dich anhand der zahlreichen Layoutbeispiele von deren Funktion?
Gruß
Dirk
Samstag, 13.03.10 (16:05 Uhr)
Hallo Dirk,
danke für die Erklärung. Ohne online-Demo wäre deine Lösung für mich weniger überzeugend gewesen.