Design-System: Fokus vereinheitlichen, Formularfeld-Ring WCAG-fest machen
Letzter Teil. base.css hatte längst eine globale :focus-visible-Regel, trotzdem existierten 15 Fokus-Regeln — die meisten davon Ballast. Fünf Overrides wiederholten die Basisregel praktisch wortgleich (.back-to-top, .ad-banner__dot, .faq__q, .table-scroll, .btn) und sind ersatzlos entfallen; sie unterschieden sich höchstens um 1px Offset. Die Basisregel selbst läuft jetzt über --focus-ring/--focus-offset, .position-card__head wechselt von Akzentrot auf Weiß. Übrig bleiben zehn Regeln, davon deklarieren nur noch vier überhaupt eine outline — jede mit Grund (inset-Offset bei .tab und .position-card__head, geschichteter Ring beim Formularfeld). Die anderen sechs sind reine Zusatz- effekte (transform, Farbe, Fläche) und lassen den Basis-Ring durch. Der eigentliche Fund steckte im Formularfeld: es setzte outline: none und ersetzte den Ring durch einen roten Rahmen mit 30%-Glow. Akzentrot erreicht auf der Feldfläche aber nur 2,8:1 und verfehlt damit WCAG 1.4.11 (>=3:1) für Fokus-Indikatoren — Tastaturnutzer hatten dort also den schwächsten Fokus der ganzen Seite. Roter Rahmen und Glow bleiben als Marken-Signal, der weiße Standard-Ring kommt zurück; outline-offset entspricht der Glow-Breite, beide Ringe liegen bündig nebeneinander statt übereinander. CLAUDE.md dokumentiert die neuen Regeln: Token-Disziplin mit Tabelle (Spacing, Typo, Icons, Sperrung, Fokus, Trefferfläche) samt der bewussten em-Ausnahme, das Button- und Tab-System inklusive der (0,2,0)-Falle durch die CSS-Ladereihenfolge, und die Fokus-Regel. Dazu ein Punkt in der Checkliste. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MSsVdooFLgPA8gPFp7HwZU
This commit is contained in:
45
CLAUDE.md
45
CLAUDE.md
@@ -138,10 +138,51 @@ Sichtbare FAQ + FAQPage-Schema aus derselben `data/*.json`-Quelle, damit Inhalt
|
||||
- Headings: Coolvetica, uppercase, line-height ~0.85. Body: Abel. Beide self-hosted als woff2.
|
||||
- **Coolvetica nie kleiner als `--fs-650` (28px)** — der schmale Condensed-Schnitt wird in Uppercase
|
||||
darunter unleserlich (mehrfach bestätigt: Footer-Headings, Timeline-Nav). Für kleinere Auszeichnungen
|
||||
Abel verwenden, ggf. mit `letter-spacing` + uppercase.
|
||||
Abel verwenden, ggf. mit `letter-spacing` + uppercase. `base.css` setzt das durch: `h1`–`h3` tragen
|
||||
Coolvetica, **`h4`–`h6` laufen über `--ff-body` + uppercase + `--ls-caps`**.
|
||||
- Rot nie für Fließtext auf dunklem Grund (Kontrast!) — nur als Button-/Akzent-Fläche mit weißem Text.
|
||||
- Alle Werte als CSS Custom Properties in `tokens.css`; Breakpoints: 1249 (Burger) / 1024 / 992 / 768 / 600 / 500.
|
||||
|
||||
### Token-Disziplin (gilt für jede CSS-Änderung)
|
||||
|
||||
Keine rohen Werte in `base/layout/components/utilities.css` — es gibt für alles eine Stufe:
|
||||
|
||||
| Was | Token | Anmerkung |
|
||||
|---|---|---|
|
||||
| `padding` / `margin` / `gap` | `--space-4xs … --space-7xl` (14 Stufen, 4px-Raster) | `--space-4xs` (2px) **nur** für optische Korrekturen (Icon-/Baseline-Nudges), nie fürs Layout |
|
||||
| `font-size` (Text) | `--fs-300 … --fs-1000` | `--fs-550`/`--fs-690` sind die fluiden Zwischenstufen (Lead, Karten-Überschrift) |
|
||||
| `font-size` (Icons) | `--icon-s/m/l/xl` (rem), `--icon-inline-sm/--icon-inline/--icon-inline-lg` (em) | em-Stufen für Icons, die mit ihrer Textzeile mitwachsen sollen |
|
||||
| `letter-spacing` | `--ls-tight/caps/wide/wider` | |
|
||||
| Fokus | `--focus-ring` / `--focus-offset` | |
|
||||
| Trefferfläche | `--tap-min` (44px) | |
|
||||
|
||||
**Ausnahmen, bewusst roh:** `em`-Werte, die mit der `font-size` skalieren sollen (Button-Padding
|
||||
`--btn-pad-*`, Badge-Innenabstände) — die gehören nicht auf ein festes px-Raster.
|
||||
|
||||
### Buttons & Trefferflächen
|
||||
|
||||
- Varianten in `utilities.css`: `.btn` (primary) · `.btn--outline` · `.btn--ghost` (tertiär, ohne Fläche)
|
||||
· `.btn--icon` (quadratisch, nur Icon + `aria-label`) · `.btn--sm` / `.btn--lg`.
|
||||
`.btn` ist `inline-flex` mit `gap` — Icon + Label brauchen **kein** Zusatz-Markup und keinen eigenen Abstand.
|
||||
- Tabs (Filterleiste wie Panel-Tabs) laufen über die gemeinsame **`.tab`**-Basis in `components.css`;
|
||||
aktiv wird über `aria-pressed` **oder** `aria-selected` erkannt. Nie eine dritte Tab-Optik bauen.
|
||||
- **Jedes Control ≥ `--tap-min` (44px, WCAG 2.5.5).** Soll es optisch kleiner bleiben, streckt
|
||||
`.tap-target` die Trefferfläche unsichtbar per `::after` (bei `.btn--sm` automatisch). Voraussetzung:
|
||||
kein `overflow: hidden`, `::after` frei. In Reihen (Nav, Footer) stattdessen `min-height` verwenden —
|
||||
überlappende Pseudo-Flächen würden sonst den Nachbarn verdecken.
|
||||
- Ausnahme: die Formular-Checkbox bleibt bei 24px (WCAG 2.5.8). `<input>` ist ein Replaced Element,
|
||||
`::after` greift dort nicht zuverlässig; das klickbare `<label>` liefert die große Fläche.
|
||||
- **Komponenten-Deltas zu `.btn` brauchen (0,2,0)** — z. B. `.btn.ad-banner__nav-btn`. `utilities.css`
|
||||
lädt nach `components.css`, bei gleicher Spezifität gewinnt sonst die `.btn`-Basis.
|
||||
|
||||
### Fokus
|
||||
|
||||
`base.css` setzt **eine** globale `:focus-visible`-Regel mit `--focus-ring` (2px weiß, 15,9:1 auf
|
||||
`--clr-bg`). Komponenten überschreiben sie **nur** mit Begründung — zulässig sind ein negativer
|
||||
`outline-offset` (Ring soll inset liegen, z. B. `.tab`) und zusätzliche Effekte ohne eigene `outline`.
|
||||
**Nie `outline: none`** und nie Akzentrot als alleiniger Indikator: Rot erreicht auf der Feldfläche nur
|
||||
2,8:1 und verfehlt WCAG 1.4.11 (≥3:1).
|
||||
|
||||
## Icons
|
||||
|
||||
- **Bootstrap Icons** (MIT), lokal & self-hosted unter `public/assets/icons/<name>.svg` — die erlaubte Icon-Quelle. Kein Icon-Font, kein CDN, keine gezeichneten CSS-/Unicode-Icons.
|
||||
@@ -155,6 +196,8 @@ Sichtbare FAQ + FAQPage-Schema aus derselben `data/*.json`-Quelle, damit Inhalt
|
||||
- [ ] Alt-Texte für Bilder; dekorative Bilder `alt=""`
|
||||
- [ ] Tastatur-bedienbar, `:focus-visible` sichtbar, sinnvolle Tab-Reihenfolge
|
||||
- [ ] Kontrast ≥ 4.5:1 (Fließtext) / 3:1 (große Schrift, UI)
|
||||
- [ ] Keine rohen Werte: Spacing, `font-size`, `letter-spacing`, Fokus und Trefferflächen über die
|
||||
Token aus `tokens.css` (siehe „Token-Disziplin"). Interaktive Elemente ≥ `--tap-min`
|
||||
- [ ] `prefers-reduced-motion` respektiert (globaler Kill-Switch in reset.css)
|
||||
- [ ] `$meta` gesetzt: title, description (+ og_image falls abweichend)
|
||||
- [ ] Bilder: `srcset`/`sizes`, `width`/`height`, `loading="lazy"` (above-the-fold: eager + fetchpriority).
|
||||
|
||||
@@ -80,7 +80,6 @@ h4 { font-size: var(--fs-650); }
|
||||
h5 { font-size: var(--fs-600); }
|
||||
h6 { font-size: var(--fs-500); }
|
||||
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration-thickness: 1px;
|
||||
@@ -92,8 +91,8 @@ a:hover {
|
||||
}
|
||||
|
||||
:focus-visible {
|
||||
outline: 2px solid var(--clr-text);
|
||||
outline-offset: 3px;
|
||||
outline: var(--focus-ring);
|
||||
outline-offset: var(--focus-offset);
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
|
||||
@@ -1106,7 +1106,7 @@
|
||||
}
|
||||
|
||||
.position-card__head:focus-visible {
|
||||
outline: 2px solid var(--clr-accent);
|
||||
outline: var(--focus-ring);
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
@@ -1342,11 +1342,16 @@
|
||||
border-color: var(--clr-field-border-hover);
|
||||
}
|
||||
|
||||
/* Markenroter Fokus-Ring (Akzent nur als Rahmen/Glow, nie als Text). */
|
||||
/* Roter Rahmen + Glow als Marken-Signal, dazu der weiße Standard-Ring.
|
||||
Der rote Rahmen allein reichte nicht: Akzentrot kommt auf der Feldfläche nur
|
||||
auf 2,8:1 und verfehlt damit WCAG 1.4.11 (>=3:1) für Fokus-Indikatoren.
|
||||
outline-offset entspricht der Glow-Breite, beide Ringe liegen also bündig
|
||||
nebeneinander statt übereinander. */
|
||||
.form__field input:focus-visible,
|
||||
.form__field select:focus-visible,
|
||||
.form__field textarea:focus-visible {
|
||||
outline: none;
|
||||
outline: var(--focus-ring);
|
||||
outline-offset: var(--focus-offset);
|
||||
border-color: var(--clr-accent);
|
||||
box-shadow: 0 0 0 3px rgb(var(--clr-accent-rgb) / 0.3);
|
||||
}
|
||||
@@ -1818,11 +1823,6 @@
|
||||
background: rgb(255 255 255 / 0.35);
|
||||
}
|
||||
|
||||
.ad-banner__dot:focus-visible {
|
||||
outline: 2px solid var(--clr-text);
|
||||
outline-offset: 3px;
|
||||
}
|
||||
|
||||
.ad-banner__progress {
|
||||
background: var(--clr-accent);
|
||||
transform: scaleX(0);
|
||||
@@ -2524,11 +2524,6 @@
|
||||
color: var(--clr-text-muted);
|
||||
}
|
||||
|
||||
.faq__q:focus-visible {
|
||||
outline: 2px solid var(--clr-text);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.faq__chevron {
|
||||
flex: none;
|
||||
font-size: var(--icon-s);
|
||||
@@ -3695,7 +3690,6 @@
|
||||
font-size: var(--fs-600);
|
||||
}
|
||||
|
||||
|
||||
/* Band „Nächste Spiele" — abgesetzte Fläche (.section--sunken) mit roter Oberkante,
|
||||
damit der Bereich nicht als Hero-Überhang gelesen wird. */
|
||||
.next-matches {
|
||||
@@ -3942,11 +3936,6 @@ a.match-row__link:hover {
|
||||
}
|
||||
}
|
||||
|
||||
.table-scroll:focus-visible {
|
||||
outline: 2px solid var(--clr-text);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.league-table {
|
||||
width: 100%;
|
||||
min-width: 520px;
|
||||
|
||||
@@ -537,11 +537,6 @@
|
||||
border-color: var(--clr-accent);
|
||||
}
|
||||
|
||||
.back-to-top:focus-visible {
|
||||
outline: 2px solid var(--clr-text);
|
||||
outline-offset: 3px;
|
||||
}
|
||||
|
||||
@media print {
|
||||
.back-to-top {
|
||||
display: none;
|
||||
|
||||
@@ -56,11 +56,6 @@
|
||||
color: var(--clr-text);
|
||||
}
|
||||
|
||||
.btn:focus-visible {
|
||||
outline: var(--focus-ring);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.btn:active {
|
||||
transform: translateY(1px);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user