Most component codebases degrade because components accumulate responsibilities. A component that handles display, state, data fetching, and business logic becomes impossible to reuse, test, or reason about.
Composition over configuration
Bad — config Hell:
<Card
title="..."
variant="compact"
showAvatar={true}
avatarUrl="..."
onClick={handler}
badge="new"
badgeColor="green"
actions={[{ label: 'Edit', onClick: edit }]}
/>
Good — composition:
<Card>
<Card.Header>
<Avatar src="..." />
<Card.Title>...</Card.Title>
<Badge color="green">new</Badge>
</Card.Header>
<Card.Body>...</Card.Body>
<Card.Footer>
<Button onClick={edit}>Edit</Button>
</Card.Footer>
</Card>
The composed version is more verbose but infinitely more flexible. Each piece can be rearranged, replaced, or omitted without changing the Card API.
Prop design principles
-
Minimize required props. If a prop is almost always the same value, make it a default and let callers override it.
-
Avoid booleans.
isCompact,showHeader,hasBorder— these multiply combinatorially. Usevariantorasinstead:// Bad <Button isLarge isOutline isDisabled /> // Good <Button variant="outline" size="large" disabled /> -
Don’t pass objects for everything. A
styleprop is fine. Passing{{ padding: 10, margin: 5, background: 'red' }}as a prop means your component is actually a pass-through. -
Use
childrenfor content, props for configuration.
State placement
Push state to the lowest common ancestor that needs it:
- Local state (useState/useReducer): UI state that doesn’t affect other components (hover, open/close, form input)
- Context: state shared between distant components (theme, auth, locale)
- External store (Zustand/Redux): complex state with many readers/writers, or state that survives page navigation
- Server state (React Query/SWR): data from APIs — never store API responses in local state
When to split
Split a component when:
- It exceeds 200 lines (not counting types/styles)
- You can name two distinct sub-parts meaningfully
- You’re passing more than 7 props
- You find yourself writing
if (props.variant === 'foo')in multiple places
See references/prop-patterns.md for more prop design patterns.
When it triggers
- designing a new UI component
- component has too many props
- deciding where to put state
- splitting a large component