On this page
Atomic Design Pattern
Date: 2025-22-09
Status: accepted
Current Architecture Analysis
The Vega web application is built with Remix (React Router v7) and follows a feature-based routing structure with the following characteristics:
Current Structure
services/remix/
├── app/
│ ├── components/ # Shared components
│ │ ├── ui/ # Design system components (shadcn/ui based)
│ │ ├── chat/ # Chat-related components
│ │ ├── forms/ # Form components
│ │ └── shared/ # Shared business components
│ ├── routes/ # Route-based organization
│ │ ├── alerts+/ # Alerts feature
│ │ ├── dashboards+/ # Dashboards feature
│ │ ├── detections+/ # Detections feature
│ │ ├── assessment+/ # Data assessment feature
│ │ ├── intel+/ # Intelligence feature
│ │ ├── notebooks+/ # Notebooks feature
│ │ ├── query+/ # Query feature
│ │ ├── settings+/ # Settings feature
│ │ └── _auth+/ # Authentication
│ ├── queries/ # GraphQL queries
│ ├── utils/ # Utility functions
│ └── transformations/ # Data transformations
Key Technologies
- Framework: Remix (React Router v7) with Vite
- UI Library: shadcn/ui components + Radix UI primitives
- State Management: Zustand + React Query
- Styling: Tailwind CSS
- Data Fetching: GraphQL with custom hooks
- Testing: Vitest + Playwright
Atomic Design Pattern Migration
What is Atomic Design?
Atomic Design is a methodology for creating design systems that breaks down UI components into five distinct levels:
- Atoms: Basic building blocks (buttons, inputs, labels)
- Molecules: Simple combinations of atoms (search form, navigation item)
- Organisms: Complex UI components (header, sidebar, data table)
- Templates: Page-level layouts without content
- Pages: Specific instances of templates with real content
Atomic Design Structure
src/
├── components/
│ ├── atoms/ # Basic building blocks
│ │ ├── button/ # Button atom
│ │ ├── input/ # Input atom
│ │ ├── label/ # Label atom
│ │ ├── icon/ # Icon atom
│ │ └── typography/ # Typography atom
│ ├── molecules/ # Simple combinations
│ │ ├── search-form/ # Search form molecule
│ │ ├── nav-item/ # Navigation item molecule
│ │ ├── form-field/ # Form field molecule
│ │ └── data-cell/ # Data cell molecule
│ ├── organisms/ # Complex components
│ │ ├── header/ # Header organism
│ │ ├── sidebar/ # Sidebar organism
│ │ ├── data-table/ # Data table organism
│ │ ├── dashboard-grid/ # Dashboard grid organism
│ │ └── chat-panel/ # Chat panel organism
│ ├── templates/ # Page layouts
│ │ ├── dashboard/ # Dashboard template
│ │ ├── detection/ # Detection template
│ │ └── settings/ # Settings template
│ └── pages/ # Specific page instances
│ ├── dashboard/ # Dashboard page
│ ├── detections/ # Detections page
│ └── settings/ # Settings page
Migration Strategy
Phase 1: Extract Atoms
Step 1: Button Atom
// src/components/atoms/button/index.ts
export { Button } from './Button';
export type { ButtonProps } from './Button.types';
// src/components/atoms/button/Button.tsx
import React from 'react';
import { cn } from '@/lib/utils';
import type { ButtonProps } from './Button.types';
export function Button({
children,
variant = 'default',
size = 'default',
className,
...props
}: ButtonProps) {
return (
<button
className={cn(
'inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none ring-offset-background',
{
'bg-primary text-primary-foreground hover:bg-primary/90': variant === 'default',
'bg-destructive text-destructive-foreground hover:bg-destructive/90': variant === 'destructive',
'border border-input hover:bg-accent hover:text-accent-foreground': variant === 'outline',
'bg-secondary text-secondary-foreground hover:bg-secondary/80': variant === 'secondary',
'hover:bg-accent hover:text-accent-foreground': variant === 'ghost',
'underline-offset-4 hover:underline text-primary': variant === 'link',
},
{
'h-10 py-2 px-4': size === 'default',
'h-9 px-3 rounded-md': size === 'sm',
'h-11 px-8 rounded-md': size === 'lg',
'h-10 w-10': size === 'icon',
},
className
)}
{...props}
>
{children}
</button>
);
}
// src/components/atoms/button/Button.types.ts
export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
variant?: 'default' | 'destructive' | 'outline' | 'secondary' | 'ghost' | 'link';
size?: 'default' | 'sm' | 'lg' | 'icon';
}
// src/components/atoms/button/Button.stories.tsx
import type { Meta, StoryObj } from '@storybook/react';
import { Button } from './Button';
const meta: Meta<typeof Button> = {
title: 'Atoms/Button',
component: Button,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
argTypes: {
variant: {
control: { type: 'select' },
options: ['default', 'destructive', 'outline', 'secondary', 'ghost', 'link'],
},
size: {
control: { type: 'select' },
options: ['default', 'sm', 'lg', 'icon'],
},
},
};
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: {
children: 'Button',
},
};
export const Destructive: Story = {
args: {
variant: 'destructive',
children: 'Delete',
},
};Step 2: Input Atom
// src/components/atoms/input/index.ts
export { Input } from './Input';
export type { InputProps } from './Input.types';
// src/components/atoms/input/Input.tsx
import React from 'react';
import { cn } from '@/lib/utils';
import type { InputProps } from './Input.types';
export function Input({ className, type, ...props }: InputProps) {
return (
<input
type={type}
className={cn(
'flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
className
)}
{...props}
/>
);
}
// src/components/atoms/input/Input.types.ts
export interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {}
// src/components/atoms/input/Input.stories.tsx
import type { Meta, StoryObj } from '@storybook/react';
import { Input } from './Input';
const meta: Meta<typeof Input> = {
title: 'Atoms/Input',
component: Input,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
};
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: {
placeholder: 'Enter text...',
},
};
export const Password: Story = {
args: {
type: 'password',
placeholder: 'Enter password...',
},
};Step 3: Icon Atom
// src/components/atoms/icon/index.ts
export { Icon } from './Icon';
export type { IconProps } from './Icon.types';
// src/components/atoms/icon/Icon.tsx
import React from 'react';
import { cn } from '@/lib/utils';
import type { IconProps } from './Icon.types';
export function Icon({ name, size = 'default', className, ...props }: IconProps) {
return (
<svg
className={cn(
{
'h-4 w-4': size === 'sm',
'h-5 w-5': size === 'default',
'h-6 w-6': size === 'lg',
'h-8 w-8': size === 'xl',
},
className
)}
{...props}
>
<use href={`#icon-${name}`} />
</svg>
);
}
// src/components/atoms/icon/Icon.types.ts
export interface IconProps extends React.SVGProps<SVGSVGElement> {
name: string;
size?: 'sm' | 'default' | 'lg' | 'xl';
}Phase 2: Extract Molecules
Step 1: Search Form Molecule
// src/components/molecules/search-form/index.ts
export { SearchForm } from './SearchForm';
export type { SearchFormProps } from './SearchForm.types';
// src/components/molecules/search-form/SearchForm.tsx
import React from 'react';
import { Button } from '@/components/atoms/button';
import { Input } from '@/components/atoms/input';
import { Icon } from '@/components/atoms/icon';
import type { SearchFormProps } from './SearchForm.types';
export function SearchForm({ onSearch, placeholder = 'Search...', className }: SearchFormProps) {
const [query, setQuery] = React.useState('');
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
onSearch(query);
};
return (
<form onSubmit={handleSubmit} className={className}>
<div className="relative flex items-center">
<Input
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder={placeholder}
className="pr-10"
/>
<Button
type="submit"
size="icon"
className="absolute right-0 top-0 h-full rounded-l-none"
>
<Icon name="search" size="sm" />
</Button>
</div>
</form>
);
}
// src/components/molecules/search-form/SearchForm.types.ts
export interface SearchFormProps {
onSearch: (query: string) => void;
placeholder?: string;
className?: string;
}Step 2: Navigation Item Molecule
// src/components/molecules/nav-item/index.ts
export { NavItem } from './NavItem';
export type { NavItemProps } from './NavItem.types';
// src/components/molecules/nav-item/NavItem.tsx
import React from 'react';
import { Link } from 'react-router-dom';
import { cn } from '@/lib/utils';
import { Icon } from '@/components/atoms/icon';
import type { NavItemProps } from './NavItem.types';
export function NavItem({
to,
icon,
label,
isActive = false,
className,
onClick
}: NavItemProps) {
return (
<Link
to={to}
onClick={onClick}
className={cn(
'flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground',
{
'bg-accent text-accent-foreground': isActive,
},
className
)}
>
{icon && <Icon name={icon} size="sm" />}
<span>{label}</span>
</Link>
);
}
// src/components/molecules/nav-item/NavItem.types.ts
export interface NavItemProps {
to: string;
icon?: string;
label: string;
isActive?: boolean;
className?: string;
onClick?: () => void;
}Step 3: Form Field Molecule
// src/components/molecules/form-field/index.ts
export { FormField } from './FormField';
export type { FormFieldProps } from './FormField.types';
// src/components/molecules/form-field/FormField.tsx
import React from 'react';
import { Label } from '@/components/atoms/label';
import { Input } from '@/components/atoms/input';
import { TextArea } from '@/components/atoms/textarea';
import { Select } from '@/components/atoms/select';
import type { FormFieldProps } from './FormField.types';
export function FormField({
label,
type = 'text',
value,
onChange,
error,
helperText,
required = false,
className,
...props
}: FormFieldProps) {
const id = React.useId();
const errorId = `${id}-error`;
const helperId = `${id}-helper`;
const renderInput = () => {
switch (type) {
case 'textarea':
return (
<TextArea
id={id}
value={value}
onChange={onChange}
aria-describedby={error ? errorId : helperId}
aria-invalid={!!error}
{...props}
/>
);
case 'select':
return (
<Select
id={id}
value={value}
onChange={onChange}
aria-describedby={error ? errorId : helperId}
aria-invalid={!!error}
{...props}
/>
);
default:
return (
<Input
id={id}
type={type}
value={value}
onChange={onChange}
aria-describedby={error ? errorId : helperId}
aria-invalid={!!error}
{...props}
/>
);
}
};
return (
<div className={className}>
<Label htmlFor={id} className={required ? 'after:content-["*"] after:ml-0.5 after:text-red-500' : ''}>
{label}
</Label>
{renderInput()}
{error && (
<p id={errorId} className="text-sm text-red-500 mt-1">
{error}
</p>
)}
{helperText && !error && (
<p id={helperId} className="text-sm text-muted-foreground mt-1">
{helperText}
</p>
)}
</div>
);
}
// src/components/molecules/form-field/FormField.types.ts
export interface FormFieldProps {
label: string;
type?: 'text' | 'email' | 'password' | 'number' | 'textarea' | 'select';
value: string;
onChange: (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>) => void;
error?: string;
helperText?: string;
required?: boolean;
className?: string;
placeholder?: string;
options?: Array<{ value: string; label: string }>;
}Phase 3: Extract Organisms
Step 1: Header Organism
// src/components/organisms/header/index.ts
export { Header } from './Header';
export type { HeaderProps } from './Header.types';
// src/components/organisms/header/Header.tsx
import React from 'react';
import { Button } from '@/components/atoms/button';
import { Icon } from '@/components/atoms/icon';
import { UserMenu } from '@/components/molecules/user-menu';
import { SearchForm } from '@/components/molecules/search-form';
import type { HeaderProps } from './Header.types';
export function Header({
user,
onSearch,
onLogout,
onToggleSidebar,
className
}: HeaderProps) {
return (
<header className={className}>
<div className="flex h-16 items-center justify-between border-b bg-background px-4">
<div className="flex items-center gap-4">
<Button
variant="ghost"
size="icon"
onClick={onToggleSidebar}
className="md:hidden"
>
<Icon name="menu" size="sm" />
</Button>
<div className="flex items-center gap-2">
<Icon name="logo" size="lg" />
<span className="text-lg font-semibold">Vega</span>
</div>
</div>
<div className="flex items-center gap-4">
<SearchForm onSearch={onSearch} className="hidden md:block" />
<UserMenu user={user} onLogout={onLogout} />
</div>
</div>
</header>
);
}
// src/components/organisms/header/Header.types.ts
export interface HeaderProps {
user?: {
name: string;
email: string;
avatar?: string;
};
onSearch: (query: string) => void;
onLogout: () => void;
onToggleSidebar: () => void;
className?: string;
}Step 2: Sidebar Organism
// src/components/organisms/sidebar/index.ts
export { Sidebar } from './Sidebar';
export type { SidebarProps } from './Sidebar.types';
// src/components/organisms/sidebar/Sidebar.tsx
import React from 'react';
import { cn } from '@/lib/utils';
import { NavItem } from '@/components/molecules/nav-item';
import type { SidebarProps } from './Sidebar.types';
export function Sidebar({
navigationItems,
isOpen = true,
className
}: SidebarProps) {
return (
<aside className={cn(
'flex h-full w-64 flex-col border-r bg-background transition-transform',
{
'translate-x-0': isOpen,
'-translate-x-full': !isOpen,
},
className
)}>
<nav className="flex-1 space-y-2 p-4">
{navigationItems.map((item) => (
<NavItem
key={item.to}
to={item.to}
icon={item.icon}
label={item.label}
isActive={item.isActive}
/>
))}
</nav>
</aside>
);
}
// src/components/organisms/sidebar/Sidebar.types.ts
export interface SidebarProps {
navigationItems: Array<{
to: string;
icon?: string;
label: string;
isActive?: boolean;
}>;
isOpen?: boolean;
className?: string;
}Step 3: Data Table Organism
// src/components/organisms/data-table/index.ts
export { DataTable } from './DataTable';
export type { DataTableProps, Column } from './DataTable.types';
// src/components/organisms/data-table/DataTable.tsx
import React from 'react';
import { Button } from '@/components/atoms/button';
import { Icon } from '@/components/atoms/icon';
import { DataCell } from '@/components/molecules/data-cell';
import type { DataTableProps, Column } from './DataTable.types';
export function DataTable<T>({
data,
columns,
onSort,
onRowClick,
className,
...props
}: DataTableProps<T>) {
const [sortField, setSortField] = React.useState<string | null>(null);
const [sortDirection, setSortDirection] = React.useState<'asc' | 'desc'>('asc');
const handleSort = (field: string) => {
if (sortField === field) {
setSortDirection(sortDirection === 'asc' ? 'desc' : 'asc');
} else {
setSortField(field);
setSortDirection('asc');
}
onSort?.(field, sortDirection);
};
return (
<div className={className}>
<table className="w-full border-collapse">
<thead>
<tr className="border-b">
{columns.map((column) => (
<th
key={column.key}
className="px-4 py-2 text-left font-medium"
>
<Button
variant="ghost"
onClick={() => handleSort(column.key)}
className="flex items-center gap-2"
>
{column.label}
{sortField === column.key && (
<Icon
name={sortDirection === 'asc' ? 'chevron-up' : 'chevron-down'}
size="sm"
/>
)}
</Button>
</th>
))}
</tr>
</thead>
<tbody>
{data.map((row, index) => (
<tr
key={index}
className="border-b hover:bg-accent cursor-pointer"
onClick={() => onRowClick?.(row)}
>
{columns.map((column) => (
<td key={column.key} className="px-4 py-2">
<DataCell
value={row[column.key as keyof T]}
type={column.type}
formatter={column.formatter}
/>
</td>
))}
</tr>
))}
</tbody>
</table>
</div>
);
}
// src/components/organisms/data-table/DataTable.types.ts
export interface Column<T> {
key: keyof T;
label: string;
type?: 'text' | 'number' | 'date' | 'boolean' | 'custom';
formatter?: (value: any) => React.ReactNode;
sortable?: boolean;
}
export interface DataTableProps<T> {
data: T[];
columns: Column<T>[];
onSort?: (field: string, direction: 'asc' | 'desc') => void;
onRowClick?: (row: T) => void;
className?: string;
}Phase 4: Extract Templates
Step 1: Dashboard Template
// src/components/templates/dashboard/index.ts
export { DashboardTemplate } from './DashboardTemplate';
export type { DashboardTemplateProps } from './DashboardTemplate.types';
// src/components/templates/dashboard/DashboardTemplate.tsx
import React from 'react';
import { Header } from '@/components/organisms/header';
import { Sidebar } from '@/components/organisms/sidebar';
import { DashboardGrid } from '@/components/organisms/dashboard-grid';
import type { DashboardTemplateProps } from './DashboardTemplate.types';
export function DashboardTemplate({
user,
navigationItems,
onSearch,
onLogout,
onToggleSidebar,
sidebarOpen,
children,
className,
}: DashboardTemplateProps) {
return (
<div className={className}>
<Header
user={user}
onSearch={onSearch}
onLogout={onLogout}
onToggleSidebar={onToggleSidebar}
/>
<div className="flex h-[calc(100vh-4rem)]">
<Sidebar
navigationItems={navigationItems}
isOpen={sidebarOpen}
/>
<main className="flex-1 overflow-auto">
{children}
</main>
</div>
</div>
);
}
// src/components/templates/dashboard/DashboardTemplate.types.ts
export interface DashboardTemplateProps {
user?: {
name: string;
email: string;
avatar?: string;
};
navigationItems: Array<{
to: string;
icon?: string;
label: string;
isActive?: boolean;
}>;
onSearch: (query: string) => void;
onLogout: () => void;
onToggleSidebar: () => void;
sidebarOpen: boolean;
children: React.ReactNode;
className?: string;
}Step 2: Detection Template
// src/components/templates/detection/index.ts
export { DetectionTemplate } from './DetectionTemplate';
export type { DetectionTemplateProps } from './DetectionTemplate.types';
// src/components/templates/detection/DetectionTemplate.tsx
import React from 'react';
import { Header } from '@/components/organisms/header';
import { Sidebar } from '@/components/organisms/sidebar';
import { DataTable } from '@/components/organisms/data-table';
import type { DetectionTemplateProps } from './DetectionTemplate.types';
export function DetectionTemplate({
user,
navigationItems,
onSearch,
onLogout,
onToggleSidebar,
sidebarOpen,
detections,
onDetectionClick,
children,
className,
}: DetectionTemplateProps) {
return (
<div className={className}>
<Header
user={user}
onSearch={onSearch}
onLogout={onLogout}
onToggleSidebar={onToggleSidebar}
/>
<div className="flex h-[calc(100vh-4rem)]">
<Sidebar
navigationItems={navigationItems}
isOpen={sidebarOpen}
/>
<main className="flex-1 overflow-auto p-6">
{children}
<DataTable
data={detections}
columns={[
{ key: 'name', label: 'Name', type: 'text' },
{ key: 'severity', label: 'Severity', type: 'text' },
{ key: 'status', label: 'Status', type: 'text' },
{ key: 'createdAt', label: 'Created', type: 'date' },
]}
onRowClick={onDetectionClick}
/>
</main>
</div>
</div>
);
}
// src/components/templates/detection/DetectionTemplate.types.ts
export interface DetectionTemplateProps {
user?: {
name: string;
email: string;
avatar?: string;
};
navigationItems: Array<{
to: string;
icon?: string;
label: string;
isActive?: boolean;
}>;
onSearch: (query: string) => void;
onLogout: () => void;
onToggleSidebar: () => void;
sidebarOpen: boolean;
detections: Array<{
id: string;
name: string;
severity: string;
status: string;
createdAt: string;
}>;
onDetectionClick: (detection: any) => void;
children: React.ReactNode;
className?: string;
}Phase 5: Extract Pages
Step 1: Dashboard Page
// src/components/pages/dashboard/index.ts
export { DashboardPage } from './DashboardPage';
// src/components/pages/dashboard/DashboardPage.tsx
import React from 'react';
import { DashboardTemplate } from '@/components/templates/dashboard';
import { DashboardGrid } from '@/components/organisms/dashboard-grid';
import { useDashboard } from '@/hooks/useDashboard';
import { useNavigation } from '@/hooks/useNavigation';
import { useAuth } from '@/hooks/useAuth';
export function DashboardPage() {
const { user, logout } = useAuth();
const { navigationItems } = useNavigation();
const { dashboards, createDashboard } = useDashboard();
const [sidebarOpen, setSidebarOpen] = React.useState(true);
const handleSearch = (query: string) => {
// Handle search logic
console.log('Search:', query);
};
const handleToggleSidebar = () => {
setSidebarOpen(!sidebarOpen);
};
return (
<DashboardTemplate
user={user}
navigationItems={navigationItems}
onSearch={handleSearch}
onLogout={logout}
onToggleSidebar={handleToggleSidebar}
sidebarOpen={sidebarOpen}
>
<DashboardGrid dashboards={dashboards} onCreateDashboard={createDashboard} />
</DashboardTemplate>
);
}Step 2: Detections Page
// src/components/pages/detections/index.ts
export { DetectionsPage } from './DetectionsPage';
// src/components/pages/detections/DetectionsPage.tsx
import React from 'react';
import { DetectionTemplate } from '@/components/templates/detection';
import { CreateDetectionForm } from '@/components/molecules/create-detection-form';
import { useDetections } from '@/hooks/useDetections';
import { useNavigation } from '@/hooks/useNavigation';
import { useAuth } from '@/hooks/useAuth';
export function DetectionsPage() {
const { user, logout } = useAuth();
const { navigationItems } = useNavigation();
const { detections, createDetection } = useDetections();
const [sidebarOpen, setSidebarOpen] = React.useState(true);
const handleSearch = (query: string) => {
// Handle search logic
console.log('Search:', query);
};
const handleToggleSidebar = () => {
setSidebarOpen(!sidebarOpen);
};
const handleDetectionClick = (detection: any) => {
// Navigate to detection detail
console.log('Detection clicked:', detection);
};
return (
<DetectionTemplate
user={user}
navigationItems={navigationItems}
onSearch={handleSearch}
onLogout={logout}
onToggleSidebar={handleToggleSidebar}
sidebarOpen={sidebarOpen}
detections={detections}
onDetectionClick={handleDetectionClick}
>
<CreateDetectionForm onCreateDetection={createDetection} />
</DetectionTemplate>
);
}Atomic Design Benefits
- Consistency: Standardized components across the application
- Reusability: Components can be reused in different contexts
- Maintainability: Easy to update and maintain individual components
- Scalability: Easy to add new components following the atomic pattern
- Design System: Clear hierarchy and organization
- Testing: Each component can be tested independently
Migration Timeline
Week 1-2: Extract atoms (button, input, icon, label, typography) Week 3-4: Extract molecules (search-form, nav-item, form-field, data-cell) Week 5-6: Extract organisms (header, sidebar, data-table, dashboard-grid) Week 7-8: Extract templates (dashboard, detection, settings) Week 9-10: Extract pages and integrate with existing routes Week 11-12: Testing, optimization, and documentation
Implementation Considerations
- Component Hierarchy: Maintain clear atomic design hierarchy
- Props Interface: Keep component props simple and focused
- Styling: Use consistent styling patterns across components
- Testing: Test each component level independently
- Documentation: Document each component’s purpose and usage
Potential Challenges
- Component Complexity: Some components might not fit neatly into atomic levels
- Props Drilling: Passing props through multiple levels can be complex
- Styling Consistency: Maintaining consistent styling across components
- Performance: Multiple component levels might impact performance
- Learning Curve: Team needs to understand atomic design principles
Success Metrics
- Component Reusability: Percentage of components reused across pages
- Design Consistency: Consistency score across UI components
- Development Velocity: Time to implement new UI features
- Bug Reduction: Reduction in UI-related bugs
- Design System Adoption: Percentage of components following atomic design
Tools and Resources
- Storybook: For component documentation and testing
- Design Tokens: For consistent styling across components
- Component Library: For sharing components across teams
- Testing Tools: Jest, React Testing Library for component testing
- Design Tools: Figma for design system documentation