Components & Props
Design reusable components with clear prop contracts, composition, and children patterns.
Props as Component API
Props are read-only inputs to components. Treat them as a public API — changes are breaking for consumers. Keep prop surfaces minimal and name them consistently across your design system.
Destructure props in the function signature for clarity. Provide default values via default parameters or defaultProps (legacy). Document required vs optional props with TypeScript interfaces.
- Props are immutable for child
- Minimal, stable prop APIs
- TypeScript interfaces document contracts
function Avatar({ src, alt, size = 40 }: AvatarProps) {
return <img src={src} alt={alt} width={size} height={size} />;
}Composition Over Configuration
Prefer composable children and slots over dozens of boolean props (`showHeader`, `showFooter`, `showSidebar`). Pass JSX as children or render props when consumers need flexible layout control.
Compound components — parent + child subcomponents sharing context — model complex UI like tabs, accordions, and menus without prop drilling configuration through every layer.
- Children for flexible layout
- Avoid boolean prop explosion
- Compound components for complex UI
<Card> <Card.Header>Title</Card.Header> <Card.Body>Content</Card.Body> </Card>
Spreading and Rest Props
Spread remaining props onto native elements to support pass-through attributes: `function Input({ label, ...rest }) { return <input {...rest} /> }`. This enables aria attributes and event handlers without listing every HTML attribute.
Exclude props that should not reach the DOM (internal flags) before spreading. Type rest props with `ComponentPropsWithoutRef` utilities for correct HTML attribute typing.
- Spread rest onto native elements
- Strip internal props before spread
- Type with ComponentPropsWithoutRef
Controlled vs Presentational
Presentational components receive data and callbacks, containing no data fetching or global state. Container components (or hooks) fetch data and pass props down. This separation improves testability — presentational components render from props alone.
Modern code often uses custom hooks instead of container components, colocating data logic with UI while keeping the hook testable independently.
- Presentational: UI from props
- Containers/hooks: data and logic
- Hooks replace many container components
Prop Validation and Defaults
TypeScript catches most prop errors at compile time. For JavaScript codebases, PropTypes still validate at runtime in development. Default values should match the expected type — undefined optional props need null checks or defaults.
Avoid passing new object or function literals inline to memoized children on every render — they break referential equality. Stabilize callbacks with useCallback when passing to optimized children.
- TypeScript for compile-time validation
- Stable references for memoized children
- Sensible defaults for optional props