Atomic Design Methodology
Why Component Organization Falls Apart at Scale
Every project starts clean. A components/ folder with 10 files. Then 30. Then 80. Then someone creates components/shared/. Then components/common/. Then components/ui/shared/common/. You now have three Button components and nobody knows which one to use.
Atomic design, created by Brad Frost, gives you a mental model for classifying components by their level of complexity. It is not a religion — it is a vocabulary that prevents the "shared/common/misc" chaos.
Think of components like chemistry. Atoms are elements on the periodic table — indivisible. Molecules are simple compounds — a few atoms bonded together. Organisms are complex structures — molecules working as a unit. Templates are the blueprint of a page. Pages are the final built thing with real data. You would never confuse an oxygen atom with a water molecule. Atomic design gives your components the same clarity.
The Five Levels
Atoms
The smallest, indivisible UI elements. They cannot be broken down further without ceasing to be useful.
// atoms/Button.tsx
interface ButtonProps {
children: ReactNode;
variant?: "primary" | "secondary" | "ghost";
size?: "sm" | "md" | "lg";
disabled?: boolean;
onClick?: () => void;
}
function Button({ children, variant = "primary", size = "md", ...props }: ButtonProps) {
return (
<button className={buttonVariants({ variant, size })} {...props}>
{children}
</button>
);
}
Other atoms: Input, Label, Badge, Avatar, Icon, Spinner, Separator.
Molecules
Small groups of atoms functioning as a unit. A molecule does one thing.
// molecules/SearchInput.tsx
function SearchInput({ value, onChange, placeholder }: SearchInputProps) {
return (
<div className="relative">
<Icon name="search" className="absolute left-3 top-1/2 -translate-y-1/2" />
<Input
value={value}
onChange={onChange}
placeholder={placeholder}
className="pl-10"
/>
</div>
);
}
A SearchInput is an Icon atom + an Input atom. Neither is useful alone in this context. Together, they form a recognizable, reusable unit.
Organisms
Complex, distinct sections of an interface. Organisms combine molecules and atoms into meaningful UI blocks.
// organisms/CourseCard.tsx
function CourseCard({ course }: { course: Course }) {
return (
<Card>
<CardImage src={course.thumbnail} alt={course.title} />
<CardContent>
<Badge variant={course.difficulty}>{course.difficulty}</Badge>
<Heading level={3}>{course.title}</Heading>
<Text muted>{course.description}</Text>
<ProgressBar value={course.progress} />
</CardContent>
<CardFooter>
<Button variant="primary">Continue</Button>
</CardFooter>
</Card>
);
}
Templates
Page-level layouts that define the structure without real data. Templates are the skeleton — they answer "where does everything go?"
// templates/CourseListTemplate.tsx
function CourseListTemplate({
header,
filters,
courseGrid,
pagination,
}: CourseListTemplateProps) {
return (
<div className="grid grid-cols-1 gap-8 lg:grid-cols-[280px_1fr]">
<aside>{filters}</aside>
<main>
{header}
{courseGrid}
{pagination}
</main>
</div>
);
}
Pages
Templates with real data. In Next.js App Router, these are your page.tsx files.
// app/courses/page.tsx
async function CoursesPage() {
const courses = await getCourses();
const categories = await getCategories();
return (
<CourseListTemplate
header={<PageHeader title="All Courses" count={courses.length} />}
filters={<CourseFilters categories={categories} />}
courseGrid={<CourseGrid courses={courses} />}
pagination={<Pagination total={courses.length} perPage={12} />}
/>
);
}
Mapping to File Structure
Here is a practical file structure that maps atomic design to a real Next.js project:
src/
components/
ui/ # Atoms + simple molecules (design system)
button.tsx
input.tsx
badge.tsx
search-input.tsx
dialog.tsx
blocks/ # Organisms (composed from ui/)
course-card.tsx
nav-header.tsx
sidebar-nav.tsx
quiz-block.tsx
layouts/ # Templates
course-list-layout.tsx
lesson-layout.tsx
dashboard-layout.tsx
app/ # Pages (Next.js routes)
courses/
page.tsx
dashboard/
page.tsx
Notice: I did not name the folders atoms/, molecules/, organisms/. The atomic model is a thinking tool, not a folder-naming convention. ui/, blocks/, and layouts/ communicate intent better to developers who have not read Brad Frost's book.
- 1Use atomic thinking for classification, not for folder names — ui/, blocks/, layouts/ are clearer
- 2Atoms and molecules belong in ui/ — they are your design system primitives
- 3Organisms go in blocks/ — they are feature-specific compositions
- 4Templates go in layouts/ — they define page structure without data
- 5Pages are Next.js route files — they wire data to templates
When to Promote a Component
A common question: when does a molecule become an organism? When does an atom graduate to a molecule?
The promotion signals:
- Atom to Molecule: When two atoms always appear together and have a combined purpose (Icon + Input = SearchInput)
- Molecule to Organism: When the component needs its own state management, data fetching, or has more than 3-4 sub-components
- Organism to Template: When the component defines spatial relationships between organisms without knowing what data fills them
The Problem with Shared and Common
Let us be direct: shared/, common/, utils/, and misc/ are where component architecture goes to die. They are non-categories. They tell you nothing about what is inside.
// THE GRAVEYARD
components/
shared/
Button.tsx # atom
DataTable.tsx # organism
useDebounce.ts # hook (not a component)
formatDate.ts # utility (not a component)
CourseCard.tsx # organism
Spinner.tsx # atom
Every file has a different abstraction level. Finding anything requires opening every file.
| What developers do | What they should do |
|---|---|
| Putting everything reusable in components/shared/ Shared is a non-category. It will grow unbounded and developers will stop trusting the folder structure. | Classify by complexity level: ui/ for primitives, blocks/ for compositions, hooks/ for logic |
| Strict 1-to-1 mapping of atomic levels to folders (atoms/ molecules/ organisms/) Most developers do not know atomic design terminology. Folder names should communicate intent without requiring prerequisite knowledge. | Use intuitive names (ui/ blocks/ layouts/) and use atomic thinking as a mental model |
| Making every small component an atom and every composition a molecule Premature abstraction creates atoms nobody uses and molecules that serve one page. YAGNI applies to design systems too. | Only extract when reuse is actual, not hypothetical. Three instances minimum before abstracting. |
Feature-Sliced Design: The Complement
Atomic design classifies by complexity. Feature-sliced design (FSD) classifies by domain. They complement each other beautifully.
src/
components/
ui/ # Atomic: shared primitives (atoms + molecules)
button.tsx
input.tsx
dialog.tsx
features/
course-progress/ # FSD: domain-specific organisms
course-card.tsx
progress-ring.tsx
hooks.ts
quiz/
quiz-block.tsx
quiz-timer.tsx
hooks.ts
layouts/ # Templates
dashboard-layout.tsx
lesson-layout.tsx
Shared primitives live in ui/. Domain-specific compositions live in their feature folder. This is how Vercel, Linear, and Stripe organize large codebases. The design system is atomic. The features are domain-sliced.
Real-World Design System Scale
Let us look at how this scales. A mature design system might have:
| Level | Count | Examples | Change Frequency |
|---|---|---|---|
| Atoms (ui/) | 15-25 | Button, Input, Badge, Avatar, Icon | Rarely — these are stable primitives |
| Molecules (ui/) | 20-40 | SearchInput, FormField, NavLink, Tooltip | Occasionally — when atoms evolve |
| Organisms (blocks/) | 30-60 | CourseCard, NavHeader, QuizBlock | Frequently — feature-driven |
| Templates (layouts/) | 5-10 | DashboardLayout, LessonLayout | Rarely — structural changes are expensive |
| Pages (app/) | 20-50 | CoursesPage, DashboardPage | Constantly — features ship here |
Notice the pattern: the lower levels are stable, the higher levels change frequently. This is by design. Atoms are your foundation — you do not rebuild the foundation every sprint.
You are building a design system for a team of 30 engineers working on an app with 200+ components. How would you organize the component hierarchy? What rules would you set for when to create a new shared component vs keeping it feature-local? How would you handle component promotion (from feature-local to shared)?