Skip to main content

Component Design

Component design is the process of breaking a user interface into small, focused, reusable parts that can be combined into larger screens.

Large Page
↓
Smaller Components
↓
Reusable UI System
↓
Scalable Frontend Architecture

One-line idea: Do not design one large page first; design a system of components that work together.

Core Concepts​

Think in Components, Not Full Pages​

Instead of treating an entire screen as one component, identify sections with separate responsibilities.

App
├── Header
├── MainContent
│ ├── FeaturedSection
│ └── CardsSection
└── BottomNavigation

Each component has a clear job:

ComponentResponsibility
HeaderBranding and top-level navigation
MainContentOrganizing the main page sections
CardsSectionRendering a related group of cards
BottomNavigationNavigation actions at the bottom

This structure makes the UI easier to understand, debug, redesign and extend.

Component Hierarchy​

A component hierarchy describes how larger components contain and coordinate smaller components.

App.jsx
export default function App() {
return (
<div className="app">
<Header />

<main>
<FeaturedSection />
<CardsSection />
</main>

<BottomNavigation />
</div>
);
}

The hierarchy helps answer:

  • Which component owns this part of the UI?
  • Which components repeat?
  • Which component controls the layout?
  • Where should state and behavior live?

Single Responsibility Principle​

The Single Responsibility Principle (SRP) means a component should have one clear responsibility.

A button component can be responsible for:

  • rendering its content
  • applying button styles
  • handling its click interaction
Button.jsx
export default function Button({ children, onClick }) {
return (
<button className="button" onClick={onClick}>
{children}
</button>
);
}

The same button should not also manage unrelated concerns such as authentication, API requests, routing, notifications and theme changes.

One component + one clear responsibility
↓
Easier reuse, testing, debugging and maintenance

Separation of Concerns​

Separation of concerns keeps different types of work in the components that own them.

For example:

Card               -> displays content
CardRow -> controls horizontal scrolling
CardContainer -> handles data and filtering
MenuButton -> triggers menu state changes
Sidebar -> renders the menu state

A component should not take responsibility for behavior that belongs to its parent or container.

Props, State and Composition​

These three ideas work together in component design:

ConceptRole
PropsPass data and callbacks into a component
StateStores information that can change the UI
CompositionBuilds larger components from smaller components
Props provide input
State chooses the current UI
Composition assembles the final screen

Readability Matters​

A component system should be easy for another developer to understand.

Readable component design uses:

  • small focused components
  • meaningful names
  • predictable structure
  • limited nesting
  • organized layouts

Clear names:

<ProductCard />
<SidebarMenu />
<UserProfile />

Unclear names:

<Data />
<Stuff />
<ContainerThing />

A component name should communicate what the component represents or does.

Practical Component Patterns​

Decomposing a Page​

Start by identifying visually and behaviorally independent sections.

Suppose a dashboard contains:

Dashboard
├── Header
│ ├── Logo
│ └── HeaderActions
├── Sidebar
│ └── MenuItem × N
└── Content
├── SummarySection
└── ProductRow
└── ProductCard × N

Then create the screen by composing those parts:

Dashboard.jsx
export default function Dashboard() {
return (
<div className="dashboard">
<Header />

<div className="dashboard__body">
<Sidebar />

<main>
<SummarySection />
<ProductRow />
</main>
</div>
</div>
);
}

The page component describes the layout while child components handle their own focused UI.

Composing a Header​

The header acts as a container for smaller pieces.

Header.jsx
export default function Header() {
return (
<header className="header">
<Logo />

<div className="header__right">
<SearchButton />
<ProfileButton />
<MenuButton />
</div>
</header>
);
}

This keeps branding, actions and menu behavior separate instead of placing all header logic in one large component.

Rendering Reusable Menu Items​

Repeated UI should usually come from reusable components and data rather than manually duplicated markup.

Sidebar.jsx
const menuItems = [
{ id: 1, label: "Home", href: "/" },
{ id: 2, label: "Products", href: "/products" },
{ id: 3, label: "Profile", href: "/profile" },
];

export default function Sidebar() {
return (
<nav className="sidebar">
{menuItems.map((item) => (
<MenuItem key={item.id} item={item} />
))}
</nav>
);
}
MenuItem.jsx
export default function MenuItem({ item }) {
return (
<a className="menu-item" href={item.href}>
{item.label}
</a>
);
}

Why this is better:

  • avoids duplicated markup
  • keeps every item visually consistent
  • makes new items easy to add
  • allows one component update to affect every item

Presentational Card and Data Container​

A reusable card should mainly display the data it receives.

ProductCard.jsx
export default function ProductCard({ product }) {
return (
<article className="product-card">
<img src={product.image} alt={product.title} />
<h3>{product.title}</h3>
<p>{product.description}</p>
</article>
);
}

The parent or container handles the list, state, filtering or data-loading responsibility.

ProductList.jsx
export default function ProductList({ products }) {
return (
<section className="product-list">
{products.map((product) => (
<ProductCard key={product.id} product={product} />
))}
</section>
);
}
ProductList owns the collection
↓
ProductCard receives one product
↓
ProductCard renders only that product

This separation makes the card reusable in multiple sections.

Keep Scrolling in the Container​

If several cards should scroll horizontally, scrolling belongs to their container—not to each card.

ScrollableCardRow.jsx
export default function ScrollableCardRow({ items }) {
return (
<div className="card-row">
{items.map((item) => (
<ProductCard key={item.id} product={item} />
))}
</div>
);
}
styles.css
.card-row {
display: flex;
gap: 16px;
overflow-x: auto;
}

.product-card {
flex: 0 0 280px;
}

Responsibility split:

ComponentOwns
ScrollableCardRowLayout, gap and scrolling behavior
ProductCardImage, title, description and card styling

The card does not need to know whether it appears in a carousel, grid or dashboard row.

State-Driven Menu Expansion​

State-driven UI renders different layouts based on the current state.

DashboardLayout.jsx
import { useState } from "react";

export default function DashboardLayout() {
const [isMenuExpanded, setIsMenuExpanded] = useState(false);

return (
<div className="dashboard-layout">
<Header onMenuClick={() => setIsMenuExpanded((current) => !current)} />

{isMenuExpanded ? <ExpandedMenu /> : <CollapsedMenu />}

<DashboardContent />
</div>
);
}
Menu button clicked
↓
isMenuExpanded changes
↓
React renders ExpandedMenu or CollapsedMenu

This pattern is common in sidebars, dashboards, mobile navigation and responsive layouts.

A section component gives related content a consistent structure.

Section.jsx
export default function Section({ title, children }) {
return (
<section className="section">
<h2>{title}</h2>
<div className="section__content">{children}</div>
</section>
);
}
<Section title="Recommended Products">
<ProductList products={recommendedProducts} />
</Section>

<Section title="Recently Viewed">
<ProductList products={recentProducts} />
</Section>

This pattern improves layout consistency, readability and responsiveness.

Component Composition​

Composition builds flexible components by combining smaller parts.

Card.jsx
export default function Card({ children }) {
return <article className="card">{children}</article>;
}
<Card>
<CardHeader title="Premium Plan" />
<CardBody>Includes all advanced features.</CardBody>
<CardFooter>
<Button>Choose plan</Button>
</CardFooter>
</Card>

The parent defines the arrangement while smaller components provide focused pieces of UI.

Benefits of composition:

  • flexible layouts
  • reusable building blocks
  • easier updates
  • cleaner component structure

Benefits, Trade-Offs and Common Mistakes​

Benefits

  • Smaller components are easier to understand and debug.
  • Reusable components reduce duplicated code.
  • Focused responsibilities make testing easier.
  • Data-driven rendering keeps repeated UI consistent.
  • Composition helps the system grow without creating giant components.
  • Clear names and predictable hierarchy improve team collaboration.

Design trade-offs

Too largeToo fragmented
Mixes unrelated responsibilitiesCreates unnecessary wrappers
Becomes difficult to reuseMakes the hierarchy harder to follow
Makes testing and debugging harderSplits simple UI without a clear benefit

The goal is not to create the maximum number of components. The goal is to create components with clear boundaries and useful responsibilities.

Common mistakes

MistakeBetter approach
Building the whole page in one componentSplit the page into responsible sections
Repeating the same menu or card markupCreate a reusable component and map data
Putting scrolling logic inside every cardLet the parent container own scrolling
Mixing API, filtering and display logic in a cardSeparate the data container from the presentational card
Giving components vague namesUse names that describe purpose
Letting one component handle unrelated tasksApply the Single Responsibility Principle
Hard-coding expanded and collapsed layoutsDrive the UI from state

Summary​

Good component design turns large pages into focused, reusable components that form a readable and scalable UI system.

Identify the page sections
↓
Give each section one responsibility
↓
Extract repeated UI into reusable components
↓
Pass data through props
↓
Drive changing UI with state
↓
Keep layout behavior in parent containers
↓
Compose the components into the final screen