Wir alle suchen nach einfachen Möglichkeiten, unsere Websites und Apps zugänglicher zu gestalten. Eine der einfacheren Dinge, die wir tun können, ist sicherzustellen, dass die von uns verwendeten Farben für die Augen angenehm sind. Hoher Farbkontrast ist etwas, das jedem zugutekommt. Es reduziert nicht nur die Augenbelastung im Allgemeinen, sondern ist entscheidend für Menschen mit eingeschränktem Sehvermögen.
Lassen Sie uns also nicht nur bessere Farbkombinationen in unseren Designs verwenden, sondern auch einen Weg finden, um die Implementierung hoher Kontraste für uns zu erleichtern. Es gibt eine spezielle Strategie, die wir bei Oomph verwenden, bei der eine Sass-Funktion die gesamte schwere Arbeit für uns erledigt. Ich werde Sie durch den Prozess führen, wie wir das zusammenstellen.
Möchten Sie direkt zum Code springen, weil Sie bereits alles über Farbzugänglichkeit wissen? Hier entlang.
Was wir unter „zugänglichen Farbkombinationen“ verstehen
Farbkontrast ist auch eines dieser Dinge, bei denen wir denken, wir hätten es im Griff. Aber hoher Farbkontrast bedeutet mehr als nur das Betrachten eines Designs. Es gibt verschiedene Stufen akzeptabler Kriterien, die die WCAG als zugänglich definiert hat. Es ist tatsächlich demütigend, den WebAIM Contrast Checker zu öffnen und die Farbkombinationen einer Website damit zu testen.
Mein Team hält sich standardmäßig an die Level AA-Richtlinien der WCAG. Das bedeutet:
- Text, der 24px und größer oder 19px und größer ist, wenn fett gedruckt, sollte ein Farbkontrastverhältnis (CCR) von 3,0:1 haben.
- Text, der kleiner als 24px ist, sollte ein CCR von 4,5:1 haben.
Wenn eine Website die erweiterten Richtlinien für Level AAA einhalten muss, sind die Anforderungen etwas höher
- Text, der 24px und größer oder 19px und größer ist, wenn fett gedruckt, sollte ein CCR von 4,5:1 haben.
- Text, der kleiner als 24px ist, sollte ein CCR von 7:1 haben.
Verhältnisse? Hä? Ja, da steckt etwas Mathematik dahinter. Aber die gute Nachricht ist, dass wir das nicht selbst tun müssen und nicht einmal das gleiche gründliche Verständnis dafür haben müssen, wie sie berechnet werden, wie Stacie Arellano kürzlich beschrieben hat (was ein Muss ist, wenn Sie sich für die Wissenschaft der Farbzugänglichkeit interessieren).
Dafür ist Sass da. Wir können es nutzen, um schwierige mathematische Berechnungen durchzuführen, die uns sonst alle über den Kopf gehen würden. Aber zuerst denke ich, es lohnt sich, sich mit zugänglichen Farben auf Designebene zu befassen.
Zugängliche Farbpaletten beginnen mit dem Design
Das ist richtig. Der Kern der Arbeit an einer zugänglichen Farbpalette beginnt mit dem Design. Idealerweise sollte jedes Webdesign ein Tool konsultieren, um zu überprüfen, ob alle verwendeten Farbkombinationen die etablierten Richtlinien bestehen – und dann die Farben anpassen, die nicht bestehen. Wenn unser Designteam dies tut, verwenden wir ein Tool, das wir intern entwickelt haben. Es arbeitet mit einer Liste von Farben, testet sie auf einem dunklen und einem hellen Hintergrund und bietet auch eine Möglichkeit, andere Kombinationen zu testen.

Das ist das Erste, was unser Team tut. Ich würde vermuten, dass viele Markenfarben nicht mit Blick auf Barrierefreiheit ausgewählt werden. Ich stelle oft fest, dass diese Farben geändert werden müssen, wenn sie in ein Webdesign übersetzt werden. Durch Aufklärung, Gespräche und visuelle Beispiele holen wir uns die Zustimmung des Kunden für die neue Farbpalette ein. Ich gebe zu: Dieser Teil kann schwieriger sein als die eigentliche Arbeit, zugängliche Farbkombinationen zu implementieren.

Das Problem, das ich mit Automatisierung lösen wollte, sind die Ausnahmefälle. Man kann einem Designer keinen Vorwurf machen, wenn er eine Kombination von zwei Farben übersieht, die auf eine unbeabsichtigte Weise zusammenwirken – das passiert einfach. Und diese Ausnahmefälle werden auftreten, sei es während der Entwicklung oder sogar ein Jahr später, wenn dem System neue Farben hinzugefügt werden.
Entwicklung für Barrierefreiheit unter Beibehaltung der Absicht eines Farbsystems
Der Trick bei der Änderung von Farben, um die Anforderungen an die Barrierefreiheit zu erfüllen, besteht darin, sie nicht so stark zu ändern, dass sie nicht mehr wie die gleiche Farbe aussehen. Eine Marke, die ihre smaragdgrüne Farbe liebt, möchte die Absicht dieser Farbe beibehalten – ihre „Smaragd-heit“. Damit sie für Barrierefreiheit geeignet ist, wenn sie als Text auf weißem Hintergrund verwendet wird, müssen wir das Grün möglicherweise abdunkeln und seine Sättigung erhöhen. Aber wir wollen immer noch, dass die Farbe genauso „liest“ wie die Originalfarbe.
Um dies zu erreichen, verwenden wir das HSL-Farbmodell (Hue Saturation Lightness). HSL gibt uns die Möglichkeit, den Farbton (Hue) beizubehalten, aber die Sättigung (d.h. die Farbintensität erhöhen oder verringern) und die Helligkeit (d.h. mehr Schwarz oder mehr Weiß hinzufügen) anzupassen. Der Farbton bestimmt, ob ein Grün dieses Grün oder ein Blau dieses Blau ist. Es ist die „Seele“ der Farbe, um es etwas mystisch auszudrücken.
Farbton (Hue) wird als Farbrad mit einem Wert zwischen 0° und 360° dargestellt – Gelb bei 60°, Grün bei 120°, Cyan bei 180° usw. Sättigung ist ein Prozentsatz von 0 % (keine Sättigung) bis 100 % (volle Sättigung). Helligkeit ist ebenfalls ein Wert von 0 % bis 100 %, wobei 0 % keine Helligkeit (kein Schwarz), 50 % keine Schwarz- und Weißanteile und 100 % volle Helligkeit (sehr hell) bedeuten.
Eine schnelle visuelle Darstellung, wie das Anpassen einer Farbe in unserem Tool aussieht

Um mehr zu erfahren, spielen Sie mit dem lustigen HSL-Visualisierer mothereffinghsl.com. Eine ausführlichere Beschreibung von Farbenblindheit, WCAG-Farbkontraststufen und dem HSL-Farbraum finden Sie in unserem ausführlichen Blogbeitrag.
Der Anwendungsfall, den ich lösen möchte
Designer können Farben mit den gerade besprochenen Tools anpassen, aber bisher konnte kein Sass, das ich gefunden habe, dies mit mathematischer Magie tun. Es musste einen Weg geben.
Hier sind einige ähnliche Ansätze, die ich in der Praxis gesehen habe
- Eine Idee von Josh Bader verwendet CSS-Variablen und Farben, die in ihre RGB-Werte aufgeteilt sind, um zu berechnen, ob Weiß oder Schwarz die beste zugängliche Farbe für eine gegebene Situation ist.
- Eine weitere Idee von Facundo Corradini macht etwas Ähnliches mit HSL-Werten und einer sehr coolen „Switch-Funktion“ in CSS.
Diese Ansätze mochte ich nicht. Ich wollte nicht auf Weiß oder Schwarz zurückgreifen. Ich wollte, dass die Farben beibehalten, aber zugänglich gemacht werden. Außerdem erschien die Umwandlung von Farben in ihre RGB- oder HSL-Komponenten und deren Speicherung mit CSS-Variablen für eine große Codebasis unübersichtlich und nicht nachhaltig.
Ich wollte einen Präprozessor wie Sass verwenden, um Folgendes zu tun: Gegeben zwei Farben, passe automatisch eine davon an, damit das Paar eine bestandene WCAG-Bewertung erhält. Die Regeln besagen auch ein paar andere Dinge zu berücksichtigen – Schriftgröße und ob die Schrift fett ist. Die Lösung musste dies berücksichtigen.
In Codebegriffen wollte ich Folgendes tun
// Transform this non-passing color pair:
.example {
background-color: #444;
color: #0094c2; // a 2.79 contrast ratio when AA requires 4.5
font-size: 1.25rem;
font-weight: normal;
}
// To this passing color pair:
.example {
background-color: #444;
color: #00c0fc; // a 4.61 contrast ratio
font-size: 1.25rem;
font-weight: normal;
}
Eine Lösung, die dies tut, wäre in der Lage, die zuvor erwähnten Ausnahmefälle zu erkennen und zu behandeln. Vielleicht hat der Designer die Markenblau-Farbe für die Verwendung über Hellblau berücksichtigt, aber nicht über Hellgrau. Vielleicht muss das Rot, das in Fehlermeldungen verwendet wird, für dieses eine Formular, das eine einmalige Hintergrundfarbe hat, angepasst werden. Vielleicht möchten wir eine Dark-Mode-Funktion zur Benutzeroberfläche hinzufügen, ohne alle Farben erneut testen zu müssen. Dies sind die Anwendungsfälle, die ich im Sinn hatte.
Mit Formeln kann Automatisierung kommen
Das W3C hat der Community Formeln zur Verfügung gestellt, die helfen, zwei zusammen verwendete Farben zu analysieren. Die Formel multipliziert die RGB-Kanäle beider Farben mit magischen Zahlen (ein visuelles Gewicht basierend darauf, wie Menschen diese Farbkanäle wahrnehmen) und teilt sie dann, um ein Verhältnis von 0,0 (kein Kontrast) bis 21,0 (voller Kontrast, nur möglich mit Weiß und Schwarz) zu erhalten. Obwohl unvollkommen, ist dies die Formel, die wir derzeit verwenden
If L1 is the relative luminance of a first color
And L2 is the relative luminance of a second color, then
- Color Contrast Ratio = (L1 + 0.05) / (L2 + 0.05)
Where
- L = 0.2126 * R + 0.7152 * G + 0.0722 * B
And
- if R sRGB <= 0.03928 then R = R sRGB /12.92 else R = ((R sRGB +0.055)/1.055) ^ 2.4
- if G sRGB <= 0.03928 then G = G sRGB /12.92 else G = ((G sRGB +0.055)/1.055) ^ 2.4
- if B sRGB <= 0.03928 then B = B sRGB /12.92 else B = ((B sRGB +0.055)/1.055) ^ 2.4
And
- R sRGB = R 8bit /255
- G sRGB = G 8bit /255
- B sRGB = B 8bit /255
Obwohl die Formel komplex aussieht, ist es nur Mathematik, richtig? Nun, nicht so schnell. Es gibt einen Teil am Ende einiger Zeilen, wo der Wert mit einer Dezimalpotenz multipliziert wird – hoch 2,4 potenziert. Haben Sie das bemerkt? Es stellt sich heraus, dass es sich um komplexe Mathematik handelt, die die meisten Programmiersprachen bewältigen können – denken Sie an die `math.pow()`-Funktion von Javascript –, aber Sass ist nicht leistungsfähig genug, um dies zu tun.
Es muss einen anderen Weg geben…
Natürlich gibt es den. Es hat nur etwas gedauert, ihn zu finden. 🙂
Meine erste Version verwendete eine komplexe Reihe von mathematischen Berechnungen, die die Arbeit mit Dezimalpotenzen innerhalb der begrenzten Möglichkeiten von Sass erledigten. Viel Googeln fand Leute, die viel intelligenter waren als ich, die Funktionen lieferten. Leider erhöhte die Berechnung nur einer Handvoll von Farbkontrastkombinationen die Sass-Build-Zeiten exponentiell. Das bedeutet also, Sass kann es tun, aber das heißt nicht, dass es es sollte. In der Produktion könnten die Build-Zeiten für eine große Codebasis auf mehrere Minuten ansteigen. Das ist nicht akzeptabel.
Nach weiterem Googeln stieß ich auf einen Beitrag von jemandem, der etwas Ähnliches versuchte. Auch er stieß auf den Mangel an Exponentienerstützung in Sass. Er wollte „die Möglichkeit der Newtonschen Approximation für die Bruchteile des Exponenten“ untersuchen. Ich verstehe den Impuls total (nicht). Stattdessen beschloss er, eine „Lookup-Tabelle“ zu verwenden. Das ist eine geniale Lösung. Anstatt jedes Mal von Grund auf zu rechnen, liefert eine Lookup-Tabelle alle möglichen Antworten, die vorab berechnet wurden. Die Sass-Funktion ruft die Antwort aus der Liste ab und ist fertig.
Mit ihren Worten
Der einzige Teil [des Sass, der] eine Potenzierung beinhaltet, sind die kanalbezogenen Farbkonvertierungen, die als Teil der Luminanzberechnung durchgeführt werden. [Es] gibt nur 256 mögliche Werte für jeden Kanal. Das bedeutet, wir können leicht eine Lookup-Tabelle erstellen.
Jetzt geht es voran. Ich hatte eine performantere Richtung gefunden.
Anwendungsbeispiel
Die Verwendung der Funktion sollte einfach und flexibel sein. Gegeben eine Menge von zwei Farben, passe die erste Farbe so an, dass sie den korrekten Kontrastwert für das gegebene WCAG-Level bei Verwendung mit der zweiten Farbe erzielt. Optionale Parameter berücksichtigen auch die Schriftgröße oder Fettdruck.
// @function a11y-color(
// $color-to-adjust,
// $color-that-will-stay-the-same,
// $wcag-level: 'AA',
// $font-size: 16,
// $bold: false
// );
// Sass sample usage declaring only what is required
.example {
background-color: #444;
color: a11y-color(#0094c2, #444); // a 2.79 contrast ratio when AA requires 4.5 for small text that is not bold
}
// Compiled CSS results:
.example {
background-color: #444;
color: #00c0fc; // which is a 4.61 contrast ratio
}
Ich habe eine Funktion anstelle eines Mixins verwendet, weil ich die Ausgabe eines einzelnen Wertes, unabhängig von einer CSS-Regel, bevorzugte. Mit einer Funktion kann der Autor bestimmen, welche Farbe geändert werden soll.
Ein Beispiel mit mehr Parametern sieht so aus
// Sass
.example-2 {
background-color: a11y-color(#0094c2, #f0f0f0, 'AAA', 1.25rem, true); // a 3.06 contrast ratio when AAA requires 4.5 for text 19px or larger that is also bold
color: #f0f0f0;
font-size: 1.25rem;
font-weight: bold;
}
// Compiled CSS results:
.example-2 {
background-color: #087597; // a 4.6 contrast ratio
color: #f0f0f0;
font-size: 1.25rem;
font-weight: bold;
}
Ein tieferer Einblick in das Herz der Sass-Funktion
Um den Ansatz zu erklären, gehen wir Schritt für Schritt durch, was die endgültige Funktion tut. Es gibt viele Hilfsfunktionen unterwegs, aber die Kommentare und die Logik in der Kernfunktion erklären den Ansatz
// Expected:
// $fg as a color that will change
// $bg as a color that will be static and not change
// Optional:
// $level, default 'AA'. 'AAA' also accepted
// $size, default 16. PX expected, EM and REM allowed
// $bold, boolean, default false. Whether or not the font is currently bold
//
@function a11y-color($fg, $bg, $level: 'AA', $size: 16, $bold: false) {
// Helper: make sure the font size value is acceptable
$font-size: validate-font-size($size);
// Helper: With the level, font size, and bold boolean, return the proper target ratio. 3.0, 4.5, or 7.0 results expected
$ratio: get-ratio($level, $font-size, $bold);
// Calculate the first contrast ratio of the given pair
$original-contrast: color-contrast($fg, $bg);
@if $original-contrast >= $ratio {
// If we pass the ratio already, return the original color
@return $fg;
} @else {
// Doesn't pass. Time to get to work
// Should the color be lightened or darkened?
// Helper: Single color input, 'light' or 'dark' as output
$fg-lod: light-or-dark($fg);
$bg-lod: light-or-dark($bg);
// Set a "step" value to lighten or darken a color
// Note: Higher percentage steps means faster compile time, but we might overstep the required threshold too far with something higher than 5%
$step: 2%;
// Run through some cases where we want to darken, or use a negative step value
@if $fg-lod == 'light' and $bg-lod == 'light' {
// Both are light colors, darken the fg (make the step value negative)
$step: - $step;
} @else if $fg-lod == 'dark' and $bg-lod == 'light' {
// bg is light, fg is dark but does not pass, darken more
$step: - $step;
}
// Keeping the rest of the logic here, but our default values do not change, so this logic is not needed
//@else if $fg-lod == 'light' and $bg-lod == 'dark' {
// // bg is dark, fg is light but does not pass, lighten further
// $step: $step;
//} @else if $fg-lod == 'dark' and $bg-lod == 'dark' {
// // Both are dark, so lighten the fg
// $step: $step;
//}
// The magic happens here
// Loop through with a @while statement until the color combination passes our required ratio. Scale the color by our step value until the expression is false
// This might loop 100 times or more depending on the colors
@while color-contrast($fg, $bg) < $ratio {
// Moving the lightness is most effective, but also moving the saturation by a little bit is nice and helps maintain the "power" of the color
$fg: scale-color($fg, $lightness: $step, $saturation: $step/2);
}
@return $fg;
}
}
Die finale Sass-Datei
Hier ist die gesamte Sammlung von Funktionen! Öffnen Sie dies in CodePen, um die Farbvariablen oben in der Datei zu bearbeiten und die Anpassungen zu sehen, die Sass vornimmt
Alle Hilfsfunktionen sind vorhanden, ebenso wie die 256-zeilige Lookup-Tabelle. Viele Kommentare sollten den Leuten helfen zu verstehen, was vor sich geht.
Wenn ein Ausnahmefall aufgetreten ist, war eine Version in SassMeister mit Debug-Ausgabe hilfreich, während ich sie entwickelte, um zu sehen, was passieren könnte. (Ich habe die Hauptfunktion in ein Mixin umgewandelt, damit ich die Ausgabe debuggen kann.) Fühlen Sie sich frei, auch das zu untersuchen.
Spielen Sie mit diesem Gist auf SassMeister.
Und schließlich wurden die Funktionen aus CodePen herausgelöst und in ein GitHub-Repository gestellt. Reichen Sie Probleme in die Warteschlange ein, wenn Sie auf Probleme stoßen.
Coole Sache! Aber kann ich das in der Produktion verwenden?
Vielleicht.
Ich würde gerne Ja sagen, aber ich arbeite schon eine Weile an diesem kniffligen Problem. Ich bin zuversichtlich in diesem Code, hätte aber gerne mehr Input. Verwenden Sie ihn für ein kleines Projekt und testen Sie ihn ausgiebig. Teilen Sie mir mit, wie die Build-Zeiten ausfallen. Teilen Sie mir mit, ob Sie auf Ausnahmefälle stoßen, bei denen Farbwerte nicht korrekt übergeben werden. Reichen Sie Probleme im GutHub-Repo ein. Schlagen Sie Verbesserungen auf Basis von anderen Codes vor, die Sie in der Praxis gesehen haben.
Ich würde gerne sagen, dass ich alle A11y-Dinge automatisiert habe, aber ich weiß auch, dass sie auf Herz und Nieren geprüft werden muss, bevor sie als Produktionsreif™ bezeichnet werden kann. Ich bin begeistert, sie der Welt vorzustellen. Danke fürs Lesen und ich hoffe, bald zu hören, wie Sie sie verwenden.
Sehr cool. Dinge wie diese lassen mich „Design“ in Code mehr lieben als Designprogramme
Auch mit Ausnahmefällen wird das sehr nützlich sein.
Danke, ich werde das testen und Feedback geben.
Vielen Dank für dieses großartige Stück Code!
Ich habe eine Weile mit CCR gekämpft. Ich erstelle barrierefreie Websites für Kommunen und das war immer ein echter Schmerz...
Ich werde das morgen ausprobieren.
Vermutlich könnte man diese Logik umkehren, damit sie zum Auswählen von Farben basierend auf der Hintergrundfarbe funktioniert? Ich persönlich sehe diesen Anwendungsfall häufiger als diesen.
Ich habe es absichtlich zu einer Funktion gemacht, damit sie überall dort verwendet werden kann, wo ein Farbwert erwartet wird. Ein schöner Anwendungsfall ist die Verwendung für die Farbe eines Fokusrings, was hier möglich ist.
Sie haben einen kleinen Fehler bei den Schriftgrößen gemacht, die bestimmen, ob Text als groß gilt oder nicht. In Ihrem Beitrag verwenden Sie
px(Pixel), aber die Website von WebAIM gibt dies tatsächlich alspt(Punkte) an: https://webaim.org/resources/contrastchecker/Das macht tatsächlich viel mehr Sinn, da die Sehkraft mehr von der physischen Größe von etwas abhängt. Messungen in Pixeln variieren stark bei unterschiedlichen Auflösungen, aber Punkte basieren auf Zoll und beziehen sich stärker auf eine reale Größe.
Ich würde das gerne weiter diskutieren, wenn Sie möchten, aber die Verwendung von PX war beabsichtigt
CSS-Frontend-Entwickler denken nicht in PT, die normalerweise nur mit Druckmedien assoziiert werden
PX in CSS sind (meistens) bildschirmdichtelunabhängig, was bedeutet, dass 72px unabhängig von der Bildschirmdichte bei gleicher physischer Größe (etwa einen Zoll) gerendert werden. Ähnlich wie bei Typografie… 16px auf einem 72dpi-Bildschirm sind nicht plötzlich halb so groß für einen 144dpi-Bildschirm, mit der Ausnahme des iPad mini, das ein 1024x768px-Display auf einem kleineren physischen Bildschirm (und höherer Dichte) rendert.
Die Funktion
validate-font-size()könnte dies alles berücksichtigen, aber zu diesem Zeitpunkt halte ich es für unnötig, da sie PX in PT umwandeln würde, wenn ich die Ziele für die Schriftgröße bereits in PX-Werten festgelegt habe. WCAG gibt 14pt fett an, was 19px entspricht, um als „groß“ zu gelten, und 18pt oder höher, was 24px entspricht, um als „groß“ zu gelten.Aber nochmals, ich höre gerne mehr darüber, wenn ich die Richtlinien nicht richtig berücksichtigt habe.
Wenn ich das Demo auf Codepen bearbeite und die Variable auf diesen Wert ändere
$bg-1: #32866f;, funktioniert es nichtWenn ich diesen Wert ändere
$black: #101010;zu$black: #000;funktioniert es