Files
leocrm/docs/ui-design-guidelines.md
T
Agent Zero 25b2581653 feat(C): Phase C — Core UI prüfen, vervollständigen, testen
C-ERR-BOUNDARY: ErrorBoundary erweitert (trace_id, Fallback-UI, PluginErrorBoundary)
C-NOTIF: NotificationDropdown mit System-Channel-Link
C-DOCS: ApiDocs.tsx Seite (Swagger UI iframe)
C-A11Y: aria-label, min-h-touch, focus-visible ergänzt
C-FE-TEST: 29 neue Tests (ErrorBoundary, ApiDocs, PrintButton, themeStore, NotificationDropdown)
C-DOC: ui-design-guidelines.md aktualisiert

Verifiziert (keine Änderungen nötig):
- C-TAGS, C-CF, C-FILTER, C-DEDUP, C-ONBOARD, C-THEME, C-PWA, C-PRINT

TSC: 0 errors, Build: erfolgreich, Vitest: 29/29 passed
2026-08-13 21:57:59 +02:00

647 lines
22 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# LeoCRM UI-Design-Richtlinien
> **Version:** 1.0
> **Datum:** 2026-07-23
> **Gültig für:** Alle Frontend-Komponenten, Plugin-Seiten und zukünftige Entwicklungen
---
## 1. Farbsystem
Alle Farben sind als Tailwind Design Tokens in `tailwind.config.js` definiert. Jede Farbe hat Schattierungen von 50 (hell) bis 900 (dunkel) plus einen `DEFAULT`-Wert.
| Token | Hex (DEFAULT) | Verwendung |
|---|---|---|
| `primary` | `#2563eb` (Blau) | Hauptaktionen, aktive Zustände, Links, Fokus-Ringe |
| `secondary` | `#64748b` (Slate) | Text, Borders, Hintergründe, inaktive Zustände |
| `accent` | `#d946ef` (Fuchsia) | Hervorhebungen, Info-Badges, KI-Features |
| `danger` | `#dc2626` (Rot) | Löschen, Fehler, destruktive Aktionen |
| `warning` | `#f59e0b` (Amber) | Warnungen, ausstehende Aktionen |
| `success` | `#16a34a` (Grün) | Erfolg, Bestätigungen, aktive Status |
### Verwendungsregeln
- **Primary** nur für die wichtigste Aktion pro View. Nicht mehr als eine Primary-Button pro Formular.
- **Secondary** für Text, Borders und inaktive UI-Elemente. `secondary-50` für Card-Footer, `secondary-100` für Hover-Zustände.
- **Accent** sparsam für KI-Features und Hervorhebungen. Nicht für Standard-Aktionen.
- **Danger** ausschließlich für destruktive Aktionen (Löschen, Entfernen). Immer mit `ConfirmDialog` kombinieren.
- **Warning** für Status-Badges und Warnhinweise. Nicht als Button-Farbe.
- **Success** für Erfolgsmeldungen und Status-Indikatoren. Nicht als Standard-Button.
### Dark Mode
- Aktiviert via `darkMode: 'class'` in Tailwind Config.
- CSS-Variablen in `:root` (Light) und `.dark` (Dark) definiert.
- Dark Mode-Toggle in Settings.
- Beim Dark Mode werden `secondary-900` als Hintergrund und `secondary-50` als Text verwendet.
---
## 2. Typografie
| Eigenschaft | Wert |
|---|---|
| Font Family | `Inter` (system-ui fallback) |
| Mono Font | `JetBrains Mono` für Code/Daten |
| Rendering | `antialiased` |
### Schriftgrößen-Hierarchie
| Token | Größe | Zeilenhöhe | Verwendung |
|---|---|---|---|
| `text-xs` | 0.75rem | 1rem | Badges, Tooltips, Metadaten |
| `text-sm` | 0.875rem | 1.25rem | Labels, Helper-Text, Tabellen-Spalten |
| `text-base` | 1rem | 1.5rem | Body-Text, Input-Felder |
| `text-lg` | 1.125rem | 1.75rem | Card-Titel, Section-Header |
| `text-xl` | 1.25rem | 1.75rem | Seiten-Titel |
| `text-2xl` | 1.5rem | 2rem | Dashboard-Überschriften |
| `text-3xl` | 1.875rem | 2.25rem | Große Überschriften |
| `text-4xl` | 2.25rem | 2.5rem | Hero-Text, Login-Titel |
### Font-Weight
- `font-medium` (500) — Buttons, Labels, Tab-Header
- `font-semibold` (600) — Card-Titel, Seiten-Titel
- `font-bold` (700) — Nur für Hervorhebungen, sparsam
---
## 3. Layout-Patterns
### 3-Spalten-Explorer-Layout
Standard-Layout für Explorer-Plugins (Calendar, Mail, DMS, Contacts):
```
┌─────────────┬──────────────────┬──────────────────────┐
│ Tree │ Liste/Explorer │ Detail │
│ (224px) │ (flex-1) │ (flex-1 / 60%) │
│ ResizablePanel│ ResizablePanel │ ResizablePanel │
└─────────────┴──────────────────┴──────────────────────┘
```
- Linke Spalte: `ResizablePanel` mit `initialWidth=224`, `minWidth=150`, `maxWidth=600`
- Mittlere Spalte: `ResizablePanel` mit `resizable=false` (flex-1)
- Rechte Spalte: `ResizablePanel` mit `resizable=false` oder `handleSide="left"`
- Drag-Handle auf der rechten Kante der linken Spalte
```tsx
import { ResizablePanel } from '@/components/ui/ResizablePanel';
<div className="flex h-full">
<ResizablePanel initialWidth={224} minWidth={150} maxWidth={600}>
<TreeView />
</ResizablePanel>
<div className="flex-1 overflow-auto">
<ListView />
</div>
<div className="flex-1 overflow-auto">
<DetailView />
</div>
</div>
```
### PluginToolbar
Jede Plugin-Seite registriert Aktionen über den `usePluginToolbarStore`:
```tsx
import { usePluginToolbarStore, type ToolbarItem } from '@/store/pluginToolbarStore';
const { setItems, setActivePlugin } = usePluginToolbarStore();
useEffect(() => {
setActivePlugin('calendar');
setItems([
{ id: 'create', plugin: 'calendar', type: 'button', label: 'Neu', icon: <Plus />, onClick: handleCreate, group: 'actions' },
{ id: 'search', plugin: 'calendar', type: 'search', searchPlaceholder: 'Suchen...', onSearch: handleSearch, group: 'search' },
]);
}, []);
```
- Toolbar-Items werden nach `group` gruppiert mit Trennern zwischen Gruppen.
- Button-Labels sind auf Mobile (`hidden sm:inline`) ausgeblendet, Icons bleiben sichtbar.
- Toolbar-Höhe: `min-h-[43px]`, Hintergrund `bg-white`, Border-Bottom `border-secondary-200`.
### Modal-Dialoge
Für Formulare, Bestätigungen und Dialoge:
```tsx
import { Modal } from '@/components/ui/Modal';
<Modal open={open} onClose={onClose} title="Kontakt bearbeiten" size="lg">
<ContactForm />
</Modal>
```
| Size | max-width | Verwendung |
|---|---|---|
| `sm` | max-w-md | Bestätigungsdialoge |
| `md` | max-w-lg | Einfache Formulare |
| `lg` | max-w-2xl | Komplexe Formulare, Edit-Dialoge |
| `xl` | max-w-4xl | Große Formulare, Multi-Step |
- `ConfirmDialog` für destruktive Aktionen (Löschen, Entfernen).
- Focus-Trap: Fokus wird beim Öffnen auf erstes fokussierbares Element gesetzt, beim Schließen auf ursprüngliches Element zurückgegeben.
- Escape-Taste schließt Modal (sofern `closeOnEscape=true`).
- Backdrop-Klick schließt Modal (sofern `closeOnBackdrop=true`).
### Settings-Layout
Settings-Seiten verwenden einen Tree-Navigator links und das Formular rechts:
```
┌─────────────┬──────────────────────────────────┐
│ Settings │ Settings-Formular │
│ Tree │ (Cards mit Sections) │
│ (224px) │ (flex-1, scrollable) │
└─────────────┴──────────────────────────────────┘
```
---
## 4. Komponenten-Referenz
### Button
```tsx
import { Button } from '@/components/ui/Button';
<Button variant="primary" size="md" onClick={handleSave} isLoading={saving}>
Speichern
</Button>
```
| Prop | Typ | Default | Beschreibung |
|---|---|---|---|
| `variant` | `'primary' \| 'secondary' \| 'danger' \| 'ghost'` | `'primary'` | Visuelle Variante |
| `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Größe |
| `isLoading` | `boolean` | `false` | Zeigt Spinner, deaktiviert Button |
| `icon` | `ReactNode` | — | Icon links vom Text |
| `fullWidth` | `boolean` | `false` | `width: 100%` |
- Alle Buttons haben `min-h-touch` (44px) für Touch-Accessibility.
- `focus-visible:ring-2` für Tastatur-Navigation.
- `motion-safe:duration-200` für Übergänge (respektiert `prefers-reduced-motion`).
### Card
```tsx
import { Card } from '@/components/ui/Card';
<Card title="Kontaktdaten" description="Stammdaten" actions={<Button>Edit</Button>}>
<CardContent />
</Card>
```
| Prop | Typ | Beschreibung |
|---|---|---|
| `title` | `string` | Card-Header-Titel |
| `description` | `string` | Subtitel im Header |
| `actions` | `ReactNode` | Aktionen rechts im Header |
| `footer` | `ReactNode` | Footer-Bereich (bg-secondary-50) |
- Hintergrund: `bg-white`, Border: `border-secondary-200`, Radius: `rounded-lg`, Shadow: `shadow-sm`.
- Body-Padding: `px-6 py-4`, Footer-Padding: `px-6 py-3`.
### Badge
```tsx
import { Badge } from '@/components/ui/Badge';
<Badge variant="success" dot>Aktiv</Badge>
```
| Variant | Verwendung |
|---|---|
| `default` | Neutrale Tags |
| `primary` | Primäre Zustände |
| `success` | Aktiv, bestätigt, online |
| `warning` | Ausstehend, Warnung |
| `danger` | Fehler, inaktiv, abgelaufen |
| `info` | Info, KI-Vorschläge |
| `secondary` | Sekundäre Tags |
- `dot` prop zeigt einen farbigen Punkt links an.
- Größe: `text-xs`, `px-2.5 py-0.5`, `rounded-full`.
### Input / Select
```tsx
import { Input } from '@/components/ui/Input';
<Input label="Name" error={errors.name} helperText="Vollständiger Name" required />
```
- `focus:ring-2 focus:ring-primary-500` bei Fokus.
- Error-State: `border-danger-500`, `text-danger-900`.
- `aria-invalid`, `aria-describedby` für Accessibility.
- `min-h-touch` (44px) für Touch-Targets.
- Label: `text-sm font-medium text-secondary-700`.
### Table / DataGrid
- Verwendet TanStack Table für Sortierung, Filterung, Pagination.
- `aria-label` auf sortierbare Headers.
- Zebra-Stiping optional: `even:bg-secondary-50`.
### Toast
```tsx
import { useToast } from '@/components/ui/Toast';
const { toast } = useToast();
toast({ title: 'Gespeichert', description: 'Kontakt wurde gespeichert', variant: 'success' });
```
- Wird nach jeder CRUD-Aktion verwendet (Erfolg/Fehler).
- Auto-Dismiss nach 5 Sekunden.
- Position: Top-Right (Desktop), Top (Mobile).
### EmptyState
```tsx
import { EmptyState } from '@/components/ui/EmptyState';
<EmptyState icon={<Users />} title="Keine Kontakte" description="Erstellen Sie einen neuen Kontakt" action={<Button>Neu</Button>} />
```
- Verwendet wenn Liste leer ist.
- Icon groß zentriert, Titel + Beschreibung, optional Aktion.
### Skeleton
```tsx
import { Skeleton } from '@/components/ui/Skeleton';
<Skeleton className="h-8 w-full" />
```
- Verwendet während Daten laden.
- `animate-pulse` Animation.
- Respektiert `prefers-reduced-motion`.
---
## 5. Spacing & Sizing
### Padding
| Element | Padding |
|---|---|
| Card Body | `px-6 py-4` |
| Card Footer | `px-6 py-3` |
| Panel | `p-4` |
| Modal Body | `p-6` |
| Input | `px-3 py-2` |
### Gap
| Verwendung | Gap |
|---|---|
| Button-Gruppen | `gap-2` |
| Form-Sections | `gap-4` |
| Spalten / Panels | `gap-6` |
| Toolbar-Items | `gap-1` |
### Border-Radius
| Token | Wert | Verwendung |
|---|---|---|
| `rounded-sm` | 0.375rem | Badges, kleine Elemente |
| `rounded-md` | 0.5rem | Inputs, Buttons, Panels (Standard) |
| `rounded-lg` | 0.75rem | Cards, Modals |
| `rounded-xl` | 1rem | Große Container |
| `rounded-full` | 9999px | Badges, Avatars |
### Shadow
| Token | Verwendung |
|---|---|
| `shadow-sm` | Cards, Panels |
| `shadow-md` | Dropdowns, Popovers |
| `shadow-lg` | Modals, Dialoge |
---
## 6. Accessibility
### Pflicht-Regeln
1. **Focus-Ring**: Alle interaktiven Elemente haben `focus-visible:ring-2 focus-visible:ring-primary-500`.
2. **Touch-Targets**: Mindestens 44×44px (`min-h-touch min-w-touch`).
3. **ARIA-Labels**: Dekorative SVGs erhalten `aria-hidden="true"`. Interaktive Elemente ohne sichtbaren Text erhalten `aria-label`.
4. **Screen Reader**: `sr-only` Klasse für Text nur für Screen Reader. `sr-only-focusable` für Skip-Links.
5. **Reduced Motion**: `motion-safe:` und `motion-reduce:` Präfixe verwenden. `prefers-reduced-motion` Media Query wird respektiert.
6. **Tastatur-Navigation**: Tab-Reihenfolge folgt visueller Reihenfolge. Escape schließt Modals/Dropdowns.
7. **Farbkontrast**: Mindestens 4.5:1 für Body-Text, 3:1 für große Texte und UI-Komponenten.
### Implementierte Patterns
- `focus-ring` Klasse: `focus-visible:ring-2 focus-visible:ring-primary-500`
- `btn-touch` Klasse: `min-h-touch min-w-touch` (44px)
- `sr-only` und `sr-only-focusable` Klassen
- `prefers-reduced-motion` Media Query
- `aria-hidden="true"` auf dekorativen Icons
- `aria-label` auf Icon-Only-Buttons
- `aria-busy="true"` auf ladenden Buttons
- `aria-invalid` und `aria-describedby` auf Inputs mit Fehlern
---
## 7. Plugin-UI-Patterns
### Neue Plugin-Seite — Checkliste
1. **3-Spalten-Layout** verwenden (wenn anwendbar): Tree | Liste | Detail
2. **PluginToolbar** registrieren: Create, Import, Export, Search als Toolbar-Items
3. **Plugin-Settings** als eigene Settings-Sub-Seite (Settings-Tree-Navigation)
4. **Detail-Tabs** für Entity-Detail (z.B. "Dateien", "Verlauf", "Notizen")
5. **EmptyState** wenn keine Daten vorhanden
6. **Skeleton/LoadingState** während Daten laden
7. **Toast** nach jeder CRUD-Aktion (Erfolg/Fehler)
8. **ConfirmDialog** vor destruktiven Aktionen
9. **i18n** — alle Texte über `useTranslation()` (DE/EN)
10. **Dark Mode** — alle Komponenten müssen in Light und Dark funktionieren
### Plugin-Toolbar Registrierung
```tsx
useEffect(() => {
setActivePlugin('myplugin');
setItems([
{ id: 'create', plugin: 'myplugin', type: 'button', label: t('actions.create'), icon: <Plus size={16} />, onClick: handleCreate, group: 'actions' },
{ id: 'import', plugin: 'myplugin', type: 'button', label: t('actions.import'), icon: <Upload size={16} />, onClick: handleImport, group: 'actions' },
{ id: 'search', plugin: 'myplugin', type: 'search', searchPlaceholder: t('search'), onSearch: handleSearch, group: 'search' },
]);
return () => setItems([]);
}, []);
```
### i18n
- Alle Texte über `useTranslation()` Hook.
- Übersetzungen in `src/i18n/locales/de.json` und `src/i18n/locales/en.json`.
- Keys nach Plugin-Präfix: `myplugin.actions.create`, `myplugin.search`, etc.
- Ca. 750 Keys pro Sprache aktuell.
---
## 8. Do's & Don'ts
### Do's
- ✅ Bestehende UI-Komponenten aus `components/ui/` verwenden
-`clsx` für bedingte Klassen verwenden
-`lucide-react` Icons verwenden (keine inline SVGs)
-`date-fns` für Datumsformatierung verwenden
- ✅ Zustand-Stores für State Management verwenden
- ✅ TanStack Query für API-Calls verwenden
-`min-h-touch` (44px) für alle interaktiven Elemente
-`focus-visible:ring-2` für Tastatur-Accessibility
-`motion-safe:` / `motion-reduce:` für Animationen
- ✅ Toast nach jeder CRUD-Aktion anzeigen
- ✅ ConfirmDialog vor jeder destruktiven Aktion
- ✅ EmptyState für leere Listen
- ✅ Skeleton für Lade-Zustände
### Don'ts
- ❌ Keine inline SVGs — immer `lucide-react` verwenden
- ❌ Keine `Date.parse()` oder `new Date()` Formatierung — `date-fns` verwenden
- ❌ Keine hardcoded Farben — Tailwind Design Tokens verwenden
- ❌ Keine `alert()` oder `confirm()` — Toast und ConfirmDialog verwenden
- ❌ Keine CSS-Module oder styled-components — Tailwind-Klassen verwenden
- ❌ Keine `useEffect` für State-Management — Zustand-Stores verwenden
- ❌ Keine direkten `fetch()` Calls — TanStack Query Hooks verwenden
- ❌ Keine `any` Types — TypeScript-Interfaces definieren
- ❌ Keine deutschen Strings im Code — i18n-Keys verwenden
- ❌ Keine `px-` Werte für Touch-Targets unter 44px
- ❌ Keine `display: none` für Accessibility-relevante Elemente — `sr-only` verwenden
---
## 9. Code-Beispiele
### Beispiel: Plugin-Seite mit 3-Spalten-Layout
```tsx
import { useEffect } from 'react';
import { Plus, Search } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { ResizablePanel } from '@/components/ui/ResizablePanel';
import { EmptyState } from '@/components/ui/EmptyState';
import { Button } from '@/components/ui/Button';
import { usePluginToolbarStore } from '@/store/pluginToolbarStore';
export function MyPluginPage() {
const { t } = useTranslation();
const { setItems, setActivePlugin } = usePluginToolbarStore();
useEffect(() => {
setActivePlugin('myplugin');
setItems([
{ id: 'create', plugin: 'myplugin', type: 'button', label: t('actions.create'), icon: <Plus size={16} />, onClick: handleCreate, group: 'actions' },
{ id: 'search', plugin: 'myplugin', type: 'search', searchPlaceholder: t('search'), onSearch: handleSearch, group: 'search' },
]);
return () => setItems([]);
}, []);
const handleCreate = () => { /* ... */ };
const handleSearch = (q: string) => { /* ... */ };
return (
<div className="flex h-full">
<ResizablePanel initialWidth={224} minWidth={150} maxWidth={600}>
<TreeView />
</ResizablePanel>
<div className="flex-1 overflow-auto">
{items.length === 0 ? (
<EmptyState icon={<FileIcon />} title={t('empty.title')} description={t('empty.description')} action={<Button onClick={handleCreate}>{t('actions.create')}</Button>} />
) : (
<ListView items={items} />
)}
</div>
<div className="flex-1 overflow-auto">
<DetailView />
</div>
</div>
);
}
```
### Beispiel: Formular mit Validation
```tsx
import { Input } from '@/components/ui/Input';
import { Button } from '@/components/ui/Button';
import { Modal } from '@/components/ui/Modal';
import { useToast } from '@/components/ui/Toast';
function ContactForm({ open, onClose }) {
const { toast } = useToast();
const [errors, setErrors] = useState({});
const handleSubmit = async (e) => {
e.preventDefault();
try {
await saveContact(formData);
toast({ title: 'Gespeichert', variant: 'success' });
onClose();
} catch (err) {
toast({ title: 'Fehler', description: err.message, variant: 'danger' });
}
};
return (
<Modal open={open} onClose={onClose} title="Kontakt bearbeiten" size="lg">
<form onSubmit={handleSubmit} className="space-y-4">
<Input label="Vorname" error={errors.firstname} required />
<Input label="Nachname" error={errors.surname} required />
<div className="flex justify-end gap-2 pt-4">
<Button variant="secondary" onClick={onClose}>Abbrechen</Button>
<Button type="submit" variant="primary">Speichern</Button>
</div>
</form>
</Modal>
);
}
```
---
## 10. Error Boundaries & Plugin-Fehlerbehandlung
### ErrorBoundary (common)
Die zentrale `ErrorBoundary` in `components/common/ErrorBoundary.tsx` fängt React-Render-Fehler ab und zeigt eine Fallback-UI mit:
- **Trace-ID**: Jeder Fehler erhält eine eindeutige `trace_id` (Format: `err-<timestamp>-<random>`), die im Fehlerbuffer und im Backend-Log gespeichert wird
- **Fehlerdetails**: Ausklappbare Details mit Fehlermeldung und Stack-Trace
- **Retry-Button**: Setzt den Error-State zurück und versucht erneut zu rendern
- **Neu laden-Button**: Lädt die Seite neu (`window.location.reload()`)
- **ARIA**: `role="alert"` und `aria-live="assertive"` für Screenreader
```tsx
import { ErrorBoundary } from '@/components/common/ErrorBoundary';
<ErrorBoundary>
<MyComponent />
</ErrorBoundary>
// Custom Fallback
<ErrorBoundary fallback={(error, retry, traceId) => <CustomErrorUI error={error} retry={retry} traceId={traceId} />}>
<MyComponent />
</ErrorBoundary>
```
### PluginErrorBoundary
Plugin-Seiten werden zusätzlich durch `PluginErrorBoundary` in `PluginLoader.tsx` umschlossen. Diese zeigt den Plugin-Namen im Fehlerfall und loggt den Fehler mit `pluginName` und `trace_id`.
### PluginRouteRenderer
Der `PluginRouteRenderer` (Catch-All Route) ist mit `ErrorBoundary` umschlossen, sodass Plugin-Seiten-Fehler nicht die gesamte App crashen.
---
## 11. Print & PDF-Export
### PrintButton
Die `PrintButton`-Komponente in `components/common/PrintButton.tsx` bietet ein Dropdown mit Druck- und PDF-Export-Funktionen:
- **Drucken**: Ruft `printElement(targetId)` oder `printCurrentPage()` auf
- **Als PDF**: Ruft `exportToPDF(targetId, filename)` auf (Browser-Print-to-PDF)
- **Print-CSS**: `public/print.css` mit `@media print` Regeln — versteckt Sidebars, TopBar, Buttons und formatiert Tabellen/Card für Druck
```tsx
import { PrintButton } from '@/components/common/PrintButton';
<PrintButton targetId="contact-detail" filename="kontakt-john-doe" />
```
Verwendung in: `ContactDetailPage`, `Reports`, `Calendar`.
---
## 12. API-Dokumentation (Swagger UI)
Die `ApiDocsPage` in `pages/ApiDocs.tsx` bettet die FastAPI Swagger UI in einem iframe ein:
- Route: `/api-docs` (erfordert `settings:read` Permission)
- iframe src: `/docs` (FastAPI Swagger UI)
- External-Link: Öffnet `/docs` in neuem Tab
- Vollbild-iframe mit Header-Leiste
---
## 13. Notification-System
### NotificationBell + Dropdown
Die `NotificationBell` in `components/layout/NotificationBell.tsx` zeigt ungelesene Benachrichtigungen mit:
- **Unread-Badge**: Roter Zähler (max. 99+) mit 30s Polling
- **Dropdown**: Liste der Benachrichtigungen, „Alle als gelesen"-Button
- **System-Channel-Link**: Öffnet den Kommunikations-System-Channel unter `/communication?channel=system`
- **Alle anzeigen**: Navigiert zu `/settings/notifications`
Das Backend delegiert `/notifications` an den Kommunikations-System-Channel (`post_system_message`).
---
## 14. Accessibility (A11Y) Patterns
### Verbindliche ARIA-Regeln
| Element | Attribut | Wert |
|---|---|---|
| Buttons | `aria-label` | Beschreibung der Aktion |
| Icon-only Buttons | `aria-label` + `aria-hidden` auf Icon | Icon dekorativ |
| Dropdowns | `aria-haspopup="menu"` + `aria-expanded` | true/false |
| Menüs | `role="menu"` + `aria-label` | Menüname |
| Error-Bereiche | `role="alert"` + `aria-live="assertive"` | Für Screenreader |
| Navigation | `aria-label` auf `<nav>` und `<aside>` | „Hauptnavigation" etc. |
| Skip-Link | `sr-only focus:not-sr-only` | „Zum Hauptinhalt springen" |
### Touch Targets
Alle interaktiven Elemente müssen `min-h-touch` (44px) und `min-w-touch` (44px) haben:
```tsx
<button className="min-h-touch min-w-touch ...">Klick</button>
```
### Focus-Management
- Sichtbarer Focus-Ring: `focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500`
- Skip-Link in `App.tsx`: `sr-only focus:not-sr-only`
- `main`-Element mit `tabIndex={-1}` für Tastatur-Fokus
---
## 15. Datei-Struktur
```
frontend/src/
├── components/
│ ├── ui/ # Basis-Komponenten (Button, Card, Modal, etc.)
│ ├── layout/ # Layout-Komponenten (PluginToolbar, etc.)
│ └── [plugin]/ # Plugin-spezifische Komponenten
├── pages/ # Seiten-Komponenten (Routes)
├── store/ # Zustand-Stores
├── hooks/ # Custom Hooks (aufgeteilt nach Domain)
├── utils/ # Utilities (date.ts, api.ts, etc.)
├── i18n/ # Übersetzungen
│ └── locales/
│ ├── de.json
│ └── en.json
└── routes/ # React Router Konfiguration
```
---
*Diese Richtlinien sind verbindlich für alle Frontend-Entwicklung an LeoCRM.*