Files
flyer-crawler.projectium.com/docs/adr/0012-frontend-component-library-and-design-system.md
Torben Sorensen ef42fee982
All checks were successful
Deploy to Test Environment / deploy-to-test (push) Successful in 32m3s
integration test fixes - claude for the win? try 3
2026-01-09 04:23:23 -08:00

8.2 KiB

ADR-012: Frontend Component Library and Design System

Date: 2025-12-12

Status: Partially Implemented

Context

The frontend is built with React, but there is no formal strategy for component reuse, styling consistency, or UI documentation. As more features are added, this can lead to a fragmented user experience, duplicated effort, and a codebase that is difficult to maintain.

Decision

We will establish a formal Design System and Component Library. This will involve using a tool like Storybook to develop, document, and test UI components in isolation. It will establish clear guidelines for styling, theming (e.g., dark mode), and accessibility.

Consequences

  • Positive: Ensures a consistent and high-quality user interface. Accelerates frontend development by providing reusable, well-documented components. Improves maintainability and reduces technical debt.
  • Negative: Requires an initial investment in setting up Storybook and migrating existing components. Adds a new dependency and a new workflow for frontend development.

Implementation Status

What's Implemented

The codebase has a solid foundation for a design system:

  • Tailwind CSS v4.1.17 as the styling solution
  • Dark mode fully implemented with system preference detection
  • 55 custom icon components for consistent iconography
  • Component organization with shared vs. feature-specific separation
  • Accessibility patterns with ARIA attributes and focus management

What's Not Yet Implemented

  • Storybook is not yet installed or configured
  • Formal design token documentation (colors, typography, spacing)
  • Visual regression testing for component changes

Implementation Details

Component Library Structure

src/
├── components/              # 30+ shared UI components
│   ├── icons/              # 55 SVG icon components
│   ├── Header.tsx
│   ├── Footer.tsx
│   ├── LoadingSpinner.tsx
│   ├── ErrorDisplay.tsx
│   ├── ConfirmationModal.tsx
│   ├── DarkModeToggle.tsx
│   ├── StatCard.tsx
│   ├── PasswordInput.tsx
│   └── ...
├── features/               # Feature-specific components
│   ├── charts/            # PriceChart, PriceHistoryChart
│   ├── flyer/             # FlyerDisplay, FlyerList, FlyerUploader
│   ├── shopping/          # ShoppingListComponent, WatchedItemsList
│   └── voice-assistant/   # VoiceAssistant
├── layouts/               # Page layouts
│   └── MainLayout.tsx
├── pages/                 # Page components
│   └── admin/components/  # Admin-specific components
└── providers/             # Context providers

Styling Approach

Tailwind CSS with utility-first classes:

// Component example with consistent styling patterns
<button className="px-4 py-2 bg-brand-primary text-white rounded-lg
                   hover:bg-brand-dark transition-colors duration-200
                   focus:outline-none focus:ring-2 focus:ring-brand-primary
                   focus:ring-offset-2 dark:focus:ring-offset-gray-800">
  Click me
</button>

Common Utility Patterns:

Pattern Classes
Card container bg-white dark:bg-gray-800 rounded-lg shadow-md p-6
Primary button bg-brand-primary hover:bg-brand-dark text-white rounded-lg px-4 py-2
Secondary button bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-200
Input field border border-gray-300 dark:border-gray-600 rounded-md px-3 py-2
Focus ring focus:outline-none focus:ring-2 focus:ring-brand-primary

Color System

Brand Colors (Tailwind theme extensions):

  • brand-primary - Primary brand color (blue/teal)
  • brand-light - Lighter variant
  • brand-dark - Darker variant for hover states
  • brand-secondary - Secondary accent color

Semantic Colors:

  • Gray scale: gray-50 through gray-950
  • Error: red-500, red-600
  • Success: green-500, green-600
  • Warning: yellow-500, orange-500
  • Info: blue-500, blue-600

Dark Mode Implementation

Dark mode is fully implemented using Tailwind's dark: variant:

// Initialization in useAppInitialization hook
const initializeDarkMode = () => {
  // Priority: user profile > localStorage > system preference
  const stored = localStorage.getItem('darkMode');
  const systemPreference = window.matchMedia('(prefers-color-scheme: dark)').matches;
  const isDarkMode = stored ? stored === 'true' : systemPreference;

  document.documentElement.classList.toggle('dark', isDarkMode);
  return isDarkMode;
};

Usage in components:

<div className="bg-white dark:bg-gray-800 text-gray-900 dark:text-white">
  Content adapts to theme
</div>

Icon System

55 custom SVG icon components in src/components/icons/:

// Icon component pattern
interface IconProps extends React.SVGProps<SVGSVGElement> {
  title?: string;
}

export const CheckCircleIcon: React.FC<IconProps> = ({ title, ...props }) => (
  <svg {...props} fill="currentColor" viewBox="0 0 24 24">
    {title && <title>{title}</title>}
    <path d="..." />
  </svg>
);

Usage:

<CheckCircleIcon className="w-5 h-5 text-green-500" title="Success" />

External icons: Lucide React (lucide-react v0.555.0) used for additional icons.

Accessibility Patterns

ARIA Attributes:

// Modal pattern
<div role="dialog" aria-modal="true" aria-labelledby="modal-title">
  <h2 id="modal-title">Modal Title</h2>
</div>

// Button with label
<button aria-label="Close modal">
  <XMarkIcon aria-hidden="true" />
</button>

// Loading state
<div role="status" aria-live="polite">
  <LoadingSpinner />
</div>

Focus Management:

  • Consistent focus rings: focus:ring-2 focus:ring-brand-primary focus:ring-offset-2
  • Dark mode offset: dark:focus:ring-offset-gray-800
  • No outline: focus:outline-none (using ring instead)

State Management

Context Providers (see ADR-005):

Provider Purpose
AuthProvider Authentication state
ModalProvider Modal open/close state
FlyersProvider Flyer data
MasterItemsProvider Grocery items
UserDataProvider User-specific data

Provider Hierarchy in AppProviders.tsx:

<QueryClientProvider>
  <ModalProvider>
    <AuthProvider>
      <FlyersProvider>
        <MasterItemsProvider>
          <UserDataProvider>
            {children}
          </UserDataProvider>
        </MasterItemsProvider>
      </FlyersProvider>
    </AuthProvider>
  </ModalProvider>
</QueryClientProvider>

Key Files

  • tailwind.config.js - Tailwind CSS configuration
  • src/index.css - Tailwind CSS entry point
  • src/components/ - Shared UI components
  • src/components/icons/ - Icon component library (55 icons)
  • src/providers/AppProviders.tsx - Context provider composition
  • src/hooks/useAppInitialization.ts - Dark mode initialization

Component Guidelines

When to Create Shared Components

Create a shared component in src/components/ when:

  1. Used in 3+ places across the application
  2. Represents a reusable UI pattern (buttons, cards, modals)
  3. Has consistent styling/behavior requirements

Naming Conventions

  • Components: PascalCase (LoadingSpinner.tsx)
  • Icons: PascalCase with Icon suffix (CheckCircleIcon.tsx)
  • Hooks: camelCase with use prefix (useModal.ts)
  • Contexts: PascalCase with Context suffix (AuthContext.tsx)

Styling Guidelines

  1. Use Tailwind utility classes exclusively
  2. Include dark mode variants for all colors: bg-white dark:bg-gray-800
  3. Add focus states for interactive elements
  4. Use semantic color names from the design system

Future Enhancements (Storybook Setup)

To complete ADR-012 implementation:

  1. Install Storybook:

    npx storybook@latest init
    
  2. Create stories for core components:

    • Button variants
    • Form inputs (PasswordInput, etc.)
    • Modal components
    • Loading states
    • Icon showcase
  3. Add visual regression testing with Chromatic or Percy

  4. Document design tokens formally in Storybook

  5. Create component composition guidelines