はじめに
この記事では以下のことがわかります。
- Atomic Designの5つの階層(Atom・Molecule・Organism・Template・Page)の意味
- 各階層にどんなReactコンポーネントが対応するか
- 実際のコード例と推奨ディレクトリ構成
- 導入時にはまりやすい注意点
想定読者: Reactでコンポーネントを書いたことがあるが、「どう分割すれば良いかわからない」と悩んでいる方
Atomic Designとは?
Atomic Designは、デザイナーのBrad Frostが提唱したUIコンポーネント設計手法です。
名前の通り、化学の概念(原子・分子・有機体)をUIの設計に応用しています。UIを小さな部品から積み上げていくことで、再利用性が高く、保守しやすいコンポーネント群を作ることができます。
5つの階層は次のとおりです。
| 階層 | 化学の例え | UIの例 |
|---|---|---|
| Atom | 水素 | ボタン、テキストフィールド |
| Molecule | 水(H₂O) | 検索バー |
| Organism | 川や水たまり、雲 | ヘッダー、記事リスト |
| Template | 景色の静止画写真 | ページのレイアウト骨格 |
| Page | 景色写真の現地 | 実際の運用ページ |
なぜReactとAtomic Designは相性が良いのか
Reactはそもそもコンポーネント指向のライブラリです。UIを小さな部品(コンポーネント)に分割し、組み合わせて画面を作るというReactの思想は、Atomic Designの「小さなものを積み上げる」考え方と自然に一致します。
Atomic Designを導入することで、以下のメリットが得られます。
- 再利用性の向上: Atomレベルのコンポーネントを使い回すことでコードの重複が減る
- 保守性の向上: 変更の影響範囲が明確になる
- チーム開発のしやすさ: コンポーネントの責務が明確になり、分担しやすくなる
5つの階層をReactコンポーネントに当てはめる
Atom(原子)
化学の例え: 水素のように、それ以上分解できない最小単位です。
ReactのAtomは、単独で意味をなす最小のUIパーツです。他のコンポーネントに依存せず、propsを受け取って表示するだけのシンプルな役割を持ちます。
具体例: ボタン、テキストフィールド、ラベル、アイコン
type ButtonProps = {
label: string;
onClick: () => void;
variant?: "primary" | "secondary";
};
export const Button = ({ label, onClick, variant = "primary" }: ButtonProps) => {
return (
<button className={`btn btn-${variant}`} onClick={onClick}>
{label}
</button>
);
};
type InputProps = {
value: string;
onChange: (value: string) => void;
placeholder?: string;
};
export const Input = ({ value, onChange, placeholder }: InputProps) => {
return (
<input
type="text"
value={value}
onChange={(e) => onChange(e.target.value)}
placeholder={placeholder}
/>
);
};
Atomのポイントはロジックを持たないことです。状態管理やAPIコールはここでは行いません。
Molecule(分子)
化学の例え: 水(H₂O)のように、複数の原子が結合して初めて意味を持つ単位です。
ReactのMoleculeは、複数のAtomを組み合わせた、ひとつの機能を持つUIパーツです。
具体例: テキストフィールドと検索ボタンを組み合わせた検索バー
import { Button } from "@/components/atoms/Button";
import { Input } from "@/components/atoms/Input";
import { useState } from "react";
type SearchBarProps = {
onSearch: (query: string) => void;
};
export const SearchBar = ({ onSearch }: SearchBarProps) => {
const [query, setQuery] = useState("");
return (
<div className="search-bar">
<Input
value={query}
onChange={setQuery}
placeholder="キーワードを入力"
/>
<Button label="検索" onClick={() => onSearch(query)} />
</div>
);
};
Moleculeは内部に最小限の状態(ローカルステート)を持つことがありますが、グローバルな状態管理には関与しません。
Organism(有機体)
化学の例え: 川や水たまり、雲のように、分子が集まって独自の形・役割を持った存在です。
ReactのOrganismは、複数のMoleculeやAtomを組み合わせた、ページの一区画を担うコンポーネントです。ヘッダーや記事一覧など、「コンテンツのまとまり」として認識できるレベルです。
具体例: サイトヘッダー(ロゴ + ナビゲーション + 検索バー)
import { SearchBar } from "@/components/molecules/SearchBar";
type HeaderProps = {
onSearch: (query: string) => void;
};
export const Header = ({ onSearch }: HeaderProps) => {
return (
<header className="header">
<div className="header__logo">MyApp</div>
<nav className="header__nav">
<a href="/">ホーム</a>
<a href="/about">About</a>
</nav>
<SearchBar onSearch={onSearch} />
</header>
);
};
Organism以上になると、ビジネスロジックに近い判断(どのイベントハンドラを渡すか、など)が入ってきます。
Template(テンプレート)
化学の例え: 川や水たまり、雲などを配置した景色の「静止画写真」です。実際の水は流れていない、あくまでも構図だけを示したものです。
ReactのTemplateは、Organismを配置してページのレイアウト骨格を定義するコンポーネントです。動的なデータはまだ含まれておらず、「どこに何を置くか」というフレームだけを担います。
import { Header } from "@/components/organisms/Header";
type SearchPageTemplateProps = {
onSearch: (query: string) => void;
children: React.ReactNode;
};
export const SearchPageTemplate = ({
onSearch,
children,
}: SearchPageTemplateProps) => {
return (
<div className="search-page">
<Header onSearch={onSearch} />
<main className="search-page__main">{children}</main>
<footer className="search-page__footer">© 2026 MyApp</footer>
</div>
);
};
Templateはデータを持たないのがポイントです。実際のコンテンツはPageから渡されます。
Page(ページ)
化学の例え: 景色写真の「現地」そのものです。実際に水が流れ、風が吹いている、ライブの状態です。
ReactのPageは、Templateに実際のデータを流し込んだ、実運用のページコンポーネントです。APIからのデータ取得やグローバル状態の管理はここで行います。
import { useState, useEffect } from "react";
import { SearchPageTemplate } from "@/components/templates/SearchPageTemplate";
import { ArticleList } from "@/components/organisms/ArticleList";
type Article = {
id: number;
title: string;
};
export const SearchPage = () => {
const [articles, setArticles] = useState<Article[]>([]);
const [query, setQuery] = useState("");
useEffect(() => {
const fetchArticles = async () => {
const response = await fetch(`/api/articles?q=${encodeURIComponent(query)}`);
if (!response.ok) return;
const data = await response.json();
setArticles(data);
};
fetchArticles();
}, [query]);
return (
<SearchPageTemplate onSearch={setQuery}>
<ArticleList articles={articles} />
</SearchPageTemplate>
);
};
Pageは唯一、外部とのやり取り(APIコール・ルーティング・グローバルステート)を担う層です。
ディレクトリ構成の例
以上の設計を反映したディレクトリ構成の例を示します。
src/
├── components/
│ ├── atoms/
│ │ ├── Button/
│ │ │ └── index.tsx
│ │ └── Input/
│ │ └── index.tsx
│ ├── molecules/
│ │ └── SearchBar/
│ │ └── index.tsx
│ ├── organisms/
│ │ ├── Header/
│ │ │ └── index.tsx
│ │ └── ArticleList/
│ │ └── index.tsx
│ └── templates/
│ └── SearchPageTemplate/
│ └── index.tsx
└── pages/
└── SearchPage/
└── index.tsx
Next.jsのPage Routerを使う場合、
pages/ディレクトリ名が衝突することがあります。その際はAtomic Designのページ層をviews/やscreens/に配置するチームもあります。チームの規約に合わせて調整してください。
まとめ
この記事で学んだことをまとめます。
- Atom: それ以上分解できない最小のUIパーツ(ボタン、テキストフィールドなど)
- Molecule: 複数のAtomを組み合わせた、ひとつの機能を持つパーツ(検索バーなど)
- Organism: 複数のMolecule/Atomが集まったコンテンツブロック(ヘッダーなど)
- Template: データを持たないページのレイアウト骨格
- Page: Templateに実データを流し込んだ実運用ページ
Atomic Designは「コンポーネントをどう分割すれば良いかわからない」という悩みへの一つの答えです。ただし、設計方針を採用するだけでなく、チームで運用ルールを整備することが長期的な一貫性につながります。まずは小さなプロジェクトや新機能の一部から試してみることをおすすめします。