React is all about components. But while building components is easy, building reusable ones is a skill that separates junior developers from experienced front-end engineers.
Reusable components help you:
- Speed up development
- Maintain consistent UI
- Improve scalability
- Reduce code duplication
In this guide, we’ll show you how to create truly reusable React components like a pro. Whether you’re working in a startup, a large codebase, or building your own design system — this article will give you best practices, real-world examples, and performance tips.
What Is a Reusable Component?
A reusable component is a component that is abstracted enough to be used in different parts of your application without modification.
Not reusable:
<button className="bg-red-600 text-white rounded-md">Delete</button>
Reusable version:
const Button = ({ label, onClick, variant = "primary" }) => {
const baseStyle = "px-4 py-2 rounded-md font-medium";
const variants = {
primary: "bg-blue-600 text-white",
danger: "bg-red-600 text-white",
ghost: "bg-transparent text-black border",
};
return (
<button onClick={onClick} className={`${baseStyle} ${variants[variant]}`}>
{label}
</button>
);
};
Core Principles of Reusability
- Props over hardcoded values – Always use props to customize components.
- Avoid assumptions – Don’t bind a component to a specific layout or use-case.
- Keep logic separate – UI logic should be decoupled from business logic.
- Composition over configuration – Use
children
to allow flexible layouts.
Step-by-Step: Creating a Reusable Input
Component
Let’s walk through creating a basic but customizable input component.
1. Basic Structure
const Input = ({ type = "text", placeholder, value, onChange }) => {
return (
<input
type={type}
placeholder={placeholder}
value={value}
onChange={onChange}
className="border px-3 py-2 rounded-md w-full"
/>
);
};
2. Add Label & Error Handling
const Input = ({
label,
type = "text",
placeholder,
value,
onChange,
error,
}) => {
return (
<div className="mb-4">
{label && <label className="block mb-1 font-medium">{label}</label>}
<input
type={type}
placeholder={placeholder}
value={value}
onChange={onChange}
className={`border px-3 py-2 rounded-md w-full ${
error ? "border-red-500" : "border-gray-300"
}`}
/>
{error && <p className="text-red-500 text-sm mt-1">{error}</p>}
</div>
);
};
Props You Should Expose in Reusable Components
label
: descriptive texttype
: “text”, “password”, etc.value
,onChange
: for state controlplaceholder
: UX helpererror
: validation feedbackclassName
: for styling overridesdisabled
,readOnly
, etc.
Reusable Design with Tailwind or Styled Components
Instead of hardcoding styles, use utility-first CSS or theming libraries:
const Card = ({ children, className = "" }) => {
return (
<div className={`shadow-md rounded-lg p-4 bg-white ${className}`}>
{children}
</div>
);
};
Now you can customize layout with:
<Card className="max-w-md mx-auto mt-10">Welcome</Card>
Advanced Reusability Patterns
1. Slot Pattern
Instead of using only children
, expose slots via props:
const Modal = ({ title, footer, children }) => (
<div className="bg-white rounded-md p-6">
<h2 className="text-xl font-bold mb-4">{title}</h2>
<div>{children}</div>
<div className="mt-6">{footer}</div>
</div>
);
Usage:
<Modal
title="Confirm Delete"
footer={<Button variant="danger" label="Delete" />}>
Are you sure you want to delete this?
</Modal>
2. Compound Components
Useful for more complex UIs like tabs or dropdowns.
const Tabs = ({ children }) => <div>{children}</div>;
Tabs.TabList = ({ children }) => <div className="flex">{children}</div>;
Tabs.TabPanel = ({ children }) => <div>{children}</div>;
This pattern offers maximum flexibility with shared context.
3. Render Props or Hooks
Split reusable logic with a custom hook:
function useToggle(initial = false) {
const [open, setOpen] = useState(initial);
const toggle = () => setOpen((prev) => !prev);
return { open, toggle };
}
Use it in components like Modal, Accordion, Sidebar, etc.
Testing Reusable Components
Always test your components in isolation:
- Use Storybook for visual testing and documentation
- Write unit tests for props and behavior with Jest + React Testing Library
- Run a11y checks with tools like Axe or Lighthouse
Organizing Components in Your Codebase
Use a folder structure like:
/components
/Button
index.jsx
styles.css
/Input
index.jsx
test.jsx
Use an index barrel:
export { default as Button } from './Button';
export { default as Input } from './Input';
This allows:
import { Button, Input } from "@/components";
Final Thoughts
Reusable components are the foundation of scalable React applications. They simplify development, improve consistency, and accelerate delivery. By thinking in components, abstracting logic, and exposing the right props, you’ll build a UI system that’s flexible, maintainable, and ready to scale.
Remember:
- Design for flexibility, not just current needs
- Don’t over-engineer – keep components focused
- Use
children
and props wisely - Test and document components for team collaboration