all articles
Design SystemsSep 8, 2026· 6 min read

How to Build Reusable Components Without Creating a Mess

Reusable components go wrong in a predictable way: too many props, too many booleans, and no clear boundary. Here's how to avoid that.

JH
Jaker Hossain
Frontend Team Lead

Reusable doesn't mean "handles every case"

The most common mistake in building a shared component library is treating "reusable" as "configurable for absolutely everything." That's how you end up with a Button that takes 14 boolean props and a switch statement inside its render logic. That's not reusable — it's fragile in a way that's just spread across more call sites.

Composition over configuration

Instead of one component with a prop for every variation, I favor composing smaller pieces:

<Card>
  <Card.Header>Order #1234</Card.Header>
  <Card.Body>...</Card.Body>
  <Card.Footer>
    <Button variant="primary">Confirm</Button>
  </Card.Footer>
</Card>

This lets consumers rearrange, omit, or extend pieces without the base component needing to know about every possible layout in advance.

Variants, not booleans

isPrimary, isDanger, isSmall, isOutlined as four separate booleans creates 16 possible combinations, most of which were never designed or tested. A single variant and size prop with a fixed set of allowed values keeps the design system's actual decisions enforced in the type system:

type ButtonVariant = 'primary' | 'secondary' | 'danger' | 'ghost';
type ButtonSize = 'sm' | 'md' | 'lg';

Define the boundary before you write the component

Before building a shared component, I answer one question: what is this component explicitly not responsible for? A Modal is responsible for overlay, focus trapping, and close behavior. It is not responsible for what content goes inside it, and it shouldn't need to know. Components that stay disciplined about their boundary compose well. Components that try to do a little bit of everything become the thing nobody wants to touch.

What this actually paid off in practice

Building a shared component library this way — clear variants, composition-first, strict boundaries — was the single biggest factor in cutting our deploy time by 25% across projects. New features stopped rebuilding basic UI, because the existing components were flexible enough to compose into new layouts without being modified.

The short version

  • Compose small pieces instead of configuring one large one
  • Use typed variants instead of boolean flags
  • Define what a component is not responsible for before building it
  • If a component needs a comment explaining its props, its API is already too complex

Documenting components as part of the definition of done

A reusable react component isn't truly reusable if nobody besides its author knows it exists or how to use it. Alongside the component itself, we maintain lightweight usage examples (often via Storybook) showing each variant and common composition pattern. This turns "ask in Slack if this component can do X" into "check the story file," which scales far better as a team grows.

Handling the exceptions gracefully

No component library best practices guide survives contact with a genuinely unique one-off design request. My rule: if a one-off need appears once, solve it locally without touching the shared component. If the same one-off need appears a second or third time, that's the signal to formally extend the shared component's variant set — not before. Premature flexibility is just complexity paid for in advance.

Accessibility as a default, not an add-on

Shared components are the highest-leverage place to bake in accessibility, because the investment pays off everywhere the component is used. Focus states, ARIA attributes, and keyboard navigation built into a base Button or Modal component means every feature using it inherits that behavior automatically, instead of every team reimplementing (or forgetting) it independently.

The long-term test of a good component

The real test of whether a component was designed well isn't how it looks in its first use case — it's how gracefully it handles its fifth, unplanned use case six months later. If extending it requires modifying its internals rather than composing around it, the original boundary was drawn in the wrong place. That's a normal, fixable outcome — not a failure — as long as the fix is composition-first, not another boolean prop.

tags
design-systemsreactcomponent-architecture