# 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';
``` ### 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: , 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'; ``` | 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'; ``` | 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'; Edit}> ``` | 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'; Aktiv ``` | 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'; ``` - `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'; } title="Keine Kontakte" description="Erstellen Sie einen neuen Kontakt" action={} /> ``` - Verwendet wenn Liste leer ist. - Icon groß zentriert, Titel + Beschreibung, optional Aktion. ### Skeleton ```tsx import { Skeleton } from '@/components/ui/Skeleton'; ``` - 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: , onClick: handleCreate, group: 'actions' }, { id: 'import', plugin: 'myplugin', type: 'button', label: t('actions.import'), icon: , 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: , 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 (
{items.length === 0 ? ( } title={t('empty.title')} description={t('empty.description')} action={} /> ) : ( )}
); } ``` ### 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 (
); } ``` --- ## 10. 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.*