diff --git a/CODE_GUIDELINES.md b/CODE_GUIDELINES.md index 50c9eca..68e7887 100644 --- a/CODE_GUIDELINES.md +++ b/CODE_GUIDELINES.md @@ -60,6 +60,9 @@ We are building a membership management system (Mila) using the following techno 7. [Documentation Standards](#7-documentation-standards) 8. [Accessibility Guidelines](#8-accessibility-guidelines) +**Related documents:** +- **UI / UX:** [`DESIGN_DUIDELINES.md`](../DESIGN_DUIDELINES.md) defines visual and interaction consistency: use of CoreComponents (no raw DaisyUI in views), page skeleton (`<.header>`, `mt-6 space-y-6`), typography, buttons, forms, tables, flash/toast, and microcopy (e.g. German "du" and glossary). Follow "components first" and semantic variants instead of hard-coded colors. + --- ## 1. Setup and Architectural Conventions diff --git a/DESIGN_DUIDELINES.md b/DESIGN_DUIDELINES.md new file mode 100644 index 0000000..b0372ef --- /dev/null +++ b/DESIGN_DUIDELINES.md @@ -0,0 +1,361 @@ +# UI Design Guidelines (Mila / Phoenix LiveView + DaisyUI) + +## Purpose +This document defines Mila’s **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. + <:actions> + <.button variant="primary" navigate={...}>Primary action + + + +
+ +
+ +## 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 +
+

No members yet.

+ <.button variant="primary" navigate={~p"/members/new"}>Create member +
+ +### 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 **``** 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 :let={m} label="Newsletter"> + JS.stop_propagation()} + /> + + + <:action :let={m}> + <.button + variant="ghost" + size="sm" + navigate={~p"/members/#{m.id}/edit"} + phx-click={JS.stop_propagation()} + > + Edit + + + + +Notes: +- The checkbox uses `phx-click={JS.push(...) |> JS.stop_propagation()}` so it won’t 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: 4–6s + - warning: 6–8s + - error: 8–12s (or manual dismiss for critical errors) +- **MUST:** Keep a dismiss button for accessibility and user control. + +### 9.3 Variants + special “email copied” +- Supported semantic variants: `info`, `success`, `warning`, `error`. +- **Special case:** clipboard “Email copied” uses a **soft/light blue** tone distinct from normal info. +- **MUST:** Model this as `tone="soft"` (or similar prop) on the flash component, not hard-coded colors in views. + +### 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. + +---