# Component Library Architecture Complete guide to building, organizing, and maintaining a scalable component library. ## Component Architecture Principles ### 1. Atomic Design Organize components hierarchically from smallest to largest. **Hierarchy:** ``` Atoms → Molecules → Organisms → Templates → Pages ``` **Example Structure:** ``` components/ ├── atoms/ │ ├── Button/ │ ├── Input/ │ ├── Icon/ │ └── Label/ ├── molecules/ │ ├── FormField/ │ ├── SearchBar/ │ └── Card/ ├── organisms/ │ ├── Header/ │ ├── LoginForm/ │ └── ProductCard/ ├── templates/ │ ├── DashboardLayout/ │ └── AuthLayout/ └── pages/ ├── HomePage/ └── ProfilePage/ ``` ### 2. Component Types **Presentational (Dumb) Components:** - Focus on how things look - No state management - Receive data via props - Highly reusable ```tsx // Presentational Button interface ButtonProps { variant?: 'primary' | 'secondary'; size?: 'sm' | 'md' | 'lg'; children: React.ReactNode; onClick?: () => void; } export const Button: React.FC = ({ variant = 'primary', size = 'md', children, onClick, }) => { return ( ); }; ``` **Container (Smart) Components:** - Focus on how things work - Manage state - Connect to data sources - Use presentational components ```tsx // Container Component export const UserProfileContainer: React.FC = () => { const [user, setUser] = useState(null); const [loading, setLoading] = useState(true); useEffect(() => { fetchUser().then(data => { setUser(data); setLoading(false); }); }, []); if (loading) return ; if (!user) return ; return ; }; ``` ### 3. Component Composition **Build complex components from simple ones.** ```tsx // Atoms const Avatar: React.FC = ({ src, alt }) => ( {alt} ); const Badge: React.FC = ({ children }) => ( {children} ); // Molecule (composed of atoms) const UserBadge: React.FC = ({ user }) => (
{user.name} {user.role}
); // Organism (composed of molecules) const UserList: React.FC = ({ users }) => (
    {users.map(user => (
  • ))}
); ``` ## File Structure ### Standard Component Structure ``` Button/ ├── Button.tsx # Main component ├── Button.test.tsx # Tests ├── Button.stories.tsx # Storybook stories ├── Button.module.css # Styles (CSS Modules) ├── Button.types.ts # TypeScript types └── index.ts # Barrel export ``` ### Example Files **Button/Button.tsx** ```tsx import React from 'react'; import styles from './Button.module.css'; import { ButtonProps } from './Button.types'; export const Button: React.FC = ({ variant = 'primary', size = 'md', disabled = false, loading = false, children, onClick, ...props }) => { const className = [ styles.button, styles[`button--${variant}`], styles[`button--${size}`], disabled && styles['button--disabled'], loading && styles['button--loading'], ].filter(Boolean).join(' '); return ( ); }; ``` **Button/Button.types.ts** ```tsx export interface ButtonProps extends React.ButtonHTMLAttributes { /** Visual style variant */ variant?: 'primary' | 'secondary' | 'ghost' | 'danger'; /** Size variant */ size?: 'sm' | 'md' | 'lg'; /** Disabled state */ disabled?: boolean; /** Loading state */ loading?: boolean; /** Button content */ children: React.ReactNode; /** Click handler */ onClick?: (event: React.MouseEvent) => void; } ``` **Button/Button.test.tsx** ```tsx import { render, screen, fireEvent } from '@testing-library/react'; import { Button } from './Button'; describe('Button', () => { it('renders children correctly', () => { render(); expect(screen.getByText('Click me')).toBeInTheDocument(); }); it('handles click events', () => { const handleClick = jest.fn(); render(); fireEvent.click(screen.getByText('Click me')); expect(handleClick).toHaveBeenCalledTimes(1); }); it('applies variant classes', () => { render(); const button = screen.getByText('Click me'); expect(button).toHaveClass('button--secondary'); }); it('disables interaction when loading', () => { const handleClick = jest.fn(); render(); const button = screen.getByText('Click me'); expect(button).toBeDisabled(); expect(button).toHaveAttribute('aria-busy', 'true'); }); }); ``` **Button/Button.stories.tsx** ```tsx import type { Meta, StoryObj } from '@storybook/react'; import { Button } from './Button'; const meta: Meta = { title: 'Components/Button', component: Button, argTypes: { variant: { control: 'select', options: ['primary', 'secondary', 'ghost', 'danger'], }, size: { control: 'select', options: ['sm', 'md', 'lg'], }, }, }; export default meta; type Story = StoryObj; export const Primary: Story = { args: { variant: 'primary', children: 'Primary Button', }, }; export const Secondary: Story = { args: { variant: 'secondary', children: 'Secondary Button', }, }; export const Loading: Story = { args: { loading: true, children: 'Loading...', }, }; export const Disabled: Story = { args: { disabled: true, children: 'Disabled Button', }, }; ``` **Button/index.ts** ```tsx export { Button } from './Button'; export type { ButtonProps } from './Button.types'; ``` ## Common Components ### 1. Button Component ```tsx interface ButtonProps { variant?: 'primary' | 'secondary' | 'ghost' | 'danger'; size?: 'sm' | 'md' | 'lg'; disabled?: boolean; loading?: boolean; icon?: React.ReactNode; iconPosition?: 'left' | 'right'; children: React.ReactNode; onClick?: () => void; } ``` ### 2. Input Component ```tsx interface InputProps { type?: 'text' | 'email' | 'password' | 'number' | 'tel'; label?: string; placeholder?: string; value?: string; error?: string; helpText?: string; required?: boolean; disabled?: boolean; onChange?: (value: string) => void; } export const Input: React.FC = ({ type = 'text', label, placeholder, value, error, helpText, required, disabled, onChange, }) => { const id = useId(); const errorId = `${id}-error`; const helpId = `${id}-help`; return (
{label && ( )} onChange?.(e.target.value)} /> {error && ( {error} )} {helpText && !error && ( {helpText} )}
); }; ``` ### 3. Card Component ```tsx interface CardProps { variant?: 'default' | 'outlined' | 'elevated'; padding?: 'none' | 'sm' | 'md' | 'lg'; clickable?: boolean; children: React.ReactNode; onClick?: () => void; } export const Card: React.FC = ({ variant = 'default', padding = 'md', clickable = false, children, onClick, }) => { const Component = clickable ? 'button' : 'div'; return ( {children} ); }; ``` ### 4. Modal Component ```tsx interface ModalProps { open: boolean; title?: string; size?: 'sm' | 'md' | 'lg' | 'full'; children: React.ReactNode; onClose: () => void; } export const Modal: React.FC = ({ open, title, size = 'md', children, onClose, }) => { useEffect(() => { if (open) { // Trap focus in modal document.body.style.overflow = 'hidden'; return () => { document.body.style.overflow = ''; }; } }, [open]); if (!open) return null; return createPortal(
e.stopPropagation()} role="dialog" aria-modal="true" aria-labelledby={title ? 'modal-title' : undefined} > {title && (
)}
{children}
, document.body ); }; ``` ### 5. Dropdown Component ```tsx interface DropdownProps { trigger: React.ReactNode; children: React.ReactNode; align?: 'left' | 'right'; } export const Dropdown: React.FC = ({ trigger, children, align = 'left', }) => { const [open, setOpen] = useState(false); const dropdownRef = useRef(null); useEffect(() => { const handleClickOutside = (event: MouseEvent) => { if ( dropdownRef.current && !dropdownRef.current.contains(event.target as Node) ) { setOpen(false); } }; document.addEventListener('mousedown', handleClickOutside); return () => document.removeEventListener('mousedown', handleClickOutside); }, []); return (
{open && (
{children}
)}
); }; ``` ## Component Patterns ### 1. Compound Components **Allow components to work together while sharing implicit state.** ```tsx // Context for shared state const AccordionContext = createContext<{ activeIndex: number | null; setActiveIndex: (index: number | null) => void; } | null>(null); // Parent component export const Accordion: React.FC<{ children: React.ReactNode }> = ({ children, }) => { const [activeIndex, setActiveIndex] = useState(null); return (
{children}
); }; // Child component export const AccordionItem: React.FC<{ index: number; title: string; children: React.ReactNode; }> = ({ index, title, children }) => { const context = useContext(AccordionContext); if (!context) throw new Error('AccordionItem must be used within Accordion'); const { activeIndex, setActiveIndex } = context; const isOpen = activeIndex === index; return (
{isOpen &&
{children}
}
); }; // Usage Content 1 Content 2 ``` ### 2. Render Props **Pass rendering logic as a prop.** ```tsx interface DataFetcherProps { url: string; children: (data: { data: T | null; loading: boolean; error: Error | null; }) => React.ReactNode; } export function DataFetcher({ url, children }: DataFetcherProps) { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { fetch(url) .then(res => res.json()) .then(setData) .catch(setError) .finally(() => setLoading(false)); }, [url]); return <>{children({ data, loading, error })}; } // Usage url="/api/user"> {({ data, loading, error }) => { if (loading) return ; if (error) return ; if (!data) return null; return ; }} ``` ### 3. Custom Hooks Pattern **Extract reusable logic into custom hooks.** ```tsx // useToggle hook export function useToggle(initialValue = false) { const [value, setValue] = useState(initialValue); const toggle = useCallback(() => setValue(v => !v), []); const setTrue = useCallback(() => setValue(true), []); const setFalse = useCallback(() => setValue(false), []); return { value, toggle, setTrue, setFalse }; } // Usage in component export const Modal: React.FC = ({ children }) => { const { value: isOpen, setTrue: open, setFalse: close } = useToggle(); return ( <> {isOpen && (
{children}
)} ); }; ``` ## Styling Strategies ### 1. CSS Modules ```css /* Button.module.css */ .button { display: inline-flex; align-items: center; padding: var(--space-3) var(--space-4); border-radius: var(--radius-md); } .button--primary { background: var(--color-primary); color: var(--color-white); } .button--secondary { background: transparent; color: var(--color-primary); border: 1px solid var(--color-primary); } ``` ```tsx import styles from './Button.module.css'; export const Button = ({ variant }) => ( ); ``` ### 2. CSS-in-JS (Styled Components) ```tsx import styled from 'styled-components'; const StyledButton = styled.button<{ variant: string }>` display: inline-flex; align-items: center; padding: ${({ theme }) => theme.space[3]} ${({ theme }) => theme.space[4]}; background: ${({ theme, variant }) => variant === 'primary' ? theme.colors.primary : 'transparent'}; color: ${({ theme, variant }) => variant === 'primary' ? theme.colors.white : theme.colors.primary}; `; export const Button = ({ variant, children }) => ( {children} ); ``` ### 3. Tailwind CSS ```tsx import clsx from 'clsx'; export const Button: React.FC = ({ variant, size, children }) => { return ( ); }; ``` ## Documentation ### Component Documentation Template ```tsx /** * Button component for triggering actions and navigation. * * @example * ```tsx * * ``` * * @see {@link https://design-system.example.com/button | Design System Docs} */ export const Button: React.FC = ({ ... }) => { // Implementation }; ``` ### Storybook Documentation ```tsx import type { Meta } from '@storybook/react'; import { Button } from './Button'; const meta: Meta = { title: 'Components/Button', component: Button, parameters: { docs: { description: { component: ` Button component for triggering actions. ## Usage \`\`\`tsx import { Button } from '@/components/Button'; \`\`\` ## Accessibility - Keyboard accessible - Screen reader friendly - WCAG 2.1 AA compliant `, }, }, }, }; ``` ## Testing Strategies ### Unit Tests ```tsx describe('Button', () => { it('renders with correct variant', () => { render(); expect(screen.getByRole('button')).toHaveClass('button--primary'); }); it('calls onClick when clicked', () => { const handleClick = jest.fn(); render(); fireEvent.click(screen.getByRole('button')); expect(handleClick).toHaveBeenCalledTimes(1); }); }); ``` ### Accessibility Tests ```tsx import { axe, toHaveNoViolations } from 'jest-axe'; expect.extend(toHaveNoViolations); it('has no accessibility violations', async () => { const { container } = render(); const results = await axe(container); expect(results).toHaveNoViolations(); }); ``` ### Visual Regression Tests ```tsx // Using Chromatic or Percy it('matches snapshot', () => { const { container } = render(); expect(container).toMatchSnapshot(); }); ``` ## Performance Optimization ### Code Splitting ```tsx // Lazy load heavy components const HeavyComponent = lazy(() => import('./HeavyComponent')); export const App = () => ( }> ); ``` ### Memoization ```tsx // Memoize expensive components export const ExpensiveComponent = memo(({ data }) => { // Expensive rendering logic return
{processData(data)}
; }); // Memoize callbacks const handleClick = useCallback(() => { // Handle click }, [dependencies]); // Memoize values const expensiveValue = useMemo(() => { return computeExpensiveValue(data); }, [data]); ``` ## Version Control ### Semantic Versioning ``` MAJOR.MINOR.PATCH 1.0.0 → Initial release 1.1.0 → New feature (backwards compatible) 1.1.1 → Bug fix (backwards compatible) 2.0.0 → Breaking change ``` ### Changelog ```markdown # Changelog ## [2.0.0] - 2024-01-15 ### Breaking Changes - Removed `type` prop from Button (use `variant` instead) ### Added - New `loading` state for Button - Icon support in Button component ### Fixed - Button focus indicator contrast ratio ## [1.1.0] - 2024-01-01 ### Added - New Input component - Card component variants ``` ## Resources - [React Component Patterns](https://kentcdodds.com/blog/compound-components-with-react-hooks) - [Design Systems Handbook](https://www.designbetter.co/design-systems-handbook) - [Atomic Design](https://bradfrost.com/blog/post/atomic-web-design/) - [Storybook Best Practices](https://storybook.js.org/docs/react/writing-docs/introduction) --- **"Good components are reusable, accessible, and well-documented."**