Atomic Design: Kỹ thuật chia nhỏ và quản trị design system ở quy mô doanh nghiệp
7/19/2026 · 15p đọc
title: "Atomic Design: Kỹ thuật chia nhỏ và quản trị design system ở quy mô doanh nghiệp"
series: "UX Architect: 40 bài Expert-Deep Dive"
part: "Phần 3 — UI Design System & Tính nhất quán"
order: 22
audience: "Product Designer & UX Architect"
reading_time: "13 phút"
tags: ["Atomic Design", "Design System", "Component Hierarchy", "Frontend Architecture", "Component Reuse", "Storybook", "Design Tokens"]
Atomic Design: Kỹ thuật chia nhỏ và quản trị design system ở quy mô doanh nghiệp
Một design system sau hai năm phát triển đã có 340 component trong file Figma và hơn 400 component React trong codebase. Một kỹ sư mới join team cần một ô tìm kiếm có icon kính lúp, nút xóa nhanh và gợi ý dropdown — đúng thứ đã tồn tại, tên là SearchBox, nằm trong components/shared/. Nhưng anh ta không tìm thấy nó, vì không biết tìm ở đâu trong hàng trăm file phẳng không phân cấp, và Figma cũng không giúp được gì hơn — search bằng từ khóa "search" trả về 12 kết quả trùng tên khác nhau do nhiều designer tự đặt tên theo ý riêng. Kết quả: anh ta code lại một CustomerSearchInput mới, gần như giống hệt SearchBox cũ, chỉ khác vài pixel padding.
Đây không phải là câu chuyện hiếm. Nó là số phận tất yếu của bất kỳ design system nào phát triển đến quy mô hàng trăm component mà không có một hệ thống phân cấp rõ ràng để tổ chức chúng. Vấn đề không nằm ở số lượng component — nó nằm ở việc không ai, kể cả người tạo ra chúng, có thể trả lời nhanh câu hỏi "cái tôi cần đã tồn tại chưa, và nếu có thì nó nằm ở đâu, thuộc cấp độ nào". Khi số lượng component tăng tuyến tính, chi phí tìm kiếm và khả năng trùng lặp tăng theo cấp số nhân nếu không có cấu trúc.
Atomic Design, mô hình do Brad Frost đề xuất, không phải là một trào lưu thẩm mỹ hay một cách đặt tên đẹp — nó là một hệ thống phân loại (taxonomy) giải quyết đúng bài toán "tìm và tái sử dụng đúng cái đã có" ở quy mô lớn. Và với một UX Architect, giá trị lớn nhất của nó không nằm ở tầng thiết kế mà ở chỗ nó ánh xạ gần như 1-1 vào cấu trúc thư mục component thật trong codebase — biến một hệ thống phân loại thiết kế thành một kiến trúc tổ chức code có thể thực thi được.
The UX Problem
Pain point cốt lõi: khi design system phát triển vượt qua ngưỡng vài chục component ban đầu để chạm tới hàng trăm component, không có một hệ thống phân cấp (hierarchy) rõ ràng nào để tổ chức chúng khiến việc tìm kiếm và tái sử dụng component đúng trở nên hỗn loạn — và hệ quả trực tiếp, đau nhất về mặt kỹ thuật, là kỹ sư tự tạo lại (re-implement) một component đã tồn tại chỉ vì không biết nó có sẵn.
Vấn đề này phổ biến vì ba lý do cụ thể, tất cả đều bắt nguồn từ việc thiếu một mô hình tư duy phân cấp:
Thứ nhất, một danh sách phẳng không chịu được quy mô. Ở giai đoạn 20-30 component đầu tiên, một thư mục components/ phẳng với tên file rõ ràng vẫn còn "quét mắt" được. Nhưng khi vượt quá 100, việc tìm một component cụ thể trong danh sách phẳng đòi hỏi hoặc nhớ chính xác tên, hoặc lướt qua hàng trăm mục — cả hai đều không mở rộng được (không scale). Không có phân cấp nghĩa là không có cách thu hẹp không gian tìm kiếm.
Thứ hai, không phân biệt được component nào là "khối xây dựng cơ bản" và component nào là "tổ hợp đã lắp ráp" dẫn tới nhầm lẫn về việc nên sửa ở đâu. Khi một nút bấm (button) cần đổi màu, kỹ sư cần biết sửa ở tầng nguyên tử gốc để lan tỏa xuống mọi nơi dùng nó, hay chỉ sửa một biến thể cụ thể. Không có phân cấp rõ ràng, nhiều team sửa trực tiếp vào từng nơi sử dụng, tạo ra hàng chục bản sao gần giống nhau của cùng một ý tưởng thiết kế.
Thứ ba, chi phí tra cứu tỷ lệ nghịch với động lực tái sử dụng. Về mặt tâm lý, nếu tìm một component có sẵn tốn nhiều thời gian hơn code lại từ đầu, kỹ sư sẽ luôn chọn phương án nhanh hơn — dù về lâu dài tạo ra nợ kỹ thuật (technical debt) lớn hơn nhiều lần chi phí tra cứu ban đầu. Đây là lý do một hệ thống phân cấp tốt không phải là "làm đẹp tài liệu" — nó trực tiếp quyết định tổng lượng trùng lặp code trong toàn bộ hệ thống.
Design Framework
Atomic Design chia nhỏ mọi giao diện thành 5 tầng phân cấp, mượn ẩn dụ từ hóa học — từ đơn vị nhỏ nhất không thể chia nhỏ hơn (nguyên tử), đến toàn bộ trang có dữ liệu thật:
1. Atoms (Nguyên tử) — các khối xây dựng nhỏ nhất, không thể chia nhỏ hơn mà vẫn giữ được chức năng độc lập: một nút bấm (Button), một ô nhập liệu (Input), một nhãn (Label), một icon, một đường viền (Divider). Atoms thường không có ý nghĩa nghiệp vụ riêng — một Input trống không "biết" nó dùng để nhập email hay số điện thoại. Đây là tầng chứa các design token cơ bản nhất: màu sắc, font, spacing, border-radius — mọi atom đều tiêu thụ trực tiếp từ token, không tự định nghĩa giá trị riêng.
2. Molecules (Phân tử) — nhóm nhỏ atoms kết hợp lại để tạo thành một đơn vị chức năng đơn giản, có mục đích cụ thể. Ví dụ kinh điển: một ô tìm kiếm (SearchBox) là tổ hợp của một Input + một Icon (kính lúp) + có thể một Button (xóa nhanh). Molecule là đơn vị nhỏ nhất bắt đầu có "ý nghĩa" — nó làm một việc cụ thể (tìm kiếm), trong khi từng atom riêng lẻ thì không.
3. Organisms (Cơ thể/Khối lớn) — các khối giao diện phức tạp hơn, tổ hợp nhiều molecules và/hoặc atoms để tạo thành một phần rõ rệt, tự đứng độc lập của giao diện: một header hoàn chỉnh (logo + navigation menu + SearchBox + user avatar dropdown), một card danh sách sản phẩm, một bảng dữ liệu có filter đi kèm. Organisms bắt đầu mang tính ngữ cảnh nghiệp vụ rõ rệt — một header luôn xuất hiện ở đầu trang, một filter sidebar luôn nằm ở một vị trí cụ thể trong luồng.
4. Templates (Bố cục trang) — cấp độ bố cục (layout), quy định các organisms và molecules được sắp xếp ở đâu trên trang, nhưng KHÔNG chứa dữ liệu thật — chỉ là khung xương với dữ liệu giữ chỗ (placeholder). Template trả lời câu hỏi "cấu trúc của một loại trang" (ví dụ: trang danh sách luôn có header trên cùng, sidebar filter bên trái, bảng dữ liệu ở giữa) mà không quan tâm nội dung cụ thể là gì.
5. Pages (Trang hoàn chỉnh) — chính là Template đó nhưng được đổ đầy dữ liệu thật (real content) từ hệ thống — trang "Danh sách khách hàng" thực tế với 500 bản ghi khách hàng thật, không phải dữ liệu mẫu. Đây là nơi các biến thể nội dung thực (dữ liệu rỗng, dữ liệu lỗi, dữ liệu quá dài) bộc lộ ra — những vấn đề mà Template với dữ liệu giữ chỗ không bao giờ thấy được.
Nguyên tắc vận hành cốt lõi: mỗi tầng chỉ được phép build từ tầng ngay dưới nó (hoặc thấp hơn) — Organism không tự định nghĩa lại một Button mới, nó luôn import Button đã có ở tầng Atom. Nguyên tắc "chỉ đi xuống, không đi ngang" này chính là cơ chế ngăn trùng lặp: mọi nhu cầu về nút bấm trong toàn hệ thống, dù ở organism nào, đều bắt buộc quay về cùng một Atom Button gốc.
The "Architect's" Insight
Đây là điểm khác biệt cốt lõi khiến Atomic Design không chỉ là một khung tư duy thiết kế mà là một quyết định kiến trúc phần mềm thật sự: 5 tầng phân cấp của Atomic Design ánh xạ gần như 1-1 vào cấu trúc thư mục component trong một codebase frontend hiện đại, và khi thiết kế được tổ chức đúng theo mô hình này, kỹ sư có một cấu trúc thư mục để tổ chức code component theo cùng logic phân cấp — không cần tự phát minh lại một hệ thống phân loại khác.
1. Cấu trúc thư mục khớp trực tiếp với tầng thiết kế.
src/components/
├── atoms/
│ ├── Button/
│ │ ├── Button.tsx
│ │ ├── Button.stories.tsx
│ │ └── Button.test.tsx
│ ├── Input/
│ ├── Icon/
│ └── Label/
├── molecules/
│ ├── SearchBox/ # dùng Input + Icon + Button (atoms)
│ ├── FormField/ # dùng Label + Input (atoms)
│ └── Avatar/
├── organisms/
│ ├── Header/ # dùng SearchBox + Avatar (molecules) + Icon (atom)
│ ├── FilterSidebar/ # dùng SearchBox + FormField (molecules)
│ └── DataTable/
├── templates/
│ ├── ListPageTemplate/ # bố cục Header + FilterSidebar + slot nội dung
│ └── DetailPageTemplate/
└── pages/ # thường không phải thư mục component riêng,
# mà là các route thật (app/customers/page.tsx)
# sử dụng Template + dữ liệu fetch thật
Khi một designer phân loại đúng "SearchBox là một Molecule", kỹ sư biết chính xác nó phải nằm ở components/molecules/SearchBox/, được build từ những gì trong atoms/, và immediate consumer của nó là các file trong organisms/. Không cần một cuộc họp riêng để quyết định "component này nên đặt ở đâu trong codebase" — quyết định phân loại thiết kế đã tự động trả lời câu hỏi tổ chức code.
2. Quy tắc phụ thuộc một chiều tạo ra một dependency graph có thể kiểm chứng bằng công cụ (lint rule). Nguyên tắc "Organism chỉ được import Molecule/Atom, không được import ngược từ Template" có thể được ép buộc bằng các quy tắc lint kiến trúc (ví dụ eslint-plugin-boundaries hoặc dependency-cruiser), chặn import trái phép ngay ở bước build/CI — biến một nguyên tắc thiết kế trừu tượng thành một constraint kỹ thuật có thể tự động kiểm tra, không phụ thuộc vào việc kỹ sư "nhớ quy tắc" hay không.
3. Bán kính ảnh hưởng của một thay đổi (blast radius) tỷ lệ thuận với tầng — điều này quyết định chiến lược test và review. Sửa một Atom (ví dụ đổi border-radius mặc định của Button) có blast radius lớn nhất — nó lan tỏa tới mọi Molecule, Organism, Template, Page dùng Button, tức gần như toàn bộ ứng dụng. Vì vậy, thay đổi ở tầng Atom cần review kỹ nhất, test coverage cao nhất (thường có snapshot test/visual regression test bắt buộc), và thường đi kèm một quy trình versioning riêng (semver: đổi API của Atom là breaking change, cần bump major version của package design system). Ngược lại, sửa một Organism cụ thể (ví dụ Header cho riêng module Kế toán) có blast radius hẹp, review nhẹ hơn. Atomic Design cho kỹ sư một tín hiệu rõ ràng: càng thấp tầng, càng cần cẩn trọng.
4. Tầng Atom là nơi tự nhiên để đặt design token — một nguồn sự thật duy nhất (single source of truth) cho database cấu hình giao diện. Vì mọi Atom đều tiêu thụ token (màu, spacing, typography scale) thay vì hardcode giá trị, một thay đổi ở tầng token (ví dụ đổi màu primary từ xanh sang tím theo rebrand) chỉ cần sửa ở một nơi duy nhất — file token — và lan tỏa tự động xuống toàn bộ Atom, rồi từ Atom lan lên Molecule, Organism mà không cần sửa từng component riêng lẻ. Về mặt kiến trúc, đây chính là nguyên tắc "đơn nguồn sự thật" quen thuộc trong thiết kế database (một bảng cấu hình trung tâm, không lặp lại giá trị ở nhiều nơi) áp dụng vào tầng giao diện.
5. Tầng Template/Page tách biệt rõ "cấu trúc bố cục" khỏi "nguồn dữ liệu" — ánh xạ trực tiếp vào phân tách Server Component/Client Component hoặc Presentational/Container component trong kiến trúc frontend hiện đại. Template không fetch dữ liệu, chỉ nhận props/children — nó tương ứng với các component thuần trình bày (presentational), có thể test độc lập không cần mock API. Page mới là nơi gọi data fetching thật (ví dụ Server Component gọi trực tiếp service layer, hoặc gọi API route). Phân tách này giúp Template được tái sử dụng cho nhiều nguồn dữ liệu khác nhau (ví dụ cùng một ListPageTemplate dùng cho cả trang khách hàng và trang đơn hàng, chỉ khác Page truyền vào dữ liệu khác nhau) mà không cần viết lại bố cục.
Nói ngắn gọn: một designer phân loại đúng tầng Atomic Design cho một component không chỉ giúp designer khác tìm lại nó dễ hơn — họ đang gián tiếp quyết định luôn thư mục nó sẽ nằm, phạm vi ảnh hưởng khi sửa, chiến lược test cần áp dụng, và cách nó tiêu thụ design token. Đây là lý do Atomic Design, dù ra đời như một framework thiết kế, lại được các kỹ sư frontend đón nhận gần như nguyên vẹn thành kiến trúc thư mục thật.
Practical Example
Tình huống: một CRM nội bộ cần chức năng tìm kiếm ở hai nơi khác nhau — thanh tìm kiếm toàn cục trên header (tìm kiếm khách hàng/deal/tài liệu xuyên suốt hệ thống) và ô tìm kiếm bên trong sidebar filter của trang danh sách khách hàng (lọc trong phạm vi danh sách hiện tại). Nếu không có tư duy Atomic Design, rất dễ xảy ra tình huống hai designer khác nhau thiết kế hai ô tìm kiếm gần giống nhau cho hai ngữ cảnh này, và hai kỹ sư khác nhau code hai component riêng biệt.
Với Atomic Design, cả hai chức năng này được nhận diện là cùng một Molecule — SearchBox — chỉ khác nhau ở cách nó được sử dụng trong hai Organism khác nhau:
| Thuộc tính | SearchBox trong Header | SearchBox trong FilterSidebar |
|---|---|---|
| Molecule gốc | SearchBox (giống nhau) |
SearchBox (giống nhau) |
| Organism chứa nó | Header |
FilterSidebar |
| Props khác nhau | placeholder="Tìm khách hàng, deal...", scope="global" |
placeholder="Lọc trong danh sách", scope="local" |
| Hành vi khi submit | Điều hướng sang trang kết quả tìm kiếm toàn cục | Lọc lại danh sách hiện tại (không điều hướng) |
// components/molecules/SearchBox/SearchBox.tsx
// Molecule dùng chung: 1 lần thiết kế, 1 lần code, nhiều nơi tái sử dụng
type SearchBoxProps = {
placeholder: string;
onSearch: (query: string) => void;
size?: "sm" | "md";
};
export function SearchBox({ placeholder, onSearch, size = "md" }: SearchBoxProps) {
return (
<div className={`search-box search-box--${size}`}>
<Icon name="search" /> {/* Atom */}
<Input placeholder={placeholder} onChange={...} /> {/* Atom */}
</div>
);
}
// components/organisms/Header/Header.tsx
import { SearchBox } from "@/components/molecules/SearchBox";
export function Header() {
const router = useRouter();
return (
<header>
<Logo />
<SearchBox
placeholder="Tìm khách hàng, deal..."
onSearch={(q) => router.push(`/search?q=${q}`)}
/>
<UserAvatarMenu />
</header>
);
}
// components/organisms/FilterSidebar/FilterSidebar.tsx
import { SearchBox } from "@/components/molecules/SearchBox";
export function FilterSidebar({ onFilterChange }) {
return (
<aside>
<SearchBox
placeholder="Lọc trong danh sách"
size="sm"
onSearch={onFilterChange}
/>
{/* các bộ lọc khác */}
</aside>
);
}
Kết quả: một Molecule được thiết kế và code đúng một lần, tái sử dụng ở hai Organism khác nhau với hành vi khác nhau thông qua props — thay vì mỗi nơi tự implement lại từ đầu một ô tìm kiếm gần giống hệt nhau, dẫn tới hai bug fix song song không đồng bộ khi phát hiện lỗi (ví dụ lỗi debounce khi gõ nhanh) chỉ được sửa ở một nơi mà quên nơi còn lại.
Common Pitfalls
1. Phân loại quá cứng nhắc, gây tranh cãi vô ích về việc một component là Molecule hay Organism. Team dành hàng giờ tranh luận "cái card sản phẩm này có 3 thành phần con, vậy nó là Molecule hay Organism?" thay vì tập trung vào giá trị thật của phân loại — giúp tìm và tái sử dụng dễ hơn. Ranh giới giữa các tầng, đặc biệt Molecule/Organism, vốn dĩ mờ và mang tính tương đối theo ngữ cảnh sản phẩm cụ thể — không có một quy tắc toán học tuyệt đối. Cách xử lý đúng: chốt một quy ước thực dụng cho riêng team (ví dụ "có từ 3 molecule trở lên hoặc mang ý nghĩa nghiệp vụ độc lập → Organism") và áp dụng nhất quán, thay vì tìm một định nghĩa hoàn hảo không tồn tại.
2. Không có tài liệu rõ ràng cho từng tầng khiến thành viên mới không biết nên tái sử dụng cái gì. Việc tạo ra cấu trúc thư mục atoms/molecules/organisms chỉ giải quyết một nửa vấn đề — nửa còn lại là khả năng khám phá (discoverability). Nếu không có một công cụ catalog trực quan (ví dụ Storybook, nơi mỗi component có story minh họa kèm mô tả props) đi kèm cấu trúc thư mục, một thành viên mới vẫn phải mở từng file để hiểu component đó dùng khi nào — quay lại đúng vấn đề ban đầu ở quy mô nhỏ hơn. Cấu trúc thư mục là điều kiện cần, không phải điều kiện đủ.
3. Tạo Organism/Molecule mới thay vì mở rộng props của cái đã có, vì "thêm props có vẻ phức tạp hơn tạo file mới". Đây là một cạm bẫy tinh vi: kỹ sư thấy SearchBox hiện tại thiếu tính năng mình cần (ví dụ hỗ trợ voice search), và thay vì thêm một prop variant hay enableVoice vào SearchBox gốc, họ tạo SearchBoxWithVoice mới bên cạnh — phá vỡ chính nguyên tắc "một nguồn sự thật" mà Atomic Design hướng tới, tạo ra hai Molecule gần giống hệt nhau cùng tồn tại song song.
Checklist
- Mọi component trong design system đã được gán đúng một tầng (Atom/Molecule/Organism/Template/Page), không có component "mồ côi" chưa phân loại
- Cấu trúc thư mục codebase phản ánh đúng 5 tầng, có quy tắc lint/CI chặn import sai chiều (tầng cao import ngược tầng thấp hơn cùng cấp)
- Mỗi Atom chỉ tiêu thụ giá trị từ design token, không hardcode màu/spacing/font riêng
- Có công cụ catalog trực quan (ví dụ Storybook) cho từng component, kèm mô tả khi nào nên dùng, khi nào không
- Có quy ước thực dụng, đã thống nhất trong team, cho các trường hợp phân loại mơ hồ giữa Molecule/Organism — không để mỗi người tự quyết
- Trước khi tạo component mới, có bước bắt buộc kiểm tra catalog xem đã tồn tại Molecule/Organism tương đương chưa
- Thay đổi ở tầng Atom có quy trình review/test/versioning riêng, nghiêm ngặt hơn thay đổi ở tầng Organism/Page do blast radius lớn hơn
🔗 Bài viết liên quan
- Design System: Xây dựng hệ sinh thái thiết kế nhất quán
- Documentation cho dev hiểu đúng ý đồ thiết kế
Bài trước: Design System · Bài tiếp theo: Typography & Hierarchy