Front Runner Front End Web Development Blog

How to Write Reusable Components Well

Learn how to write reusable components with clear APIs, sensible defaults, and flexible patterns that keep front-end code easier to maintain.

| June 24, 2026 | 8 min read

You can usually spot a component that was only built for one screen. It has props like `isHomepageCard`, three hard-coded class names, and one deeply suspicious boolean called `compactButNotTooCompact`. If you are trying to learn how to write reusable components, that is the mess you are trying to avoid.

Reusable components are not components that can do everything. They are components that solve one job cleanly, can be used in more than one place, and do not force every future developer to read your mind. That sounds simple, but in practice it means making better choices about scope, API design, styling, and state.

How to write reusable components without overengineering

The first trap is thinking reusability means maximum flexibility. It does not. A component with 17 props, six variants, and enough conditional logic to frighten your build tools is not reusable. It is a small framework wearing a fake moustache.

A reusable component starts with a narrow purpose. A button should handle button-like behaviour. A card should arrange content in a predictable way. A modal should manage overlay and focus concerns. If you start by defining one clear responsibility, the rest gets easier.

This is where newer developers often get caught out. They try to predict every future need before the component has even earned its keep. In real projects, the better move is usually to build for the current use case, then extract patterns when a second or third use case appears. Reuse should come from evidence, not fortune-telling.

Start with the job, not the styling

Before writing code, ask what the component is actually responsible for. Not what it looks like. Not which page it lives on. What job it does.

Take a `Button` component. Its job might be to render a clickable control with consistent states, support different visual treatments, and pass through native button behaviour. That is a sensible boundary. Its job is not to know whether it lives in a pricing table, newsletter form, or hero section with a background image your designer swears is “subtle”.

That separation matters because visual context changes more often than the core behaviour. If your component is tied too tightly to one layout or one page, it will fight reuse from day one.

A good test is this: if you rename the component after the page it came from, it is probably too specific. `HomepageFeatureBox` is a clue. `FeatureCard` is at least trying.

Look for repeated patterns, not repeated pixels

Two components can look slightly different and still share the same underlying structure. That is usually where reuse lives. Instead of copying exact styling, identify what is actually repeated: content hierarchy, spacing rules, interaction states, layout behaviour, or data shape.

For example, if several parts of your UI display an image, title, description, and action, that might justify a reusable card pattern. If they merely share a border radius and shadow, that is probably a styling token problem, not a component problem.

Design a small, clear API

If component reuse rises or falls anywhere, it is here. Your props are the public API. If they are confusing, inconsistent, or too clever, the component becomes harder to use than rewriting it from scratch.

Aim for props that are obvious at a glance. Prefer names that describe intent, such as `variant`, `size`, `disabled`, or `onClose`. Avoid props that encode layout context or implementation details, like `useBlueVersion` or `renderWithExtraPadding`.

Defaults matter too. A component should behave sensibly with minimal configuration. If every use requires five mandatory props just to stop it exploding, something has gone wrong. Sensible defaults reduce friction and make adoption more likely.

There is also a trade-off between explicitness and flexibility. Sometimes a simple `variant=”primary”` is exactly right. Sometimes passing children or composition hooks is better than inventing ten specialised props. If your API keeps growing, that is often a sign the component should expose structure rather than more switches.

Prefer composition over prop soup

A common mistake when learning how to write reusable components is stuffing every variation into props. You start with `title` and `description`, then add `icon`, `footerText`, `buttonLabel`, `buttonHref`, `showBadge`, and before long your tidy little component is one bad day away from sentience.

Composition is often cleaner. Rather than forcing one component to know every possible content arrangement, let it provide a useful wrapper and accept children for the parts that vary.

That keeps the component flexible without making the API unreadable. It also makes edge cases easier to handle, because you are not trying to predict all future content in advance.

Keep state where it belongs

Reusable components tend to get weird when they own too much state. If a component controls data fetching, business logic, UI state, and layout all at once, it becomes hard to lift into new contexts.

A helpful rule is to keep presentational components as dumb as practical and move app-specific logic higher up. For instance, a `Tabs` component can manage which tab is active, because that is core to being tabs. But it probably should not know how to fetch account settings or decide whether the user has permission to view billing.

This is not a strict law. Sometimes a component benefits from internal state, especially for local interaction like toggles, menus, or disclosure panels. The question is whether the state supports the component’s core role or ties it to one feature.

If you cannot reuse the component without dragging half the application with it, the boundary needs work.

Build for extension, not mutation

Reusable components age better when they are easy to extend without rewriting their internals. In practice, that means exposing well-chosen hooks for class names, children, render props, slots, or style overrides, depending on your stack.

For front-end work, this often comes down to not hard-coding too much. Leave room for additional attributes. Support native HTML behaviour where appropriate. Allow custom content in sensible places. Respect accessibility patterns instead of fighting them.

A button component, for example, should still behave like a real button unless there is a strong reason otherwise. The more your abstraction drifts from native behaviour, the more work you create for everyone later.

Reuse should not break accessibility

This bit is not optional. A component is not reusable if teams have to patch accessibility problems every time they use it.

Use semantic elements first. Make focus states visible. Handle keyboard interaction properly. Pass through useful ARIA attributes when needed, but do not sprinkle them around like parsley and hope for the best. Good reusable components reduce repeated mistakes as well as repeated code.

That is one of the best reasons to create them in the first place. Done well, they bake good habits into the product.

Write less logic inside the JSX

When a component becomes a tangle of nested ternaries and conditional wrappers, reuse gets harder because nobody wants to touch it. Keep rendering logic readable. Pull complex decisions into helper functions or derived values before the return.

This is partly about maintainability, but also about trust. Developers are far more likely to reuse a component they can understand in thirty seconds than one that reads like a hostage note from the bundler.

Clarity beats cleverness here. Every time.

Know when not to reuse

This is the part people skip, usually right before creating `GenericContentBlock`. Not everything deserves a reusable abstraction.

If a pattern appears once, leave it alone. If two versions share a name but behave differently, forcing them into one component may create more complexity than it saves. If a component only becomes reusable through a maze of options, it may be trying to merge things that should stay separate.

Duplication is not always failure. Sometimes a bit of repetition is cheaper than a bad abstraction. The goal is not to eliminate every repeated line. The goal is to make change easier.

A practical checklist for writing reusable components

When you are building or reviewing a component, ask a few blunt questions. Does it have one clear responsibility? Are the prop names obvious? Does it work with minimal setup? Is it tied to one page or feature? Can another developer use it without reading its entire source code? If the answer to that last one is no, your component may be technically reusable and practically avoided.

It also helps to test reuse early. Drop the component into a second context before calling it done. That is where awkward assumptions usually show up. You will spot hard-coded spacing, over-specific naming, missing flexibility, or state that should live elsewhere.

A component is proven reusable when it survives a second use without drama. Not before.

How to write reusable components that stay useful

The best reusable components are boring in the best possible way. They are predictable, readable, and easy to extend. They do one thing well, avoid leaking page-specific assumptions, and give developers enough flexibility without turning every prop into a puzzle.

That means resisting two urges at once: the urge to duplicate everything forever, and the urge to abstract every idea after seeing it twice. Good component design sits in the middle. Practical, not precious.

If you want a decent rule of thumb, write components for the use case in front of you, then refine them when patterns actually repeat. Your future self will still complain sometimes – that is just software development – but at least they will complain less.

Post Tags