innercontext/docs/frontend-design-cookbook.md

195 lines
7.7 KiB
Markdown

# Frontend Design Cookbook
This cookbook defines the visual system for the frontend so every new change extends the existing style instead of inventing a new one.
## Agent workflow
- For any frontend edit, consult this cookbook before implementing changes.
- If a change introduces or alters reusable UI patterns, wrappers, component variants, tokens, motion rules, or shared classes, update this cookbook in the same change.
- Keep updates concise and actionable so future edits remain consistent.
## Design intent
- Core tone: light editorial, calm and information-first.
- Product feel: premium personal logbook, not generic SaaS dashboard.
- Contrast model: neutral paper and ink do most of the work; accents are restrained.
## Non-negotiables
- Keep layouts readable first. Aesthetic details support hierarchy, not the other way around.
- Use one visual language across the app shell, cards, forms, tables, and actions.
- Prefer subtle depth (borders, layered paper, soft shadows) over loud gradients.
- Keep motion purposeful and short; always preserve `prefers-reduced-motion` behavior.
## Typography
- Display/headings: `Cormorant Infant`.
- Body/UI text: `Manrope`.
- Use display typography for page titles and section heads only.
- Keep paragraph text in body font for legibility.
## Color system
Global neutrals are defined in `frontend/src/app.css` using CSS variables.
- `--background`, `--card`, `--foreground`, `--muted-foreground`, `--border`
- `--page-accent` drives route-level emphasis.
- `--page-accent-soft` is the low-contrast companion tint.
### Domain accents (muted and professional)
- Dashboard: `--accent-dashboard`
- Products: `--accent-products`
- Routines: `--accent-routines`
- Skin: `--accent-skin`
- Health labs: `--accent-health-labs`
- Health medications: `--accent-health-meds`
The app shell assigns a domain class per route and maps it to `--page-accent`.
## Where to use accent color
Use accent for:
- active navigation state
- important buttons and key badges
- focus ring tint and hover border tint
- section separators and small status markers
Do not use accent for:
- full-page backgrounds
- body text
- large surfaces that reduce readability
Guideline: accent should occupy roughly 10-15% of visual area per screen.
## Layout rules
- Use the app shell spacing rhythm from `app.css` (`.app-main`, editorial cards).
- Keep max content width constrained for readability.
- Prefer asymmetry in hero/summary areas, but keep forms and dense data grids regular.
### Shared page wrappers
Use these wrappers before introducing route-specific structure:
- `editorial-page`: standard constrained content width for route pages.
- `editorial-hero`: top summary strip for title, subtitle, and primary actions.
- `editorial-panel`: primary surface for forms, tables, and ledgers.
- `editorial-toolbar`: compact action row under hero copy.
- `editorial-backlink`: standard top-left back navigation style.
- `editorial-alert`, `editorial-alert--error`, `editorial-alert--success`: feedback banners.
## Component rules
- Reuse UI primitives under `frontend/src/lib/components/ui/*`.
- Keep primitive APIs stable when changing visual treatment.
- Style via tokens and shared classes, not one-off hardcoded colors.
- New variants must be documented in this file.
### Existing shared utility patterns
These classes are already in use and should be reused:
- Lists and ledgers: `routine-ledger-row`, `products-mobile-card`, `health-entry-row`
- Group headers: `products-section-title`
- Table shell: `products-table-shell`
- Tabs shell: `products-tabs`, `editorial-tabs`
- Health semantic pills: `health-kind-pill*`, `health-flag-pill*`
## Forms and data views
- Inputs should remain high-contrast and calm.
- Validation/error states should be explicit and never color-only.
- Tables and dense lists should prioritize scanning: spacing, row separators, concise metadata.
### DRY form primitives
- Use shared form components for repeated native select markup:
- `frontend/src/lib/components/forms/SimpleSelect.svelte`
- `frontend/src/lib/components/forms/GroupedSelect.svelte`
- Use shared checkbox helper for repeated label+hint toggles:
- `frontend/src/lib/components/forms/HintCheckbox.svelte`
- Use shared input field helper for repeated label+input rows:
- `frontend/src/lib/components/forms/LabeledInputField.svelte`
- Use shared section card helper for repeated titled form panels:
- `frontend/src/lib/components/forms/FormSectionCard.svelte`
- Use shared class tokens from:
- `frontend/src/lib/components/forms/form-classes.ts`
- Prefer passing option labels from route files via `m.*` to keep i18n explicit.
### Select policy (performance + maintainability)
- Default to native `<select>` for enum-style fields and simple pickers.
- Use `SimpleSelect` / `GroupedSelect` for consistency and to reduce duplication.
- Avoid `ui/select` primitives unless search, custom keyboard behavior, or richer popup UX is truly needed.
- For grouped option sets (e.g. products by category), use `<optgroup>` via `GroupedSelect`.
### Chunk hygiene
- Large forms should be split into focused section components.
- Lazy-load heavyweight modal and optional sections where practical.
- After structural UI refactors, run `pnpm build` and inspect output for large chunks.
- Track and prevent regressions in known hotspots (form-heavy pages and shared interaction primitives).
## Motion
- Entry animations: short upward reveal with slight stagger.
- Hover interactions: subtle translation or tint only.
- Never stack multiple strong animations in the same viewport.
## Accessibility baseline
- Keyboard navigation for all interactive elements.
- Visible focus states on controls and links.
- Respect reduced motion.
- Maintain readable contrast for text, borders, and controls.
## i18n rules
- Do not add hardcoded UI labels in route components when a message key already exists.
- Prefer existing `m.*` keys from paraglide for headings, labels, empty states, and helper text.
- If a new label is required, add a new translation key instead of shipping English literals.
- Keep fallback display values localized (`m.common_unknown()` over hardcoded `n/a`).
- Avoid language-specific toggle text in reusable components unless fully localized.
## Implementation checklist for new UI work
1. Pick the route domain and rely on `--page-accent`.
2. Use existing typography pair and spacing rhythm.
3. Build with primitives first; add route-level wrappers only if needed.
4. Validate mobile and desktop layout.
5. Run:
- `pnpm check`
- `pnpm lint`
- `pnpm build` (for significant component architecture changes)
## File touchpoints
- Core tokens and global look: `frontend/src/app.css`
- App shell and route domain mapping: `frontend/src/routes/+layout.svelte`
- Route examples using the pattern:
- `frontend/src/routes/+page.svelte`
- `frontend/src/routes/products/+page.svelte`
- `frontend/src/routes/routines/+page.svelte`
- `frontend/src/routes/health/lab-results/+page.svelte`
- `frontend/src/routes/skin/+page.svelte`
- Primitive visuals:
- `frontend/src/lib/components/ui/button/button.svelte`
- `frontend/src/lib/components/ui/card/card.svelte`
- `frontend/src/lib/components/ui/input/input.svelte`
- `frontend/src/lib/components/ui/badge/badge.svelte`
- Shared form DRY helpers:
- `frontend/src/lib/components/forms/SimpleSelect.svelte`
- `frontend/src/lib/components/forms/GroupedSelect.svelte`
- `frontend/src/lib/components/forms/HintCheckbox.svelte`
- `frontend/src/lib/components/forms/LabeledInputField.svelte`
- `frontend/src/lib/components/forms/FormSectionCard.svelte`
- `frontend/src/lib/components/forms/form-classes.ts`
- i18n message source:
- `frontend/src/lib/paraglide/messages/*`
When introducing new visual patterns, update this cookbook in the same change.