How to Build Reusable UI Components in React

Learn the principles behind reusable React components — from proper prop design to folder structure. Build a production-ready Button component with variants, and understand why component reuse is the cornerstone of scalable frontends.

U

UIXplor Team

March 11, 2026 · 9 min read

01Why Reusable Components Matter

Every large React codebase eventually reaches a painful moment: you need to change a button style and you realize you've written the same button component 40 times across 40 files. Reusable components solve this by creating a single source of truth for your UI elements.

A reusable component is one that: - Accepts props to control its appearance and behavior - Has a predictable interface (clear prop names, sensible defaults) - Works in isolation — it doesn't depend on global state to render correctly - Is documented enough that another developer can use it without reading its source

Think of it like building with Lego. Each piece is standardized, self-contained, and can be combined in infinite ways.

02Anatomy of a Reusable React Button

Let's build a Button component from scratch that supports multiple variants, sizes, loading states, and icons:

jsx
// components/Button/Button.jsx
import React from 'react';
import './Button.css';

export function Button({
  children,
  variant = 'primary',
  size = 'md',
  loading = false,
  disabled = false,
  onClick,
  ...props
}) {
  return (
    <button
      className={`btn btn--${variant} btn--${size} ${loading ? 'btn--loading' : ''}`}
      disabled={disabled || loading}
      onClick={onClick}
      {...props}
    >
      {loading ? <span className="btn__spinner" /> : null}
      {children}
    </button>
  );
}

export default Button;

The key design decisions here: - `variant` controls the visual style (`primary`, `secondary`, `ghost`, `destructive`) - `size` controls padding and font size (`sm`, `md`, `lg`) - `loading` adds a spinner and prevents double-submit - `...props` passes through any native button attributes (`type`, `form`, `aria-*`)

03CSS for the Button System

Structure your CSS using BEM naming for variant flexibility:

css
/* components/Button/Button.css */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  font-family: inherit;
  font-weight: 600;
  border: none;
  border-radius: 8px;
  cursor: pointer;
  transition: all 0.2s ease;
  position: relative;
  overflow: hidden;
}

/* Sizes */
.btn--sm { padding: 6px 14px; font-size: 12px; }
.btn--md { padding: 10px 20px; font-size: 14px; }
.btn--lg { padding: 14px 28px; font-size: 16px; }

/* Variants */
.btn--primary {
  background: #6C63FF;
  color: #fff;
}
.btn--primary:hover { background: #7c74ff; transform: translateY(-1px); }

.btn--secondary {
  background: rgba(108, 99, 255, 0.1);
  color: #6C63FF;
  border: 1px solid rgba(108, 99, 255, 0.3);
}

.btn--ghost {
  background: transparent;
  color: rgba(255, 255, 255, 0.7);
  border: 1px solid rgba(255, 255, 255, 0.15);
}

.btn--loading { cursor: not-allowed; opacity: 0.7; }

.btn__spinner {
  width: 14px;
  height: 14px;
  border: 2px solid rgba(255,255,255,0.3);
  border-top-color: #fff;
  border-radius: 50%;
  animation: spin 0.7s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

05Composing Components

The real power of reusable components is composition. Build a `CardAction` component that combines your `Card` and `Button`:

jsx
import Card from '@/components/Card';
import Button from '@/components/Button';

export function FeatureCard({ title, description, onCTA }) {
  return (
    <Card>
      <h3>{title}</h3>
      <p>{description}</p>
      <Button variant="primary" onClick={onCTA}>
        Get Started
      </Button>
    </Card>
  );
}

This is the React philosophy: small, focused components that compose into larger UIs. Each piece is testable, replaceable, and understandable in isolation.

06Best Practices Summary

1. Single Responsibility — each component does one thing well 2. Props over state where possible — prefer controlled components 3. Sensible defaults — `variant='primary'`, `size='md'` so the simple case is simple 4. Document your props — even inline JSDoc comments help 5. Barrel exports — `index.js` files keep imports clean 6. Co-locate styles and tests — keep everything about a component in one folder 7. Use UIXplor — browse 200+ production-ready components you can drop directly into your project