mitgliederverwaltung/DESIGN_DUIDELINES.md

361 lines
13 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.

# UI Design Guidelines (Mila / Phoenix LiveView + DaisyUI)
## Purpose
This document defines Milas **UI system** to ensure **UX consistency**, **accessibility**, and **maintainability** across Phoenix LiveView pages:
- consistent DaisyUI usage
- typography & spacing
- button intent & labeling
- list/search/filter UX
- tables behavior (row click, tooltips, alignment)
- flash/toast UX (position, stacking, auto-dismiss, tones)
- standard page skeletons (index/detail/form)
- microcopy conventions (German “du” tone)
> Engineering practices (LiveView load budget, testing, security, etc.) are defined in `docs/CODE_GUIDELINES.md`.
> This document focuses on **visual + UX** consistency and references engineering rules where needed.
---
## 1) Principles
### 1.1 Components first (no raw DaisyUI classes in views)
- **MUST:** Use `MvWeb.CoreComponents` (e.g. `<.button>`, `<.header>`, `<.table>`, `<.input>`, `<.flash_group>`, `<.form_section>`).
- **MUST NOT:** Write DaisyUI component classes directly in LiveViews/HEEX (e.g. `btn`, `alert`, `table`, `input`, `select`, `tooltip`) unless you are implementing them **inside** CoreComponents.
- **MAY:** Use Tailwind for layout only: `flex`, `grid`, `gap-*`, `p-*`, `max-w-*`, `sm:*`, etc.
### 1.2 DaisyUI for look, Tailwind for layout
- DaisyUI: component visuals + semantic variants (`btn-primary`, `alert-error`, `badge`, `tooltip`).
- Tailwind: spacing, alignment, responsiveness.
### 1.3 Semantics over hard-coded colors
- **MUST NOT:** Use “status colors” in views (`bg-green-500`, `text-blue-500`, …).
- **MUST:** Express intent via component props / DaisyUI semantic variants.
---
## 2) Page Skeleton & “Chrome” (mandatory)
### 2.1 Standard page layout
Every authenticated page should follow the same structure:
1) `<.header>` (title + optional subtitle + actions)
2) content area with consistent vertical rhythm (`mt-6 space-y-6`)
3) optional footer actions for forms
**MUST:** Use `<.header>` on every page (except login/public pages).
**SHOULD:** Put short explanations into `<:subtitle>` rather than sprinkling random text blocks.
**Template:**
```heex
<.header>
Title
<:subtitle>Short explanation of what the page is for.</:subtitle>
<:actions>
<.button variant="primary" navigate={...}>Primary action</.button>
</:actions>
</.header>
<div class="mt-6 space-y-6">
<!-- Sections / Cards / Tables -->
</div>
## 3) Typography (system)
Use these standard roles:
| Role | Use | Class |
|---|---|---|
| Page title (H1) | main page title | `text-xl font-semibold leading-8` |
| Subtitle | helper under title | `text-sm text-base-content/70` |
| Section title (H2) | section headings | `text-lg font-semibold` |
| Helper text | under inputs | `text-sm text-base-content/70` |
| Fine print | small hints | `text-xs text-base-content/60` |
| Empty state | no data | `text-base-content/60 italic` |
| Destructive text | danger | `text-error` |
**MUST:** Page titles via `<.header>`.
**MUST:** Section titles via `<.form_section title="…">` (for forms) or a consistent section wrapper (if you introduce a `<.card>` later).
---
## 4) States: Loading, Empty, Error (mandatory consistency)
### 4.1 Loading state
- **MUST:** Show a consistent loading indicator when data is not ready.
- **MUST NOT:** Render empty states while loading (avoid flicker).
- **SHOULD:** Prefer “skeleton rows” for tables or a spinner in content area.
### 4.2 Empty state pattern
Empty states must be consistent:
- short message
- optional primary CTA (“Create …”)
- optional secondary help link
**Example:**
```heex
<div class="space-y-3">
<p class="text-base-content/60 italic">No members yet.</p>
<.button variant="primary" navigate={~p"/members/new"}>Create member</.button>
</div>
### 4.3 Error state pattern
- **MUST:** Use flash/toast for global errors.
- **SHOULD:** Also show inline error state near the relevant content area if the page cannot proceed.
---
## 5) Buttons (intent, labels, variants)
### 5.1 Decision rule: action vs status
- **MUST:** Button labels describe **actions** (verb-first):
- ✅ Save, Create member, Send invite, Import CSV
- ❌ Active, Success, Done (status belongs elsewhere)
- **MUST:** Status belongs in badges/labels or read-only text, not in CTAs.
### 5.2 Standard variants (mandatory set)
Buttons must be rendered via `<.button>` and mapped to DaisyUI internally.
**Supported variants:**
- `primary` (main CTA)
- `secondary` (supporting)
- `neutral` (cancel/back)
- `ghost` (low emphasis; table/toolbars)
- `outline` (alternative CTA)
- `danger` (destructive)
- `link` (inline; rare)
- `icon` (icon-only)
**Sizes:** `sm`, `md` (default), `lg` (rare)
### 5.3 Placement rules
- Header CTA inside `<.header><:actions>`.
- Form footer: primary right; cancel/secondary left.
- Tables: use `ghost`/`icon` for row actions (avoid `primary` inside rows).
### 5.4 Primary vs Secondary (UX consistency rules)
#### One primary action per screen
- MUST: Each screen/section has at most one **primary** action (e.g. Save, Create, Start import).
- SHOULD: Additional actions are secondary/neutral/ghost, not additional primary.
#### Primary vs Secondary meaning
- Primary = the most important/most common action to complete the user task.
- Secondary = supporting actions (Cancel/Back/Edit in tool contexts), lower emphasis.
#### Order and placement (choose and apply consistently)
We follow these ordering rules:
- MUST: Order buttons by priority: **Primary → Secondary → Tertiary**.
- Forms: Decide once (primary-left OR primary-right) and apply everywhere.
- Dialogs/confirmations: Place the confirmation action consistently (e.g. trailing edge, confirmation closest to edge).
#### Cancel/Back consistency
- MUST: Cancel/Back is **never** styled as primary.
- MUST: Cancel/Back placement is consistent across the app (same side, same label).
#### Implementation requirement
- MUST: Use CoreComponents (`<.button>`) with `variant`/`size` props.
- MUST NOT: Use ad-hoc classes like `class="secondary"` on `<.button>`; instead extend CoreComponents to support `secondary`, `neutral`, `ghost`, `danger`, etc.
#### Ghost buttons (accessibility requirements)
Ghost buttons are allowed for low-emphasis actions (toolbars, table actions), but:
- MUST: Focus indicator is clearly visible (do not remove outlines).
- MUST: UI contrast for the control (and meaningful icons) meets WCAG non-text contrast (≥ 3:1).
- MUST: Icon-only ghost buttons provide an accessible name (`aria-label`) and preferably a tooltip.
- SHOULD: Hit target is large enough for touch/motor accessibility (recommend ~44x44px).
If these cannot be met, use `secondary`/`outline` instead of `ghost`.
---
## 6) Forms (structure + interaction rules)
### 6.1 Structure
- **MUST:** Forms are grouped into `<.form_section title="…">`.
- **MUST:** All inputs via `<.input>`.
### 6.2 Validation timing (consistent UX)
- **MUST:** Validate on submit always.
- **SHOULD:** Validate on change only where it helps; use debounce to avoid “error spam”.
- **MUST:** Define a consistent “when errors appear” rule:
- Preferred: show field errors after first submit attempt OR after the field has been touched (pick one and apply everywhere).
> Engineering note (implementation): follow LiveView load budget in `CODE_GUIDELINES.md` (no DB reads on `phx-change` by default).
### 6.3 Required fields
- **MUST:** Required fields are marked consistently (UI indicator + accessible text).
- **SHOULD:** If required-ness is configurable via settings, display it consistently in the form.
---
## 7) Lists, Search & Filters (mandatory UX consistency)
### 7.1 Standard filter/search bar pattern
- **MUST:** All list pages use the same search/filter placement (choose one layout and apply everywhere).
- Recommended: top area above the table, aligned with page actions.
- **MUST:** Always provide “Clear filters” when filters are active.
- **MUST:** Filter state is reflected in URL params (so reload/back/share works consistently).
### 7.2 URL behavior (UX rule)
- Use `push_patch` for in-page state changes: filters, sorting, pagination, tabs.
- Use `push_navigate` for actual page transitions: details, edit, new.
---
## 8) Tables (mandatory UX)
### 8.1 Default behavior: row click opens details
- **DEFAULT:** Clicking a row navigates to the details page.
- **EXCEPTIONS:** Highly interactive rows may disable row-click (document why).
**IMPORTANT (correctness with our `<.table>` CoreComponent):**
Our table implementation attaches the `phx-click` to the **`<td>`** when `row_click` is set. That means click events bubble from inner elements up to the cell unless we stop propagation.
So, for interactive elements inside a clickable row, you must **stop propagation using `Phoenix.LiveView.JS.stop_propagation/1`**, not a custom attribute.
✅ Correct pattern (one click handler that both stops propagation and triggers an event):
```heex
<.table
id="members"
rows={@members}
row_click={fn m -> JS.navigate(~p"/members/#{m.id}") end}
>
<:col :let={m} label="Name">
<%= m.last_name %>, <%= m.first_name %>
</:col>
<:col :let={m} label="Newsletter">
<input
type="checkbox"
class="checkbox checkbox-sm"
checked={m.newsletter}
phx-click={JS.push("toggle_newsletter", value: %{id: m.id}) |> JS.stop_propagation()}
/>
</:col>
<:action :let={m}>
<.button
variant="ghost"
size="sm"
navigate={~p"/members/#{m.id}/edit"}
phx-click={JS.stop_propagation()}
>
Edit
</.button>
</:action>
</.table>
Notes:
- The checkbox uses `phx-click={JS.push(...) |> JS.stop_propagation()}` so it wont trigger row navigation.
- The Edit button also stops propagation to avoid accidental row navigation when clicked.
### 8.2 Tooltips (mandatory where needed)
- **MUST:** Tooltips for:
- icon-only actions
- truncated content
- status badges that require explanation
- **MUST:** Provide tooltips via a shared wrapper (recommended `<.tooltip>` CoreComponent).
- **MUST NOT:** Scatter ad-hoc tooltip markup in views.
### 8.3 Alignment & density conventions
- **MUST:** Text columns left-aligned.
- **MUST:** Numeric columns right-aligned.
- **MUST:** Action column right-aligned.
- **SHOULD:** Table density is consistent:
- default density for most tables
- a single “dense” option only if needed (via a prop, not per-page random classes)
### 8.4 Truncation standard
- **MUST:** Truncate long values consistently (same max widths for name/email-like fields).
- **MUST:** Tooltip reveals full value when truncated.
---
## 9) Flash / Toast messages (mandatory UX)
### 9.1 Location + stacking
- **MUST:** Position flash/toasts at the bottom of the viewport (pick bottom-right or bottom-center; be consistent).
- **MUST:** Stack all flash messages with consistent spacing.
- **SHOULD:** Newest appears on top.
### 9.2 Auto-dismiss
- **MUST:** Flash messages disappear automatically:
- info/success: 46s
- warning: 68s
- error: 812s (or manual dismiss for critical errors)
- **MUST:** Keep a dismiss button for accessibility and user control.
- **Status:** Not yet implemented. See [feature-roadmap](docs/feature-roadmap.md) → Flash: Auto-dismiss and consistency.
### 9.3 Variants (unified)
- Supported semantic variants: `info`, `success`, `warning`, `error`.
- **MUST:** Use the same variants for all flash types, : e.g. `success` for copy success, no separate tone or styling. This keeps flash UX consistent across the app.
### 9.4 Accessibility
- Flash must work with screen readers (live region behavior belongs in the flash component implementation).
- See `CODE_GUIDELINES.md` Accessibility → live regions.
---
## 10) Mutations & feedback patterns (create/update/delete/import)
### 10.1 Mutation feedback is always two-part
For create/update/delete:
- **MUST:** Show a toast/flash message
- **MUST:** Show a visible UI update (navigate, row removed, values updated)
No “silent success”.
### 10.2 Destructive actions: one standard confirmation pattern
- **MUST:** All destructive actions use the same confirm style and wording conventions.
- Choose one approach and standardize:
- `JS.confirm("…")` everywhere (simple, consistent)
- or a modal component everywhere (more flexible, more work)
**Recommended copy style:**
- Title/confirm text is clear and specific (what will be deleted, consequences).
- Buttons: `Cancel` (neutral) + `Delete` (danger).
---
## 11) Detail pages (consistent structure)
Detail pages should not drift into random layouts.
**MUST:** Use consistent structure:
- header with primary action (Edit)
- sections/cards for grouped info
- “Danger zone” section at bottom for destructive actions
---
## 12) Navigation rules (UX consistency)
- **MUST:** `push_patch` for in-page state: sorting, filtering, pagination, tabs.
- **MUST:** `push_navigate` for page transitions: detail/edit/new.
- **SHOULD:** Back button behavior must feel predictable (URL reflects state).
---
## 13) Microcopy conventions (German “du” tone + glossary)
### 13.1 Tone
- **MUST:** All German user-facing text uses informal address (“du”).
- **MUST:** Use consistent verbs for common actions:
- Save: “Speichern”
- Cancel: “Abbrechen”
- Delete: “Löschen”
- Edit: “Bearbeiten”
### 13.2 Preferred terms (starter glossary)
- Member: “Mitglied”
- Fee/Contribution: “Beitrag”
- Settings: “Einstellungen”
- Group: “Gruppe”
- Import/Export: “Import/Export”
- Clear filters: “Filter zurücksetzen” (use when filters are active; button label in list/filter UX)
Add to this glossary when new terminology appears.
---