Shuffle/docs/design-system.md
cschlaefke ec8b2ab4c1 feat(ui): add auth form components and screen layouts
Refs #1

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

5.9 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), 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.