Files
votianlt/UI_GUIDELINES.md

24 KiB
Raw Blame History

VotianLT UI Theme & Gestaltungsrichtlinien

Gilt für alle Views unter src/main/java/de/assecutor/votianlt/pages/view/ Theme-Datei: src/main/frontend/themes/votian-modern/styles.css


1. Design-Prinzipien

  • Konsistenz: Alle Views nutzen dieselben Klassen, Abstände und Farben.
  • Kein Overflow: Kein View darf horizontal scrollen. Alle Container haben box-sizing: border-box, max-width: 100% und min-width: 0.
  • Kein externes Margin/Padding am View-Root: Views füllen ihren Container vollständig aus. Abstände zum Bildschirmrand entstehen ausschließlich durch den view-container.
  • Lumo-Overrides: Vaadin-Lumo-Standardstile werden durch gezielte CSS-Regeln mit höherer Spezifizität überschrieben nicht mit inline Styles in Java-Code lösen.

2. Farben & Custom Properties

Alle Farben sind als CSS-Custom-Properties auf html definiert und müssen statt hartcodierter Hex-Werte verwendet werden.

Hauptfarben

Variable Wert Verwendung
--app-accent #2563eb Primäraktionen, Links, Highlights
--app-accent-strong #1d4ed8 Hover-Zustand von Primärbuttons
--app-accent-soft rgba(37,99,235,0.12) Selektierter Zeilenhintergrund
--app-success #059669 Erfolgsstatus, abgeschlossene Aufgaben
--app-warning #d97706 Warnungen
--app-danger #dc2626 Fehler, kritische Zustände

Oberflächen & Hintergründe

Variable Wert Verwendung
--app-surface rgba(255,255,255,0.78) Karten, Panels (Glasmorphismus)
--app-surface-solid #ffffff Volldeckende Flächen
--app-surface-muted rgba(247,250,255,0.88) Gedämpfte Panels
--app-shell-background Radial+Linear-Gradient Seitenhintergrund (Body)
--app-sidebar-background Linear-Gradient dunkelblau Drawer/Sidebar

Text

Variable Wert Verwendung
--app-text-strong #0f172a Überschriften, wichtige Labels
--app-text #1e293b Fließtext
--app-text-muted #64748b Sekundärtext, Beschreibungen

Ränder

Variable Wert Verwendung
--app-border rgba(148,163,184,0.18) Subtile Trennlinien
--app-border-strong rgba(148,163,184,0.28) Sichtbare Kartenränder
--app-field-border #d6dde7 Formularfeld-Rand
--app-field-border-hover #c6d0dd Formularfeld-Rand (Hover)

Schatten

Variable Wert Verwendung
--app-shadow-sm 0 12px 28px rgba(15,23,42,0.08) Kleine Karten
--app-shadow-md 0 20px 44px rgba(15,23,42,0.10) Mittlere Panels
--app-shadow-lg 0 28px 72px rgba(15,23,42,0.12) Surface-Panels
--app-shadow-xl 0 36px 88px rgba(8,18,36,0.24) Hero-Bereiche, Dialoge

3. Typografie

Schriftart: Manrope (Google Fonts, Gewichte 400800) Fallbacks: Avenir Next, Segoe UI, sans-serif

Lumo-Überschreibungen:

--lumo-font-family: "Manrope", "Avenir Next", "Segoe UI", sans-serif;
--lumo-base-color: #f5f7fb;

Alle h1h6 haben letter-spacing: -0.03em und color: var(--app-text-strong).

Responsive Schriftgrößen (clamp)

Element Wert
.form-title clamp(1.55rem, 3vw, 2.2rem), font-weight 800
.section-title clamp(1.4rem, 3.4vw, 2rem), font-weight 800
.hero-panel-title clamp(2rem, 5vw, 3.4rem), font-weight 800
.dashboard-title clamp(1.7rem, 3.2vw, 2.5rem), font-weight 800
.login-card-title clamp(1.8rem, 4vw, 2.4rem), font-weight 800

Regel: Überschriften mit font-weight: 800 und letter-spacing: -0.05em bis -0.06em für Großtitel.


4. Layout-Struktur (App Shell)

vaadin-app-layout (336px Drawer)
├── [part=drawer]          ← Sidebar (Dunkelblau-Gradient)
│   ├── .app-drawer-header
│   ├── .app-drawer-scroll (Scroller, flex: 1)
│   │   └── .app-nav-container
│   │       └── .app-nav-tree (TreeGrid)
│   └── .app-user-menu
└── [Light DOM default slot]
    └── .view-container    ← Routed View Container
        └── <geroutete View>

view-container

Der Container, in dem alle gerouteten Views dargestellt werden:

.view-container {
  display: flex;
  flex-direction: column;
  margin: 20px;                    /* Abstand zum Browserrand */
  width: calc(100% - 40px);
  height: calc(100dvh - 40px);
  overflow: hidden;
  border-radius: 12px;
  box-sizing: border-box;
}

Wichtig: Views, die in .view-container landen, erhalten automatisch:

flex: 1 1 0; min-height: 0; min-width: 0; max-width: 100%; box-sizing: border-box;

5. View-Klassen

Jede View muss genau eine der folgenden Root-Klassen tragen:

Klasse Verwendung Extends
data-view Listen, Tabellen, Grids Main oder VerticalLayout
form-page Formulare mit zentriertem Inhalt VerticalLayout
admin-form-view Admin-Formulare VerticalLayout
message-hub-view Nachrichten-Übersicht Main
statistics-chat-view Statistik/Chat VerticalLayout
dashboard-view Dashboard-Seiten VerticalLayout
landing-view Startseite (unauthentifiziert) Main
login-view Login-Seite Main

Kritische Java-Regeln

// ✅ Richtig für Daten-Views:
addClassName("data-view");
// KEIN setJustifyContentMode(CENTER) — wird per CSS auf START erzwungen

// ✅ Richtig für Formular-Views (zentrierter Inhalt):
addClassName("form-page");
setJustifyContentMode(JustifyContentMode.CENTER);
setDefaultHorizontalComponentAlignment(Alignment.CENTER);

// ❌ Falsch: setPadding(true) auf View-Root oder form-shell ohne form-card
// → wird per CSS auf 0 zurückgesetzt, erzeugt keine visuelle Wirkung

6. Interne Layout-Klassen

form-shell Vollbreite-Wrapper

VerticalLayout layout = new VerticalLayout();
layout.setPadding(false);
layout.setSpacing(true);
layout.setWidthFull();
layout.addClassName("form-shell");

Regeln:

  • Kein setPadding(true) → hat keine Wirkung, da via CSS überschrieben
  • Kein horizontales Margin
  • box-sizing: border-box, max-width: 100%, min-width: 0

form-card Schmales zentriertes Karten-Formular

container.addClassNames("form-shell", "narrow", "form-card");
container.setWidth("400px");
container.setMaxWidth("100%");

Bekommt weißen Hintergrund (0.9 Alpha), border-radius: 28px, backdrop-filter: blur(18px).

surface-panel Datenpanel / Tabellenkarte

Div panel = new Div(grid);
panel.addClassNames("surface-panel", "data-grid-panel");
panel.setWidthFull();
.surface-panel {
  border: 1px solid var(--app-border-strong);
  background: var(--app-surface);
  backdrop-filter: blur(18px);
  box-shadow: var(--app-shadow-lg);
  border-radius: 28px;
  box-sizing: border-box;
  max-width: 100%;
}
.data-grid-panel {
  padding: 0.8rem;
  min-height: 420px;
  box-sizing: border-box;
  max-width: 100%;
}

7. ViewToolbar

Einheitliche Toolbar für alle Seiten-Überschriften:

add(new ViewToolbar(getTranslation("my.view.title")));
// Mit Aktionsbutton:
add(new ViewToolbar(getTranslation("my.view.title"), myButton));
.view-toolbar {
  width: 100%;
  padding: 0.75rem 1rem;
  box-sizing: border-box;
}
.view-toolbar-title {
  color: var(--app-text-strong);
  font-weight: 800;
  letter-spacing: -0.05em;
}

8. Daten-Grids

Standard-Muster für alle Tabellen-Views:

// View-Root
setSizeFull();
addClassName("data-view");

// ViewToolbar
add(new ViewToolbar(getTranslation("title"), addButton));

// Grid
Grid<T> grid = new Grid<>(T.class, false);
grid.setWidthFull();
grid.setHeightFull();
grid.addClassName("data-grid");

// Panel-Wrapper
Div panel = new Div(grid);
panel.addClassNames("surface-panel", "data-grid-panel");
panel.setWidthFull();
add(panel);

Grid-Styling:

  • Hintergrund: rgba(255,255,255,0.88), border-radius: 24px
  • Header: Gedämpftes Grau, Uppercase, font-weight: 800, font-size: 0.76rem
  • Selektierte Zeile: --app-accent-soft Hintergrund

9. Formulare (Form-Views)

// View-Root
setSizeFull();
setJustifyContentMode(JustifyContentMode.CENTER);
setDefaultHorizontalComponentAlignment(Alignment.CENTER);
setPadding(true);
addClassName("form-page");

// Formular-Container
VerticalLayout container = new VerticalLayout();
container.setWidth("600px");
container.setMaxWidth("100%");
container.setPadding(true);
container.setSpacing(true);
container.addClassNames("form-shell", "form-card");

// Formular-Felder
FormLayout form = new FormLayout();
form.setWidthFull();
form.setResponsiveSteps(new FormLayout.ResponsiveStep("0", 2));

Formularfelder haben automatisch:

  • border-radius: 20px
  • background: #ffffff
  • border: 1px solid var(--app-field-border)
  • Hover: Blaue Highlight-Farbe
  • Fokus: Blauer Ring rgba(37,99,235,0.18)

10. Filter-Panels

HorizontalLayout filterBar = new HorizontalLayout();
filterBar.addClassName("filter-panel");
filterBar.setWidthFull();
.filter-panel {
  padding: 1rem 1.15rem;
  border-radius: 26px;
  flex-wrap: wrap;
  gap: 1rem;
  min-width: 0;
  max-width: 100%;
  box-sizing: border-box;
}

Wichtig: Filter-Felder niemals mit fixen Pixel-Breiten versehen → flex-wrap: wrap und min-width: 0 sorgen für korrektes Responsive-Verhalten.


11. Sidebar / Navigation

Nav-Rows

.app-nav-row {
  border-radius: 18px;
  border: 1px solid rgba(255,255,255,0.06);
  background: rgba(255,255,255,0.08);
  max-width: calc(100% - 4px);
  transition: transform 0.18s ease, background-color 0.18s ease;
}
.app-nav-row:hover {
  transform: translateX(4px);
  background: rgba(255,255,255,0.14);
}

User Menu

.app-user-menu {
  margin: auto 0.5rem 0.5rem;
  padding: 0.45rem 0.65rem;
  border-radius: 22px;
  border: 1px solid rgba(255,255,255,0.1);
  background: rgba(255,255,255,0.08);
  backdrop-filter: blur(14px);
}

12. Karten-Hierarchie

Klasse Radius Hintergrund Einsatz
surface-panel 28px rgba(255,255,255,0.78) + Blur Hauptdatenpanels
form-card 28px rgba(255,255,255,0.90) + Blur Formular-Cards
section-card, content-card 26px rgba(255,255,255,0.88) Abschnittspanels
simple-card 26px rgba(255,255,255,0.88) Einfache Karten
detail-card 24px rgba(255,255,255,0.88) Detail-Informationen
hero-panel 34px Dunkelblau-Gradient Hero-Bereiche
feature-card 24px Linear-Gradient weiß/blau Feature-Kacheln
message-card 22px Linear-Gradient weiß Nachrichten
station-tile 24px Linear-Gradient weiß Station-Kacheln
job-task-card 22px Linear-Gradient weiß Aufgaben-Karten

13. Station Tiles & Kacheln

Station Tile

.station-tile {
  border-radius: 24px;
  border: 1px solid var(--app-border-strong);
  background: linear-gradient(180deg, rgba(255,255,255,0.98), rgba(244,248,255,0.9));
  box-shadow: var(--app-shadow-sm);
  transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease;
}
.station-tile:hover {
  transform: translateY(-3px);
  box-shadow: var(--app-shadow-md);
  border-color: rgba(37,99,235,0.24);
}
.station-tile.validated {
  background: linear-gradient(180deg, rgba(236,253,245,0.98), rgba(209,250,229,0.88));
  border-color: rgba(5,150,105,0.26);
}

Add Station Tile (gestrichelt)

.add-station-tile {
  background: linear-gradient(180deg, rgba(241,245,249,0.9), rgba(248,250,252,0.98));
  border-style: dashed;
}

Stations Grid

.stations-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 1rem;
  width: 100%;
}

14. Job Task Cards

.job-task-card {
  display: flex;
  align-items: center;
  gap: 1rem;
  padding: 1rem;
  border: 1px solid var(--app-border-strong);
  border-radius: 22px;
  background: linear-gradient(180deg, rgba(255,255,255,0.98), rgba(246,249,255,0.9));
  box-shadow: var(--app-shadow-sm);
  transition: transform 0.2s ease, box-shadow 0.2s ease;
  cursor: pointer;
}
.job-task-card:hover {
  transform: translateY(-2px);
  box-shadow: var(--app-shadow-md);
  border-color: rgba(37,99,235,0.24);
}
.job-task-card.completed {
  border-color: rgba(5,150,105,0.22);
  background: linear-gradient(180deg, rgba(236,253,245,0.98), rgba(220,252,231,0.88));
}

Task Status Badges

.job-task-status.open {
  background: rgba(220,38,38,0.12);
  color: var(--lumo-error-text-color);
}
.job-task-status.completed {
  background: rgba(5,150,105,0.12);
  color: var(--lumo-success-text-color);
}

15. Dashboard Stat Cards

.dashboard-stat-card {
  position: relative;
  border-radius: 24px;
  padding: 1.15rem;
  border: 1px solid var(--app-border-strong);
  background: linear-gradient(180deg, rgba(255,255,255,0.98), rgba(246,249,255,0.92));
  box-shadow: var(--app-shadow-md);
}
.dashboard-stat-card::before {
  content: "";
  position: absolute;
  inset: 0 auto 0 0;
  width: 4px;
  background: var(--dashboard-accent, var(--app-accent));
}

Akzent-Farben

.accent-blue   { --dashboard-accent: #2563eb; }
.accent-green  { --dashboard-accent: #059669; }
.accent-purple { --dashboard-accent: #7c3aed; }
.accent-orange { --dashboard-accent: #d97706; }
.accent-gray   { --dashboard-accent: #64748b; }
.accent-red    { --dashboard-accent: #dc2626; }

16. Message & Chat Components

Message Card

.message-card {
  padding: 1rem;
  border: 1px solid var(--app-border-strong);
  border-radius: 22px;
  background: linear-gradient(180deg, rgba(255,255,255,0.98), rgba(246,249,255,0.9));
  box-shadow: var(--app-shadow-sm);
  transition: transform 0.22s ease, box-shadow 0.22s ease;
}
.message-card:hover {
  transform: translateY(-4px);
  box-shadow: 0 24px 48px rgba(15,23,42,0.14);
}

Chat Bubbles

.message-bubble {
  max-width: min(78%, 720px);
  padding: 0.85rem 1rem;
  border-radius: 22px;
  box-shadow: var(--app-shadow-sm);
}
.message-bubble.client {
  background: rgba(255,255,255,0.96);
  border: 1px solid var(--app-border-strong);
}
.message-bubble.server {
  background: linear-gradient(135deg, rgba(191,219,254,0.8), rgba(167,243,208,0.74));
  border: 1px solid rgba(59,130,246,0.22);
}

Chat AI/User Bubbles

.chat-bubble--user {
  background: linear-gradient(135deg, #2563eb, #1d4ed8);
  color: #eff6ff;
  padding: 0.85rem 1rem;
  border-radius: 24px;
}
.chat-bubble--ai {
  background: rgba(255,255,255,0.94);
  border: 1px solid var(--app-border-strong);
  padding: 1rem;
  border-radius: 24px;
}

17. Dialoge

vaadin-dialog-overlay::part(content) {
  border-radius: 28px;
  border: 1px solid var(--app-border-strong);
  background: rgba(255,255,255,0.94);
  backdrop-filter: blur(20px);
  box-shadow: var(--app-shadow-xl);
}

Dialog Panels

Klasse Verwendung
.dialog-form-panel Formulare im Dialog
.dialog-content-panel Inhalte im Dialog
.dialog-task-card Aufgaben-Karten im Dialog
.dialog-cargo-card Fracht-Karten im Dialog
.dialog-form-panel,
.dialog-content-panel {
  backdrop-filter: blur(12px);
  padding: 1rem;
  border-radius: 24px;
}

18. Detail Cards

.detail-card {
  border: 1px solid var(--app-border-strong);
  border-radius: 24px;
  background: rgba(255,255,255,0.9);
  box-shadow: var(--app-shadow-sm);
  padding: 1rem;
}
.detail-card--accent {
  border-color: rgba(37,99,235,0.22);
  background: linear-gradient(180deg, rgba(219,234,254,0.62), rgba(239,246,255,0.9));
}
.detail-card--code,
.detail-card--comment {
  font-family: ui-monospace, "SFMono-Regular", monospace;
}

19. Route & Summary Cards

.route-card,
.summary-card {
  border: 1px solid var(--app-border-strong);
  border-radius: 24px;
  background: rgba(255,255,255,0.84);
  box-shadow: var(--app-shadow-sm);
  padding: 1rem;
}
.services-panel,
.notes-panel {
  border: 1px solid var(--app-border-strong);
  border-radius: 24px;
  background: rgba(255,255,255,0.84);
  box-shadow: var(--app-shadow-sm);
  padding: 0.75rem 1rem 1rem;
}

20. Login Seite

.login-shell {
  width: min(1120px, 100%);
  display: grid;
  grid-template-columns: minmax(0, 1.15fr) minmax(360px, 440px);
  gap: 1.5rem;
}
.login-highlight {
  position: relative;
  overflow: hidden;
  min-height: 560px;
  padding: clamp(1.8rem, 4vw, 3rem);
  border-radius: 32px;
  border: 1px solid rgba(255,255,255,0.14);
  background: linear-gradient(140deg, #081224 0%, #173d8d 52%, #0f766e 100%);
  box-shadow: var(--app-shadow-xl);
}
.login-card {
  width: 100%;
  padding: clamp(1.35rem, 3vw, 2rem);
  border-radius: 32px;
  border: 1px solid var(--app-border-strong);
  background: rgba(255,255,255,0.88);
  backdrop-filter: blur(20px);
  box-shadow: var(--app-shadow-lg);
}

Eyebrow Chip

.eyebrow-chip {
  display: inline-flex;
  align-items: center;
  padding: 0.45rem 0.8rem;
  border-radius: 999px;
  background: rgba(255,255,255,0.14);
  color: rgba(248,250,252,0.92);
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

21. Landing Page

.landing-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
}
.landing-logo {
  font-size: clamp(1.6rem, 4vw, 2.2rem);
  font-weight: 800;
  letter-spacing: -0.05em;
}
.landing-nav-button,
.landing-language-button {
  border-radius: 999px;
}

Hero Panel

.hero-panel {
  position: relative;
  overflow: hidden;
  min-height: 340px;
  justify-content: center;
  text-align: center;
  border-radius: 34px;
  border: 1px solid rgba(255,255,255,0.14);
  background: linear-gradient(135deg, #081224 0%, #1d4ed8 54%, #0f766e 100%);
  box-shadow: var(--app-shadow-xl);
}
.hero-panel-title {
  color: #f8fafc;
  font-size: clamp(2rem, 5vw, 3.4rem);
  font-weight: 800;
  letter-spacing: -0.06em;
}
.hero-panel-text {
  color: rgba(226,232,240,0.92);
  font-size: clamp(1rem, 2vw, 1.15rem);
}

22. Timeline Components

.timeline-entry-card {
  margin-bottom: 0.75rem;
  padding: 1rem;
  border: 1px solid var(--app-border-strong);
  border-radius: 24px;
  background: rgba(255,255,255,0.9);
  box-shadow: var(--app-shadow-sm);
  border-left: 4px solid var(--timeline-accent, var(--app-accent));
}
.timeline-reason {
  font-weight: 700;
  color: var(--app-text-strong);
}
.timeline-timestamp {
  color: var(--app-text-muted);
  font-size: 0.78rem;
}

23. Invoice Components

Invoice Generator

.invoice-generator-panel {
  backdrop-filter: blur(16px);
  border-radius: 24px;
  padding: 1rem;
  border: 1px solid var(--app-border-strong);
  background: rgba(255,255,255,0.9);
}
.invoice-generator-template {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.95rem 1rem;
  border: 1px solid var(--app-border-strong);
  border-radius: 18px;
  background: linear-gradient(180deg, rgba(255,255,255,0.98), rgba(246,249,255,0.92));
  cursor: grab;
  transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.invoice-generator-template:hover {
  transform: translateY(-2px);
  box-shadow: var(--app-shadow-sm);
}
.invoice-generator-canvas {
  position: relative;
  overflow: hidden;
  border: 2px dashed rgba(148,163,184,0.5);
  border-radius: 24px;
  background:
    linear-gradient(180deg, rgba(255,255,255,0.95), rgba(241,245,249,0.9)),
    linear-gradient(90deg, rgba(148,163,184,0.08) 1px, transparent 1px),
    linear-gradient(rgba(148,163,184,0.08) 1px, transparent 1px);
  background-size: auto, 28px 28px, 28px 28px;
}

Price Table

.price-table {
  width: 100%;
}
.price-row {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.35rem 0;
}
.price-row--strong .price-row-label,
.price-row--strong .price-row-value {
  font-weight: 800;
  color: var(--app-text-strong);
}

24. Buttons

vaadin-button {
  font-weight: 700;
  border-radius: var(--lumo-border-radius-m);
  transition: transform 0.18s ease, box-shadow 0.18s ease, opacity 0.18s ease;
}
vaadin-button:not([disabled]):hover {
  transform: translateY(-1px);
}
vaadin-button[theme~="primary"] {
  box-shadow: 0 8px 20px rgba(37,99,235,0.22);
}
vaadin-button[theme~="primary"][theme~="success"] {
  box-shadow: 0 8px 20px rgba(5,150,105,0.24);
}
vaadin-button[theme~="primary"][theme~="error"] {
  box-shadow: 0 8px 20px rgba(220,38,38,0.24);
}
vaadin-button[disabled] {
  opacity: 0.46;
  transform: none !important;
}

Pill-Buttons (Nav, Landing): border-radius: 999px


25. Overflow & Box-Model Regeln

Pflicht bei allen Container-Elementen

component.getStyle()
    .set("box-sizing", "border-box")
    .set("max-width", "100%")
    .set("min-width", "0");

Warum box-sizing: border-box kritisch ist

Ohne box-sizing: border-box gilt:

Gesamtbreite = width + padding-left + padding-right + border-left + border-right
  • surface-panel hat border: 1px → ohne box-sizing ist es 100% + 2px → Overflow
  • data-grid-panel hat padding: 0.8rem → ohne box-sizing ist es 100% + 1.6rem → Overflow

Warum min-width: 0 kritisch ist

In Flex-Containern verhindert die Default-Eigenschaft min-width: auto, dass Flex-Items kleiner werden als ihr Inhalt. min-width: 0 erlaubt das Schrumpfen unter die Inhaltsgröße.


26. Lumo-Override-Strategie

Padding-Override

/* Lumo: vaadin-vertical-layout[theme~="padding"] → Spezifizität 0,0,1,1 */
/* Override braucht mindestens 0,0,2,0: */
vaadin-vertical-layout.form-shell:not(.form-card) { padding: 0; }

Inline-Style-Override

setJustifyContentMode() setzt Inline-Styles → nur !important überschreibt:

vaadin-vertical-layout.data-view {
  justify-content: flex-start !important;
  align-items: stretch !important;
}

Regel: !important nur für Inline-Style-Overrides verwenden.


27. Responsive Verhalten

Breakpoints

Breakpoint Änderungen
max-width: 980px Login-Shell wird einspaltig
max-width: 720px view-container Margin reduziert auf 10px, border-radius auf 8px
max-height: 820px Drawer-Header und Nav-Rows bekommen kompakteres Padding

Mobile View-Container

@media (max-width: 720px) {
  .view-container {
    margin: 10px;
    width: calc(100% - 20px);
    height: calc(100dvh - 20px);
    border-radius: 8px;
  }
}

Flex-Wrap-Pflicht

Alle horizontalen Layouts, die Felder oder Buttons enthalten, müssen flex-wrap: wrap haben:

layout.getStyle().set("flex-wrap", "wrap");

.job-photo-gallery {
  max-width: 600px;
  min-height: 500px;
  height: 500px;
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1rem;
  background: rgba(255,255,255,0.96);
  border-radius: 24px;
}
.job-photo-counter {
  position: absolute;
  top: var(--lumo-space-s);
  right: var(--lumo-space-s);
  padding: var(--lumo-space-xs) var(--lumo-space-s);
  border-radius: 999px;
  background: rgba(15,23,42,0.72);
  color: #f8fafc;
  font-weight: 700;
}
.job-photo-nav {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  border-radius: 999px;
  background: rgba(255,255,255,0.9);
  box-shadow: var(--app-shadow-sm);
}

29. Empty State

.empty-state-card {
  text-align: center;
  color: var(--app-text-muted);
  padding: 1rem;
  border: 1px dashed var(--app-border-strong);
  border-radius: 24px;
  background: rgba(248,250,252,0.74);
}

30. Inline Flash (Fehlermeldungen)

.inline-flash {
  width: 100%;
  padding: 0.95rem 1rem;
  border-radius: 18px;
  border: 1px solid rgba(220,38,38,0.22);
  background: rgba(254,242,242,0.92);
  color: var(--lumo-error-text-color);
  box-sizing: border-box;
}

31. Checkliste für neue Views

Beim Erstellen einer neuen View folgende Punkte prüfen:

  • Root-Element hat genau eine View-Klasse (data-view, form-page, etc.)
  • Kein setPadding(true) auf dem View-Root oder form-shell-Wrappern (ohne form-card)
  • Kein setJustifyContentMode(CENTER) auf Daten-Views (data-view, admin-form-view)
  • Alle Container haben max-width: 100% und box-sizing: border-box
  • Alle Flex-Container mit wechselnden Inhalten haben flex-wrap: wrap
  • Formularfelder haben keine fixen Pixel-Breiten (setWidth("200px") verboten)
  • ViewToolbar ist das erste Kind-Element
  • Grid-Panel verwendet surface-panel data-grid-panel mit setWidthFull()
  • Schmale Formular-Container haben setWidth("Xpx") und setMaxWidth("100%")
  • Inline-Styles nur für dynamische Werte verwenden, alles andere per CSS-Klassen