Version 1.0 · Stand 16.10.2026 · Light Mode · Vorschau-Projekt (nicht veröffentlicht)
Diese Datei ist die Übergabe-Referenz. In der Bibliothek ist sie unter „Verzeichnis“ (/verzeichnis) lesbar. Entstehungsprotokoll und Prüfstände: docs/DESIGNSYSTEM.md.
1. Stack und Quellen
| Paket | Version (package.json) | Verwendung |
|---|---|---|
| TanStack Start / Router | 1.168 / 1.170 | Routen, Suchparameter |
| shadcn/ui | CLI 4.21.5, Stil new-york, Radix-Familie | Basis-UI in src/components/ui |
| Tailwind CSS | 4.2 (CSS-first, src/styles.css) | Tokens, Textrollen |
| Radix UI | je Primitive ^1–2 | Dialog, Popover, Tabs usw. |
| react-day-picker | 9.14 | Calendar |
| @tanstack/react-table | 8.21.3 | Data Table |
| recharts | 2.15.4 | Chart |
| cmdk | 1.1 | Command / Combobox |
| react-hook-form + zod | 7.71 / 3.25 | Formularrezept |
| sonner | 2.0 | Toast |
| lucide-react | 0.575 | einzige Iconbibliothek |
Quelle der Basiskomponenten: offizielle shadcn-Registry (ui.shadcn.com, new-york-v4). Die Registry-Combobox basiert auf @base-ui/react und ist mit der Radix-Familie nicht kompatibel; stattdessen gilt das offizielle Radix-Rezept Popover + Command.
2. Schichten
| Ordner | Inhalt | Regel |
|---|---|---|
src/components/ui | offizielle shadcn-Komponenten | nur über Tokens/cva-Varianten anpassen, keine Parallelelemente |
src/components/compositions | generische Kompositionen | nur aus ui zusammengesetzt |
src/components/domain | Immobilien-Fachkomponenten | Daten per Props, Links per title/actions-Slot |
src/lib | Formatierung, Datum, Filter, Demo-Daten | reine Funktionen, getestet |
3. Basis-UI (@/components/ui/<name>)
| Gruppe | Komponenten | Projektvarianten |
|---|---|---|
| Aktionen | button, button-group, toggle, toggle-group | Button: default (Navy-Hauptaktion), product (Alias von default), accent (optional Blau), secondary, outline, ghost, link, destructive, quick (92-px-Kachel, ohne Schatten); Größen sm, default (40 px), lg, tile, icon, icon-sm, icon-xs |
| Formulare | input, input-group, textarea, field, label, form, input-otp | Höhe 40 px, Radius 8, Fläche #FDFDFD, Kontur 1 px rgba(6,3,20,.50) (≥3:1, bewusst dunkler als sevdesk .20), Hover neutral rgba(6,3,20,.71), Fokus 1 px Blau + 2-px-Akzent |
| Auswahl | checkbox, radio-group, switch, slider, select, native-select, command | – |
| Datum | calendar | deutsch, Montag, data-day = lokaler Schlüssel JJJJ-MM-TT |
| Struktur | card, item, avatar, badge, separator, aspect-ratio, scroll-area, resizable, carousel, kbd | Card: Radius 12, Rahmen rgba(6,3,20,.17), Schatten 0 1px 6px, Innenabstand 20/24/24 |
| Navigation | sidebar, tabs, accordion, collapsible, navigation-menu, menubar, breadcrumb, pagination | Tabs variant status (12/16); Pagination-Links 48 px (Basis), Data-Table-Footer kompakt; mobile Trigger 44 px; SidebarMenuButton variant quiet + size nav (40 px, Produkt-Rahmen) |
| Overlays | dialog, alert-dialog, sheet, drawer, popover, dropdown-menu, context-menu, tooltip, hover-card | Popover-Schatten 0 6px 14px |
| Feedback | alert, progress, skeleton, spinner („Lädt“), empty, sonner, attachment | – |
| Daten | table, chart | Tabellenkopf 40 px #F7F7F7, Zeile 64 px |
4. Kompositionen (@/components/compositions/<datei>)
| Export | Datei | Basis |
|---|---|---|
| StatusBadge (success, warning, danger, info, neutral) | status-badge | badge + Icon + Text |
| PageHeader | page-header | breadcrumb |
| ComponentDoc | component-doc | card, badge, separator |
| ListPageHeader, ListPanel, ListPanelToolbar, StatusTabs, ListPanelFilters, FilterField, FilterChips, SummaryLine, ListPanelFooter | list-panel | Listen-/Detailseiten nach sevdesk (A2); Basis tabs (variant status), badge, Card-Tokens |
| LibrarySidebar | library-sidebar | sidebar |
| CloseMobileSidebarOnNavigate | close-mobile-sidebar | sidebar (schließt mobil nach Navigation) |
| MadeByTag | made-by-tag | fest eingeblendetes Kennzeichen unten rechts (Card-Tokens, keine Aktion) |
| UnitInput, parseDecimalDe (€, %, m²) | unit-input | input-group |
| Combobox (einzeln/mehrfach, Chips, Gruppen, Reset) | combobox | popover + command + badge |
| DatePicker, DateRangePicker (8 Presets), TimeInput (24 h) | date-picker | calendar, popover, input-group |
| useDataTable, DataTableView, DataTablePagination, SortHeader, ColumnToggle, selectColumn | data-table | table + TanStack Table |
| useUploadQueue, UploadDropzone, UploadErrors, UploadItemList | file-upload | item, progress, spinner, button |
5. Fachkomponenten (@/components/domain/<datei>)
| Export | Datei | Wichtige Zustände |
|---|---|---|
| PropertyCard, PropertyRow, PropertyHeader | property | Leerstand, vollvermietet, Rückstand |
| UnitRow, RentSummary, PaymentRow | rent | belegt/leer; bezahlt, offen, teilbezahlt, überfällig |
| PersonSummary, TenancySummary | people | unvollständige Kontaktdaten, fehlende Kaution |
| TaskRow, TaskCard | work | offen, in Bearbeitung, erledigt, überfällig; Priorität hoch/normal/niedrig |
| DocumentRow, DocumentPreview | work | zugeordnet, nicht zugeordnet, Demo-Upload; Vorschau als Platzhalter gekennzeichnet |
| ActivityTimeline | work | Liste, leer |
| Checklist, Stepper | work | bedienbar/lesend; aktueller Schritt mit aria-current |
| NoticeCard (eigenes Fachmuster auf shadcn Alert) | work | info, success, warning, danger |
| ChargeStatusBadge, TaskStatusBadge, PriorityBadge, OccupancyBadge | status | immer Icon + Text |
| Facts, MissingValue | facts | „Noch nicht erfasst“ |
| LinkedRecordPicker, linkedRecords | linked-record-picker | Objekt/Einheit/Mietverhältnis mit stabilen IDs |
| KpiCard | kpi-card | Label 13/20, Wert direkt darunter (32/40 oder kompakt 24/32), Hinweis, Trend mit Icon+Text |
ChartPanel, ChartKpiHeader, useBarMarker, chartGridProps (direkt in <CartesianGrid {...chartGridProps} />), chartAxis/chartYAxis (Y_AXIS_WIDTH 64, einzeiliger Kompakttick), groupedBarSize, usePlotFrame, PLOT_HEIGHT, eurTooltip, legendLine | @/components/compositions/chart-frame | gemeinsame Diagrammhülle (Kopf/40 px/Plot 200/24 px/Footer 56), Umbruch nach Kartenbreite, ein Marker in der Mitte des berührten Balkens |
| CashflowChart / CashflowContent | cashflow-chart | Karte (Akte) bzw. Inhalt ohne Hülle; Tag/Monat, Tabelle, leer |
| FinanceCard, InsetStat, StatRowLink, DashCard | dashboard-cards | Dashboard-Finanzkarte, warmgraue Kennzahl-Insets, Statuszeilen mit Zähl-Badge; Basis Card, Progress, Toggle |
| PeriodPicker | period-picker | gemeinsamer Zeitraum |
| ProductShell, DemoResetButton | product-shell | Reset mit Bestätigung |
6. Tokens (src/styles.css)
Alle Hex-/RGBA-Referenzen sind exakt in OKLCH umgerechnet; der Ursprungswert steht als Kommentar daneben.
| Token | Wert | Verwendung |
|---|---|---|
| --background, --card | #FFFFFF | Arbeitsfläche, Karten |
| --sidebar | #FBF9F9 | Sidebar |
| --product / --product-hover | #1E3A8A / #172554 | Button default = Hauptaktion Navy (product ist Alias, Weiß 10,36:1) |
| --search-surface | #F3EDED | Sidebar-Suche, warmgrauer Hover (SidebarMenuButton variant="quiet") |
| --inset-surface | #F6F4F3 | flache Kennzahl-Insets (InsetStat) |
| --foreground | #100B2D | Haupttext |
| --muted-foreground | #5E5E74 | Sekundärtext |
| --subtle-foreground | #717387 | Nebeninformation, „Noch nicht erfasst“ |
| --primary / --ring / --info | #2563EB | Links, Fokus, Diagramme, ausgewählte Kontrollen; Button accent (optional) |
| --primary-hover | #1D4ED8 | Hover, Auswahltext |
| --selection / --accent | #EFF6FF | Auswahlfläche |
| --border, --card-border, --disabled | rgba(6,3,20,.17) | dekorative Konturen, deaktiviert |
| --input | rgba(6,3,20,.50) | Kontrollkontur, 3,79–3,85:1 |
| --input-hover | rgba(6,3,20,.71) | neutrale Hover-Kontur von Feldern/Checkbox/Radio |
| --hover-surface | rgba(6,3,20,.06) | neutraler Hover von Buttons, Toggles, Menü-/Optionszeilen |
| --chart-line | #1E40AF | dünne Datenlinien, 8,72:1 auf Weiß |
| --input-background | #FDFDFD | Inputfläche |
| --muted | #F7F7F7 | Tabellenkopf, Feldfläche im Fokus, Readonly |
| --pressed | rgba(6,3,20,.11) | aktiver Filter-Tab |
| --disabled-foreground | #989CAA | deaktivierter Text |
| --success / -soft | #15803D / #DCFCE7 | Status grün |
| --warning / -soft | #A16207 / #FEF9C3 | Status gelb |
| --danger / -soft | #B91C1C / #FEE2E2 | Status rot |
| --neutral / -soft | #5E5E74 / #F1F1F4 | Status grau |
| --chart-1 … --chart-5 | Blau-Familie und Neutraltöne | Diagrammserien |
| --radius-control / --radius-card | 8 px / 12 px | Kontrollen / Karten |
| --control-height | 40 px | Buttons, Inputs |
| --shadow-card | 0 1px 6px rgba(6,3,20,.10) | Karten |
| --shadow-control | 0 1px 1px rgba(6,3,20,.07) | Buttons |
| --shadow-popover | 0 6px 14px rgba(6,3,20,.10) | Popover |
| --shadow-pressed | inset 0 1px 2px rgba(6,3,20,.15) | gedrückt |
--field-ring-value (shadow-field-focus) | 0 0 0 2px #2563EB/16 % | einziger Außenakzent fokussierter Felder |
--field-ring-error-value (shadow-field-error) | 0 0 0 2px Rot/16 % | Fehlerfeld im Fokus |
Textrollen (@utility): text-greeting 30/40 400 · text-page-title 24/32 500 · text-card-title 20/28 500 · text-ui 14/22 · text-meta 13/20 · text-small 12/16 · text-kpi 32/40 600 · text-kpi-compact 24/32 600. Raster: page-container nur Dashboard (fluide ohne Maximalbreite; horizontaler Rand stetig 32→80 px nach Breite des Hauptbereichs als Inline-Size-Container, mobil <768 px 20 px; vertikal 40 px, schmal 24 px), list-container für Listen, Akten und alle Bibliotheksseiten (32 px, mobil 20 px), dashboard-grid (2 × minmax(0, 1fr), 24 px Abstand, einspaltig nach Containerbreite; frühere 2 × max. 480 px abgelöst).
Feldzustände (@utility control-field, gültig seit Nutzerrevision Fokus): Input, Textarea, Select-Trigger, NativeSelect, InputGroup, Combobox-Feld, Kalender-Dropdown. Priorität Disabled > Invalid > Fokus > Hover > Default; Rahmen konstant 1 px. Fokus = 1 px Blau + ein 2-px-Akzent, Fläche #F7F7F7, kein weißer Spalt. Fehler bleibt beim Fokus rot. Readonly durchgezogene neutrale Kontur auf Grau, fokussierbar. Checkbox/Radio/Switch: Hover nur bei gültigen, aktiven Kontrollen; Disabled überschreibt Invalid/Gewählt. Fehler am Gruppenbesitzer + Fokus/Open im Kind → roter Akzent. Gruppen (InputGroup, Combobox) besitzen den Fokusrahmen allein; innere Buttons zeigen Fokus als Auswahlfüllung. Buttons/Links/Checkbox/Radio/Switch/Slider/Tabs/Kalendertage: nur Tastaturfokus als 2-px-Outline Blau (Fallback in @layer base mit :where, Spezifität 0, kein !important).
7. Formatierung (@/lib/format, @/lib/date)
| Funktion | Beispiel |
|---|---|
| formatCents(130000) | 1.300,00 € |
| formatEur, formatEur0, formatCompact | 1.300,00 € · 1.300 € · 1,3 Tsd. |
| formatDay("2026-10-03") | 03.10.2026 (ohne Date/UTC) |
| formatArea(205) | 205 m² |
| formatPercent(75, 0) | 75 % |
| MISSING, orMissing(v) | Noch nicht erfasst |
| toDateKey, fromDateKey, parseDateDe, formatDateDe, parseTime24, presetRange, validateRange | lokale Kalendertage, TT.MM.JJJJ, 24 h |
8. Demo-Daten
@/lib/demo/immo: fiktive Daten P1 Hofgarten 12 (U1–U3), P2 Parkblick 8 (U4), Mietverhältnisse T1–T3, Forderungen Nov 2025–Okt 2026, Zahlungen, Ausgaben, Dokumente D1–D12 (7 × P1, 4 × P2, D3 nicht zugeordnet), Aufgaben A1–A3, Aktivitäten. Geld in Cent, Tage als JJJJ-MM-TT, Stichtag 16.10.2026.- Selektoren: periodSummary, monthlySeries, chargeViews, occupancy, taskStats, Filterfunktionen. Mutationen: recordPayment, setTaskStatus, addTask, addDocument, assignDocument.
@/lib/demo/store:useDemo()unddemoStoreim Arbeitsspeicher;reset()bzw. Neuladen stellt den Ausgangsstand wieder her (inkl. Zeitraum Oktober 2026).@/lib/demo/search: URL-Parameter je Beispielseite.- Soll-Werte Oktober: 2.500 € Soll, 1.200 € erhalten, 1.300 € offen, 700 € Cashflow, 2 überfällige Forderungen.
9. Nutzungsregeln
- Erst
ui, danncompositions, danndomainverwenden; keine eigene Basis bauen, wenn shadcn sie liefert. - Farben nur über semantische Tokens; keine zusätzlichen Akzentfarben.
- Status immer Farbe + Icon + Text (StatusBadge).
- Kontrollen 40 px, Filter 36 px Desktop / 44 px mobil (innere Gruppenfelder erben die Höhe), mobile Trefferflächen mindestens 44 px; Radius 8 (Kontrolle) und 12 (Karte).
4a. Hauptaktion = Button default (Navy); Blau (accent) nur optional. Keine zweite Primärfarbe. 4b. Feldartige Kontrollen nutzen control-field; keine eigenen Fokusringe oder !important-Schatten hinzufügen.
- Fehlende Werte nie schätzen:
Facts/MissingValueverwenden. - Geld als Cent, Kalendertage als Schlüssel; nie
toISOString()odertoLocaleDateString()für Kalendertage. - Kennzahlen und Diagramme nur aus Selektoren ableiten.
- Destruktive Aktionen mit AlertDialog bestätigen.
- Neue Bibliotheksbereiche in
src/lib/library-sections.tsregistrieren; mitinSidebar: falsebleibt ein Bereich registriert, erscheint aber nicht als Menüpunkt der Bibliotheks-Sidebar.
10. Bereiche
| Bereich | Route | Status |
|---|---|---|
| Grundlagen, App-Rahmen, Aktionen, Formulare, Auswahl, Datum/Zeit, Tabellen/Filter, Karten/Kennzahlen, Diagramme, Dokumente, Dialoge/Feedback, Zustände, Ergänzungen | eigene Routen | fertig |
| Fachmuster | /fachmuster | fertig |
| Beispielseiten | /beispielseiten → /beispiele/* | fertig (Demo, kein Produktmodul); nicht als Sidebar-Menüpunkt, nur über Links |
| Verzeichnis | /verzeichnis | fertig |
Offen bzw. bewusste Grenzen: Hover/Pressed nicht vollständig vermessen, Demo-Zustand nicht dauerhaft gespeichert, Dokumentvorschau als Platzhalter, keine Backends.
11. Abweichungen von der sevdesk-Referenz
| Punkt | Referenz | ImmoSystem24 | Grund |
|---|---|---|---|
| Schrift Überschriften/KPIs | Roobert | Cabinet Grotesk Variable (Revision G), gleiche Größen/Gewichte; UI Inter | Nutzerentscheidung; bis G historisch Inter |
| Akzent | #534ACC Violett, #212144 | #2563EB / #1D4ED8 / #EFF6FF | Markenvorgabe |
| Dashboard-Raster | 1144 px fest, rechts 19 px abgeschnitten | fluide volle Hauptfläche, 40/80 px Rand, 2 × 1fr nach Containerbreite, stetiger Rand 32–80 px (F1/F2; feste 1144/480 px abgelöst) | Overflow nicht übernommen |
| Zeitraum-Popover | 733 × 312 px | 733 × ca. 359 px | zusätzliche manuelle Von/Bis-Eingabe |
| Uhrzeit | – | HH:MM-Textfeld statt type=time | zuverlässiges 24-h-Format |
| Produkt-App-Rahmen (A1) | Suche in Sidebar, keine Toplbar | ProductShell + SidebarMenuButton quiet/nav; aktives Icon nur stärker gezeichnet | Lucide ohne gefüllte Icons |
| Listenseiten (A2) | Titel y32, Panel ab y100, 32 px Rand | ListPanel-Komposition, gemessen identisch | – |
| Listen-Toolbar rechts | Filter, Exportieren | Zeitraum, Spalten (Mieten) bzw. Ansicht | kein Export ohne Funktion |
| Mobile | nicht gemessen | Trigger 44 px, Tabellen als Karten | Zugänglichkeit |
12. Wiederverwendung im ImmoSystem24-Demoprojekt
Dieser Workspace bietet in dieser Sitzung keine Registrierung dieses Projekts als auswählbares Lovable-Designsystem; eine Registrierung ist nicht erfolgt. Nutzbare Wege:
- Remix/Kopie dieses Projekts (empfohlen): enthält Tokens, Komponenten, Demo-Daten und Tests. Danach Bibliotheksrouten optional entfernen,
src/components/*,src/lib/*undsrc/styles.cssbehalten. - Gezielte Übernahme in ein bestehendes TanStack-Start-/shadcn-Projekt:
src/styles.css(Tokens, Textrollen, Raster) übernehmen bzw. Token-Blöcke einfügen; Inter per<link>im Root-Head laden; Cabinet Grotesk lokal auspublic/fonts/per@font-face(+ Lizenzdatei) übernehmen.src/components/ui(angepasste shadcn-Dateien),src/components/compositions,src/components/domainkopieren.src/lib/format.ts,date.ts,selection.ts,table-filters.ts,upload.ts,utils.tsund bei Bedarfsrc/lib/demo/*kopieren.- Pakete aus Abschnitt 1 in denselben Hauptversionen installieren.
- Tests aus
src/testmitnehmen und ausführen (bunx vitest run). - Fachkomponenten an echte Daten anbinden, indem
immo.ts-Typen beibehalten und die Selektoren mit echten Datensätzen gespeist werden; der Demo-Store wird dabei ersetzt.
Erweiterung H1 (Produktbeispiele)
| Baustein | Pfad | Basis |
|---|---|---|
| Workflow-Fixtures/Validatoren | src/lib/demo/workflows.ts | reine TS-Funktionen |
| Vermieten-Board | src/routes/beispiele.vermieten.tsx | Card, Progress, Sheet, Select, Checkbox, StatusTabs, InputGroup |
| Objektreport | src/routes/beispiele.optimieren.tsx | Accordion, ToggleGroup, PriorityBadge, Button |
| Service-Arbeitsplatz | src/routes/beispiele.services.tsx | StatusBadge, Collapsible, Field, RadioGroup, LinkedRecordPicker, NativeSelect, Input, Textarea |
Keine neuen generischen Kompositionen: alle Bausteine sind bislang seitenspezifisch und nutzen vorhandene shadcn-Basis.
Erweiterung H2 (Produktbeispiele)
| Baustein | Pfad | Basis |
|---|---|---|
| Übergabe-/Wissen-Zustand, Validatoren | src/lib/demo/workflows.ts | reine TS-Funktionen |
| Beitragsdaten, Filter, Kontextprüfung | src/lib/demo/wissen.ts | reine TS-Funktionen |
| Stepper (jetzt optional vertikal/wählbar) | src/components/domain/work.tsx | Komposition |
| ArticleCover, BookmarkToggle, ContextCheckDialog | src/components/domain/knowledge.tsx | Toggle, Dialog, NativeSelect, Field, Lucide |
| UnitInput (Einheiten kWh, m³, Stück ergänzt) | src/components/compositions/unit-input.tsx | InputGroup |
| Übergabeprotokoll | src/routes/beispiele.uebergabe.tsx | Stepper, Progress, Accordion, RadioGroup, DatePicker, UnitInput, Field |
| Mediathek / Beitrag | src/routes/beispiele.wissen.index.tsx, beispiele.wissen.$slug.tsx | ToggleGroup, Toggle, InputGroup, Empty, Breadcrumb, Checklist |