--- name: web-component-design description: Master React, Vue, and Svelte component patterns including CSS-in-JS, composition strategies, and reusable component architecture. Use when building UI component libraries, designing component APIs, or implementing frontend design systems. --- # Web Component Design Build reusable, maintainable UI components using modern frameworks with clean composition patterns and styling approaches. ## When to Use This Skill - Designing reusable component libraries or design systems - Implementing complex component composition patterns - Choosing and applying CSS-in-JS solutions - Building accessible, responsive UI components - Creating consistent component APIs across a codebase - Refactoring legacy components into modern patterns - Implementing compound components or render props ## Core Concepts ### 1. Component Composition Patterns **Compound Components**: Related components that work together ```tsx // Usage ``` **Render Props**: Delegate rendering to parent ```tsx {({ data, loading, error }) => ( loading ? : )} ``` **Slots (Vue/Svelte)**: Named content injection points ```vue ``` ### 2. CSS-in-JS Approaches | Solution | Approach | Best For | |----------|----------|----------| | **Tailwind CSS** | Utility classes | Rapid prototyping, design systems | | **CSS Modules** | Scoped CSS files | Existing CSS, gradual adoption | | **styled-components** | Template literals | React, dynamic styling | | **Emotion** | Object/template styles | Flexible, SSR-friendly | | **Vanilla Extract** | Zero-runtime | Performance-critical apps | ### 3. Component API Design ```tsx interface ButtonProps { variant?: 'primary' | 'secondary' | 'ghost'; size?: 'sm' | 'md' | 'lg'; isLoading?: boolean; isDisabled?: boolean; leftIcon?: React.ReactNode; rightIcon?: React.ReactNode; children: React.ReactNode; onClick?: () => void; } ``` **Principles**: - Use semantic prop names (`isLoading` vs `loading`) - Provide sensible defaults - Support composition via `children` - Allow style overrides via `className` or `style` ## Quick Start: React Component with Tailwind ```tsx import { forwardRef, type ComponentPropsWithoutRef } from 'react'; import { cva, type VariantProps } from 'class-variance-authority'; import { cn } from '@/lib/utils'; const buttonVariants = cva( 'inline-flex items-center justify-center rounded-md font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50', { variants: { variant: { primary: 'bg-blue-600 text-white hover:bg-blue-700', secondary: 'bg-gray-100 text-gray-900 hover:bg-gray-200', ghost: 'hover:bg-gray-100 hover:text-gray-900', }, size: { sm: 'h-8 px-3 text-sm', md: 'h-10 px-4 text-sm', lg: 'h-12 px-6 text-base', }, }, defaultVariants: { variant: 'primary', size: 'md', }, } ); interface ButtonProps extends ComponentPropsWithoutRef<'button'>, VariantProps { isLoading?: boolean; } export const Button = forwardRef( ({ className, variant, size, isLoading, children, ...props }, ref) => ( ) ); Button.displayName = 'Button'; ``` ## Framework Patterns ### React: Compound Components ```tsx import { createContext, useContext, useState, type ReactNode } from 'react'; interface AccordionContextValue { openItems: Set; toggle: (id: string) => void; } const AccordionContext = createContext(null); function useAccordion() { const context = useContext(AccordionContext); if (!context) throw new Error('Must be used within Accordion'); return context; } export function Accordion({ children }: { children: ReactNode }) { const [openItems, setOpenItems] = useState>(new Set()); const toggle = (id: string) => { setOpenItems(prev => { const next = new Set(prev); next.has(id) ? next.delete(id) : next.add(id); return next; }); }; return (
{children}
); } Accordion.Item = function AccordionItem({ id, title, children }: { id: string; title: string; children: ReactNode }) { const { openItems, toggle } = useAccordion(); const isOpen = openItems.has(id); return (
{isOpen &&
{children}
}
); }; ``` ### Vue 3: Composables ```vue ``` ### Svelte 5: Runes ```svelte ``` ## Best Practices 1. **Single Responsibility**: Each component does one thing well 2. **Prop Drilling Prevention**: Use context for deeply nested data 3. **Accessible by Default**: Include ARIA attributes, keyboard support 4. **Controlled vs Uncontrolled**: Support both patterns when appropriate 5. **Forward Refs**: Allow parent access to DOM nodes 6. **Memoization**: Use `React.memo`, `useMemo` for expensive renders 7. **Error Boundaries**: Wrap components that may fail ## Common Issues - **Prop Explosion**: Too many props - consider composition instead - **Style Conflicts**: Use scoped styles or CSS Modules - **Re-render Cascades**: Profile with React DevTools, memo appropriately - **Accessibility Gaps**: Test with screen readers and keyboard navigation - **Bundle Size**: Tree-shake unused component variants ## Resources - [React Component Patterns](https://reactpatterns.com/) - [Vue Composition API Guide](https://vuejs.org/guide/reusability/composables.html) - [Svelte Component Documentation](https://svelte.dev/docs/svelte-components) - [Radix UI Primitives](https://www.radix-ui.com/primitives) - [shadcn/ui Components](https://ui.shadcn.com/)