# Komponenten

Alle Klassen tragen das Präfix `gev-`. Varianten verwenden `--`, Bestandteile `__`.

## Buttons

| Klasse | Zweck |
|---|---|
| `.gev-button` | genau eine primäre Aktion im aktuellen Bereich |
| `.gev-button--secondary` | alternative oder vorbereitende Aktion |
| `.gev-button--quiet` | geringe Wichtigkeit, z. B. Details |
| `.gev-button--danger` | irreversible oder risikobehaftete Aktion |
| `.gev-button--compact` | Tabellenspalte oder dichte Werkzeugleiste |

Regeln:

- Buttontexte beginnen mit einem Verb: `Entwurf prüfen`, `Freigabe anfordern`, `Stand-down vormerken`.
- `OK`, `Ja` oder `Weiter` nur, wenn der Kontext unmittelbar eindeutig ist.
- Außenwirkung nie hinter einem Icon-only-Button verstecken.
- Eine deaktivierte Aktion braucht in der Nähe einen sichtbaren Grund.

## Status und Badges

`.gev-status` beschreibt einen fachlichen Zustand und enthält immer Text. `.gev-badge` ist Metadatum wie `DEMO`, `P2` oder eine Version. `.gev-tag` ist filterbare oder kategorisierende Information.

```html
<span class="gev-status gev-status--unknown">Nicht bestätigt</span>
<span class="gev-badge gev-badge--brand">P1</span>
<span class="gev-tag">Struktureller Einsturz</span>
```

## Hinweise

`.gev-alert` ist für eine konkrete Information im Arbeitsfluss. Varianten:

- `--info`: Kontext;
- `--success`: abgeschlossen oder bestätigt;
- `--warning`: Entscheidung oder Prüfung offen;
- `--danger`: Blocker, Fehler oder akute Inkonsistenz.

Ein Hinweis enthält eine kurze Überschrift und erklärt dann Auswirkung oder nächste Handlung.

## Formulare

Labels stehen immer oberhalb des Feldes. Platzhalter ersetzen kein Label.

Fehler:

```html
<label class="gev-field">
  <span class="gev-field__label">Freigebende Rolle</span>
  <select class="gev-select" aria-invalid="true" aria-describedby="role-error">
    <option>Bitte auswählen</option>
  </select>
  <span id="role-error" class="gev-field__error">
    Vor einer externen Aktion ist eine Rolle erforderlich.
  </span>
</label>
```

Fehlertexte beschreiben die Korrektur, nicht nur den Fehler.

### Formulargruppen

`.gev-form-group` fasst zusammengehörige Eingaben in einer Karte zusammen. Der
Titel steht **innerhalb** der Karte; der Rahmen läuft ohne Unterbrechung um die
gesamte Gruppe. Für Radio- und Checkboxgruppen sowie mehrteilige Angaben wie
Anschriften und Hundeangaben verwenden, nicht als zusätzliche Hülle für jedes
einzelne Feld.

```html
<fieldset class="gev-form-group" aria-describedby="membership-hint">
  <legend>Mitgliedschaft und Teilnahmegebühr *</legend>
  <div class="gev-form-group__body">
    <label class="gev-check">
      <input type="radio" name="membership" value="drv" required />
      <span>Mitglied im DRV: 250€</span>
    </label>
    <label class="gev-check">
      <input type="radio" name="membership" value="other" required />
      <span>Mitglied einer anderen Organisation: 280€</span>
    </label>
    <p id="membership-hint" class="gev-field__hint">Bitte den passenden Beitrag wählen.</p>
  </div>
</fieldset>
```

- Die native `legend` ist das erste Kind und benennt die Gruppe für Screenreader.
  Sie wird durch `float: left` innerhalb der Karte angeordnet. Der Body mit
  `clear: both` hält alle Inhalte unterhalb auch mehrzeiliger Titel.
- Alle Felder, Hinweise, Fehler und Aktionen stehen in `.gev-form-group__body`.
  Einzelne Eingaben behalten ihre eigenen Labels. Radios teilen sich einen `name`.
- Innenabstand: `--gev-space-6` (24 px), bis 46 rem Bildschirmbreite
  `--gev-space-4` (16 px). Titel und Inhalte trennen `--gev-space-5` (20 px).
  Titel: 16 px, semibold, Zeilenhöhe 1,5; lange Texte umbrechen.
- Pflichtfelder erhalten `required`; die Kennzeichnung `*` allein reicht nicht.
  Pflichtauswahlen nicht vorauswählen. Hinweise und Fehler mit einer eindeutigen
  ID und `aria-describedby` verbinden. `disabled` auf dem Fieldset deaktiviert
  die gesamte Gruppe; interaktive Inhalte gehören deshalb nicht in die Legend.
- Fläche, Text, Rahmen und Radius verwenden gemeinsame Tokens für Hell/Dunkel.
  Keine lokale Kopie des Kartenstils in konsumierenden Apps anlegen.

Die Events-App verwendet den React-Adapter `FormGroup` aus `components/ui.tsx`:

```tsx
<FormGroup title="Anschrift" aria-describedby="address-hint">
  {/* Eingaben und Hinweise */}
</FormGroup>
```

Er übernimmt native Fieldset-Attribute und erzeugt genau die obige HTML-Struktur.
Öffentliche Anmeldung, dynamische Felder, Vorschau, Tarifeditor und Bedingungen
nutzen denselben Baustein. Interaktive Beispiele stehen in der
[Komponentenreferenz](../showcase/komponenten.html#form-groups).

## Tabellen

Tabellen sind für Vergleiche und strukturierte Datensätze, nicht für Layout. Die Kopfzeile bleibt sprachlich kurz; Einheiten gehören in den Kopf. Zahlen sind rechtsbündig und verwenden `.gev-table__numeric`.

Auf kleinen Bildschirmen bleibt die Tabelle horizontal scrollbar. Kritische Datensätze werden nicht automatisch in ungleichartige Karten umgebaut, weil dadurch Vergleichbarkeit verloren geht.

## Tabs

Tabs wechseln gleichrangige Ansichten desselben Objekts. Sie sind keine Hauptnavigation. Implementierungen müssen:

- `role="tablist"`, `role="tab"` und `role="tabpanel"` verwenden;
- Pfeiltasten unterstützen;
- Fokus und Auswahl getrennt behandeln;
- den aktiven Tab mit `aria-selected` markieren.

## Dialoge

Native `<dialog>`-Elemente werden bevorzugt. Ein Bestätigungsdialog wiederholt:

1. Ziel der Aktion;
2. Empfänger oder betroffenen Datensatz;
3. aktuellen Freigabestatus;
4. offene Blocker;
5. Folge der Bestätigung.

Der primäre Fokus liegt standardmäßig auf `Abbrechen`, wenn die Aktion Außenwirkung oder Datenverlust erzeugt.

## Toasts

Toasts bestätigen nur bereits verständliche, reversible Ergebnisse. Kritische Fehler oder Freigabeanforderungen gehören in den Seitenkontext, nicht in eine verschwindende Meldung.

## Ladezustände

- Skeleton nur, wenn Form und ungefährer Inhalt bekannt sind.
- Nach 10 Sekunden Klartextstatus ergänzen.
- Bei fachlich zeitkritischen Aktualisierungen immer `Letzte erfolgreiche Aktualisierung` zeigen.
- Alter Inhalt darf während eines Refreshs sichtbar bleiben, muss aber als veraltet markiert werden.
