- 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
- // Usage
- <
- Select
- value
- =
- {
- value
- }
- onChange
- =
- {
- setValue
- }
- >
- <
- Select.Trigger
- >
- Choose option
- </
- Select.Trigger
- >
- <
- Select.Options
- >
- <
- Select.Option
- value
- =
- "
- a
- "
- >
- Option A
- </
- Select.Option
- >
- <
- Select.Option
- value
- =
- "
- b
- "
- >
- Option B
- </
- Select.Option
- >
- </
- Select.Options
- >
- </
- Select
- >
- Render Props
-
- Delegate rendering to parent
- <
- DataFetcher
- url
- =
- "
- /api/users
- "
- >
- {
- (
- {
- data
- ,
- loading
- ,
- error
- }
- )
- =>
- loading
- ?
- <
- Spinner
- />
- :
- <
- UserList
- users
- =
- {
- data
- }
- />
- }
- </
- DataFetcher
- >
- Slots (Vue/Svelte)
- Named content injection points
Title Body text 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 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 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 < typeof buttonVariants
{ isLoading ? : boolean ; } export const Button = forwardRef < HTMLButtonElement , ButtonProps
( ( { className , variant , size , isLoading , children , ... props } , ref ) => ( < button ref = { ref } className = { cn ( buttonVariants ( { variant , size } ) , className ) } disabled = { isLoading || props . disabled } { ... props }
{ isLoading && < Spinner className = " mr-2 h-4 w-4 " /> } { children } </ button
) , ) ; Button . displayName = "Button" ; Framework Patterns React: Compound Components import { createContext , useContext , useState , type ReactNode } from "react" ; interface AccordionContextValue { openItems : Set < string
; toggle : ( id : string ) => void ; } const AccordionContext = createContext < AccordionContextValue | null
( 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 < Set < string
( 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 ( < AccordionContext.Provider value = { { openItems , toggle } }
< div className = " divide-y "
{ children } </ div
</ AccordionContext.Provider
) ; } Accordion . Item = function AccordionItem ( { id , title , children , } : { id : string ; title : string ; children : ReactNode ; } ) { const { openItems , toggle } = useAccordion ( ) ; const isOpen = openItems . has ( id ) ; return ( < div
< button onClick = { ( ) => toggle ( id ) } className = " w-full text-left py-3 "
{ title } </ button
{ isOpen && < div className = " pb-3 "
{ children } </ div
} </ div
) ; } ; Vue 3: Composables
Svelte 5: Runes
- {@render children()}
- Best Practices
- Single Responsibility
-
- Each component does one thing well
- Prop Drilling Prevention
-
- Use context for deeply nested data
- Accessible by Default
-
- Include ARIA attributes, keyboard support
- Controlled vs Uncontrolled
-
- Support both patterns when appropriate
- Forward Refs
-
- Allow parent access to DOM nodes
- Memoization
-
- Use
- React.memo
- ,
- useMemo
- for expensive renders
- 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