--- name: ui-component-patterns description: Build reusable, maintainable UI components following modern design patterns. Use when creating component libraries, implementing design systems, or building scalable frontend architectures. Handles React patterns, composition, prop design, TypeScript, and component best practices. metadata: tags: UI-components, React, design-patterns, composition, TypeScript, reusable platforms: Claude, ChatGPT, Gemini --- # UI Component Patterns ## When to use this skill - **Building Component Libraries**: Creating reusable UI components - **Implementing Design Systems**: Applying consistent UI patterns - **Complex UI**: Components requiring multiple variants (Button, Modal, Dropdown) - **Refactoring**: Extracting duplicate code into components ## Instructions ### Step 1: Props API Design Design Props that are easy to use and extensible. **Principles**: - Clear names - Reasonable defaults - Type definitions with TypeScript - Optional Props use optional marker (?) **Example** (Button): ```tsx interface ButtonProps { // Required children: React.ReactNode; // Optional (with defaults) variant?: 'primary' | 'secondary' | 'outline' | 'ghost'; size?: 'sm' | 'md' | 'lg'; disabled?: boolean; isLoading?: boolean; // Event handlers onClick?: (event: React.MouseEvent) => void; // HTML attribute inheritance type?: 'button' | 'submit' | 'reset'; className?: string; } function Button({ children, variant = 'primary', size = 'md', disabled = false, isLoading = false, onClick, type = 'button', className = '', ...rest }: ButtonProps) { const baseClasses = 'btn'; const variantClasses = `btn-${variant}`; const sizeClasses = `btn-${size}`; const classes = `${baseClasses} ${variantClasses} ${sizeClasses} ${className}`; return ( ); } // Usage example ``` ### Step 2: Composition Pattern Combine small components to build complex UI. **Example** (Card): ```tsx // Card component (Container) interface CardProps { children: React.ReactNode; className?: string; } function Card({ children, className = '' }: CardProps) { return
{children}
; } // Card.Header function CardHeader({ children }: { children: React.ReactNode }) { return
{children}
; } // Card.Body function CardBody({ children }: { children: React.ReactNode }) { return
{children}
; } // Card.Footer function CardFooter({ children }: { children: React.ReactNode }) { return
{children}
; } // Compound Component pattern Card.Header = CardHeader; Card.Body = CardBody; Card.Footer = CardFooter; export default Card; // Usage import Card from './Card'; function ProductCard() { return (

Product Name

Product

Product description here...

); } ``` ### Step 3: Render Props / Children as Function A pattern for flexible customization. **Example** (Dropdown): ```tsx interface DropdownProps { items: T[]; renderItem: (item: T, index: number) => React.ReactNode; onSelect: (item: T) => void; placeholder?: string; } function Dropdown({ items, renderItem, onSelect, placeholder }: DropdownProps) { const [isOpen, setIsOpen] = useState(false); const [selected, setSelected] = useState(null); const handleSelect = (item: T) => { setSelected(item); onSelect(item); setIsOpen(false); }; return (
{isOpen && (
    {items.map((item, index) => (
  • handleSelect(item)}> {renderItem(item, index)}
  • ))}
)}
); } // Usage interface User { id: string; name: string; avatar: string; } function UserDropdown() { const users: User[] = [...]; return ( (
{user.name} {user.name}
)} onSelect={(user) => console.log('Selected:', user)} /> ); } ``` ### Step 4: Separating Logic with Custom Hooks Separate UI from business logic. **Example** (Modal): ```tsx // hooks/useModal.ts function useModal(initialOpen = false) { const [isOpen, setIsOpen] = useState(initialOpen); const open = useCallback(() => setIsOpen(true), []); const close = useCallback(() => setIsOpen(false), []); const toggle = useCallback(() => setIsOpen(prev => !prev), []); return { isOpen, open, close, toggle }; } // components/Modal.tsx interface ModalProps { isOpen: boolean; onClose: () => void; title: string; children: React.ReactNode; } function Modal({ isOpen, onClose, title, children }: ModalProps) { if (!isOpen) return null; return (
e.stopPropagation()}>

{title}

{children}
); } // Usage function App() { const { isOpen, open, close } = useModal(); return ( <>

Modal content here...

); } ``` ### Step 5: Performance Optimization Prevent unnecessary re-renders. **React.memo**: ```tsx // ❌ Bad: child re-renders every time parent re-renders function ExpensiveComponent({ data }) { console.log('Rendering...'); return
{/* Complex UI */}
; } // ✅ Good: re-renders only when props change const ExpensiveComponent = React.memo(({ data }) => { console.log('Rendering...'); return
{/* Complex UI */}
; }); ``` **useMemo & useCallback**: ```tsx function ProductList({ products, category }: { products: Product[]; category: string }) { // ✅ Memoize filtered results const filteredProducts = useMemo(() => { return products.filter(p => p.category === category); }, [products, category]); // ✅ Memoize callback const handleAddToCart = useCallback((productId: string) => { // Add to cart console.log('Adding:', productId); }, []); return (
{filteredProducts.map(product => ( ))}
); } const ProductCard = React.memo(({ product, onAddToCart }) => { return (

{product.name}

); }); ``` ## Output format ### Component File Structure ``` components/ ├── Button/ │ ├── Button.tsx # Main component │ ├── Button.test.tsx # Tests │ ├── Button.stories.tsx # Storybook │ ├── Button.module.css # Styles │ └── index.ts # Export ├── Card/ │ ├── Card.tsx │ ├── CardHeader.tsx │ ├── CardBody.tsx │ ├── CardFooter.tsx │ └── index.ts └── Modal/ ├── Modal.tsx ├── useModal.ts # Custom hook └── index.ts ``` ### Component Template ```tsx import React from 'react'; export interface ComponentProps { // Props definition children: React.ReactNode; className?: string; } /** * Component description * * @example * ```tsx * Hello * ``` */ export const Component = React.forwardRef( ({ children, className = '', ...rest }, ref) => { return (
{children}
); } ); Component.displayName = 'Component'; export default Component; ``` ## Constraints ### Required Rules (MUST) 1. **Single Responsibility Principle**: One component has one role only - Button handles buttons only, Form handles forms only 2. **Props Type Definition**: TypeScript interface required - Enables auto-completion - Type safety 3. **Accessibility**: aria-*, role, tabindex, etc. ### Prohibited Rules (MUST NOT) 1. **Excessive props drilling**: Prohibited when 5+ levels deep - Use Context or Composition 2. **No Business Logic**: Prohibit API calls and complex calculations in UI components - Separate into custom hooks 3. **Inline objects/functions**: Performance degradation ```tsx // ❌ Bad example handleClick()} /> // ✅ Good example const style = { color: 'red' }; const handleClick = useCallback(() => {...}, []); ``` ## Examples ### Example 1: Accordion (Compound Component) ```tsx import React, { createContext, useContext, useState } from 'react'; // Share state with Context const AccordionContext = createContext<{ activeIndex: number | null; setActiveIndex: (index: number | null) => void; } | null>(null); function Accordion({ children }: { children: React.ReactNode }) { const [activeIndex, setActiveIndex] = useState(null); return (
{children}
); } function AccordionItem({ index, title, children }: { index: number; title: string; children: React.ReactNode; }) { const context = useContext(AccordionContext); if (!context) throw new Error('AccordionItem must be used within Accordion'); const { activeIndex, setActiveIndex } = context; const isActive = activeIndex === index; return (
{isActive &&
{children}
}
); } Accordion.Item = AccordionItem; export default Accordion; // Usage Content for section 1 Content for section 2 ``` ### Example 2: Polymorphic Component (as prop) ```tsx type PolymorphicComponentProps = { as?: C; children: React.ReactNode; } & React.ComponentPropsWithoutRef; function Text({ as, children, ...rest }: PolymorphicComponentProps) { const Component = as || 'span'; return {children}; } // Usage Default span Heading 1 Paragraph Link ``` ## Best practices 1. **Composition over Props**: Leverage children instead of many props 2. **Controlled vs Uncontrolled**: Choose based on situation 3. **Default Props**: Provide reasonable defaults 4. **Storybook**: Component documentation and development ## References - [React Patterns](https://reactpatterns.com/) - [Compound Components](https://kentcdodds.com/blog/compound-components-with-react-hooks) - [Radix UI](https://www.radix-ui.com/) - Accessible components - [Chakra UI](https://chakra-ui.com/) - Component library - [shadcn/ui](https://ui.shadcn.com/) - Copy-paste components ## Metadata ### Version - **Current Version**: 1.0.0 - **Last Updated**: 2025-01-01 - **Compatible Platforms**: Claude, ChatGPT, Gemini ### Related Skills - [web-accessibility](../web-accessibility/SKILL.md): Accessible components - [state-management](../state-management/SKILL.md): Component state management ### Tags `#UI-components` `#React` `#design-patterns` `#composition` `#TypeScript` `#frontend`