- JavaScript 100%
- Add scripts/derive-tokens.js: no-dep CLI that derives a full accessible palette (primary/secondary/text/background/surface/focusRing/muted, light + dark) from one anchor color, computes WCAG contrast for every pair, and exits 1 if any text/large pair is below AA (4.5/3.0). Supports --base, --from <url> (best-effort brand-color extraction), --secondary, --name, --out, --no-write. - generate-tokens.js now delegates to derive-tokens so JSON/YAML never drift. - Replace blue default with preset gp-green (base #2f6b4f): calm sage/forest green primary + warm terracotta secondary on warm-neutral surfaces, fitting a Hausarzt/GP practice. All pairs AA-pass. - Document referenced old-site colors and the deliberate move away from the red/signal brand toward calming green (old-colors.md). - Rewrite README as tool docs (install/run, CLI, Kirby integration, validator as acceptance gate). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|---|---|---|
| examples | ||
| scripts | ||
| a11y-template.md | ||
| old-colors.md | ||
| README.md | ||
| tokens.json | ||
| tokens.yaml | ||
| validate.js | ||
a11y-design-system
Ein Werkzeug für barrierefreie Websites: aus einer Ankerfarbe leitet es eine vollständige, WCAG-2.1-AA-geprüfte Farbpalette (Light + Dark) ab, liefert Design-Tokens (JSON/YAML) mit berechneten Kontrastwerten, ein striktes Barrierefreiheits-Regelwerk und einen Validator, der als Abnahme-Gate dient.
Die mitgelieferte Standard-Palette ist gp-green — ruhiges Salbei-/Waldgrün
als Primärfarbe, warme Terrakotta als Sekundärakzent, warm-neutrale Flächen:
passend für eine Hausarztpraxis (nicht das kühle Klinik-Blau). Wie sie
zustande kam: siehe old-colors.md.
Schnellstart
Voraussetzung: Node.js (keine Abhängigkeiten, kein npm install nötig).
# 1. Palette aus einer Ankerfarbe ableiten (schreibt tokens.json + tokens.yaml)
node scripts/derive-tokens.js --base '#2f6b4f' --name gp-green --out tokens.json
# 2. Standard-Palette neu erzeugen (identisch zu oben, One-Liner)
node scripts/generate-tokens.js
# 3. Eine Seite gegen das Regelwerk prüfen (Abnahme-Gate)
node validate.js examples/passing.html # exit 0 → a11y-ready
node validate.js examples/failing.html # exit 1 → nicht auslieferbar
Achtung Shell: Das
#in--base '#2f6b4f'immer quoten, sonst interpretiert die Shell den Rest als Kommentar.
Das Tool: scripts/derive-tokens.js
Nimmt eine Anker-/Basisfarbe und erzeugt eine vollständige, zugängliche
Palette (primary, secondary, text, background, surface, focusRing, muted) für
Light und Dark, berechnet für jedes relevante Paar den WCAG-Kontrast und
bricht mit Exit-Code 1 ab, wenn ein Text-/Large-Paar unter AA liegt
(4.5 / 3.0).
node scripts/derive-tokens.js --base <hex> [Optionen]
node scripts/derive-tokens.js --from <url> [Optionen]
| Option | Bedeutung |
|---|---|
--base <hex> |
Ankerfarbe, z. B. '#2f6b4f'. Ihr Farbton treibt die Primärfarbe. |
--from <url> |
Best-Effort: lädt eine Seite + verlinkte CSS und nimmt die dominante, gesättigte Markenfarbe als Basis. |
--secondary <hex> |
Optionaler Anker für den Sekundär-Farbton. |
--name <preset> |
Menschlicher Name, landet in meta.preset (z. B. gp-green). |
--out <datei> |
Ausgabepfad JSON (Default: tokens.json). Ein .yaml-Spiegel wird daneben geschrieben. |
--no-write |
Ergebnis nur nach stdout drucken, nichts schreiben. |
Wie die Ableitung funktioniert (bewusste Designentscheidungen):
- Primary behält den Basis-Farbton und wird so weit abgedunkelt (Light) bzw. aufgehellt (Dark), bis sie 4.5:1 auf dem Hintergrund erreicht — Markenton bleibt, Lesbarkeit ist garantiert.
- Secondary nutzt einen split-komplementären Farbton (
Basis + 225°). Bei grüner Basis landet das auf einem warmen, menschlichen Terrakotta/Amber — ruhig und nicht klinisch. - Flächen/Text verwenden einen warmen Neutral-Ton (≈ 35°), damit Hintergründe wie warmes Papier wirken statt wie kühles Klinikweiß.
- Focus-Ring übernimmt die (bereits AA-starke) Primärfarbe → unübersehbar und klar über 3:1.
Beispielausgabe (--base '#2f6b4f' --name gp-green):
LIGHT
primary #2B8058 (vs bg 4.69:1)
secondary #B45834 (vs bg 4.63:1)
text #3A332A (vs bg 12.05:1)
background #FCFBFA
surface #F6F3EE
focusRing #2B8058 (vs bg 4.69:1)
muted #7A7266 (vs bg 4.59:1)
DARK
primary #3B9169 (vs bg 4.67:1)
secondary #C7663F (vs bg 4.63:1)
text #DDD9D5 (vs bg 12.84:1)
background #191613
surface #2E2A24
focusRing #42A476 (vs bg 5.84:1)
muted #8C8273 (vs bg 4.77:1)
→ RESULT: PASS — all pairs AA.
scripts/generate-tokens.js ist ein dünner Wrapper darum: er erzeugt die
Standard-Palette (gp-green) reproduzierbar, damit JSON und YAML nie
auseinanderlaufen.
Die Tokens: tokens.json / tokens.yaml
| Bereich | Inhalt |
|---|---|
color.light.* / color.dark.* |
primary, secondary, text, background, surface, focusRing, muted — je mit value, contrast_text, contrast_bg, wcag_aa_text, wcag_aa_large |
spacing |
4-px-Raster |
typography |
Font-Stacks, Body ≥ 16px, Überschriften-Skala |
radius |
none … full |
focusRing |
width: 3px (min 2px), Offset, Stil |
minTargetSize |
44px (WCAG 2.5.5) |
Alle Kontrastwerte sind berechnet (WCAG-2.x-Relativluminanz), nicht geschätzt. Die Ableitung schlägt fehl, sobald ein Paar AA verletzt.
Tokens in ein Website-Projekt (z. B. Kirby) einbinden
-
tokens.json(und optionaltokens.yaml) ins Projekt kopieren. -
An genau einer Stelle in CSS-Custom-Properties abbilden — Light + Dark vollständig aus den Tokens:
:root { --color-primary: #2B8058; /* color.light.primary */ --color-secondary: #B45834; /* color.light.secondary */ --color-text: #3A332A; --color-background: #FCFBFA; --color-surface: #F6F3EE; --color-focus-ring: #2B8058; --color-muted: #7A7266; } @media (prefers-color-scheme: dark) { :root { --color-primary: #3B9169; /* color.dark.primary */ --color-secondary: #C7663F; --color-text: #DDD9D5; --color-background: #191613; --color-surface: #2E2A24; --color-focus-ring: #42A476; --color-muted: #8C8273; } }Keine weiteren hartkodierten Farben irgendwo. In Kirby z. B. ein Snippet/Partial, das diesen
:root-Block ausgibt; alle Templates nutzen nurvar(--color-*). Tokens ändern → Palette neu ableiten → dieser eine Block wird neu erzeugt. -
a11y-template.mdbefolgen für jede Seite: Skip-Link zuerst,<header><nav aria-label>,<main id="content">,<footer>,lang, genau eine<h1>, Labels, Kontrast ≥ 4.5:1 / ≥ 3:1. Das Dokument ist das Abnahmekriterium — vor dem Coden lesen.
Der Validator ist das Abnahme-Gate: validate.js
node validate.js dist/index.html
Prüft: Landmarken (<header>, <main id="content">, <footer>), Skip-Link als
erstes fokussierbares Element → #content, <html lang>, genau eine
<h1>, und dass hartkodierte Farben den Kontrast gegen den Token-Hintergrund
erfüllen. Exit 0 = auslieferbar, alles andere = nicht auslieferbar. Ideal
für CI:
- name: a11y gate
run: |
for f in dist/**/*.html; do node validate.js "$f"; done
KI-Tools instruieren (Claude Code / Cloud Code etc.)
KI-Generatoren wenden diese Regeln nicht von selbst an — sie müssen es gesagt
bekommen. An den Anfang der CLAUDE.md / des System-Prompts:
Lies vor jeder HTML/CSS-Ausgabe
a11y-template.mdundtokens.jsonund befolge sie strikt. Nutze nur Farben aus den Tokens (Light und Dark). Jede Seite mussnode validate.js <datei>bestehen — ein FAIL ist ein kaputter Build.
Dateien
| Datei | Zweck |
|---|---|
scripts/derive-tokens.js |
Das Tool: Palette aus einer Ankerfarbe ableiten, AA-Gate |
scripts/generate-tokens.js |
Standard-Palette gp-green reproduzierbar erzeugen |
tokens.json / tokens.yaml |
Design-Tokens mit berechneten Kontrasten |
a11y-template.md |
Striktes Regelwerk — vor dem Coden lesen |
validate.js |
Abnahme-Validator |
old-colors.md |
Referenz: Farben der Bestandsseite + bewusste Neuausrichtung |
examples/ |
Eine bestehende (passing) und eine kaputte (failing) Seite |