4 min readJun 23, 2026

What makes a component actually reusable? A mental model for better abstractions

The key question is not “Do we use this UI often enough?” but rather “Does this component provide a meaningful abstraction?

What makes a component actually reusable? A mental model for better abstractions

We create reusable components to handle recurring UI patterns. For example, if a user profile card appears in three different parts of the application, it makes more sense to design and build it once, then reuse it wherever needed. The benefits are obvious: we have less code to maintain, and the UI stays consistent.

Because reusability is closely associated with best practices, such as the DRY principle, we often rush to combine similar-looking components and move them into a shared component library. However, using visual similarity as the sole criterion for design system candidates can introduce unnecessary complexity.

While reusing presentational (a.k.a. dumb) components often delivers clear value, reusing slightly flexible or behavior-driven components can have the opposite effect.

Consider a user profile card. Imagine we display user information in several places:

  • In the application header, we display an avatar
  • In a comment section - avatar, name, and post date
  • On a profile page, avatar, user status, role, email, and user actions

At first glance, it may seem worthy to make the component reusable - the UI is largely the same, displaying an avatar and some text.

However, making a single component flexible enough to support all of these use cases can lead to an unnecessarily complex API:

// Overly flexible component with many flags

<UserProfileCard
  user={user}
  showRole
  showEmail={false}
  showStatus
  showActions={false}
  showPostDate
/>

To use the component correctly, developers need to understand all available props and know which ones to enable or disable for each context. Keeping this information in mind increases cognitive load and makes the component harder to use and reason about.

A more scalable approach is to split the UI into smaller, reusable building blocks and compose them at the usage site:

// used in header
<UserAvatar user={user} />


//used in comment section
<UserInfo>
  <UserName name={user.name} />
  <UserRole role={user.role} />
  <UserStatus status={user.status} />
</UserInfo>

//used in profile
<UserInfo>
<UserAvatar user={user} />
  <UserName name={user.name} />
  <UserRole status={user.role} />
</UserInfo>
<UserActions>
  <FollowButton userId={user.id} />
  <MessageButton userId={user.id} />
</UserActions>

Instead of forcing one component to handle every scenario through configuration, we build focused, reusable primitives (like UserAvatar, UserName, UserRole, UserActions) and compose them based on context.

Although we have duplicated code, we reduce hidden complexity and make each part of the UI easier to understand, reuse, and maintain.

This perspective raises a question: how do we decide which components to reuse and which to rewrite in a specific context?

The answer lies in the level of abstraction we provide.

Deeper abstractions

An abstraction is a simplified view of something that hides unnecessary details.

The more details it hides, compared to the interface it provides, the deeper the abstraction is. Similarly, the abstraction is shallow if it hides the same amount of complexity as the interface it provides.

Like module abstractions, we can think of every component as having a cost and a benefit. The cost is its interface: the props, variants, documentation, naming conventions, usage guidelines, and the cognitive effort required to understand and use it.

The benefit is the complexity it hides: visual consistency, accessibility requirements, interaction patterns, responsive behavior, implementation details, and design decisions that consumers no longer need to think about.

The deeper the abstraction, the greater the benefit relative to the cost.

A component is worth reusing when it provides significant functionality behind a simple interface. Consider a date picker: it may encapsulate complex behavior such as keyboard navigation, localization, validation, accessibility requirements, and calendar logic, yet consumers interact with it through a relatively small set of properties. The component hides far more complexity than it exposes.

The user card, on the other hand, isn’t worth reusing because the interface is almost as complex as the problem it solves.

The most valuable design system components are those with simple, understandable interfaces that encapsulate significant design and engineering complexity. They hide more complexity compared to the amount of information we have to learn to use them.

Considering costs and benefits rather than just visuals is a useful way to determine whether components should be reused.

From this perspective, the key question is not “Do we use this UI often enough?” but rather “Does this component provide a meaningful abstraction?”