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 `