Frontend projects can get messy fast as they scale. That's exactly where Atomic Design comes in β a methodology for building UIs in a layered, controlled way. In this article, we'll cover Atomic Design from the ground up, then translate it into a real mini-project using React and Storybook.
π§© What Is Atomic Design?
Atomic Design is a methodology that proposes building UIs by starting from small, reusable building blocks and progressively composing them into larger structures. Brad Frost was the first to systematize this approach, and today it's especially common in design system work.
Why does it matter? Because in most projects, components start out innocent enough and then spiral into chaos with names like HeaderFinal, ProductCardNew, and ProductCardUpdated. Atomic Design gives us a shared, unambiguous vocabulary to fight that entropy.
Atomic Design is organized around five layers:
β
Atom
The smallest UI building blocks at the HTML level. A button, input, label, or a tiny badge all live here.
β
Molecule
Small groups of atoms working together to perform a meaningful task. A search box (input + button) is a classic example.
β
Organism
Larger, more meaningful UI sections composed of molecules and atoms. Header, ProductCard, and Sidebar are good examples.
β
Template
Defines the skeleton of a page. Focuses on layout, not content.
β
Page
A template filled with real data. This is what the user actually sees.
Worth pausing here: Atomic Design is not just a folder-naming convention. The real goal is to separate UI responsibilities across layers and draw clear boundaries between each one.
π€ Why Does It Matter?
Imagine you're building an e-commerce app. Your homepage, category page, campaign page, and favorites screen all share very similar cards, buttons, filter areas, and header structures.
If you don't organize these pieces systematically from the start, you'll soon find yourself carrying four slightly different versions of the same component. That slows down refactoring, makes testing harder, and stretches onboarding time.
Here's why I reach for this approach:
β
Reusability increases
An atom or molecule written once can be reused across different pages.
β
Maintenance cost drops
When a design change comes in, you don't have to hunt through every file.
β
Team communication gets clearer
The moment you can say "Is this an organism or a molecule?" you've established a shared language.
β
It pairs beautifully with Storybook
Developing and testing components in isolation becomes much easier.
β
Building a design system becomes tractable
Your UI library grows in a controlled way, not a chaotic one.
Bottom line: Atomic Design doesn't just bring order, it brings long-term velocity.
βοΈ How Should You Think About Atomic Design in React?
React is a component-based JavaScript library for building UIs, which makes it a natural fit for Atomic Design. Here's how the layers map onto React:
β
Atom = Pure, small, self-contained component
Examples: Button, Input
β
Molecule = Small, purposeful UI combination
Example: SearchBox
β
Organism = A larger block responsible for a specific feature area
Examples: Header, ProductCard
β
Template = The layout skeleton of a page
Example: MainLayout
β
Page = The screen that works with real data and state
Example: HomePage
One critical point: not every large component has to be an organism. Sometimes a clean, simple Card stays comfortably at the molecule level. When deciding on a layer, look at responsibility, not size.
π οΈ Setting Up the Project
Enough theory, let's get our hands dirty. We'll scaffold a base React project first, then add Storybook to enable a component-driven development workflow.
Step 1: Scaffold the React project
Vite is a frontend toolchain that provides a fast dev server and a modern build experience. The commands below create a new React project and spin it up locally.
npm create vite@latest atomic-design-react -- --template react
cd atomic-design-react
npm install
npm run dev
Key parts:
β
npm create vite@latest generates the new project scaffold.
β
--template react selects the preconfigured React template.
β
npm run dev starts the development server.
Expected result: A blank React application running in your browser.
Step 2: Add Storybook
Storybook is a tool that lets you develop and document UI components in isolation, independent of your application.
npx storybook@latest init
Once installation is complete, launch Storybook with:
npm run storybook
A few things worth noting:
β
The init command analyzes your project structure and auto-generates the necessary config files.
β
npm run storybook lets you view components in a dedicated, sandboxed environment.
β
This setup is especially powerful for verifying each atomic layer in isolation.
Expected result: The Storybook UI opens in your browser and displays some example stories.
π Setting Up the Folder Structure
A messy folder structure will wear down even the best component architecture over time.
The structure below is a solid starting point for mid-sized projects.
src/
components/
atoms/
Button.jsx
Input.jsx
molecules/
SearchBox.jsx
organisms/
Header.jsx
ProductCard.jsx
templates/
MainLayout.jsx
pages/
HomePage.jsx
data/
products.js
App.jsx
main.jsx
styles.css
A few things to pay attention to:
-
components/atomsholds the smallest, most general-purpose pieces. -
moleculescombines atoms but stays at a controlled size. -
organismscontains larger UI blocks that carry real business value. -
templatesdefines layout, not real page data. -
pagesis where actual state and data flow begins.
π§ͺ Atom-Level Components
At the atom level, the goal is simple: write small, reusable, self-contained pieces. Components here should be as independent as possible.
Button.jsx
// src/components/atoms/Button.jsx
export default function Button({
children,
variant = "primary",
type = "button",
onClick,
}) {
return (
<button
type={type}
className={`button button--${variant}`}
onClick={onClick}
>
{children}
</button>
);
}
Key points:
-
variant = "primary"gives the button a default style type. -
childrenkeeps the button content flexible. - At the atom level, the job is purely to render. No business logic.
Input.jsx
// src/components/atoms/Input.jsx
export default function Input({
value,
onChange,
placeholder = "Search...",
...props
}) {
return (
<input
className="input"
value={value}
onChange={onChange}
placeholder={placeholder}
{...props}
/>
);
}
Key points:
-
...propskeeps the input flexible for future use cases. -
valueandonChangecome from the outside, so the component is controlled. - No unnecessary state at the atom level.
π‘ The simpler your atoms, the more freedom you have at higher layers.
𧬠Molecule-Level Components
At the molecule layer, we're building small but purposeful combinations. A standalone Input is just a field. A SearchBox represents a specific user intent.
SearchBox.jsx
// src/components/molecules/SearchBox.jsx
import Button from "../atoms/Button";
import Input from "../atoms/Input";
export default function SearchBox({ query, onQueryChange, onSearch }) {
return (
<div className="search-box">
<Input
value={query}
onChange={(event) => onQueryChange(event.target.value)}
placeholder="Search products"
/>
<Button onClick={onSearch}>Search</Button>
</div>
);
}
Design decisions worth calling out:
-
SearchBoxholds no internal state. It delegates control to the page layer. -
InputandButtonremain atoms; they simply come together here. - This component now does meaningful work on the page, which is why treating it as a molecule makes sense.
ποΈ Organism-Level Components
At the organism layer, we're building more significant UI sections. Components here represent a distinct, recognizable region of the page.
Header.jsx
// src/components/organisms/Header.jsx
import Button from "../atoms/Button";
import SearchBox from "../molecules/SearchBox";
export default function Header({ query, onQueryChange, onSearch }) {
return (
<header className="header">
<div className="header__brand">ShopSphere</div>
<SearchBox
query={query}
onQueryChange={onQueryChange}
onSearch={onSearch}
/>
<Button variant="secondary">My Cart</Button>
</header>
);
}
Key things to notice:
β
Header is no longer a single atom or molecule. It's a larger UI section.
β
It uses both a molecule and an atom internally.
β
The brand area, search area, and cart action come together to form a functional region.
ProductCard.jsx
// src/components/organisms/ProductCard.jsx
import Button from "../atoms/Button";
export default function ProductCard({ product }) {
return (
<article className="product-card">
<div className="product-card__emoji" aria-hidden="true">
{product.emoji}
</div>
<h3 className="product-card__title">{product.title}</h3>
<p className="product-card__description">{product.description}</p>
<div className="product-card__footer">
<strong>{product.price} TL</strong>
<Button>Add to Cart</Button>
</div>
</article>
);
}
Key points:
- The
productobject gains real meaning at the organism level. -
Buttonis reused as an atom. - The card represents a standalone, significant UI unit on the page, which is why it belongs at the organism level.
π Template and Page Layers
This is where most people get confused. Template and page are not the same thing.
Template defines the structure of a page.
Page is that structure filled with real data and state.
MainLayout.jsx
// src/components/templates/MainLayout.jsx
import Header from "../organisms/Header";
export default function MainLayout({
query,
onQueryChange,
onSearch,
children,
}) {
return (
<div className="layout">
<Header
query={query}
onQueryChange={onQueryChange}
onSearch={onSearch}
/>
<main className="layout__content">{children}</main>
</div>
);
}
What makes this structure important:
β
children keeps the template flexible and composable.
β
MainLayout establishes layout but has no awareness of product data.
β
This separation pays real dividends as projects grow.
A template should be as content-agnostic as possible. Data, filtering logic, and page-level behavior belong in the page layer.
ποΈ Sample Project: Building a Mini E-Commerce UI
Now let's wire together all the layers we've built into a real working page.
Step 1: Create the mock product data
// src/data/products.js
export const products = [
{
id: 1,
emoji: "β¨οΈ",
title: "Mechanical Keyboard",
description: "Hot-swappable, compact keyboard with RGB support.",
price: 2499,
},
{
id: 2,
emoji: "π±οΈ",
title: "Gaming Mouse",
description: "Lightweight body, high DPI, and low latency.",
price: 1499,
},
{
id: 3,
emoji: "π§",
title: "Wireless Headset",
description: "Long battery life with low-latency connectivity.",
price: 3299,
},
];
Why this matters:
- We can test the page layer independently from a real API.
- The same mock data can be reused in Storybook stories.
- Data flow is easy to reason about during development.
Step 2: Write the HomePage component
// src/pages/HomePage.jsx
import { useMemo, useState } from "react";
import ProductCard from "../components/organisms/ProductCard";
import MainLayout from "../components/templates/MainLayout";
import { products } from "../data/products";
export default function HomePage() {
const [query, setQuery] = useState("");
const filteredProducts = useMemo(() => {
return products.filter((product) =>
product.title.toLowerCase().includes(query.toLowerCase())
);
}, [query]);
const handleSearch = () => {
console.log(`Searching for: ${query}`);
};
return (
<MainLayout
query={query}
onQueryChange={setQuery}
onSearch={handleSearch}
>
<section className="page-intro">
<h1>Featured Products</h1>
<p>A sample catalog screen built with the Atomic Design approach.</p>
</section>
<section className="product-grid">
{filteredProducts.map((product) => (
<ProductCard key={product.id} product={product} />
))}
</section>
</MainLayout>
);
}
Most critical points:
- Real
statelives here. We're at the right layer. - Filtering logic is resolved at the page level.
-
MainLayouthandles layout only; it doesn't manage data.
Step 3: Wire up the application entry point
// src/App.jsx
import HomePage from "./pages/HomePage";
import "./styles.css";
export default function App() {
return <HomePage />;
}
Small file, but it matters:
-
Appnow renders the page-level screen. - Global styles are loaded from a single location.
- The atomic layers are properly connected from top to bottom.
Step 4: Add minimal styles
/* src/styles.css */
:root {
font-family: Inter, system-ui, sans-serif;
color: #e2e8f0;
background: #0f172a;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
}
.layout__content {
max-width: 1100px;
margin: 0 auto;
padding: 32px 20px;
}
.header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
padding: 20px;
border-bottom: 1px solid #1e293b;
background: #111827;
}
.header__brand {
font-size: 20px;
font-weight: 700;
}
.search-box {
display: flex;
gap: 12px;
flex: 1;
max-width: 520px;
}
.input,
.button {
border-radius: 12px;
border: 1px solid #334155;
padding: 12px 16px;
}
.input {
flex: 1;
background: #0f172a;
color: #e2e8f0;
}
.button {
cursor: pointer;
color: white;
background: #2563eb;
}
.button--secondary {
background: #334155;
}
.page-intro {
margin-bottom: 24px;
}
.product-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
gap: 16px;
}
.product-card {
padding: 20px;
border: 1px solid #1e293b;
border-radius: 18px;
background: #111827;
}
.product-card__emoji {
font-size: 40px;
margin-bottom: 12px;
}
.product-card__footer {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 16px;
}
Worth noting:
- The dark theme makes component boundaries more visible.
- The
product-gridlayout lets you inspect card organisms comfortably. - Variant classes like
button--secondarydemonstrate the flexibility built at the atom level.
π I realized I've been defaulting to SCSS and CSS Modules in almost all my recent articles. For a real Storybook project I'd still go with CSS Modules, but this time I wanted to mix things up and bring back some old-school vibes π₯² so I went with BEM throughout.
π Isolated Component Development with Storybook
So why is Storybook so valuable here? Because it lets you test each layer of your Atomic Design system individually, without any dependency on the running application.
Even before the app is up, you can validate Button, SearchBox, or ProductCard in complete isolation.
Step 1: Write a story for Button
// src/components/atoms/Button.stories.js
import Button from "./Button";
export default {
title: "Atoms/Button",
component: Button,
args: {
children: "Add to Cart",
variant: "primary",
},
};
export const Primary = {};
export const Secondary = {
args: {
children: "Details",
variant: "secondary",
},
};
Things to pay attention to:
-
title: "Atoms/Button"categorizes the component according to its atomic layer. -
argsmakes it trivial to test different variations quickly. - Defining multiple visual states for the same atom becomes effortless.
Step 2: Write a story for ProductCard
// src/components/organisms/ProductCard.stories.js
import ProductCard from "./ProductCard";
export default {
title: "Organisms/ProductCard",
component: ProductCard,
args: {
product: {
id: 1,
emoji: "β¨οΈ",
title: "Mechanical Keyboard",
description: "Hot-swappable, compact keyboard with RGB support.",
price: 2499,
},
},
};
export const Default = {};
Key takeaways:
- Organism-level components can also be tested independently of the page.
- We can develop without waiting on a real API by using mock data.
- Visual regression checks and UI reviews become much more manageable.
When Storybook and Atomic Design work together, producing, testing, and sharing components becomes significantly easier.
βοΈ Pros and Cons of Atomic Design
Like any solid approach, Atomic Design has both strengths and trade-offs. Rather than applying it blindly, it's worth understanding what it gives you and where it introduces overhead.
Pros
β
Component boundaries become much clearer.
β
Reusability increases significantly.
β
Storybook, testing, and design system workflows get stronger.
β
Large teams develop a shared UI vocabulary.
β
Refactoring becomes safer and more predictable.
Cons
β
Can feel over-engineered for small or single-screen apps.
β
Misinterpreting the layers leads to unnecessary file sprawl.
β
Over-decomposing every component can slow down initial development velocity.
β
Requires some upfront discipline, especially for teams new to the approach.
To be direct about it: Atomic Design is not a silver bullet. But for UI-heavy projects with real growth potential, it provides an exceptionally strong foundation.
π Common Mistakes
Here are the most frequent mistakes I see when teams adopt this approach:
β
Atomizing everything
A component isn't automatically an atom just because it's small. What matters is its responsibility.
β
Confusing templates and pages
Layout knowledge belongs in the template. Data and behavior belong in the page.
β
Embedding business logic in atoms
Atom-level components should stay as pure as possible.
β
Treating the folder structure as the end goal
Atomic Design exists for sustainable UI architecture, not for aesthetic folder organization.
β
Using Storybook purely as a showcase
The real value is in developing components in isolation and catching edge cases early.
Another critical mistake shows up in naming. The moment you see Card2, HeaderNew, or ButtonLatest, stop and reconsider.
π€ When Should You Use It?
Atomic Design shines especially in projects where component count is growing and reusable UI pieces are multiplying. When combined with React, the approach feels even more natural because you're already working in a component-based world.
If you're building a small, single-screen app, you probably don't need this many layers. But if you're building a design system, if different teams are sharing the same UI pieces, or if the project is scaling quickly, Atomic Design becomes a very compelling choice.
Think of Atomic Design not just as theory, but as a daily development practice. When paired with React and Storybook, you end up with a frontend architecture that is more organized, more readable, and far more scalable.
π¬ Feedback
While writing this article, I used GPT 5.4 High for research, source selection, and copyediting. Images were generated with Gemini 3 Pro Preview 2k (Nano Banana Pro).
I genuinely welcome feedback, suggestions, and criticism. If you'd like to get in touch, you can reach me via the social links on my website or connect with me on LinkedIn.
Best, Yasin π€
π References
- Atomic Design by Brad Frost - Used to clarify the core concepts and layer definitions of the Atomic Design methodology.
- React Documentation - Used for React component architecture, state management, and foundational patterns.
- Storybook for React - Referenced for Storybook setup and React integration.
- Vite Guide - Used to verify the Vite setup flow for bootstrapping the React project quickly.
- Storybook Writing Stories - Used to clarify how story files should be structured and written.
- BEM CSS Convention - The CSS methodology used throughout the article.



Top comments (0)