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 variantbrand-dark- Darker variant for hover statesbrand-secondary- Secondary accent color
Semantic Colors:
- Gray scale:
gray-50throughgray-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 configurationsrc/index.css- Tailwind CSS entry pointsrc/components/- Shared UI componentssrc/components/icons/- Icon component library (55 icons)src/providers/AppProviders.tsx- Context provider compositionsrc/hooks/useAppInitialization.ts- Dark mode initialization
Component Guidelines
When to Create Shared Components
Create a shared component in src/components/ when:
- Used in 3+ places across the application
- Represents a reusable UI pattern (buttons, cards, modals)
- Has consistent styling/behavior requirements
Naming Conventions
- Components: PascalCase (
LoadingSpinner.tsx) - Icons: PascalCase with
Iconsuffix (CheckCircleIcon.tsx) - Hooks: camelCase with
useprefix (useModal.ts) - Contexts: PascalCase with
Contextsuffix (AuthContext.tsx)
Styling Guidelines
- Use Tailwind utility classes exclusively
- Include dark mode variants for all colors:
bg-white dark:bg-gray-800 - Add focus states for interactive elements
- Use semantic color names from the design system
Future Enhancements (Storybook Setup)
To complete ADR-012 implementation:
-
Install Storybook:
npx storybook@latest init -
Create stories for core components:
- Button variants
- Form inputs (PasswordInput, etc.)
- Modal components
- Loading states
- Icon showcase
-
Add visual regression testing with Chromatic or Percy
-
Document design tokens formally in Storybook
-
Create component composition guidelines