image: teaser.webp:100:50 image_alt: "Der Raum der Hacker-AG: drei Laptops mit Code, einem Spiel und einer Webseite auf einem langen Tisch, am Ende sitzt ein kleiner Roboter und winkt" kind: labor duration: 4 Doppelstunden curriculum: "Sek I: Leben in und mit vernetzten Systemen (Gestalten von Internetseiten, Stylesheets; Gefahren bei E-Mails: Phishing); Oberstufe: Softwareprojekt (Ergonomie), Vertiefung Informatik und Gesellschaft" topics: [webseiten, datensicherheit, gesellschaft, zeichenketten] language: HTML / CSS / Ruby / Python languages: [ruby, python] requires: [static-website] reference: [accessibility, dark-patterns, html, css, ruby-python] parts: true

Fair, barrierefrei, sicher

Die Hacker-AG hat eine Website, die gut aussieht und trotzdem viele aussperrt: Wer nur die Tastatur benutzt, kommt nicht zum Knopf, wer schlecht sieht, kann den Text kaum lesen, und der Cookie-Banner trickst. In diesem Labor findest du die Barrieren und die Tricks und baust sie aus, Schritt für Schritt geprüft. Im dritten Teil geht es um Mails, die so tun, als kämen sie von deiner Schule.

1. Eine Seite für alle

Die Seite der Hacker-AG hat einen Fehler, den man nicht sieht, wenn man sie mit der Maus und guten Augen benutzt. Alle anderen merken ihn sofort: Wer blind ist, lässt sich die Seite von einem Screenreader vorlesen, einem Programm, das ihr HTML liest. Wer die Maus nicht halten kann, springt mit der Tastatur von Link zu Link. Wer schlecht sieht, vergrößert die Seite oder braucht kräftige Farben. Und manche Barrieren treffen alle einmal: helle Sonne auf dem Handy, ein gebrochener Arm, ein Beamer, der Farben verschluckt.

Die Regeln für eine Seite, die alle benutzen können, stehen in den Web Content Accessibility Guidelines (WCAG), im Handbuch unter Barrierefreiheit. Hier wendest du sie an.

Die Seite holen

Öffne ein Terminal in deinem Workspace und hol dir die Seite der AG:

mkdir -p fair-web/bilder
cd fair-web
wget https://workspace.hackschule.de/fair-web/index.html https://workspace.hackschule.de/fair-web/style.css https://workspace.hackschule.de/fair-web/script.js
wget -P bilder https://workspace.hackschule.de/fair-web/bilder/logo.svg https://workspace.hackschule.de/fair-web/bilder/ag.svg

Öffne fair-web/index.html in VS Code und zeig sie mit »Go Live« im Browser an, wie in Statische Webseiten. Sieh sie dir eine Minute lang an: Ein Banner für Cookies, ein Menü, ein Bild, Termine, ein Formular.

Benutzen wie andere

Leg die Maus zur Seite. Klick einmal in die Seite im Browser, damit sie die Tastatur bekommt; mit Tab springst du dann zum nächsten Link, Knopf oder Feld, mit Shift+Tab zurück, mit Enter drückst du einen Knopf.

Vermutung 1Mit der Tastatur zum Knopf

Bevor du es ausprobierst: Wie oft musst du auf der Seite der AG Tab drücken, bis der Knopf »Anmelden« unter dem Formular gewählt ist?

Mit der Tastatur zum Knopf
Ausprobieren: Drück Tab immer wieder und zähl mit. Siehst du, wo du gerade bist? Kommst du zum Knopf »Anmelden«? Vergrößere die Seite dann mit Strg++ auf 200 %: Was rutscht aus dem Bild, was wird unlesbar?

Hier geht es weiter, sobald du deine Antwort oben abgegeben hast.

Der Knopf »Anmelden« ist gar nicht erreichbar: Er ist ein Element div mit onclick, und ein div bekommt die Tastatur nie. Das Formular lässt sich so ohne Maus nicht abschicken. Und du siehst nicht einmal, wo du bist, weil outline: none in style.css den Rahmen um das gewählte Element weggenommen hat.

Ein Screenreader liest nicht, was auf dem Bildschirm steht, sondern das HTML dahinter. So klingt eine Seite, deren HTML nicht sagt, was sie ist:

Ausprobieren: Vergleiche Zeile für Zeile, was der Screenreader links und rechts sagt. Wer die Seite nur hört: Woran merkt er links, dass es weitergeht, und wie kommt er dorthin?

Auf deinem Handy oder Rechner ist ein echter Screenreader eingebaut: VoiceOver auf dem iPhone und dem Mac, TalkBack auf Android, die Sprachausgabe von Windows (Strg+Windows+Enter schaltet sie an und aus). Wer ihn einmal eine Minute lang benutzt hat, baut Seiten anders.

Bilder und Sprache

Ein Bild ist für einen Screenreader nur eine Datei, bis das Attribut alt sagt, was es zeigt. Fehlt es, liest er den Dateinamen vor, Buchstabe für Buchstabe oder als »ag punkt s v g«. Ein guter Alt-Text sagt, was ein Mensch auf dem Bild sieht und was davon für die Seite wichtig ist, in einem Satz. Ein Bild, das nur schmückt, bekommt alt="": Dann überspringt der Screenreader es.

Die Seite hat zwei Bilder. Das Logo neben dem Menü sagt, wessen Seite das ist. Das große Bild zeigt den Raum der AG:

Der Raum der AG: drei Laptops auf einem langen Tisch, auf dem ersten Code, auf dem zweiten ein Spiel, auf dem dritten eine Webseite; am Ende des Tischs sitzt ein kleiner Roboter und winkt

Aufgabe 2Ein guter Alt-Text

Welcher Alt-Text passt zum großen Bild der AG?

Ein guter Alt-Text

Die Sprache der Seite gehört in das erste Element, <html lang="de">. Fehlt sie, liest ein Screenreader, der auf Englisch eingestellt ist, den deutschen Text mit englischer Aussprache vor, und die Silbentrennung des Browsers rät.

Aufgabe 3Die Sprache der Seite

<html> in fair-web/index.html sagt, in welcher Sprache die Seite ist.

Aufgabe 4Alt-Texte

Jedes Bild in fair-web/index.html hat ein alt, das sagt, was es zeigt, und keinen Dateinamen.

Formulare

In ein Feld mit placeholder steht ein grauer Text, solange es leer ist. Er verschwindet, sobald man tippt, und manche Screenreader lesen ihn gar nicht. Was in ein Feld gehört, sagt ein <label>, verbunden mit dem Feld über for und id:

<label for="name">Dein Name</label>
<input id="name" type="text" autocomplete="name">

Ein Klick auf den Text setzt dann auch den Cursor ins Feld, eine größere Fläche zum Treffen. autocomplete sagt dem Browser, was hinein soll, damit er es vorschlagen kann: name, email, tel.

Ein Fehler, den nur ein roter Rahmen zeigt, sieht niemand, der Rot und Grün schlecht unterscheidet, und ein Screenreader sagt nichts davon. Zu einem Fehler gehört ein Satz, der sagt, was fehlt, neben dem Feld:

<p id="name-error" class="error-text" hidden>Bitte gib deinen Namen ein.</p>

Das Attribut hidden versteckt ihn, bis script.js es entfernt; mit aria-describedby="name-error" am Feld liest der Screenreader ihn zum Feld vor.

Aufgabe 5Beschriftete Felder

Jedes Feld in fair-web/index.html, auch das Häkchen, hat ein <label>.

Tipp

Ein <label for="name"> gehört zu dem Feld mit id="name". Die drei Felder haben ihre id schon.

Nächster Tipp

Beim Häkchen wird der Satz daneben zum Label: <label for="newsletter">…</label> um den Text.

Knöpfe und Links

Ein <button> kann von selbst, was ein Knopf braucht: Er bekommt die Tastatur, reagiert auf Enter und die Leertaste, und der Screenreader sagt »Schalter«. Ein <div onclick> kann nichts davon. Im Formular schickt ein <button type="submit"> das Formular ab; script.js fängt das mit einem Listener für submit ab, statt mit onclick an einem Element:

document.getElementById('form').addEventListener('submit', (event) => {
  event.preventDefault();
  // hier prüfen, was eingetippt wurde
});

Auch das »↑« rechts unten ist ein <div>. Was zu einer Stelle der Seite springt, ist ein Link: <a href="#" class="top">Nach oben</a>.

Screenreader haben eine Liste aller Links einer Seite, damit man nicht alles anhören muss. Auf der Seite der AG steht dort zweimal »hier klicken«.

Aufgabe 6Die Liste der Links

Ein Screenreader zeigt auf Wunsch eine Liste aller Links der Seite, mit ihrem Text. Was steht für die Seite der AG darin?

Die Liste der Links

Ein Link sagt, wohin er führt: <a href="#termine">Termine</a>. Und ein Menü ist ein <nav>, das kommt im Abschnitt »Struktur«.

Aufgabe 7Echte Knöpfe

In fair-web/index.html ist nichts mehr ein <div> mit onclick, weder »Anmelden« noch das »↑«.

Tipp

Wenn »Anmelden« ein <button type="submit"> ist, ruft niemand mehr send() auf. Häng den Code in script.js an das Formular, wie im Codeblock oben.

Farbe und Kontrast

Wie gut sich Text von seinem Hintergrund abhebt, misst das Kontrastverhältnis, von 1 : 1 (Grau auf demselben Grau) bis 21 : 1 (Schwarz auf Weiß). Die WCAG verlangen für normalen Text mindestens 4,5 : 1, für großen Text (ab 24 Pixel, fett ab knapp 19) mindestens 3 : 1.

Ausprobieren: Schieb das Grau langsam dunkler: Ab welchem Grau reicht es für großen Text, ab welchem für normalen? Ab wann kannst du es selbst bequem lesen?

Und manches sagt die Seite nur mit Farbe. Wer eine Rot-Grün-Schwäche hat, in Europa etwa jeder zwölfte Mann, sieht Rot und Grün als ähnliche Brauntöne:

Ausprobieren: Stell »Rot-Grün-Schwäche (grün)« ein: Welche Aufgaben sind links gelöst, welche rechts? Welches Feld hat einen Fehler? Auf der Seite der AG sagt nur die Farbe, welcher Termin ausfällt.

Wie ein Browser das Verhältnis ausrechnet, steht in den WCAG, und du kannst es selbst programmieren. Zuerst bekommt jede Farbe eine relative Leuchtdichte \(L\) zwischen 0 (Schwarz) und 1 (Weiß). Eine Farbe wie #2b8a3e besteht aus drei Kanälen, Rot 2b, Grün 8a und Blau 3e, jeder eine Zahl von 0 bis 255 in hexadezimaler Schreibweise. Jeder Kanal wird erst durch 255 geteilt, dann linearisiert: Ein Bildschirm macht die Zahlen nicht gleichmäßig hell, die Hälfte von 255 leuchtet viel weniger als halb so hell.

$$c' = \frac{c}{255}, \qquad c_{\mathrm{lin}} = \begin{cases} \dfrac{c'}{12{,}92} & \text{wenn } c' \le 0{,}04045 \\[1.2ex] \left(\dfrac{c' + 0{,}055}{1{,}055}\right)^{2{,}4} & \text{sonst} \end{cases}$$

Dann zählt jeder Kanal so viel, wie das Auge ihn wahrnimmt, Grün am meisten, Blau am wenigsten:

$$L = 0{,}2126 \cdot R_{\mathrm{lin}} + 0{,}7152 \cdot G_{\mathrm{lin}} + 0{,}0722 \cdot B_{\mathrm{lin}}$$

Aufgabe 8Die Leuchtdichte einer FarbeÜbung

Schreib luminance(color). color ist ein String wie '#2b8a3e'; die Funktion gibt die relative Leuchtdichte nach den Formeln oben zurück, 0 für Schwarz und 1 für Weiß.

Tipp

color[1, 2] sind die beiden Zeichen nach dem #, also Rot, color[3, 2] Grün, color[5, 2] Blau. '8a'.to_i(16) liest sie als hexadezimale Zahl: 138.

Nächster Tipp

Für jeden Kanal: durch 255.0 teilen, dann mit if c <= 0.04045 die eine oder die andere Formel. ** ist die Potenz: x**2.4.

Nächster Tipp

Ein Gerüst: ruby def luminance(color) channels = [color[1, 2], color[3, 2], color[5, 2]].map do |hex| c = hex.to_i(16) / 255.0 # hier: c linearisieren end # hier: die drei Kanäle gewichtet addieren end

Aufgabe 8Die Leuchtdichte einer FarbeÜbung

Schreib luminance(color). color ist ein String wie '#2b8a3e'; die Funktion gibt die relative Leuchtdichte nach den Formeln oben zurück, 0 für Schwarz und 1 für Weiß.

Tipp

color[1:3] sind die beiden Zeichen nach dem #, also Rot, color[3:5] Grün, color[5:7] Blau. int('8a', 16) liest sie als hexadezimale Zahl: 138.

Nächster Tipp

Für jeden Kanal: durch 255 teilen, dann mit if c <= 0.04045 die eine oder die andere Formel. ** ist die Potenz: x ** 2.4.

Nächster Tipp

Ein Gerüst: python def luminance(color): channels = [] for i in (1, 3, 5): c = int(color[i:i + 2], 16) / 255 # hier: c linearisieren und an channels anhängen # hier: die drei Kanäle gewichtet addieren

Das Verhältnis zweier Farben ist dann der Quotient ihrer Leuchtdichten, die hellere oben, mit 0,05 auf beiden Seiten für das Licht, das immer auf einen Bildschirm fällt:

$$\mathrm{Kontrast} = \frac{L_{\mathrm{hell}} + 0{,}05}{L_{\mathrm{dunkel}} + 0{,}05}$$

Vermutung 9Reicht dieses Grau?

Bevor du rechnest: Reicht #777777, ein mittleres Grau, auf Weiß für normalen Text, also für 4,5 : 1?

Reicht dieses Grau?
Aufgabe 10Das KontrastverhältnisEinstieg

Schreib contrast_ratio(a, b): das Kontrastverhältnis zweier Farben, egal in welcher Reihenfolge sie kommen. Kopiere deine Funktion luminance aus der Aufgabe darüber dazu.

Tipp

luminance('#000000') ist 0, luminance('#ffffff') ist 1. Mit der Formel: (1 + 0,05) / (0 + 0,05) = 21.

Nächster Tipp

[x, y].max und [x, y].min geben die größere und die kleinere von zwei Zahlen.

Aufgabe 10Das KontrastverhältnisEinstieg

Schreib contrast_ratio(a, b): das Kontrastverhältnis zweier Farben, egal in welcher Reihenfolge sie kommen. Kopiere deine Funktion luminance aus der Aufgabe darüber dazu.

Tipp

luminance('#000000') ist 0, luminance('#ffffff') ist 1. Mit der Formel: (1 + 0,05) / (0 + 0,05) = 21.

Nächster Tipp

max(x, y) und min(x, y) geben die größere und die kleinere von zwei Zahlen.

Hier geht es weiter, sobald du deine Antwort oben abgegeben hast.

#777777 auf Weiß hat 4,48 : 1 und reicht knapp nicht; #767676, eine Stufe dunkler, hat 4,54 : 1 und ist das hellste Grau, das auf Weiß für normalen Text genügt. Mit bloßem Auge siehst du den Unterschied nicht. Darum rechnet man nach.

Ausprobieren: Rechne mit deiner Funktion die Farben aus style.css nach: #999999 auf Weiß, die grünen und roten Termine, der weiße Text auf dem blauen Knopf. Welches Paar ist am schlechtesten?

Ändere style.css, bis jede Schrift auf ihrem Hintergrund 4,5 : 1 erreicht, auch im Banner und auf dem Knopf. Dunkler wird es, wenn du die Zahlen kleiner machst: #333333 statt #999999. Damit der ausgefallene Termin nicht nur rot ist, schreib dazu, was mit ihm ist: <strong>(fällt aus)</strong>.

Aufgabe 11Genug Kontrast

Jede Schrift in fair-web/style.css erreicht auf ihrem Hintergrund mindestens 4,5 : 1.

Tipp

Die Meldung nennt die Regel, die Farben und ihr Verhältnis. Rechne mit deiner Funktion nach, wie dunkel die Schrift sein muss.

Nächster Tipp

Eine Farbe wird dunkler, wenn ihre drei Zahlen kleiner werden, #2b8a3e zu #1e6b30. Auf einem farbigen Hintergrund mit weißer Schrift wird der Hintergrund dunkler.

Struktur

Ein Screenreader springt von Überschrift zu Überschrift, so wie du beim Überfliegen einer Seite. Die Ebenen sagen dabei, was wozu gehört: Nach einem <h1> kommt <h2>, darunter <h3>. Die Seite der AG springt von <h1> gleich zu <h4>, weil <h4> in style.css so groß ist, wie es aussehen sollte. Die Größe einer Überschrift gehört ins CSS, ihre Ebene ins HTML.

Dazu kommen die Bereiche der Seite: <nav> für das Menü, <main> für den Inhalt. Mit ihnen springt ein Screenreader direkt zum Inhalt, statt jedes Mal das Menü anzuhören.

Aufgabe 12Überschriften der Reihe nach

Die Überschriften in fair-web/index.html überspringen keine Ebene.

Tipp

Aus <h4> wird <h2>. Damit die Überschriften so groß bleiben, ändere in style.css auch h4 { in h2 {.

Aufgabe 13Menü und Inhalt

In fair-web/index.html ist das Menü ein <nav>, der Inhalt steht in einem <main>.

Fokus und Bewegung

Wer mit der Tastatur arbeitet, muss sehen, wo er gerade ist. Browser zeichnen dafür einen Rahmen um das Element, das den Fokus hat; outline: none in style.css nimmt ihn allen weg. Lösch die Regel und gib dem Fokus einen eigenen, gut sichtbaren Rahmen. :focus-visible gilt nur, wenn die Tastatur den Fokus gebracht hat, nicht bei einem Klick mit der Maus:

:focus-visible {
  outline: 3px solid #1a5fb4;
  outline-offset: 2px;
}
Aufgabe 14Ein sichtbarer Fokus

fair-web/style.css gibt :focus-visible einen Rahmen mit outline.

Was blinkt, lenkt ab, und manche Menschen bekommen von Bewegung auf dem Bildschirm Kopfschmerzen oder Schwindel. Im Betriebssystem lässt sich »Bewegung reduzieren« einschalten, und eine Seite kann das abfragen:

@media (prefers-reduced-motion: reduce) {
  .scarcity {
    animation: none;
  }
}

Ob die Zeile mit dem Countdown überhaupt bleiben sollte, kommt im nächsten Teil.

Aufgabe 15Weniger Bewegung

fair-web/style.css hält die Animation an, wenn jemand »Bewegung reduzieren« eingeschaltet hat.

Prüfen

Browser haben Werkzeuge, die vieles davon selbst finden. In Chrome und Edge heißt es Lighthouse (F12, Reiter »Lighthouse«, nur »Barrierefreiheit« ankreuzen), in Firefox gibt es im selben Fenster den Reiter »Barrierefreiheit«. Lass deine Seite prüfen.

Was ein Werkzeug nicht findet: ob ein Alt-Text zum Bild passt, ob ein Link sagt, wohin er führt, ob die Reihenfolge der Seite Sinn ergibt. Dafür braucht es einen Menschen, am besten einen, der die Seite wirklich mit Screenreader oder Tastatur benutzt.

Websites von Behörden müssen barrierefrei sein und eine Erklärung zur Barrierefreiheit haben, die sagt, was es noch nicht ist (das Handbuch, Was das Gesetz sagt).

Aufgabe 16Deine Erklärung zur Barrierefreiheit

Lass Lighthouse oder den Reiter »Barrierefreiheit« von Firefox über deine Seite laufen. Schreib dann in ein paar Sätzen eine Erklärung zur Barrierefreiheit für die Seite der AG: Was hast du geändert, was ist noch nicht barrierefrei, und was kann kein Werkzeug für dich prüfen?

Mindestens 30 Wörter. Nur du und deine Lehrkraft können die Antwort lesen.

Weiter mit Teil 2: Fair statt trickreich