Yasin Ateş

Atomic Design in Practice: React and Storybook from Scratch 🧩

Atomic Design in Practice: React and Storybook from Scratch 🧩

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 layers

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 <span class="nt">--</span> <span class="nt">--template</span> react
<span class="nb">cd </span>atomic-design-react
npm <span class="nb">install
</span>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.

React folder structure for storybook

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/atoms holds the smallest, most general-purpose pieces.
  • molecules combines atoms but stays at a controlled size.
  • organisms contains larger UI blocks that carry real business value.
  • templates defines layout, not real page data.
  • pages is 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

<span class="c1">// src/components/atoms/Button.jsx</span>
<span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nf">Button</span><span class="p">({</span>
  <span class="nx">children</span><span class="p">,</span>
  <span class="nx">variant</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">primary</span><span class="dl">"</span><span class="p">,</span>
  <span class="nx">type</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">button</span><span class="dl">"</span><span class="p">,</span>
  <span class="nx">onClick</span><span class="p">,</span>
<span class="p">})</span> <span class="p">{</span>
  <span class="k">return </span><span class="p">(</span>
    <span class="p"><</span><span class="nt">button</span>
      <span class="na">type</span><span class="p">=</span><span class="si">{</span><span class="nx">type</span><span class="si">}</span>
      <span class="na">className</span><span class="p">=</span><span class="si">{</span><span class="s2">`button button--</span><span class="p">${</span><span class="nx">variant</span><span class="p">}</span><span class="s2">`</span><span class="si">}</span>
      <span class="na">onClick</span><span class="p">=</span><span class="si">{</span><span class="nx">onClick</span><span class="si">}</span>
    <span class="p">></span>
      <span class="si">{</span><span class="nx">children</span><span class="si">}</span>
    <span class="p"></</span><span class="nt">button</span><span class="p">></span>
  <span class="p">);</span>
<span class="p">}</span>

Key points:

  • variant = "primary" gives the button a default style type.
  • children keeps the button content flexible.
  • At the atom level, the job is purely to render. No business logic.

Input.jsx

<span class="c1">// src/components/atoms/Input.jsx</span>
<span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nf">Input</span><span class="p">({</span>
  <span class="nx">value</span><span class="p">,</span>
  <span class="nx">onChange</span><span class="p">,</span>
  <span class="nx">placeholder</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">Search...</span><span class="dl">"</span><span class="p">,</span>
  <span class="p">...</span><span class="nx">props</span>
<span class="p">})</span> <span class="p">{</span>
  <span class="k">return </span><span class="p">(</span>
    <span class="p"><</span><span class="nt">input</span>
      <span class="na">className</span><span class="p">=</span><span class="s">"input"</span>
      <span class="na">value</span><span class="p">=</span><span class="si">{</span><span class="nx">value</span><span class="si">}</span>
      <span class="na">onChange</span><span class="p">=</span><span class="si">{</span><span class="nx">onChange</span><span class="si">}</span>
      <span class="na">placeholder</span><span class="p">=</span><span class="si">{</span><span class="nx">placeholder</span><span class="si">}</span>
      <span class="si">{</span><span class="p">...</span><span class="nx">props</span><span class="si">}</span>
    <span class="p">/></span>
  <span class="p">);</span>
<span class="p">}</span>

Key points:

  • ...props keeps the input flexible for future use cases.
  • value and onChange come 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

<span class="c1">// src/components/molecules/SearchBox.jsx</span>
<span class="k">import</span> <span class="nx">Button</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">../atoms/Button</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="nx">Input</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">../atoms/Input</span><span class="dl">"</span><span class="p">;</span>

<span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nf">SearchBox</span><span class="p">({</span> <span class="nx">query</span><span class="p">,</span> <span class="nx">onQueryChange</span><span class="p">,</span> <span class="nx">onSearch</span> <span class="p">})</span> <span class="p">{</span>
  <span class="k">return </span><span class="p">(</span>
    <span class="p"><</span><span class="nt">div</span> <span class="na">className</span><span class="p">=</span><span class="s">"search-box"</span><span class="p">></span>
      <span class="p"><</span><span class="nc">Input</span>
        <span class="na">value</span><span class="p">=</span><span class="si">{</span><span class="nx">query</span><span class="si">}</span>
        <span class="na">onChange</span><span class="p">=</span><span class="si">{</span><span class="p">(</span><span class="nx">event</span><span class="p">)</span> <span class="o">=></span> <span class="nf">onQueryChange</span><span class="p">(</span><span class="nx">event</span><span class="p">.</span><span class="nx">target</span><span class="p">.</span><span class="nx">value</span><span class="p">)</span><span class="si">}</span>
        <span class="na">placeholder</span><span class="p">=</span><span class="s">"Search products"</span>
      <span class="p">/></span>
      <span class="p"><</span><span class="nc">Button</span> <span class="na">onClick</span><span class="p">=</span><span class="si">{</span><span class="nx">onSearch</span><span class="si">}</span><span class="p">></span>Search<span class="p"></</span><span class="nc">Button</span><span class="p">></span>
    <span class="p"></</span><span class="nt">div</span><span class="p">></span>
  <span class="p">);</span>
<span class="p">}</span>

Design decisions worth calling out:

  • SearchBox holds no internal state. It delegates control to the page layer.
  • Input and Button remain 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

<span class="c1">// src/components/organisms/Header.jsx</span>
<span class="k">import</span> <span class="nx">Button</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">../atoms/Button</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="nx">SearchBox</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">../molecules/SearchBox</span><span class="dl">"</span><span class="p">;</span>

<span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nf">Header</span><span class="p">({</span> <span class="nx">query</span><span class="p">,</span> <span class="nx">onQueryChange</span><span class="p">,</span> <span class="nx">onSearch</span> <span class="p">})</span> <span class="p">{</span>
  <span class="k">return </span><span class="p">(</span>
    <span class="p"><</span><span class="nt">header</span> <span class="na">className</span><span class="p">=</span><span class="s">"header"</span><span class="p">></span>
      <span class="p"><</span><span class="nt">div</span> <span class="na">className</span><span class="p">=</span><span class="s">"header__brand"</span><span class="p">></span>ShopSphere<span class="p"></</span><span class="nt">div</span><span class="p">></span>
      <span class="p"><</span><span class="nc">SearchBox</span>
        <span class="na">query</span><span class="p">=</span><span class="si">{</span><span class="nx">query</span><span class="si">}</span>
        <span class="na">onQueryChange</span><span class="p">=</span><span class="si">{</span><span class="nx">onQueryChange</span><span class="si">}</span>
        <span class="na">onSearch</span><span class="p">=</span><span class="si">{</span><span class="nx">onSearch</span><span class="si">}</span>
      <span class="p">/></span>
      <span class="p"><</span><span class="nc">Button</span> <span class="na">variant</span><span class="p">=</span><span class="s">"secondary"</span><span class="p">></span>My Cart<span class="p"></</span><span class="nc">Button</span><span class="p">></span>
    <span class="p"></</span><span class="nt">header</span><span class="p">></span>
  <span class="p">);</span>
<span class="p">}</span>

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

<span class="c1">// src/components/organisms/ProductCard.jsx</span>
<span class="k">import</span> <span class="nx">Button</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">../atoms/Button</span><span class="dl">"</span><span class="p">;</span>

<span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nf">ProductCard</span><span class="p">({</span> <span class="nx">product</span> <span class="p">})</span> <span class="p">{</span>
  <span class="k">return </span><span class="p">(</span>
    <span class="p"><</span><span class="nt">article</span> <span class="na">className</span><span class="p">=</span><span class="s">"product-card"</span><span class="p">></span>
      <span class="p"><</span><span class="nt">div</span> <span class="na">className</span><span class="p">=</span><span class="s">"product-card__emoji"</span> <span class="na">aria-hidden</span><span class="p">=</span><span class="s">"true"</span><span class="p">></span>
        <span class="si">{</span><span class="nx">product</span><span class="p">.</span><span class="nx">emoji</span><span class="si">}</span>
      <span class="p"></</span><span class="nt">div</span><span class="p">></span>
      <span class="p"><</span><span class="nt">h3</span> <span class="na">className</span><span class="p">=</span><span class="s">"product-card__title"</span><span class="p">></span><span class="si">{</span><span class="nx">product</span><span class="p">.</span><span class="nx">title</span><span class="si">}</span><span class="p"></</span><span class="nt">h3</span><span class="p">></span>
      <span class="p"><</span><span class="nt">p</span> <span class="na">className</span><span class="p">=</span><span class="s">"product-card__description"</span><span class="p">></span><span class="si">{</span><span class="nx">product</span><span class="p">.</span><span class="nx">description</span><span class="si">}</span><span class="p"></</span><span class="nt">p</span><span class="p">></span>
      <span class="p"><</span><span class="nt">div</span> <span class="na">className</span><span class="p">=</span><span class="s">"product-card__footer"</span><span class="p">></span>
        <span class="p"><</span><span class="nt">strong</span><span class="p">></span><span class="si">{</span><span class="nx">product</span><span class="p">.</span><span class="nx">price</span><span class="si">}</span> TL<span class="p"></</span><span class="nt">strong</span><span class="p">></span>
        <span class="p"><</span><span class="nc">Button</span><span class="p">></span>Add to Cart<span class="p"></</span><span class="nc">Button</span><span class="p">></span>
      <span class="p"></</span><span class="nt">div</span><span class="p">></span>
    <span class="p"></</span><span class="nt">article</span><span class="p">></span>
  <span class="p">);</span>
<span class="p">}</span>

Key points:

  • The product object gains real meaning at the organism level.
  • Button is 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

<span class="c1">// src/components/templates/MainLayout.jsx</span>
<span class="k">import</span> <span class="nx">Header</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">../organisms/Header</span><span class="dl">"</span><span class="p">;</span>

<span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nf">MainLayout</span><span class="p">({</span>
  <span class="nx">query</span><span class="p">,</span>
  <span class="nx">onQueryChange</span><span class="p">,</span>
  <span class="nx">onSearch</span><span class="p">,</span>
  <span class="nx">children</span><span class="p">,</span>
<span class="p">})</span> <span class="p">{</span>
  <span class="k">return </span><span class="p">(</span>
    <span class="p"><</span><span class="nt">div</span> <span class="na">className</span><span class="p">=</span><span class="s">"layout"</span><span class="p">></span>
      <span class="p"><</span><span class="nc">Header</span>
        <span class="na">query</span><span class="p">=</span><span class="si">{</span><span class="nx">query</span><span class="si">}</span>
        <span class="na">onQueryChange</span><span class="p">=</span><span class="si">{</span><span class="nx">onQueryChange</span><span class="si">}</span>
        <span class="na">onSearch</span><span class="p">=</span><span class="si">{</span><span class="nx">onSearch</span><span class="si">}</span>
      <span class="p">/></span>
      <span class="p"><</span><span class="nt">main</span> <span class="na">className</span><span class="p">=</span><span class="s">"layout__content"</span><span class="p">></span><span class="si">{</span><span class="nx">children</span><span class="si">}</span><span class="p"></</span><span class="nt">main</span><span class="p">></span>
    <span class="p"></</span><span class="nt">div</span><span class="p">></span>
  <span class="p">);</span>
<span class="p">}</span>

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

<span class="c1">// src/data/products.js</span>
<span class="k">export</span> <span class="kd">const</span> <span class="nx">products</span> <span class="o">=</span> <span class="p">[</span>
  <span class="p">{</span>
    <span class="na">id</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
    <span class="na">emoji</span><span class="p">:</span> <span class="dl">"</span><span class="s2">⌨️</span><span class="dl">"</span><span class="p">,</span>
    <span class="na">title</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Mechanical Keyboard</span><span class="dl">"</span><span class="p">,</span>
    <span class="na">description</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Hot-swappable, compact keyboard with RGB support.</span><span class="dl">"</span><span class="p">,</span>
    <span class="na">price</span><span class="p">:</span> <span class="mi">2499</span><span class="p">,</span>
  <span class="p">},</span>
  <span class="p">{</span>
    <span class="na">id</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span>
    <span class="na">emoji</span><span class="p">:</span> <span class="dl">"</span><span class="s2">🖱️</span><span class="dl">"</span><span class="p">,</span>
    <span class="na">title</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Gaming Mouse</span><span class="dl">"</span><span class="p">,</span>
    <span class="na">description</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Lightweight body, high DPI, and low latency.</span><span class="dl">"</span><span class="p">,</span>
    <span class="na">price</span><span class="p">:</span> <span class="mi">1499</span><span class="p">,</span>
  <span class="p">},</span>
  <span class="p">{</span>
    <span class="na">id</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span>
    <span class="na">emoji</span><span class="p">:</span> <span class="dl">"</span><span class="s2">🎧</span><span class="dl">"</span><span class="p">,</span>
    <span class="na">title</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Wireless Headset</span><span class="dl">"</span><span class="p">,</span>
    <span class="na">description</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Long battery life with low-latency connectivity.</span><span class="dl">"</span><span class="p">,</span>
    <span class="na">price</span><span class="p">:</span> <span class="mi">3299</span><span class="p">,</span>
  <span class="p">},</span>
<span class="p">];</span>

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

<span class="c1">// src/pages/HomePage.jsx</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">useMemo</span><span class="p">,</span> <span class="nx">useState</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">react</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="nx">ProductCard</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">../components/organisms/ProductCard</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="nx">MainLayout</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">../components/templates/MainLayout</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">products</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">../data/products</span><span class="dl">"</span><span class="p">;</span>

<span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nf">HomePage</span><span class="p">()</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="p">[</span><span class="nx">query</span><span class="p">,</span> <span class="nx">setQuery</span><span class="p">]</span> <span class="o">=</span> <span class="nf">useState</span><span class="p">(</span><span class="dl">""</span><span class="p">);</span>

  <span class="kd">const</span> <span class="nx">filteredProducts</span> <span class="o">=</span> <span class="nf">useMemo</span><span class="p">(()</span> <span class="o">=></span> <span class="p">{</span>
    <span class="k">return</span> <span class="nx">products</span><span class="p">.</span><span class="nf">filter</span><span class="p">((</span><span class="nx">product</span><span class="p">)</span> <span class="o">=></span>
      <span class="nx">product</span><span class="p">.</span><span class="nx">title</span><span class="p">.</span><span class="nf">toLowerCase</span><span class="p">().</span><span class="nf">includes</span><span class="p">(</span><span class="nx">query</span><span class="p">.</span><span class="nf">toLowerCase</span><span class="p">())</span>
    <span class="p">);</span>
  <span class="p">},</span> <span class="p">[</span><span class="nx">query</span><span class="p">]);</span>

  <span class="kd">const</span> <span class="nx">handleSearch</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
    <span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="s2">`Searching for: </span><span class="p">${</span><span class="nx">query</span><span class="p">}</span><span class="s2">`</span><span class="p">);</span>
  <span class="p">};</span>

  <span class="k">return </span><span class="p">(</span>
    <span class="p"><</span><span class="nc">MainLayout</span>
      <span class="na">query</span><span class="p">=</span><span class="si">{</span><span class="nx">query</span><span class="si">}</span>
      <span class="na">onQueryChange</span><span class="p">=</span><span class="si">{</span><span class="nx">setQuery</span><span class="si">}</span>
      <span class="na">onSearch</span><span class="p">=</span><span class="si">{</span><span class="nx">handleSearch</span><span class="si">}</span>
    <span class="p">></span>
      <span class="p"><</span><span class="nt">section</span> <span class="na">className</span><span class="p">=</span><span class="s">"page-intro"</span><span class="p">></span>
        <span class="p"><</span><span class="nt">h1</span><span class="p">></span>Featured Products<span class="p"></</span><span class="nt">h1</span><span class="p">></span>
        <span class="p"><</span><span class="nt">p</span><span class="p">></span>A sample catalog screen built with the Atomic Design approach.<span class="p"></</span><span class="nt">p</span><span class="p">></span>
      <span class="p"></</span><span class="nt">section</span><span class="p">></span>
      <span class="p"><</span><span class="nt">section</span> <span class="na">className</span><span class="p">=</span><span class="s">"product-grid"</span><span class="p">></span>
        <span class="si">{</span><span class="nx">filteredProducts</span><span class="p">.</span><span class="nf">map</span><span class="p">((</span><span class="nx">product</span><span class="p">)</span> <span class="o">=></span> <span class="p">(</span>
          <span class="p"><</span><span class="nc">ProductCard</span> <span class="na">key</span><span class="p">=</span><span class="si">{</span><span class="nx">product</span><span class="p">.</span><span class="nx">id</span><span class="si">}</span> <span class="na">product</span><span class="p">=</span><span class="si">{</span><span class="nx">product</span><span class="si">}</span> <span class="p">/></span>
        <span class="p">))</span><span class="si">}</span>
      <span class="p"></</span><span class="nt">section</span><span class="p">></span>
    <span class="p"></</span><span class="nc">MainLayout</span><span class="p">></span>
  <span class="p">);</span>
<span class="p">}</span>

Most critical points:

  • Real state lives here. We're at the right layer.
  • Filtering logic is resolved at the page level.
  • MainLayout handles layout only; it doesn't manage data.

Step 3: Wire up the application entry point

<span class="c1">// src/App.jsx</span>
<span class="k">import</span> <span class="nx">HomePage</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./pages/HomePage</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="dl">"</span><span class="s2">./styles.css</span><span class="dl">"</span><span class="p">;</span>

<span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nf">App</span><span class="p">()</span> <span class="p">{</span>
  <span class="k">return</span> <span class="p"><</span><span class="nc">HomePage</span> <span class="p">/>;</span>
<span class="p">}</span>

Small file, but it matters:

  • App now 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

<span class="c">/* src/styles.css */</span>
<span class="nd">:root</span> <span class="p">{</span>
  <span class="nl">font-family</span><span class="p">:</span> <span class="n">Inter</span><span class="p">,</span> <span class="n">system-ui</span><span class="p">,</span> <span class="nb">sans-serif</span><span class="p">;</span>
  <span class="nl">color</span><span class="p">:</span> <span class="m">#e2e8f0</span><span class="p">;</span>
  <span class="nl">background</span><span class="p">:</span> <span class="m">#0f172a</span><span class="p">;</span>
<span class="p">}</span>

<span class="o">*</span> <span class="p">{</span>
  <span class="nl">box-sizing</span><span class="p">:</span> <span class="n">border-box</span><span class="p">;</span>
<span class="p">}</span>

<span class="nt">body</span> <span class="p">{</span>
  <span class="nl">margin</span><span class="p">:</span> <span class="m">0</span><span class="p">;</span>
<span class="p">}</span>

<span class="nc">.layout__content</span> <span class="p">{</span>
  <span class="nl">max-width</span><span class="p">:</span> <span class="m">1100px</span><span class="p">;</span>
  <span class="nl">margin</span><span class="p">:</span> <span class="m">0</span> <span class="nb">auto</span><span class="p">;</span>
  <span class="nl">padding</span><span class="p">:</span> <span class="m">32px</span> <span class="m">20px</span><span class="p">;</span>
<span class="p">}</span>

<span class="nc">.header</span> <span class="p">{</span>
  <span class="nl">display</span><span class="p">:</span> <span class="n">flex</span><span class="p">;</span>
  <span class="nl">align-items</span><span class="p">:</span> <span class="nb">center</span><span class="p">;</span>
  <span class="nl">justify-content</span><span class="p">:</span> <span class="n">space-between</span><span class="p">;</span>
  <span class="py">gap</span><span class="p">:</span> <span class="m">16px</span><span class="p">;</span>
  <span class="nl">padding</span><span class="p">:</span> <span class="m">20px</span><span class="p">;</span>
  <span class="nl">border-bottom</span><span class="p">:</span> <span class="m">1px</span> <span class="nb">solid</span> <span class="m">#1e293b</span><span class="p">;</span>
  <span class="nl">background</span><span class="p">:</span> <span class="m">#111827</span><span class="p">;</span>
<span class="p">}</span>

<span class="nc">.header__brand</span> <span class="p">{</span>
  <span class="nl">font-size</span><span class="p">:</span> <span class="m">20px</span><span class="p">;</span>
  <span class="nl">font-weight</span><span class="p">:</span> <span class="m">700</span><span class="p">;</span>
<span class="p">}</span>

<span class="nc">.search-box</span> <span class="p">{</span>
  <span class="nl">display</span><span class="p">:</span> <span class="n">flex</span><span class="p">;</span>
  <span class="py">gap</span><span class="p">:</span> <span class="m">12px</span><span class="p">;</span>
  <span class="nl">flex</span><span class="p">:</span> <span class="m">1</span><span class="p">;</span>
  <span class="nl">max-width</span><span class="p">:</span> <span class="m">520px</span><span class="p">;</span>
<span class="p">}</span>

<span class="nc">.input</span><span class="o">,</span>
<span class="nc">.button</span> <span class="p">{</span>
  <span class="nl">border-radius</span><span class="p">:</span> <span class="m">12px</span><span class="p">;</span>
  <span class="nl">border</span><span class="p">:</span> <span class="m">1px</span> <span class="nb">solid</span> <span class="m">#334155</span><span class="p">;</span>
  <span class="nl">padding</span><span class="p">:</span> <span class="m">12px</span> <span class="m">16px</span><span class="p">;</span>
<span class="p">}</span>

<span class="nc">.input</span> <span class="p">{</span>
  <span class="nl">flex</span><span class="p">:</span> <span class="m">1</span><span class="p">;</span>
  <span class="nl">background</span><span class="p">:</span> <span class="m">#0f172a</span><span class="p">;</span>
  <span class="nl">color</span><span class="p">:</span> <span class="m">#e2e8f0</span><span class="p">;</span>
<span class="p">}</span>

<span class="nc">.button</span> <span class="p">{</span>
  <span class="nl">cursor</span><span class="p">:</span> <span class="nb">pointer</span><span class="p">;</span>
  <span class="nl">color</span><span class="p">:</span> <span class="no">white</span><span class="p">;</span>
  <span class="nl">background</span><span class="p">:</span> <span class="m">#2563eb</span><span class="p">;</span>
<span class="p">}</span>

<span class="nc">.button--secondary</span> <span class="p">{</span>
  <span class="nl">background</span><span class="p">:</span> <span class="m">#334155</span><span class="p">;</span>
<span class="p">}</span>

<span class="nc">.page-intro</span> <span class="p">{</span>
  <span class="nl">margin-bottom</span><span class="p">:</span> <span class="m">24px</span><span class="p">;</span>
<span class="p">}</span>

<span class="nc">.product-grid</span> <span class="p">{</span>
  <span class="nl">display</span><span class="p">:</span> <span class="n">grid</span><span class="p">;</span>
  <span class="py">grid-template-columns</span><span class="p">:</span> <span class="nb">repeat</span><span class="p">(</span><span class="n">auto-fit</span><span class="p">,</span> <span class="n">minmax</span><span class="p">(</span><span class="m">240px</span><span class="p">,</span> <span class="m">1</span><span class="n">fr</span><span class="p">));</span>
  <span class="py">gap</span><span class="p">:</span> <span class="m">16px</span><span class="p">;</span>
<span class="p">}</span>

<span class="nc">.product-card</span> <span class="p">{</span>
  <span class="nl">padding</span><span class="p">:</span> <span class="m">20px</span><span class="p">;</span>
  <span class="nl">border</span><span class="p">:</span> <span class="m">1px</span> <span class="nb">solid</span> <span class="m">#1e293b</span><span class="p">;</span>
  <span class="nl">border-radius</span><span class="p">:</span> <span class="m">18px</span><span class="p">;</span>
  <span class="nl">background</span><span class="p">:</span> <span class="m">#111827</span><span class="p">;</span>
<span class="p">}</span>

<span class="nc">.product-card__emoji</span> <span class="p">{</span>
  <span class="nl">font-size</span><span class="p">:</span> <span class="m">40px</span><span class="p">;</span>
  <span class="nl">margin-bottom</span><span class="p">:</span> <span class="m">12px</span><span class="p">;</span>
<span class="p">}</span>

<span class="nc">.product-card__footer</span> <span class="p">{</span>
  <span class="nl">display</span><span class="p">:</span> <span class="n">flex</span><span class="p">;</span>
  <span class="nl">align-items</span><span class="p">:</span> <span class="nb">center</span><span class="p">;</span>
  <span class="nl">justify-content</span><span class="p">:</span> <span class="n">space-between</span><span class="p">;</span>
  <span class="nl">margin-top</span><span class="p">:</span> <span class="m">16px</span><span class="p">;</span>
<span class="p">}</span>

Worth noting:

  • The dark theme makes component boundaries more visible.
  • The product-grid layout lets you inspect card organisms comfortably.
  • Variant classes like button--secondary demonstrate 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.

Isolated components with storybook

Even before the app is up, you can validate Button, SearchBox, or ProductCard in complete isolation.

Step 1: Write a story for Button

<span class="c1">// src/components/atoms/Button.stories.js</span>
<span class="k">import</span> <span class="nx">Button</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./Button</span><span class="dl">"</span><span class="p">;</span>

<span class="k">export</span> <span class="k">default</span> <span class="p">{</span>
  <span class="na">title</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Atoms/Button</span><span class="dl">"</span><span class="p">,</span>
  <span class="na">component</span><span class="p">:</span> <span class="nx">Button</span><span class="p">,</span>
  <span class="na">args</span><span class="p">:</span> <span class="p">{</span>
    <span class="na">children</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Add to Cart</span><span class="dl">"</span><span class="p">,</span>
    <span class="na">variant</span><span class="p">:</span> <span class="dl">"</span><span class="s2">primary</span><span class="dl">"</span><span class="p">,</span>
  <span class="p">},</span>
<span class="p">};</span>

<span class="k">export</span> <span class="kd">const</span> <span class="nx">Primary</span> <span class="o">=</span> <span class="p">{};</span>

<span class="k">export</span> <span class="kd">const</span> <span class="nx">Secondary</span> <span class="o">=</span> <span class="p">{</span>
  <span class="na">args</span><span class="p">:</span> <span class="p">{</span>
    <span class="na">children</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Details</span><span class="dl">"</span><span class="p">,</span>
    <span class="na">variant</span><span class="p">:</span> <span class="dl">"</span><span class="s2">secondary</span><span class="dl">"</span><span class="p">,</span>
  <span class="p">},</span>
<span class="p">};</span>

Things to pay attention to:

  • title: "Atoms/Button" categorizes the component according to its atomic layer.
  • args makes it trivial to test different variations quickly.
  • Defining multiple visual states for the same atom becomes effortless.

Step 2: Write a story for ProductCard

<span class="c1">// src/components/organisms/ProductCard.stories.js</span>
<span class="k">import</span> <span class="nx">ProductCard</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./ProductCard</span><span class="dl">"</span><span class="p">;</span>

<span class="k">export</span> <span class="k">default</span> <span class="p">{</span>
  <span class="na">title</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Organisms/ProductCard</span><span class="dl">"</span><span class="p">,</span>
  <span class="na">component</span><span class="p">:</span> <span class="nx">ProductCard</span><span class="p">,</span>
  <span class="na">args</span><span class="p">:</span> <span class="p">{</span>
    <span class="na">product</span><span class="p">:</span> <span class="p">{</span>
      <span class="na">id</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
      <span class="na">emoji</span><span class="p">:</span> <span class="dl">"</span><span class="s2">⌨️</span><span class="dl">"</span><span class="p">,</span>
      <span class="na">title</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Mechanical Keyboard</span><span class="dl">"</span><span class="p">,</span>
      <span class="na">description</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Hot-swappable, compact keyboard with RGB support.</span><span class="dl">"</span><span class="p">,</span>
      <span class="na">price</span><span class="p">:</span> <span class="mi">2499</span><span class="p">,</span>
    <span class="p">},</span>
  <span class="p">},</span>
<span class="p">};</span>

<span class="k">export</span> <span class="kd">const</span> <span class="nx">Default</span> <span class="o">=</span> <span class="p">{};</span>

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

  1. Atomic Design by Brad Frost - Used to clarify the core concepts and layer definitions of the Atomic Design methodology.
  2. React Documentation - Used for React component architecture, state management, and foundational patterns.
  3. Storybook for React - Referenced for Storybook setup and React integration.
  4. Vite Guide - Used to verify the Vite setup flow for bootstrapping the React project quickly.
  5. Storybook Writing Stories - Used to clarify how story files should be structured and written.
  6. BEM CSS Convention - The CSS methodology used throughout the article.