Atomic Design을 알아보자 (2): Todo 앱으로 컴포넌트 나누기

Frontend

이 글은 Atomic Design 시리즈 2편입니다.

1편에서 Atomic Design의 개념을 정리했다면, 이번 글에서는 Todo 앱을 기준으로 Atoms, Molecules, Organisms, Templates, Pages를 어떻게 나눌 수 있는지 살펴보겠습니다.

Todo 앱에서 무엇을 만들고 싶은가?

예시를 단순하게 잡아보겠습니다.

  • Todo 입력
  • Todo 목록 표시
  • Todo 완료 체크
  • Todo 삭제
  • 필터 전환
  • 남은 Todo 개수 표시

화면으로 보면 아래 요소가 필요합니다.

  • 입력창
  • 추가 버튼
  • 체크박스
  • 삭제 버튼
  • 필터 탭
  • Todo 리스트 아이템
  • 요약 영역
  • 전체 페이지 레이아웃

이제 이 UI를 Atomic Design 기준으로 아래에서 위로 쌓아보겠습니다.

Atom부터 생각해보기

Atom은 더 이상 나누기 어려운 가장 작은 UI 단위입니다.

Todo 화면에서는 보통 아래 요소가 atom 후보가 됩니다.

  • Button
  • Input
  • Checkbox
  • Label
  • Badge
  • IconButton

예시:

interface CheckboxProps extends React.InputHTMLAttributes<HTMLInputElement> {}
 
export function Checkbox(props: CheckboxProps) {
  return <input type="checkbox" className="h-4 w-4 rounded border-gray-300" {...props} />;
}
interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {}
 
export function Input(props: InputProps) {
  return <input className="h-10 w-full rounded-md border px-3 text-sm" {...props} />;
}

이 단계에서는 Todo라는 도메인을 거의 몰라도 됩니다. 체크박스는 체크박스이고, 인풋은 인풋일 뿐입니다.

Molecule은 작은 의미를 가진 조합

Molecule은 atom 몇 개가 모여 하나의 작은 역할을 만드는 단계입니다.

Todo 앱에서는 아래 조합을 molecule로 볼 수 있습니다.

  • 인풋 + 버튼 = Todo 입력 폼
  • 체크박스 + 텍스트 = Todo 제목 표시
  • 탭 버튼들 = 필터 탭 그룹

예시:

import { Button } from './button';
import { Input } from './input';
 
interface TodoInputFormProps {
  value: string;
  onChange: (value: string) => void;
  onSubmit: () => void;
}
 
export function TodoInputForm({ value, onChange, onSubmit }: TodoInputFormProps) {
  return (
    <div className="flex gap-2">
      <Input value={value} onChange={(e) => onChange(e.target.value)} />
      <Button onClick={onSubmit}>추가</Button>
    </div>
  );
}

이 컴포넌트는 작은 단위이지만, 이미 "Todo 입력"이라는 분명한 의미를 가집니다. 그래서 atom보다는 molecule에 가깝습니다.

Organism은 화면 안의 큰 블록

Organism은 molecule과 atom이 합쳐져서 화면에서 독립적인 영역처럼 보이는 단위입니다.

Todo 앱에서는 아래와 같은 구성이 organism이 되기 쉽습니다.

  • Todo 입력 + 필터를 포함한 상단 툴바
  • Todo 목록 영역
  • 진행 상태 요약 영역

예시:

import { TodoInputForm } from './todo-input-form';
import { TodoFilterTabs } from './todo-filter-tabs';
 
export function TodoToolbar() {
  return (
    <section className="space-y-4">
      <TodoInputForm value="" onChange={() => {}} onSubmit={() => {}} />
      <TodoFilterTabs />
    </section>
  );
}

또는 아래처럼 Todo 리스트 전체를 organism으로 볼 수도 있습니다.

import { TodoListItem } from './todo-list-item';
 
interface Todo {
  id: string;
  title: string;
  completed: boolean;
}
 
export function TodoList({ todos }: { todos: Todo[] }) {
  return (
    <section className="space-y-3">
      {todos.map((todo) => (
        <TodoListItem key={todo.id} todo={todo} />
      ))}
    </section>
  );
}

Atomic Design에서 organism은 페이지 안에서 눈에 띄는 덩어리라고 생각하면 이해하기 쉽습니다.

Template은 페이지 구조를 정의한다

Template은 페이지의 뼈대를 만드는 단계입니다.

Todo 앱에서는 보통:

  • 제목 영역
  • 툴바 영역
  • 요약 영역
  • 리스트 영역

을 어떻게 배치할지 결정하는 레벨이 template에 가깝습니다.

예시:

interface TodoPageTemplateProps {
  header: React.ReactNode;
  toolbar: React.ReactNode;
  summary: React.ReactNode;
  list: React.ReactNode;
}
 
export function TodoPageTemplate({ header, toolbar, summary, list }: TodoPageTemplateProps) {
  return (
    <main className="mx-auto max-w-2xl space-y-6 py-10">
      {header}
      {toolbar}
      {summary}
      {list}
    </main>
  );
}

여기서는 어떤 데이터가 들어오는지보다, 페이지를 어떤 순서와 구조로 배치할지가 더 중요합니다.

Page는 실제 데이터가 들어간 결과물

Page는 template에 실제 데이터와 상태가 들어간 최종 결과물입니다.

예시:

import { TodoPageTemplate } from './todo-page-template';
import { TodoList } from './todo-list';
import { TodoToolbar } from './todo-toolbar';
import { TodoSummary } from './todo-summary';
 
export function TodoPage() {
  const todos = [
    { id: '1', title: 'Atomic Design 정리하기', completed: false },
    { id: '2', title: 'Todo 예제 작성하기', completed: true },
  ];
 
  return (
    <TodoPageTemplate
      header={<h1 className="text-3xl font-bold">Todo List</h1>}
      toolbar={<TodoToolbar />}
      summary={<TodoSummary todos={todos} />}
      list={<TodoList todos={todos} />}
    />
  );
}

template은 구조를 설명하고, page는 실제로 동작하는 화면을 만든다고 생각하면 됩니다.

Todo 앱 기준으로 한 번에 보면

정리하면 아래처럼 볼 수 있습니다.

flowchart TD
    atom1[Button Input Checkbox]
    molecule1[TodoInputForm TodoListItem]
    organism1[TodoToolbar TodoList TodoSummary]
    template1[TodoPageTemplate]
    page1[TodoPage]
 
    atom1 --> molecule1
    molecule1 --> organism1
    organism1 --> template1
    template1 --> page1

이렇게 계층을 쌓아두면 "이 컴포넌트는 어느 수준의 책임을 가지는가?"를 보기 쉬워집니다.

어디까지가 atom이고 어디부터 molecule일까?

실제로 가장 많이 헷갈리는 지점이 여기입니다.

TodoCheckbox는 atom일까?

체크박스 자체만 있다면 atom에 가깝습니다.

하지만:

  • 체크박스 + Todo 제목
  • 체크 상태에 따른 스타일 변경
  • Todo 항목 맥락 포함

까지 들어가면 molecule에 가까워질 수 있습니다.

즉, 도메인 맥락이 얼마나 붙었는지를 같이 봐야 합니다.

TodoListItem은 molecule일까 organism일까?

단일 Todo 항목 하나를 보여주는 정도라면 molecule로 볼 수 있습니다. 하지만:

  • 체크
  • 제목
  • 삭제 버튼
  • 수정 버튼
  • 상태 배지

까지 들어가서 하나의 작은 카드처럼 커지면 organism으로 보는 팀도 있습니다.

중요한 것은 정답보다 팀 안에서 같은 기준을 유지하는 것입니다.

Atomic Design을 Todo 예제에 적용할 때 장점

1. UI 단위가 더 명확해진다

작은 조각부터 큰 블록까지 계층이 생겨 구조를 보기 쉽습니다.

2. 재사용 범위를 판단하기 쉽다

이게 atom인지 molecule인지 생각하는 과정 자체가 재사용 범위를 정하는 데 도움이 됩니다.

3. 디자인 시스템과 연결하기 좋다

Button, Input, Checkbox 같은 atom은 그대로 디자인 시스템 자산이 될 수 있습니다.

한계도 있다

Todo 앱처럼 비교적 단순한 예제에서는 잘 맞아 보이지만, 실제 서비스에서는 아래 지점이 애매해질 수 있습니다.

  • atom과 molecule 경계가 흐려짐
  • organism과 feature 영역이 섞이기 쉬움
  • 상태와 비즈니스 로직은 별도로 생각해야 함

즉, Atomic Design은 UI 구조를 나누는 기준으로는 좋지만, 프로젝트 전체의 기능 구조까지 모두 설명해주지는 않습니다.

시리즈 이어서 보기

다음 글에서는 Atomic Design을 React와 Next.js에 적용할 때 어떤 식으로 폴더 구조를 잡고, 어떤 점을 주의하면 좋은지 정리해보겠습니다.

마무리

Atomic Design은 Todo 앱처럼 단순한 예제로 보면 감이 훨씬 빨리 옵니다.

핵심은 복잡한 이름을 외우는 것이 아니라, 작은 컴포넌트에서 큰 페이지까지 책임을 단계적으로 구분하는 연습을 해보는 것입니다. 그 기준이 잡히면 실제 프로젝트에서도 UI 구조를 훨씬 안정적으로 정리할 수 있습니다.