# Design-System (Theme-Basis, F6 / Issue #31) Stand: Basis + Formular-Komponenten (U1). Weitere Komponenten (Spiel-Kachel, Listen, Leer-/Ladezustände) folgen. Code: `app/lib/core/theme/` (`app_theme.dart`, `app_spacing.dart`), `app/lib/core/widgets/`; Tests: `app/test/core/theme/`, `app/test/core/widgets/`. ## Theme - Material 3, `AppTheme.light()` und `AppTheme.dark()`; `ShuffleApp` nutzt `ThemeMode.system`. - Farben: `ColorScheme.fromSeed` mit Seed `#4F46E5` (Indigo). Einzige Abweichung: `outline` ist etwas dunkler (hell `#6B6A7A`) bzw. heller (dunkel `#9A99AB`), damit Feldrahmen mindestens 3:1 erreichen. - Keine Farbwerte in Widgets: immer `Theme.of(context).colorScheme` und `textTheme` verwenden. ## Kontraste (WCAG 2.1 AA) Geprüft im Test `app_theme_test.dart` für Hell und Dunkel. Text mindestens 4.5:1, Bedienelemente (Rahmen, Fokusring, Fehlerrahmen) mindestens 3:1. | Paar | Hell | Dunkel | |---|---|---| | onSurface / surface | 16.3 | 14.4 | | onSurfaceVariant / surface | 8.9 | 10.9 | | onSurfaceVariant / Feldfüllung | 7.2 | 7.3 | | onPrimary / primary (Buttons) | 6.5 | 7.7 | | Fehlertext (error) / Feldfüllung | 5.0 | 7.3 | | SnackBar-Text / SnackBar-Fläche | 11.5 | 10.1 | | SnackBar-Aktion / SnackBar-Fläche | 7.7 | 5.0 | | Feldrahmen (outline) / Feldfüllung | 4.1 | 4.4 | | Fokusring (primary) / Feldfüllung | 5.0 | 7.2 | Regeln: Informationen nie nur über Farbe vermitteln (Fehler = Text + Rahmen + Icon). Neue Farbpaare müssen in den Test aufgenommen werden. Deaktivierte Elemente sind von der Kontrastpflicht ausgenommen. ## Touch-Ziele und Fokus - Mindestgröße 48 x 48 dp für alle Buttons und Icon-Buttons (`minimumSize`), `MaterialTapTargetSize.padded`, `VisualDensity.standard`. Nicht auf `compact` verkleinern. - Tastaturfokus (Web/Desktop): Textfelder, Outlined-/Elevated-/Text-Buttons zeigen einen 3 dp breiten Rahmen in `primary`; der FilledButton (Füllung `primary`) nutzt stattdessen `onSurface`, weil `primary` auf der Füllung unsichtbar wäre (Kontrast zu Fläche und Füllung >= 3:1, im Test geprüft). - Icons ohne Text brauchen `tooltip` bzw. `Semantics(label: ...)`; Bilder ein Label oder `excludeSemantics`. - Text muss bis 200 % Skalierung ohne Abschneiden funktionieren (kein fixes Höhen-Layout für Text). ## Typografie Material-2021-Skala (`textTheme`), Farbe `onSurface`. Überschriften `headline*`/`title*`, Fließtext `bodyLarge/Medium`, Hinweise `bodySmall` (`onSurfaceVariant`). Schriftgrößen nie hart codieren. ## Abstände, Radien, Breakpoints (`app_spacing.dart`) | Token | Wert (dp) | |---|---| | `AppSpacing` xxs / xs / sm / md / lg / xl / xxl / xxxl | 2 / 4 / 8 / 12 / 16 / 24 / 32 / 48 | | `AppRadius` sm / md / lg / xl | 8 / 12 / 16 / 28 | | Breakpoints (`AppWidthClass`) | kompakt < 600, mittel 600-1023, groß >= 1024 | | `AppBreakpoints.maxContentWidth` | 560 (allgemeine Formulare/Text auf breiten Bildschirmen begrenzen) | | `AppBreakpoints.authCardMaxWidth` | 480 (zentrierte Auth-Karte ab 600 dp, Spec #1 § 6) | `AppSpacing.pagePadding(width)` liefert den Seitenrand je Breitenklasse (16 / 24 / 32). Web darf nicht wie eine gestreckte Handy-App wirken: Inhalt zentrieren und auf `maxContentWidth` begrenzen. ## Komponenten-Themes in der Basis FilledButton, ElevatedButton, OutlinedButton, TextButton, IconButton, Checkbox, InputDecoration (gefüllt, Rahmen 1 dp, Fokus 3 dp, Fehler 2 dp), Card, AppBar, Divider, SnackBar (schwebend, mit Schließen-Icon). ## Formular-Komponenten (`app/lib/core/widgets/`, U1) Gemeinsame Regeln: Alle Texte (Labels, Fehler, Semantics-Labels) kommen als Parameter von außen (l10n), nie hart codiert. Fehler = Icon + Text + Fehlerrahmen, nie nur Farbe. Fehlertexte sind Live-Regionen (werden von Screenreadern angesagt). Layout bricht bei 200 % Textskalierung um statt abzuschneiden. Alle Komponenten sind in Hell und Dunkel gegen `androidTapTargetGuideline`, `labeledTapTargetGuideline` und `textContrastGuideline` getestet; Tests liegen in `app/test/core/widgets/` und dienen als lauffähige Vorschau. | Komponente | Zweck und API (Kurzform) | |---|---| | `AppTextField` | `label` (sichtbar über dem Feld, umbrechend, auch Semantics-Label), `helperText`, `errorText` (ersetzt Hilfetext), `controller`, `focusNode`, `keyboardType`, `textInputAction`, `autofillHints`, `maxLength` + `showCounter` (weiche Grenze: Eingabe wird nicht abgeschnitten, damit der Fehler am Feld erscheint), `inputFormatters`, `enabled`, `onChanged`, `onSubmitted`. | | `AppPasswordField` | Wie `AppTextField`, dazu `showPasswordLabel` / `hidePasswordLabel` (Pflicht; Tooltip + Semantics des 48-dp-Schalters), `ruleHint` (Regelhinweis, solange kein Fehler), `isNewPassword` (Autofill `newPassword` statt `password`), `autofillHints` zum Überschreiben. Startet verdeckt, Autokorrektur und Vorschläge aus. | | `AppCheckboxField` | `AppCheckboxField(value, onChanged, label)` bzw. `.rich(segments: [AppLabelSegment.text(..), AppLabelSegment.link(text, onTap)])`, `errorText`. Ganze Zeile (Checkbox + Label) ist Touch-Ziel >= 48 dp. Links sind per Tastatur fokussierbar und für Screenreader als benutzerdefinierte Aktionen verfügbar; Semantics-Knoten meldet Zustand und den vollen Labeltext. | | `AppPrimaryButton` | `label`, `onPressed`, `isLoading` (deaktiviert + Spinner, kein Doppelsenden), `loadingLabel` (gesprochener Text im Ladezustand), `icon`, `expand` (Standard: volle Breite). | | `AppFormScaffold` | `child`, `appBar`, `maxWidth` (Standard `AppBreakpoints.authCardMaxWidth`). Kompakt: Vollbild, scrollbar; ab 600 dp zentrierte Karte. Tastatur schiebt den Inhalt hoch (Scaffold-Resize), Ziehen schließt die Tastatur. | | `AppBanner` | `type` (`AppBannerType.info` / `error`), `message`, `typeLabel` (gesprochenes Präfix, z. B. „Fehler“), optional `actionLabel` + `onAction` (48-dp-TextButton). Live-Region mit „typeLabel: message“. | | `AppFieldError` | Interner Baustein: Fehlerzeile mit Icon als Live-Region, von Textfeld und Checkbox genutzt. | Auth-Screens: Layout-Skizzen und Textvorschläge siehe `docs/design/auth-screens.md`.