Files
tsv08kulmbach-website/public/assets/css/utilities.css
fs 82cf21085d 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
2026-07-27 10:33:53 +02:00

144 lines
3.7 KiB
CSS

/* ============================================================
UTILITIES — Buttons, visually-hidden, kleine Helfer
============================================================ */
/* --- Trefferflächen ---------------------------------------------------------
WCAG 2.5.5 wertet die Trefferfläche, nicht die gezeichnete Fläche. Controls,
die optisch klein bleiben sollen (dezente Buttons, Slider-Punkte), strecken
sie deshalb über ein unsichtbares Pseudo-Element auf --tap-min, statt zu
wachsen. Voraussetzung: das Element darf kein overflow: hidden tragen und
::after muss frei sein. */
.tap-target,
.btn--sm {
position: relative;
}
.tap-target::after,
.btn--sm::after {
content: "";
position: absolute;
top: 50%;
left: 50%;
translate: -50% -50%;
width: 100%;
height: 100%;
min-width: var(--tap-min);
min-height: var(--tap-min);
}
/* --- Buttons ---------------------------------------------------------------
inline-flex statt inline-block, damit min-height die Trefferfläche sichern
kann und Icon+Label ohne Zusatz-Markup zentriert nebeneinander liegen. */
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: var(--space-2xs);
min-height: var(--tap-min);
padding: var(--btn-pad-y) var(--btn-pad-x);
white-space: nowrap;
background: var(--clr-accent);
color: var(--clr-text);
font-size: var(--btn-font);
line-height: var(--btn-line);
text-decoration: none;
text-align: center;
border: 1px solid var(--clr-accent);
border-radius: var(--radius-btn);
cursor: pointer;
transition: background var(--transition), border-color var(--transition),
color var(--transition), transform var(--transition);
}
/* primary (Default) */
.btn:hover {
background: var(--clr-accent-dark);
border-color: var(--clr-accent-dark);
color: var(--clr-text);
}
.btn:active {
transform: translateY(1px);
}
.btn[disabled],
.btn[aria-disabled="true"] {
opacity: 0.6;
cursor: not-allowed;
transform: none;
}
/* secondary */
.btn--outline {
background: transparent;
border-color: var(--clr-text);
}
.btn--outline:hover {
background: var(--clr-glass-bg);
border-color: var(--clr-text);
color: var(--clr-text);
}
/* tertiary — ohne Fläche und Rahmen, für Aktionen neben einem primären Button.
Unterstrichen, damit der Button nicht allein über die Farbe erkennbar ist. */
.btn--ghost {
background: transparent;
border-color: transparent;
text-decoration: underline;
}
.btn--ghost:hover {
background: var(--clr-glass-bg);
border-color: transparent;
color: var(--clr-text);
}
/* Icon-Button — quadratisch, nur ein Icon, Label über aria-label.
Das Icon selbst skaliert über font-size (siehe --icon-*). */
.btn--icon {
gap: 0;
min-width: var(--tap-min);
padding: 0;
border-radius: 999px;
}
/* Größenmodifier — Padding ist em-basiert, skaliert mit der font-size.
--sm bleibt bewusst optisch klein und holt die 44px über die Trefferfläche;
dafür muss min-height hier zurückgenommen werden. */
.btn--sm {
min-height: 0;
font-size: var(--btn-font-sm);
}
.btn--lg { font-size: var(--btn-font-lg); }
.visually-hidden {
position: absolute !important;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
border: 0;
}
/* Inline-Icons (Bootstrap Icons via icon()-Helper). Größe = font-size, Farbe = currentColor. */
.icon {
display: inline-block;
width: 1em;
height: 1em;
fill: currentColor;
vertical-align: -0.125em;
flex: none;
}
.text-center {
text-align: center;
}
.text-muted {
color: var(--clr-text-muted);
}