From aa29d1b43274e9664cafa2c6488732c0fb816680 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Wed, 11 Nov 2026 22:28:39 +0100 Subject: [PATCH] docs(ui): align the UI guidelines with the binding standards The guidelines still described the v1.4 app: a drawer nobody uses, six languages instead of ten, a hover tooltip as the default inline help although a tooltip never opens on a phone, Dracula utilities for status colours, a page container and a Settings body gutter the standards removed, and components that do not exist (a status callout, a multi-select, honey-pot fields, a per-integration test log). Where the UI standards own a topic, the page now points there; the rest (button choice, form controls, settings persistence, filter bars, the dialog or sheet choice, loading, empty and error states, accessibility, i18n, motion) is kept and corrected, with the section numbers code comments cite unchanged. Inline help is an InfoPopover that opens on a tap. --- docs/ui-guidelines.md | 569 +++++++++++++++--------------------------- 1 file changed, 196 insertions(+), 373 deletions(-) diff --git a/docs/ui-guidelines.md b/docs/ui-guidelines.md index 5b00604e2..c4176a17c 100644 --- a/docs/ui-guidelines.md +++ b/docs/ui-guidelines.md @@ -1,104 +1,30 @@ # HealthLog UI Guidelines -> Visual & interaction patterns in the HealthLog PWA. The binding rules -> for card anatomy, spacing, text colour, tokens and primitives live in -> [`contributing/ui-standards.md`](./contributing/ui-standards.md); where -> this document disagrees with them, the standards win. If a pattern -> below doesn't fit your case, propose an addition in the same PR. - -Last updated: 2026-05-08 (v1.4 cycle) +> Interaction patterns for the HealthLog web app. Card anatomy, spacing, +> text colour, colour tokens, typography and the choice of primitive are +> owned by [`contributing/ui-standards.md`](./contributing/ui-standards.md); +> this page covers what those rules leave open. Where the two disagree, the +> standards win. If a pattern below doesn't fit your case, propose an +> addition in the same PR. --- ## 1. Foundations -### 1.1 Theme +Theme, spacing, typography, radius and colour tokens are defined in the +standards (§1 to §5 there). Two things to add: -HealthLog is a Dracula-themed app with a light counterpart (Alucard). Both -themes share the same six-color signal palette (purple, pink, green, cyan, -orange, red, yellow, lavender), exposed as CSS variables and as Tailwind -`dracula-*` color tokens. Use the semantic tokens (`--primary`, -`--destructive`, `--card`, …) for chrome and the `--dracula-*` tokens for -data viz only. - -| Surface | Token | -| ---------------------------- | ------------------ | -| App background | `--background` | -| Card / popover surface | `--card` | -| Primary action | `--primary` | -| Destructive action | `--destructive` | -| Secondary / muted background | `--secondary` | -| Border | `--border` | -| Focus ring | `--ring` | -| Chart series 1–5 | `--chart-1`…`-5` | -| Brand signal: purple | `--dracula-purple` | -| Brand signal: green (good) | `--dracula-green` | -| Brand signal: orange (warn) | `--dracula-orange` | -| Brand signal: red (alert) | `--dracula-red` | -| Brand signal: cyan (info) | `--dracula-cyan` | - -Dark mode is the default. The theme switcher in the sidebar three-dot menu -toggles `.dark` / `.light` on `:root`. Always test both modes before -shipping a component. - -### 1.2 Spacing - -Tailwind's default 4-pixel scale. Reach for these stops first: - -| Use | Token | Pixels | -| ------------------------------------ | ------------- | ------ | -| Inline gap between icon and label | `gap-1` | 4 | -| Tight stack inside a card | `gap-2` | 8 | -| Label-to-field stack (form fields) | `space-y-2` | 8 | -| Section block (heading + body) | `space-y-4` | 16 | -| Page section separator | `space-y-6` | 24 | -| Dashboard tile-row gap, hero spacing | `gap-4` | 16 | -| Page-edge padding (mobile / desktop) | `px-4 / px-6` | | - -Avoid arbitrary `space-y-5`, `gap-3.5`, etc. unless aligning to a fixed -external constraint. - -### 1.3 Typography - -The app uses the Tailwind defaults inherited via shadcn/ui. Reach for -these classes first: - -| Use | Class | -| ---------------------------------- | --------------------------------------- | -| Page title (h1) | `text-2xl font-semibold tracking-tight` | -| Section heading / section card | `text-lg font-semibold` | -| Tile / inner card title | `text-base font-semibold` | -| Body text | `text-sm` | -| Muted helper / metadata | `text-sm text-muted-foreground` | -| Microcopy / footer | `text-xs text-muted-foreground` | -| Tabular data (latency, large nums) | `font-mono tabular-nums` | - -Card titles come in exactly two sizes: `text-lg font-semibold` for a -section-level card (one per page region) and `text-base font-semibold` -for tiles and inner cards. Don't introduce a third level — a smaller -heading is a sign the content wants a list row, not a card. - -Never use `font-bold` for emphasis — bump to `font-semibold` instead and -let the design system breathe. - -### 1.4 Radius and elevation - -`--radius: 0.625rem` (10px) is the default. The Tailwind tokens -`rounded-md` (`--radius-md` ≈ 8px) and `rounded-lg` (`--radius` ≈ 10px) -cover 95% of cases. Use `rounded-2xl` only for hero cards or large -modals. - -Elevation is achieved via `shadow-xs`, `shadow-sm`, or -`shadow-[0_1px_0_rgba(255,255,255,0.04)]` for very subtle separators on -dark backgrounds. Avoid Tailwind's heavy shadows (`shadow-lg`+) in chrome. +- Dark is the default theme, and the light theme remaps the same semantic + tokens. Check every component in both before shipping it. +- Elevation stays light: `shadow-xs` or `shadow-sm` in chrome, never the + heavy `shadow-lg` and up. --- ## 2. Components -This section is **prescriptive, not exhaustive**. Each entry pairs a -Radix/shadcn primitive with the cases it's the right answer for and the -cases where another primitive should win. +This section is **prescriptive, not exhaustive**. Each entry names the +primitive for a case and the case where another one wins. ### 2.1 Buttons @@ -114,43 +40,35 @@ Every button comes from `@/components/ui/button`. Pick by purpose: | Inline link to external help docs | `link` | `sm` | | Toolbar / table row action when space is tight | `outline` | `xs` | -**Position rules.** +Where buttons sit in a card (one action row, last in the card, primary on +the right) is standards §12. Two additions: -- Forms (full-width or modal): primary on the **right**, cancel/back on - the **left**. Match shadcn dialog defaults. -- Settings sections: a single sticky `Save` button at the **right end** - of the section header _or_ per-row inline `Save`. Never both in the - same section. -- Test buttons (Telegram test, AI ping, etc.): immediately to the right - of the related Save / Apply control. Never below — keeps the eye - motion horizontal. -- Destructive actions live in their own confirm dialog with a - type-to-confirm input when the action is irreversible (delete account, - revoke API token, drop user data). +- A Test button (Telegram test, AI ping and the like) sits in the same row + as the Save or Apply control it belongs to. +- An irreversible action (delete account, revoke an API token, wipe data) + asks in its own confirm dialog, with a type-to-confirm field where the loss + is large. -**Disabled state.** A disabled button must always come with a tooltip or -helper text explaining why. Silent disabled buttons frustrate users. +**Disabled state.** A disabled button always comes with helper text that +says why. A tooltip is not enough: it never opens on a phone. ### 2.2 Inputs and forms Forms keep a controlled draft in component state and validate it with the Zod schemas from `src/lib/validations/`; the server's schema is the authority. -Wrap inputs in `