Replace hardcoded gray-* colors with design system tokens: - border-gray-200 → border-muted - bg-gray-50 → bg-muted/30 - text-gray-600/700 → text-muted-foreground/foreground - hover:bg-gray-100 → hover:bg-muted/50 Updated components: - MetadataDebugPanel: now matches Card aesthetic - ReasoningChainViewer: now uses warm editorial tones Benefits: - Consistent with existing reasoning/summary cards - Matches warm editorial aesthetic (hsl(42...) palette) - DRY: reuses design system tokens - Documented collapsible panel pattern in cookbook This fixes the cool gray panels that looked out of place among the warm beige editorial UI.
238 lines
11 KiB
Markdown
238 lines
11 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.
|
|
- Keep Google font loading aligned with current usage:
|
|
- `Cormorant Infant`: `600`, `700` (no italic)
|
|
- `Manrope`: `400`, `500`, `600`, `700`
|
|
|
|
## 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`
|
|
- Profile: `--accent-profile`
|
|
- 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`, `editorial-alert--warning`, `editorial-alert--info`: feedback banners.
|
|
|
|
### Collapsible panels
|
|
|
|
For secondary information (debug data, reasoning chains, metadata), use this pattern:
|
|
|
|
```svelte
|
|
<div class="border border-muted rounded-lg overflow-hidden">
|
|
<button class="w-full flex items-center gap-2 px-4 py-3 bg-muted/30 hover:bg-muted/50 transition-colors">
|
|
<Icon class="size-4 text-muted-foreground" />
|
|
<span class="text-sm font-medium text-foreground">Panel Title</span>
|
|
<ChevronIcon class="ml-auto size-4 text-muted-foreground" />
|
|
</button>
|
|
{#if expanded}
|
|
<div class="p-4 bg-card border-t border-muted">
|
|
<!-- Content -->
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
```
|
|
|
|
This matches the warm editorial aesthetic and maintains visual consistency with Card components.
|
|
|
|
## 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*`
|
|
- Lab results utilities:
|
|
- metadata chips: `lab-results-meta-strip`, `lab-results-meta-pill`
|
|
- filter/paging surfaces: `editorial-filter-row`, `lab-results-filter-banner`, `lab-results-pager`
|
|
- row/link rhythm: `lab-results-row`, `lab-results-code-link`, `lab-results-value-cell`
|
|
- mobile density: `lab-results-mobile-grid`, `lab-results-mobile-card`, `lab-results-mobile-value`
|
|
|
|
## 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.
|
|
- Filter toolbars for data-heavy routes should use `GET` forms with URL params so state is shareable and pagination links preserve active filters.
|
|
- Use the products filter pattern as the shared baseline: compact search input, chip-style toggle rows (`editorial-filter-row` + small `Button` variants), and apply/reset actions aligned at the end of the toolbar.
|
|
- For high-volume medical data lists, default the primary view to condensed/latest mode and offer full-history as an explicit secondary option.
|
|
- For profile/settings forms, reuse shared primitives (`FormSectionCard`, `LabeledInputField`, `SimpleSelect`) before creating route-specific field wrappers.
|
|
- In condensed/latest mode, group rows by collection date using lightweight section headers (`products-section-title`) to preserve report context without introducing heavy card nesting.
|
|
- Change/highlight pills in dense tables should stay compact (`text-[10px]`), semantic (new/flag change/abnormal), and avoid overwhelming color blocks.
|
|
- For lab results, keep ordering fixed to newest collection date (`collected_at DESC`) and remove non-essential controls (no lab filter and no manual sort selector).
|
|
- For lab results, keep code links visibly interactive (`lab-results-code-link`) because they are a primary in-context drill-down interaction.
|
|
- For lab results, use compact metadata chips in hero sections (`lab-results-meta-pill`) for active view/filter context instead of introducing a second heavy summary card; keep this strip terse (one context chip + one stats chip, with optional alert chip).
|
|
- In dense row-based lists, prefer `ghost` action controls; use icon-only buttons on desktop tables and short text+icon `ghost` actions on mobile cards to keep row actions subordinate to data.
|
|
- For editable data tables, open a dedicated inline edit panel above the list (instead of per-row expanded forms) and prefill it from row actions; keep users on the same filtered/paginated context after save.
|
|
- When a list is narrowed to a single entity key (for example `test_code`), display an explicit "filtered by" banner with a one-click clear action and avoid extra grouping wrappers that add no context.
|
|
|
|
### 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/profile/+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.
|