Smart household management frontend for Grocy servers. Features: - Dashboard with PulseRing status indicator - Shopping list with checkable items - Expiring soon / low stock alerts - Chores summary - Quick add with create product flow - PWA installable Tech: React 19, TypeScript, Vite, TanStack Router, Zustand, Tailwind CSS Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
27 KiB
Grocy2 Design Specification
A modern, delightful household management interface for Grocy
Table of Contents
- Vision & Goals
- User Research & Personas
- Design Principles
- Information Architecture
- Dashboard Design
- Component Library
- Responsive Strategy
- Interaction Patterns
- Visual Design System
- Accessibility
- Performance Budget
- Siri & Shortcuts Integration
- Technical Architecture
- Implementation Phases
Vision & Goals
Vision Statement
Grocy2 transforms household inventory management from a chore into a seamless part of daily life. It anticipates needs, rewards good habits, and stays out of the way when everything is running smoothly.
Primary Goals
- Reduce food waste - Surface expiring items before they go bad
- Eliminate "what do we need?" confusion - Smart shopping lists that sync
- Enable meal decisions - Know what you can cook with what you have
- Work for couples/families - Multi-user, real-time sync
- Voice-first option - Siri integration for hands-free kitchen use
Success Metrics
- Time to complete common action: < 3 seconds
- First contentful paint: < 1 second
- User returns to app daily
- Food waste reduction (self-reported)
User Research & Personas
Primary Persona: The Household Manager
Name: Sarah & Tony (shared responsibility couple)
Context:
- Both work, limited time for household admin
- Shop 2-3x per week at different stores
- Cook 4-5 dinners at home per week
- Have experienced food waste frustration
- Use iPhones, occasionally iPad in kitchen
Pain Points:
- "Did we already buy milk?"
- "What's going bad that we need to use?"
- "I'm at the store but forgot what we needed"
- "I want to cook but don't know what we can make"
Jobs to Be Done:
- When I'm leaving the store, I want to know if I forgot anything
- When I'm cooking, I want to mark ingredients as used without friction
- When I'm planning the week, I want to see what we have and what we need
- When something is expiring, I want to be reminded before it's too late
Usage Contexts
| Context | Device | Attention | Primary Need |
|---|---|---|---|
| Kitchen (cooking) | Phone/iPad | Partial | Quick consume, recipe check |
| Store (shopping) | Phone | Focused | Shopping list, barcode scan |
| Couch (planning) | iPad/Phone | Full | Meal planning, inventory review |
| On-the-go | Phone | Minimal | Quick add to list, voice command |
Design Principles
1. Context-Aware
The interface adapts to time of day and user behavior. Morning shows different priorities than evening. Shopping mode looks different than home mode.
2. Progressive Disclosure
Simple surface, power underneath. The most common actions are obvious. Advanced features are discoverable but not overwhelming.
3. Forgiving
Every action is undoable. Nothing is permanent. The system assumes good intent and makes recovery easy.
4. Glanceable
Status is visible in 1 second. Action takes 2 seconds. Exploration takes 3+. The dashboard is not a wall of text.
5. Rewarding
Completing tasks feels good. Subtle animations, haptic feedback, and visual confirmation make the mundane satisfying.
6. Calm Technology
When everything is fine, the app is quiet. It only demands attention when something needs attention. No anxiety-inducing red badges for non-urgent items.
Information Architecture
Navigation Structure
├── Dashboard (/)
│ ├── Pulse Status
│ ├── Smart Cards (contextual)
│ └── Quick Actions
│
├── Inventory (/stock)
│ ├── All Items (searchable, filterable)
│ ├── By Location (Fridge, Pantry, Freezer)
│ ├── By Category
│ └── Expiring Soon
│
├── Shopping (/shopping)
│ ├── Current List
│ ├── Shopping Mode (focused checkout)
│ └── List History
│
├── Recipes (/recipes)
│ ├── All Recipes
│ ├── Can Make Now
│ ├── Meal Planning
│ └── Recipe Detail
│
└── Settings (/settings)
├── Connection
├── Preferences
├── Locations
├── Categories
└── About
Data Model (from Grocy API)
Stock
├── Products (master list)
├── Stock Entries (actual inventory)
├── Locations (Fridge, Pantry, etc.)
├── Quantity Units (pcs, liters, kg)
└── Product Groups (Dairy, Produce, etc.)
Shopping
├── Shopping Lists (can have multiple)
└── Shopping List Items
Recipes
├── Recipes
├── Recipe Ingredients
└── Meal Plan
Dashboard Design
Layout Structure
┌─────────────────────────────────────┐
│ Status Bar (native) │
├─────────────────────────────────────┤
│ │
│ ◯ Pulse Ring ◯ │
│ (ambient status glow) │
│ │
├─────────────────────────────────────┤
│ │
│ ┌─────────────────────────┐ │
│ │ Smart Card 1 │ │
│ │ (highest priority) │ │
│ └─────────────────────────┘ │
│ │
│ ┌─────────────────────────┐ │
│ │ Smart Card 2 │ │
│ └─────────────────────────┘ │
│ │
│ ┌─────────────────────────┐ │
│ │ Smart Card 3 │ │
│ └─────────────────────────┘ │
│ │
├─────────────────────────────────────┤
│ [+ Add] [📷 Scan] [🎤 Voice] │
│ Quick Action Bar │
└─────────────────────────────────────┘
Pulse Ring Component
Purpose: Ambient awareness of household status at a glance.
States:
healthy- Soft green glow, gentle pulse. All systems nominal.attention- Amber glow, slightly faster pulse. Items expiring in 1-3 days.urgent- Red glow, prominent pulse. Expired items or critical stock.
Interaction:
- Tap to expand into list of items needing attention
- Ring shows count badge when collapsed
Technical:
- CSS animation with
@keyframes - Color transitions via CSS custom properties
- Reduced motion support for accessibility
Smart Cards
Cards appear based on relevance and urgency. Maximum 4 cards visible; scroll for more.
Card Priority Algorithm
Priority Score = Base Weight + Urgency Modifier + Time Modifier
Base Weights:
- Expired items: 100
- Expiring today: 90
- Shopping list (non-empty): 70
- Low stock items: 60
- Expiring this week: 50
- Recipe suggestions: 40
Time Modifiers:
- Morning (6-10am): +20 to expiring, +10 to shopping
- Midday (10am-2pm): +30 to shopping
- Evening (4-8pm): +30 to recipes, +20 to expiring
- Weekend: +20 to meal planning
Context Modifiers:
- Shopping list > 5 items: +20 to shopping
- Nothing expiring: -50 to expiring card (hide)
Card Types
1. Expiring Soon Card
┌─────────────────────────────────────┐
│ 🕐 Use Soon 3 ▾ │
├─────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────┐ │
│ │ Milk Tomorrow [→] │ │
│ └─────────────────────────────────┘ │
│ ┌─────────────────────────────────┐ │
│ │ Spinach 2 days [→] │ │
│ └─────────────────────────────────┘ │
│ ┌─────────────────────────────────┐ │
│ │ Greek Yogurt 3 days [→] │ │
│ └─────────────────────────────────┘ │
│ │
│ See all 7 items → │
└─────────────────────────────────────┘
Swipe Actions:
- Left swipe: "Used" - Consume 1 unit, animate out
- Right swipe: "Replace" - Add to shopping list
2. Shopping List Card
┌─────────────────────────────────────┐
│ 🛒 Shopping List 12 ▾ │
├─────────────────────────────────────┤
│ │
│ ○ Eggs (dozen) │
│ ○ Bread │
│ ○ Olive oil │
│ ○ Chicken breast │
│ +8 more... │
│ │
│ [ 🛍 Start Shopping Trip ] │
└─────────────────────────────────────┘
Interactions:
- Tap item to check off
- "Start Shopping Trip" enters focused shopping mode
- Badge shows count
3. Low Stock Card
┌─────────────────────────────────────┐
│ 📉 Running Low 4 ▾ │
├─────────────────────────────────────┤
│ │
│ Coffee ██░░░░░░ 2 bags │
│ Rice █░░░░░░░ 1 bag │
│ Pasta ██░░░░░░ 2 boxes │
│ Olive Oil █░░░░░░░ ¼ bottle │
│ │
│ [ Add All to List ] │
└─────────────────────────────────────┘
4. Recipe Suggestions Card
┌─────────────────────────────────────┐
│ 🍳 Tonight's Options 5 ▾ │
├─────────────────────────────────────┤
│ │
│ ┌─────────┐ │
│ │ 🍝 │ Pasta Carbonara │
│ │ │ ✓ Have everything │
│ └─────────┘ │
│ ┌─────────┐ │
│ │ 🥗 │ Caesar Salad │
│ │ │ Missing: croutons │
│ └─────────┘ │
│ │
│ Browse Recipes → │
└─────────────────────────────────────┘
Quick Action Bar
Fixed to bottom of screen in thumb-reach zone.
┌─────────────────────────────────────┐
│ │
│ ┌─────┐ ┌─────┐ ┌─────┐ │
│ │ + │ │ 📷 │ │ 🎤 │ │
│ │ Add │ │Scan │ │Voice│ │
│ └─────┘ └─────┘ └─────┘ │
│ │
└─────────────────────────────────────┘
Add Button:
- Opens bottom sheet with:
- Recent items (last 10 added)
- Search field
- Quick category buttons
- "New Product" option
Scan Button:
- Opens camera for barcode
- Recognizes product → shows add/consume dialog
- Unknown barcode → offers to create product
Voice Button:
- Activates speech recognition
- Shows live transcription
- Parses intent:
- "Add milk to shopping list" → adds item
- "Used 2 eggs" → consumes from stock
- "What's expiring?" → shows expiring list
Component Library
Design Tokens
/* Colors */
--color-primary: #6B8E6B; /* Sage green */
--color-primary-light: #8FAF8F;
--color-primary-dark: #4A6B4A;
--color-accent: #E8927C; /* Soft coral */
--color-accent-light: #F4B8A8;
--color-accent-dark: #C75B39;
--color-urgent: #C75B39; /* Terracotta */
--color-warning: #E4A853; /* Amber */
--color-success: #6B8E6B; /* Green */
--color-background: #FAFAF8; /* Warm white */
--color-surface: #FFFFFF;
--color-surface-elevated: #FFFFFF;
--color-text-primary: #2D3436; /* Warm charcoal */
--color-text-secondary: #636E72;
--color-text-tertiary: #9CA3A8;
--color-border: #E8E8E6;
--color-border-light: #F0F0EE;
/* Spacing */
--space-xs: 4px;
--space-sm: 8px;
--space-md: 16px;
--space-lg: 24px;
--space-xl: 32px;
--space-2xl: 48px;
/* Typography */
--font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
--font-size-xs: 12px;
--font-size-sm: 14px;
--font-size-md: 16px;
--font-size-lg: 18px;
--font-size-xl: 24px;
--font-size-2xl: 32px;
--font-weight-normal: 400;
--font-weight-medium: 500;
--font-weight-semibold: 600;
--font-weight-bold: 700;
--line-height-tight: 1.2;
--line-height-normal: 1.5;
--line-height-relaxed: 1.75;
/* Border Radius */
--radius-sm: 4px;
--radius-md: 8px;
--radius-lg: 12px;
--radius-xl: 16px;
--radius-full: 9999px;
/* Shadows */
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
--shadow-md: 0 4px 6px rgba(0, 0, 0, 0.07);
--shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.1);
--shadow-xl: 0 20px 25px rgba(0, 0, 0, 0.15);
/* Transitions */
--transition-fast: 150ms ease;
--transition-normal: 250ms ease;
--transition-slow: 350ms ease;
/* Z-Index Scale */
--z-dropdown: 100;
--z-sticky: 200;
--z-overlay: 300;
--z-modal: 400;
--z-toast: 500;
Core Components
Button
Variants: primary, secondary, ghost, danger
Sizes: sm, md, lg
<Button variant="primary" size="md" icon={<Plus />}>
Add Item
</Button>
Card
<Card
title="Use Soon"
icon={<Clock />}
badge={3}
onExpand={() => {}}
>
{children}
</Card>
SwipeableRow
<SwipeableRow
leftAction={{ icon: <Check />, color: 'success', onSwipe: handleUse }}
rightAction={{ icon: <Plus />, color: 'primary', onSwipe: handleAdd }}
>
<ProductRow product={product} />
</SwipeableRow>
BottomSheet
<BottomSheet isOpen={isOpen} onClose={onClose} snapPoints={[0.5, 0.9]}>
<AddItemForm />
</BottomSheet>
PulseRing
<PulseRing status="attention" count={3} onClick={handleExpand} />
Responsive Strategy
Breakpoints
/* Mobile first */
--bp-sm: 640px; /* Large phones, small tablets */
--bp-md: 768px; /* Tablets portrait */
--bp-lg: 1024px; /* Tablets landscape, small laptops */
--bp-xl: 1280px; /* Desktops */
--bp-2xl: 1536px; /* Large desktops */
Layout Adaptations
Mobile (< 640px)
- Single column layout
- Bottom navigation bar
- Full-screen modals
- Swipe gestures primary
- Cards stack vertically
- Quick action bar fixed to bottom
Tablet (640px - 1024px)
- Two-column card grid
- Bottom navigation or side tabs
- Slide-over sheets instead of full modals
- Split view for list + detail
- Floating action button option
Desktop (> 1024px)
- Persistent sidebar navigation
- Three-column layout possible
- Modal dialogs (not full screen)
- Hover states and tooltips
- Keyboard shortcuts
- Command palette (⌘K)
- Drag and drop
Touch Targets
- Minimum touch target: 44x44px (Apple HIG)
- Comfortable spacing between targets: 8px minimum
- Thumb zone consideration for mobile
Interaction Patterns
Gestures
| Gesture | Context | Action |
|---|---|---|
| Pull down | Any list | Refresh data |
| Swipe left | List item | Primary action (use/delete) |
| Swipe right | List item | Secondary action (add to list) |
| Long press | List item | Context menu |
| Pinch | Recipe photo | Zoom |
| Shake | Anywhere | Undo last action |
Keyboard Shortcuts (Desktop)
| Shortcut | Action |
|---|---|
/ or ⌘K |
Open command palette |
n |
New item |
s |
Focus search |
1-5 |
Navigate to section |
Escape |
Close modal/cancel |
⌘Z |
Undo |
⌘⇧Z |
Redo |
Enter |
Confirm/submit |
↑/↓ |
Navigate list |
Space |
Toggle selected item |
Feedback Patterns
Optimistic Updates:
- UI updates immediately
- Background sync with server
- Rollback on error with toast notification
Loading States:
- Skeleton screens for initial load
- Inline spinners for actions
- Pull-to-refresh indicator
Success Feedback:
- Checkmark animation
- Subtle color flash
- Haptic feedback (mobile)
- Item animates out of list
Error Handling:
- Inline error messages
- Toast for system errors
- Retry option always available
- Never lose user input
Visual Design System
Color Usage
Primary (Sage Green):
- Navigation active states
- Primary buttons
- Success states
- "Healthy" status
Accent (Coral):
- Call-to-action buttons
- Highlights and badges
- Important notifications
Urgent (Terracotta):
- Expired items
- Critical alerts
- Delete confirmations
Neutral Palette:
- Background, cards, borders
- Text hierarchy
- Disabled states
Typography Scale
Display: 32px / 700 / 1.2 - Page titles (rare)
Heading 1: 24px / 600 / 1.2 - Section headers
Heading 2: 18px / 600 / 1.3 - Card titles
Body: 16px / 400 / 1.5 - Default text
Body Small: 14px / 400 / 1.5 - Secondary text
Caption: 12px / 400 / 1.4 - Metadata, timestamps
Iconography
- Use Lucide icons (already in project)
- Consistent 24px size in navigation
- 20px in buttons and list items
- 16px for inline icons
- 2px stroke weight
Motion Principles
- Purposeful - Motion guides attention and confirms actions
- Quick - 150-300ms for most transitions
- Natural - Spring physics, not linear
- Reducible - Respect
prefers-reduced-motion
Standard Easings:
--ease-out: cubic-bezier(0.16, 1, 0.3, 1); /* Enter */
--ease-in: cubic-bezier(0.7, 0, 0.84, 0); /* Exit */
--ease-in-out: cubic-bezier(0.65, 0, 0.35, 1); /* Move */
--ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1); /* Bounce */
Accessibility
Requirements
- WCAG 2.1 AA compliance minimum
- Screen reader support (VoiceOver, TalkBack)
- Keyboard navigation complete
- Color contrast 4.5:1 minimum for text
- Focus indicators visible
- Reduced motion support
- Touch targets 44x44px minimum
Implementation
- Semantic HTML elements
- ARIA labels where needed
- Skip navigation link
- Focus management for modals
- Announce dynamic content changes
- Alt text for images
- Form labels and error messages
Performance Budget
Targets
| Metric | Target | Max |
|---|---|---|
| First Contentful Paint | < 1s | 1.5s |
| Largest Contentful Paint | < 2s | 2.5s |
| Time to Interactive | < 3s | 4s |
| Cumulative Layout Shift | < 0.1 | 0.15 |
| First Input Delay | < 50ms | 100ms |
Bundle Size Budget
| Asset | Target | Max |
|---|---|---|
| Initial JS | < 100KB | 150KB |
| Initial CSS | < 20KB | 30KB |
| Total initial | < 150KB | 200KB |
| Lazy chunks | < 50KB each | 75KB |
Strategies
- Code splitting by route
- Lazy load below-fold content
- Image optimization (WebP, lazy loading)
- Preconnect to API server
- Service worker for caching
- Virtual lists for long inventories
Siri & Shortcuts Integration
Approach
Since this is a web app, native Siri integration requires a companion approach:
- PWA with URL Schemes - Handle
grocy://URLs - Shortcuts Integration - Create iOS Shortcuts that call our web app
- Future: Native Wrapper - Capacitor/React Native for App Intents
URL Scheme Design
grocy://add?item=milk&list=shopping
grocy://consume?item=eggs&amount=2
grocy://open?page=shopping
grocy://open?page=expiring
Shortcuts Templates
"Add to Shopping List"
1. Ask for input: "What do you want to add?"
2. Open URL: grocy://add?item=[input]&list=shopping
"What's Expiring?"
1. Open URL: grocy://open?page=expiring
"Start Shopping Trip"
1. Open URL: grocy://open?page=shopping&mode=trip
Voice Input (Web Speech API)
For in-app voice commands:
const recognition = new webkitSpeechRecognition();
recognition.continuous = false;
recognition.interimResults = true;
// Parse intents from transcription
const intents = {
add: /add (.+) to (shopping list|stock)/i,
consume: /used? (\d+)? ?(.+)/i,
query: /do we have (.+)/i,
expiring: /what'?s expiring/i,
};
Technical Architecture
State Management
Zustand Store Structure:
interface AppState {
// Connection
isConnected: boolean;
serverUrl: string | null;
// User
user: User | null;
systemInfo: SystemInfo | null;
// Data
stock: StockProduct[];
products: Product[];
locations: Location[];
quantityUnits: QuantityUnit[];
productGroups: ProductGroup[];
shoppingList: ShoppingListItem[];
recipes: Recipe[];
// UI State
dashboardCards: CardConfig[];
isShoppingMode: boolean;
// Actions
connect: (url: string, key: string) => Promise<void>;
disconnect: () => void;
refreshAll: () => Promise<void>;
// Stock actions
consumeProduct: (id: number, amount: number) => Promise<void>;
addToStock: (id: number, amount: number, options?: StockOptions) => Promise<void>;
// Shopping actions
addToShoppingList: (productId: number, amount: number) => Promise<void>;
checkOffItem: (itemId: number) => Promise<void>;
clearCompletedItems: () => Promise<void>;
}
Data Fetching Strategy
- React Query for server state
- Automatic background refetching
- Optimistic updates for mutations
- Stale-while-revalidate pattern
- Offline support with persistence
Folder Structure
src/
├── api/
│ ├── client.ts # API client
│ └── hooks.ts # React Query hooks
├── components/
│ ├── ui/ # Base components
│ │ ├── Button.tsx
│ │ ├── Card.tsx
│ │ ├── Input.tsx
│ │ └── ...
│ ├── dashboard/ # Dashboard-specific
│ │ ├── PulseRing.tsx
│ │ ├── SmartCard.tsx
│ │ ├── QuickActionBar.tsx
│ │ └── ...
│ ├── stock/ # Stock page components
│ ├── shopping/ # Shopping components
│ └── layout/ # Layout components
├── hooks/
│ ├── useVoiceInput.ts
│ ├── useSwipeGesture.ts
│ └── ...
├── lib/
│ ├── cardPriority.ts # Card sorting algorithm
│ ├── voiceParser.ts # Voice command parsing
│ └── utils.ts
├── pages/
│ ├── Dashboard.tsx
│ ├── Stock.tsx
│ ├── Shopping.tsx
│ ├── Recipes.tsx
│ └── Settings.tsx
├── store/
│ └── index.ts # Zustand store
├── styles/
│ ├── tokens.css # Design tokens
│ └── global.css
├── types/
│ └── grocy.ts # TypeScript types
├── App.tsx
└── main.tsx
Implementation Phases
Phase 1: Foundation (Week 1)
Goal: Design system and responsive shell
- Set up design tokens (CSS custom properties)
- Create base UI components (Button, Card, Input, etc.)
- Implement responsive layout shell
- Create bottom navigation for mobile
- Create sidebar navigation for desktop
- Set up React Query for data fetching
- Dark mode support (CSS variables)
Deliverable: Navigable app shell that looks good on all devices
Phase 2: Smart Dashboard (Week 2)
Goal: Context-aware dashboard with smart cards
- Implement PulseRing component
- Create SmartCard component system
- Build card priority algorithm
- Implement ExpiringCard with swipe actions
- Implement ShoppingListCard
- Implement LowStockCard
- Implement RecipeSuggestionsCard
- Add time-based card prioritization
- Create QuickActionBar
Deliverable: Functional smart dashboard
Phase 3: Quick Actions (Week 3)
Goal: Friction-free adding and consuming
- Build AddItem bottom sheet
- Implement recent items list
- Add product search with typeahead
- Implement barcode scanning (camera API)
- Add voice input (Web Speech API)
- Build voice command parser
- Implement optimistic updates
- Add undo/redo support
Deliverable: Can add/consume items in < 3 seconds
Phase 4: Full Pages (Week 4)
Goal: Complete Stock, Shopping, Recipes pages
- Stock page with filtering and search
- Stock item detail view
- Shopping page with list management
- Shopping mode (focused checkout experience)
- Recipes page with filtering
- Recipe detail with ingredient availability
- Settings page
Deliverable: All pages functional
Phase 5: Polish & Native (Week 5)
Goal: PWA, Siri integration, final polish
- PWA manifest and icons
- Service worker for offline
- URL scheme handlers
- Shortcuts integration documentation
- Home screen widgets (if possible via PWA)
- Animation polish
- Performance optimization
- Accessibility audit
- User testing and fixes
Deliverable: Production-ready app
Appendix
API Endpoints Reference
GET /api/system/info
GET /api/users/me
GET /api/stock
POST /api/stock/products/{id}/add
POST /api/stock/products/{id}/consume
POST /api/stock/products/{id}/open
GET /api/objects/{entity}
POST /api/objects/{entity}
PUT /api/objects/{entity}/{id}
DELETE /api/objects/{entity}/{id}
GET /api/recipes
GET /api/recipes/{id}/fulfillment
Grocy Entity Types
- products
- locations
- quantity_units
- product_groups
- shopping_list
- shopping_lists
- recipes
- recipes_pos
- chores
- tasks
Document Version: 1.0 Last Updated: January 2025 Authors: Claude & Tony