# DESIGN.md Design reference for the Colectivo app. Stack: SvelteKit + shadcn-svelte + Tailwind CSS. --- ## Visual Style ### Direction Clean and minimal. Content is the UI — no decorative elements, no gradients, no shadows unless they carry meaning. Generous whitespace. Every element earns its place. ### Color Monochromatic slate palette. No accent color — contrast and weight carry hierarchy. ``` --background: slate-50 / slate-950 --surface: white / slate-900 --surface-raised: slate-100 / slate-800 --border: slate-200 / slate-700 --text-primary: slate-900 / slate-50 --text-secondary: slate-500 / slate-400 --text-disabled: slate-300 / slate-600 --destructive: red-500 / red-400 ← only color in the UI ``` Interactive elements (buttons, checkboxes, active states) use `slate-900` on light and `slate-50` on dark — inverting the surface. The only true color is destructive red. ### Typography Font: **Inter** (or `system-ui` as fallback — acceptable on iOS/macOS). | Role | Size | Weight | |---|---|---| | Screen title | 20px | 600 | | Section header | 13px | 600, uppercase, tracked | | Body / list item | 16px | 400 | | Secondary / meta | 13px | 400 | | Input | 16px | 400 — prevents iOS zoom on focus | Line height: 1.5 for body, 1.2 for headings. No text smaller than 13px. ### Spacing Base unit: 4px (Tailwind default). Prefer multiples of 4. Common values: - List item padding: `px-4 py-3` - Screen horizontal margin: `px-4` (mobile), `px-6` (desktop) - Section gap: `gap-6` - Bottom nav safe area: always account for `env(safe-area-inset-bottom)` ### Iconography **Lucide** (ships with shadcn-svelte). Size `20px` inline, `24px` for nav and primary actions. Stroke width `1.5`. Never fill icons. ### Dark Mode Class-based (`dark` on `
`). Respect `prefers-color-scheme` on first load, then persist user preference in `localStorage`. shadcn-svelte's CSS variable system handles theming — no additional setup needed per component. --- ## Component Library **shadcn-svelte** — copy-paste components built on bits-ui + Tailwind. No runtime dependency, full ownership of component code. ### Conventions - Copy components into `apps/web/src/lib/components/ui/` (shadcn-svelte default) - App-specific compositions go in `apps/web/src/lib/components/` (e.g. `ItemRow.svelte`, `ListCard.svelte`) - Never modify shadcn-svelte primitives directly — compose or wrap them - Use shadcn-svelte's `cn()` utility for conditional classes ### Key components to install | Component | Used for | |---|---| | `Button` | All actions | | `Input` | Add item, search, forms | | `Checkbox` | Shopping items, tasks | | `Sheet` | Mobile bottom drawers (item edit, member invite) | | `Dialog` | Confirmations (delete list, dissolve collective) | | `Popover` | Presence avatars tooltip, note color picker | | `Badge` | Status labels (archived, completed) | | `Separator` | Section dividers | | `Skeleton` | Loading states | | `Sonner` | Toast notifications (sync errors, member joined) | --- ## Layout & Navigation ### App Shell Two-column on desktop, single-column on mobile. ``` Mobile Desktop (lg+) ┌─────────────────────┐ ┌──────────┬──────────────────────┐ │ Header │ │ │ Header │ ├─────────────────────┤ │ ├──────────────────────┤ │ │ │ Nav │ │ │ Content │ │ Sidebar │ Content │ │ │ │ │ │ ├─────────────────────┤ │ │ │ │ Bottom Nav │ └──────────┴──────────────────────┘ └─────────────────────┘ ``` ### Navigation **Mobile:** fixed bottom tab bar, 4 tabs. Respects `safe-area-inset-bottom`. **Desktop (`lg+`):** left sidebar, always visible. | Tab | Icon | Route | |---|---|---| | Lists | `shopping-cart` | `/lists` | | Tasks | `check-square` | `/tasks` | | Notes | `file-text` | `/notes` | | Search | `search` | `/search` | Collective switcher and user avatar live in the header (mobile) / top of sidebar (desktop). ### Screen Structure Every screen follows the same skeleton: ``` ┌─────────────────────────────┐ │ ← Back Screen Title ⋯ │ ← Header: title + optional action menu ├─────────────────────────────┤ │ │ │ Main content │ ← Scrollable │ │ ├─────────────────────────────┤ │ [ + Add item... ] │ ← Sticky bottom input (list/task screens) └─────────────────────────────┘ ``` ### Responsive Breakpoints Use Tailwind defaults: `sm` (640), `md` (768), `lg` (1024). The sidebar appears at `lg`. Content max-width: `max-w-2xl` centered on large screens. --- ## UX Patterns ### Optimistic Updates All mutations update the local store immediately before the server responds. On error, revert and show a toast. Never block the UI waiting for a network response. ### Offline Indicator A slim bar at the top of the content area (not the header) when offline: ``` ┌─────────────────────────────┐ │ No connection · Saving locally │ ← slate-800 bg, slate-100 text, 36px tall └─────────────────────────────┘ ``` Disappears immediately when connection is restored. No animation — just mounts/unmounts. ### Add Item Input Sticky to the bottom of list and task screens. On focus, shows frequency suggestions above it: ``` ┌──────────────────────────────┐ │ Milk · Bread · Eggs │ ← Frequency chips (top 5, scrollable row) ├──────────────────────────────┤ │ 🔍 Add item... [+] │ ← Input + confirm button └──────────────────────────────┘ ``` - Chips are horizontally scrollable, `text-sm`, `rounded-full`, `border` style - Tapping a chip fills the input; the user can still edit before confirming - While typing, chips are replaced by prefix-filtered suggestions (max 10, vertical list) - Confirm with the `+` button or `Enter` ### Swipe to Delete On list items and tasks. Swipe left reveals a red destructive zone: ``` ┌─────────────────────────────────────┐ │ ☐ Whole milk 1L ░░░░░░ │ ← item sliding left │ [Del] │ ← red zone, icon only └─────────────────────────────────────┘ ``` Full swipe confirms deletion immediately (no dialog). Partial swipe snaps back. A toast with "Undo" appears for 4 seconds. ### Drag & Drop Reordering Handle on the left side of each item (visible on long-press on mobile, always visible on desktop). Use `svelte-dnd-action`. During drag: item has `shadow-md`, others compress slightly to show insertion point. ### Shopping Session Mode Full-screen takeover. Clean, distraction-free: ``` ┌─────────────────────────────┐ │ ✕ Compra semanal 👤👤 │ ← close + presence avatars ├─────────────────────────────┤ │ ☐ Whole milk 1L │ │ ☐ Bread │ ← large tap targets (min 56px height) │ ☐ Eggs 12 uds │ │ ──────── Checked ──────── │ ← divider, auto-scrolled below │ ☑ Butter 250g │ ← muted, struck through ├─────────────────────────────┤ │ Finish shopping │ ← full-width button └─────────────────────────────┘ ``` - No bottom nav, no sidebar — only the list - Items checked during the session slide down past the divider automatically - Presence avatars are `size-7`, overlap slightly if multiple members present ### Confirmations **Destructive, hard-to-reverse actions** (delete list, remove member, dissolve collective): use a `Dialog` with explicit confirmation text. No shortcut. **Soft deletions** (single item, single task): swipe with undo toast — no dialog. ### Empty States Simple, text-only. No illustrations. ``` No lists yet. Tap + to create one. ``` Two lines max. Secondary text color. Centered in the content area. ### Loading States Use `Skeleton` components that match the shape of the content (list rows, not spinners). Spinner only for full-screen blocking operations (login, initial data fetch).