Shuffle/docs/design-system.md
cschlaefke e632da2344 feat(auth): add registration, check-email and email-link screens
Implements KTO-01 criteria 1-4, 10 and 11 (client part).

Refs #1

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-03 00:27:16 +01:00

6.1 KiB

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), counterText (eigener Zählertext, wenn die Zähleinheit vom Standard abweicht, z. B. getrimmte Codepoints beim Anzeigenamen), 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.