--- type: Frontend Guideline status: APPROVED owner: đŸ–Œïž UI/UX Designer last_update: 2026-04-03 related: - docs/06_Frontend/Guidelines/Editier-Formulare_Dialog-vs-Fullscreen_v1.md - docs/04_Agents/Roadmaps/UIUX_Roadmap.md - docs/04_Agents/Roadmaps/Frontend_Roadmap.md --- # Empty States — Design-Spezifikation & Richtlinie > **Ziel:** Konsistente, hilfreiche Leer-ZustĂ€nde fĂŒr alle Listenansichten im Meldestelle-Desktop. > **Leitsatz:** Ein leerer Zustand ist kein Fehler — er ist eine Einladung zur ersten Aktion. --- ## 1. Warum Empty States wichtig sind - **Erster Start / Onboarding:** Neue Nutzer sehen leere Listen — ohne Hinweis wirkt die App kaputt. - **Nach Filterung:** Kein Treffer bei Suche/Filter muss klar kommuniziert werden (≠ Ladefehler). - **Nach Löschung:** Letzte EntitĂ€t gelöscht → leere Liste braucht Orientierung. - **Vertrauen:** Klare Kommunikation verhindert Unsicherheit ("Ist das ein Bug?"). --- ## 2. Empty-State-Typen | Typ | Auslöser | PrimĂ€re Aktion | |------------------|-------------------------------------------------|-----------------------------| | **`EMPTY_LIST`** | Liste ist leer (noch keine Daten angelegt) | CTA: Ersten Eintrag anlegen | | **`NO_RESULTS`** | Suche/Filter liefert keine Treffer | CTA: Filter zurĂŒcksetzen | | **`ERROR`** | Ladefehler (Netzwerk, Backend nicht erreichbar) | CTA: Erneut versuchen | | **`LOADING`** | Daten werden geladen (Skeleton/Spinner) | Kein CTA | > `LOADING` wird durch `MsLoadingIndicator` abgedeckt — kein separater Empty State nötig. --- ## 3. Visuelle Anatomie — `MsEmptyState` ``` ┌─────────────────────────────────────────────────────────┐ │ │ │ [ Icon / 48dp ] │ │ │ │ Titel (MaterialTheme.titleMedium) │ │ │ │ Beschreibung (MaterialTheme.bodyMedium, zentriert, │ │ max. 2 Zeilen, gedimmt: onSurface 60%) │ │ │ │ [ PrimĂ€rer CTA-Button ] (optional) │ │ │ └─────────────────────────────────────────────────────────┘ ``` ### Maße & AbstĂ€nde | Element | Wert | |-----------------------------|---------------------------------------------------| | Icon-GrĂ¶ĂŸe | 48 dp | | Icon-Farbe | `MaterialTheme.colorScheme.onSurface` @ 38% Alpha | | Abstand Icon→Titel | 16 dp | | Abstand Titel→Beschreibung | 8 dp | | Abstand Beschreibung→Button | 24 dp | | Max. Breite Textblock | 360 dp (zentriert) | | Padding gesamt | 32 dp rundum | ### Typografie | Element | Style | |--------------|-------------------------------------------------------| | Titel | `MaterialTheme.typography.titleMedium` | | Beschreibung | `MaterialTheme.typography.bodyMedium`, Alpha 60% | | Button | Standard `MsButton` (outlined fĂŒr sekundĂ€re Aktionen) | --- ## 4. Icon-Konzept Wir verwenden **Material Symbols (Outlined)** — konsistent mit dem restlichen Design-System. Kein Custom-Illustration-Set fĂŒr MVP (zu aufwĂ€ndig, zu wenig Mehrwert bei High-Density-Desktop). | Screen / Kontext | Icon | BegrĂŒndung | |--------------------------|----------------------------------|-----------------------------| | Veranstalter-Liste | `person_off` | Kein Veranstalter vorhanden | | Veranstaltungs-Liste | `event_busy` | Keine Veranstaltung | | Turnier-Liste | `emoji_events` (durchgestrichen) | Kein Turnier | | Bewerb-Liste | `list_alt` | Keine Bewerbe | | Nennungs-Liste | `assignment_ind` | Keine Nennungen | | Reiter-Liste | `person_search` | Kein Reiter gefunden | | Pferde-Liste | `search_off` | Kein Pferd gefunden | | Verein-Liste | `group_off` | Kein Verein | | FunktionĂ€r-Liste | `badge` | Kein FunktionĂ€r | | Abteilungs-Startliste | `format_list_numbered` | Keine Starter | | Abteilungs-Ergebnisliste | `leaderboard` | Keine Ergebnisse | | Suche / Filter (allg.) | `search_off` | Kein Treffer | | Fehler (allg.) | `cloud_off` | Verbindungsfehler | --- ## 5. Texte je Screen ### Allgemeine Regel - **Titel:** Kurz, prĂ€zise, max. 5 Wörter. Keine Ausrufezeichen. - **Beschreibung:** ErklĂ€rt den Zustand UND gibt Handlungshinweis. Max. 2 SĂ€tze. - **CTA:** Verb + Objekt. Klar und direkt. ### Texte je Kontext #### Veranstalter | Typ | Titel | Beschreibung | CTA | |--------------|----------------------------|---------------------------------------------------------|----------------------| | `EMPTY_LIST` | Noch kein Veranstalter | Lege den ersten Veranstalter an, um loszulegen. | Veranstalter anlegen | | `NO_RESULTS` | Kein Veranstalter gefunden | Kein Eintrag passt zur Suche. Passe den Suchbegriff an. | Suche zurĂŒcksetzen | | `ERROR` | Laden fehlgeschlagen | Veranstalter konnten nicht geladen werden. | Erneut versuchen | #### Veranstaltungen | Typ | Titel | Beschreibung | CTA | |--------------|------------------------------|-----------------------------------------------------------|-----------------------| | `EMPTY_LIST` | Noch keine Veranstaltung | Erstelle die erste Veranstaltung fĂŒr diesen Veranstalter. | Veranstaltung anlegen | | `NO_RESULTS` | Keine Veranstaltung gefunden | Kein Treffer fĂŒr den gewĂ€hlten Filter. | Filter zurĂŒcksetzen | | `ERROR` | Laden fehlgeschlagen | Veranstaltungen konnten nicht geladen werden. | Erneut versuchen | #### Turniere | Typ | Titel | Beschreibung | CTA | |--------------|-----------------------|-------------------------------------------------------|---------------------| | `EMPTY_LIST` | Noch kein Turnier | FĂŒge das erste Turnier zu dieser Veranstaltung hinzu. | Turnier anlegen | | `NO_RESULTS` | Kein Turnier gefunden | Kein Turnier entspricht dem aktuellen Filter. | Filter zurĂŒcksetzen | | `ERROR` | Laden fehlgeschlagen | Turniere konnten nicht geladen werden. | Erneut versuchen | #### Bewerbe | Typ | Titel | Beschreibung | CTA | |--------------|----------------------|-----------------------------------------------|---------------------| | `EMPTY_LIST` | Noch kein Bewerb | Lege den ersten Bewerb fĂŒr dieses Turnier an. | Bewerb anlegen | | `NO_RESULTS` | Kein Bewerb gefunden | Kein Bewerb entspricht dem aktuellen Filter. | Filter zurĂŒcksetzen | | `ERROR` | Laden fehlgeschlagen | Bewerbe konnten nicht geladen werden. | Erneut versuchen | #### Nennungen | Typ | Titel | Beschreibung | CTA | |--------------|------------------------|-----------------------------------------------------------|---------------------| | `EMPTY_LIST` | Noch keine Nennung | Es wurden noch keine Nennungen fĂŒr diesen Bewerb erfasst. | Nennung erfassen | | `NO_RESULTS` | Keine Nennung gefunden | Kein Treffer fĂŒr den gewĂ€hlten Filter oder die Suche. | Filter zurĂŒcksetzen | | `ERROR` | Laden fehlgeschlagen | Nennungen konnten nicht geladen werden. | Erneut versuchen | #### Reiter | Typ | Titel | Beschreibung | CTA | |--------------|----------------------|-------------------------------------------------------|--------------------| | `EMPTY_LIST` | Noch kein Reiter | Importiere Stammdaten oder lege den ersten Reiter an. | Reiter anlegen | | `NO_RESULTS` | Kein Reiter gefunden | Kein Reiter entspricht dem Suchbegriff. | Suche zurĂŒcksetzen | | `ERROR` | Laden fehlgeschlagen | Reiter konnten nicht geladen werden. | Erneut versuchen | #### Pferde | Typ | Titel | Beschreibung | CTA | |--------------|----------------------|-----------------------------------------------------|--------------------| | `EMPTY_LIST` | Noch kein Pferd | Importiere Stammdaten oder lege das erste Pferd an. | Pferd anlegen | | `NO_RESULTS` | Kein Pferd gefunden | Kein Pferd entspricht dem Suchbegriff. | Suche zurĂŒcksetzen | | `ERROR` | Laden fehlgeschlagen | Pferde konnten nicht geladen werden. | Erneut versuchen | #### Vereine | Typ | Titel | Beschreibung | CTA | |--------------|----------------------|-----------------------------------------|--------------------| | `EMPTY_LIST` | Noch kein Verein | Lege den ersten Verein an. | Verein anlegen | | `NO_RESULTS` | Kein Verein gefunden | Kein Verein entspricht dem Suchbegriff. | Suche zurĂŒcksetzen | | `ERROR` | Laden fehlgeschlagen | Vereine konnten nicht geladen werden. | Erneut versuchen | #### FunktionĂ€re | Typ | Titel | Beschreibung | CTA | |--------------|--------------------------|---------------------------------------------|--------------------| | `EMPTY_LIST` | Noch kein FunktionĂ€r | Lege den ersten FunktionĂ€r an. | FunktionĂ€r anlegen | | `NO_RESULTS` | Kein FunktionĂ€r gefunden | Kein FunktionĂ€r entspricht dem Suchbegriff. | Suche zurĂŒcksetzen | | `ERROR` | Laden fehlgeschlagen | FunktionĂ€re konnten nicht geladen werden. | Erneut versuchen | #### Abteilungs-Startliste | Typ | Titel | Beschreibung | CTA | |--------------|----------------------|-----------------------------------------------------------|------------------| | `EMPTY_LIST` | Keine Starter | FĂŒr diese Abteilung wurden noch keine Starter zugewiesen. | Nennungen prĂŒfen | | `ERROR` | Laden fehlgeschlagen | Startliste konnte nicht geladen werden. | Erneut versuchen | #### Abteilungs-Ergebnisliste | Typ | Titel | Beschreibung | CTA | |--------------|----------------------|-----------------------------------------------------------|------------------| | `EMPTY_LIST` | Keine Ergebnisse | FĂŒr diese Abteilung wurden noch keine Ergebnisse erfasst. | — | | `ERROR` | Laden fehlgeschlagen | Ergebnisliste konnte nicht geladen werden. | Erneut versuchen | --- ## 6. Composable-Spezifikation — `MsEmptyState` Ablageort: `frontend/core/design-system/src/commonMain/kotlin/at/mocode/frontend/core/designsystem/components/MsEmptyState.kt` ### API ```kotlin /** * Einheitlicher Empty-State fĂŒr alle Listenansichten. * * @param icon Material-Symbol (ImageVector) passend zum Kontext. * @param title Kurzer Titel (max. 5 Wörter). * @param description ErklĂ€rung + Handlungshinweis (max. 2 SĂ€tze). Optional. * @param actionLabel Text des primĂ€ren CTA-Buttons. Null = kein Button. * @param onAction Callback fĂŒr den CTA-Button. * @param modifier Modifier fĂŒr Ă€ußeres Layout. */ @Composable fun MsEmptyState( icon: ImageVector, title: String, description: String? = null, actionLabel: String? = null, onAction: (() -> Unit)? = null, modifier: Modifier = Modifier ) ``` ### Verhalten - Zentriert (horizontal + vertikal) im verfĂŒgbaren Raum. - Icon gedimmt (38% Alpha), Beschreibung gedimmt (60% Alpha). - Button nur anzeigen wenn `actionLabel != null && onAction != null`. - Button-Stil: `OutlinedButton` (sekundĂ€re Aktion, nicht zu dominant). - Bei `ERROR`-Typ: Icon `cloud_off`, Button als `FilledButton` (Retry ist primĂ€re Aktion). ### Verwendungsbeispiel ```kotlin // In einer LazyColumn / Column wenn items.isEmpty(): if (state.reiter.isEmpty() && !state.isLoading) { MsEmptyState( icon = Icons.Outlined.PersonSearch, title = "Noch kein Reiter", description = "Importiere Stammdaten oder lege den ersten Reiter an.", actionLabel = "Reiter anlegen", onAction = { onIntent(ReiterIntent.OpenCreateDialog) } ) } ``` --- ## 7. Implementierungs-Reihenfolge (Sprint C-1) | PrioritĂ€t | Screen | Typ(en) | |-----------|--------------------------|----------------------------| | 1 | Veranstalter-Auswahl | `EMPTY_LIST`, `NO_RESULTS` | | 2 | Veranstaltungs-Übersicht | `EMPTY_LIST`, `NO_RESULTS` | | 3 | Turnier-Bewerbe-Tab | `EMPTY_LIST` | | 4 | Turnier-Nennungen-Tab | `EMPTY_LIST`, `NO_RESULTS` | | 5 | Reiter-Liste | `EMPTY_LIST`, `NO_RESULTS` | | 6 | Pferde-Liste | `EMPTY_LIST`, `NO_RESULTS` | | 7 | Verein-Liste | `EMPTY_LIST`, `NO_RESULTS` | | 8 | FunktionĂ€r-Liste | `EMPTY_LIST`, `NO_RESULTS` | | 9 | Abteilungs-Startliste | `EMPTY_LIST` | | 10 | Abteilungs-Ergebnisliste | `EMPTY_LIST` | | Alle | Alle Listen | `ERROR` (einheitlich) | > **Hinweis:** `ERROR`-State wird einmalig als Default-Variante in `MsEmptyState` implementiert > und kann ĂŒberall mit `type = EmptyStateType.ERROR` aufgerufen werden. --- ## 8. Abgrenzung zu anderen Komponenten | Situation | Komponente | |-------------------------------|-----------------------| | Daten werden geladen | `MsLoadingIndicator` | | Liste leer / kein Treffer | `MsEmptyState` | | Kritischer Fehler (App-Ebene) | Eigener Error-Screen | | Inline-Validierungsfehler | `MsValidationWrapper` | | Status einer EntitĂ€t | `MsStatusBadge` |