Phần đầu của sách làm hai việc. Nó lập luận rằng frontend cũng xứng đáng có một architect giống như backend, và nó đặt nền cho pillar (trụ cột) đầu tiên trong bốn pillar: bản thân code. Godbolt dành ít thời gian cho cú pháp. Điều ông quan tâm là mối quan hệ giữa HTML và CSS, vì chính mối quan hệ đó quyết định một component có thể được di chuyển, tái sử dụng, test và xoá đi mà không gây side effect hay không.

Với mình, đây là phần bền nhất của sách. Những tranh luận về specificity, style phụ thuộc vào vị trí và ai sở hữu một variation vẫn là những tranh luận mình còn gặp trong code review React. Tool đã đổi; các kiểu lỗi thì không.

Ứng với chương 1 đến 7 trong sách.

Ý chính

Người dùng của architect là các developer khác

Sách định nghĩa frontend architecture là một tập hợp tool và process nhằm tạo ra frontend code tốt hơn và một workflow trơn tru, bền vững hơn. Architect thiết kế hệ thống mà mọi người viết code bên trong, lên kế hoạch cho con đường từ một dòng code tới production, và liên tục điều chỉnh con đường đó khi dự án thay đổi. Godbolt nói rõ điều này không có nghĩa là viết ít code hơn. Nó có nghĩa là người đọc code của bạn thay đổi: bạn viết linter, build script, template và quy tắc cho các developer xung quanh mình thay vì chỉ viết feature cho người dùng cuối.

Có hai hệ quả. Architect cần có mặt trong dự án từ sớm, vì một số quyết định (ai sở hữu markup, quy ước CSS, cách chia sẻ template với CMS) rất tốn kém để đảo ngược. Và bốn pillar là một checklist các cuộc trao đổi, không phải một giáo trình: chương 3 chấp nhận rằng một dự án nhỏ có thể làm rất ít ở một số pillar.

Pillar Câu hỏi mà nó buộc phải trả lời
Code HTML, CSS và JS của chúng ta trông thế nào, và làm sao ta biết?
Process Một ý tưởng trở thành code đã deploy bằng cách nào, và phần nào được tự động hoá?
Testing Làm sao ta biết thay đổi hôm nay không làm hỏng công việc hôm qua?
Documentation Kiến thức nằm ở đâu, và được viết cho ai?

Chương 2, "Alpha Project", dựng nền cho case study: Red Hat muốn chia sẻ các band (dải nội dung) của website sang những site khác trong công ty, và wish list của team (modular content, comprehensive testing, streamlined processes, exhaustive documentation) đã trở thành bốn pillar. Những tháng đầu chậm hơn bình thường, nhưng mỗi yêu cầu về sau lại cần ít việc mới hơn.

Không có trang, chỉ có hệ thống

Chương 4 đi qua ba cách markup được tạo ra. Trong thế giới do CMS điều khiển, backend sinh ra các lớp wrapper lồng nhau rất sâu và frontend chỉ có thể style những gì được đưa cho. Trong thế giới viết tay, frontend kiểm soát mọi thứ nhưng phải cập nhật từng trang bằng tay, điều này đẩy mọi người tới các descendant selector dài. Mục tiêu là lựa chọn thứ ba: markup được sinh từ template do team frontend sở hữu, nên mọi instance của một navigation hay card đều dùng cùng cấu trúc và cùng class.

Điều đó dẫn tới cách nhìn lại cốt lõi của sách. Một URL không còn ứng với một trang cố định, vì nội dung thay đổi theo người dùng, thời điểm và thiết bị. Thứ bạn thực sự xây dựng là một design system: phiên bản bằng code của visual language mà designer đã tạo ra, được chia thành các phần nhỏ kèm quy tắc để kết hợp lại chúng. Khi đã nhìn như vậy, một ticket ghi "cập nhật trang About" không còn hợp lý nữa, và đơn vị công việc trở thành một component hoặc một variation.

Chọn phương pháp CSS vì lý do của nó

Sách so sánh OOCSS, SMACSS và BEM trên cùng một toggle widget, rồi từ chối chọn ra người thắng. Điều quan trọng là hiểu mỗi phương pháp giải quyết vấn đề gì:

Phương pháp Ý chính đáng giữ lại
OOCSS Tách structure khỏi skin, và thôi dùng vị trí làm điều kiện cho style
SMACSS Chia style thành base, layout, module, state và theme, đặt layout và module ở những chỗ riêng
BEM Mỗi class ghi rõ block, element và modifier của nó, nên nó mang cùng một nghĩa ở mọi nơi

Chương 5 cho thấy vì sao điều này quan trọng bằng ví dụ một heading trong sidebar. Style #sidebar h2 tạo ra năm vấn đề cùng lúc: một cuộc đua specificity, màu sắc phải reset, một component thay đổi khi bị di chuyển, một element kế thừa từ nhiều nguồn, và selector ngày càng sâu cho mọi thứ lồng bên trong. Selector phẳng, chỉ một class, giải quyết cả năm. Red Hat kết hợp SMACSS và BEM, và Godbolt thấy việc kết hợp ý tưởng là ổn miễn là team cam kết theo một cách tiếp cận được viết ra.

A single selector for everything

Chương 5 giới thiệu các nguyên tắc và chương 7 cho thấy Red Hat áp dụng chúng dưới tiêu đề "a single selector for everything" (một selector cho mọi thứ). Godbolt đã mất nhiều năm viết các class chung chung có thể gắn lên bất kỳ element nào, và nhận ra không ai dám sửa chúng, nên mọi người cứ thêm class mới. Cách sửa là làm ngược lại: mỗi element có một class phẳng, duy nhất, thuộc về component của nó.

Single responsibility (trách nhiệm duy nhất) ở đây nghĩa là một class được tạo cho một mục đích ở một chỗ, không phải cho một nhóm thuộc tính. Nếu tiêu đề blog và tiêu đề calendar cùng dùng .primary-header, sửa cái này sẽ âm thầm sửa cái kia, và xoá một component có nguy cơ làm hỏng bất cứ thứ gì đã mượn style của nó. Single source of truth (nguồn sự thật duy nhất) đi xa hơn: mọi rule có thể ảnh hưởng tới một element, kể cả cách nó trông thế nào bên trong một context khác, đều nằm trong file của component đó.

Variation cũng theo cùng logic. Red Hat đặt modifier và context vào data attribute thay vì thêm class, nên class nói thứ đó là gì còn attribute nói nó thay đổi ra sao. Một class chỉ có bật hoặc tắt; một attribute mang tên và giá trị, nên nó có sẵn một namespace. Và mọi modifier đều là opt-in: một component chỉ phản ứng với data-align nếu chính file của nó định nghĩa điều đó nghĩa là gì. Đây là ý tưởng đó theo lời của mình và trong một domain khác:

@layer components {
  .price-tag {
    display: grid;
    gap: var(--space-1);
  }

  .price-tag__amount {
    font-size: var(--font-size-lg);
    color: var(--color-text);
  }

  .price-tag[data-tone="sale"] .price-tag__amount {
    color: var(--color-danger);
  }

  [data-theme="dark"] .price-tag__label {
    color: var(--color-text-inverse);
  }
}

Rule cho dark theme là một context, nhưng nó nằm trong file của price tag, nên ai đọc file này cũng thấy mọi trạng thái hình ảnh mà component có thể có. Đó là điều khiến visual regression coverage về sau trở nên khả thi: danh sách variation là hữu hạn và được viết ra.

type PriceTagProps = {
  label: string;
  amount: string;
  tone?: "default" | "sale";
};

export function PriceTag({ label, amount, tone = "default" }: PriceTagProps) {
  return (
    <div className="price-tag" data-tone={tone}>
      <span className="price-tag__label">{label}</span>
      <strong className="price-tag__amount">{amount}</strong>
    </div>
  );
}

Cái giá là một chút lặp lại khi nhiều component cùng hỗ trợ một modifier. Godbolt chấp nhận điều đó, và mình cũng vậy: mixin hoặc token dùng chung loại bỏ phần lớn sự lặp lại mà không mang lại những bất ngờ từ cascade.

Road Runner Rules và semantic grid

Chương 7 mở đầu bằng site Red Hat đã launch và lý do nó không thể chia sẻ được: dependency bloat (mọi band đều ngầm giả định có Bootstrap và một stylesheet lõi khổng lồ) và các selector phụ thuộc vị trí, gắn vào class của trang và của band. Lần xây lại đã chia các bản thiết kế thành vài layout và một nhóm nhỏ component "ngốc" (không biết gì về xung quanh). Để mỗi merge request giữ đúng sự phân chia đó, team viết ra Road Runner Rules, đặt theo tên một danh sách quy tắc được cho là đã đưa cho những người viết kịch bản phim hoạt hình Road Runner để giữ thế giới đó nhất quán. Họ còn viết quy tắc cho các quy tắc: chỉ gồm quy tắc bất biến, không phải hướng dẫn; mỗi quy tắc rút gọn tới dạng ngắn nhất; nêu quy tắc trước, rồi nói nên làm gì thay thế; và mọi quy tắc dùng một từ như always, never, only, every, do hoặc don't. Đây là các quy tắc theo cách mình hiểu:

Quy tắc, theo lời của mình Vì sao quan trọng
Layout chỉ lo căn chỉnh và khoảng cách cho các phần tử con, không bao giờ lo padding hay style bên trong của chúng Layout và component vẫn hoán đổi được cho nhau
Theme và data attribute là context mà các thành phần tự opt in, không bao giờ là thay đổi bị áp đặt Theme không thể style lại một component không yêu cầu điều đó
Component lấp đầy container của nó cả bốn phía: không có margin trên hoặc trái bên trong, margin cuối được xoá Mọi component đặt cạnh nhau đều thẳng hàng
Root của component không có background, width, float, padding hay margin; style chỉ nhắm tới các element bên trong Layout bao quanh quyết định những thứ đó
Mỗi element có một class duy nhất, gắn với component, chỉ bị thay đổi bởi context và theme Bạn có thể sửa hoặc xoá một component mà không gây side effect
Element không bao giờ dùng margin trên; element đầu tiên nằm sát mép trên Các hàng component thẳng hàng mà không cần sửa
JavaScript không bao giờ bám vào class; behaviour được opt in qua một data attribute Đổi tên hay style lại không thể làm hỏng behaviour

Semantic grid là cách áp dụng layout. Thay vì một band riêng cho mỗi layout, hoặc các wrapper row và column kiểu Bootstrap quanh từng phần tử con, vùng cha nhận một data attribute đặt tên cho một mẫu grid (gallery bốn hoặc năm cột, các cách chia của grid 12 cột). Các phần tử con không mang class grid nào và không có wrapper, nên markup chỉ nói về nội dung, và chuyển một logo wall từ năm sang bốn cột chỉ là đổi một attribute. Một layout cần thiết nằm ngoài danh mục sẽ được xây, viết tài liệu và thêm vào danh mục. Với CSS grid, danh mục đó rất ngắn:

@layer layouts {
  [data-layout] {
    display: grid;
    gap: var(--space-4);
  }

  [data-layout="gallery-4"] {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }

  [data-layout="gallery-5"] {
    grid-template-columns: repeat(5, minmax(0, 1fr));
  }

  [data-layout="split-3-9"] {
    grid-template-columns: 1fr 3fr;
  }
}

JavaScript: bắt đầu với ít thứ hơn

Chương 6 ngắn. Lời khuyên của nó là giả định bạn không cần framework, CMS hay thư viện CSS nào cho tới khi một yêu cầu chứng minh điều ngược lại, lint mọi thứ trong bước build, và viết các hàm nhỏ có tên thay vì những chuỗi selector cộng callback dài. Các ví dụ jQuery đã cũ đi nhiều, nhưng nguyên tắc rằng architect đặt ra một chuẩn tối thiểu được viết ra để code review công bằng thì không.

Đọc lại ở năm 2026

Các vấn đề trong những chương này là có thật; bộ công cụ để giải quyết chúng bây giờ tốt hơn nhiều.

  • Cascade layers. @layer cho phép bạn khai báo rõ thứ tự của reset, tokens, layouts và components, nên một rule của component thắng nhờ layer của nó, không phải nhờ selector dài hơn. Cascade layers đã là Baseline Widely available từ tháng 9 năm 2024.
  • Scoping. CSS Modules, style được scope bởi framework và giờ là @scope cho bạn ranh giới component mà không bắt quy ước đặt tên làm hết mọi việc. @scope trở thành Baseline Newly available vào tháng 12 năm 2025 cùng Firefox 146, nên hãy kiểm tra các browser bạn nhắm tới trước khi dựa vào nó.
  • Container queries. Quy tắc component không phụ thuộc context trở nên dễ hơn khi component có thể phản ứng theo chiều rộng container của chính nó thay vì viewport. Container size queries là Baseline Widely available tính đến năm 2025.
  • Design tokens. Màu sắc, khoảng cách và thang chữ mà Godbolt gọi là visual language giờ thường nằm dưới dạng token. Design Tokens Community Group đã công bố format ổn định đầu tiên, phiên bản 2025.10, vào tháng 10 năm 2025, với Style Dictionary, Tokens Studio và Terrazzo nằm trong số các reference implementation.
  • Data attribute cho state giờ đã phổ biến trong các component library. Các headless library như Radix expose các state như data-state="open" để style, rất gần với ý tưởng opt-in modifier.
  • Sass. Nếu bạn vẫn dùng Sass, @import đã bị deprecate trong Dart Sass để nhường chỗ cho @use và @forward, và dự kiến bị loại bỏ trong Dart Sass 3.0. JSHint phần lớn đã nhường chỗ cho ESLint và các kiểm tra của chính TypeScript.
  • Layouts. CSS grid khiến semantic grid gần như miễn phí: một grid-template-columns có tên cho mỗi giá trị data attribute, như trong ví dụ ở trên.

Thứ mình sẽ không mang theo là việc dựa nặng vào cách đặt tên BEM trong một codebase React vốn đã có scoping. Các quy tắc bên dưới (selector phẳng, mỗi style một nơi sở hữu, variation rõ ràng) vẫn áp dụng; cách đặt tên trở thành tuỳ chọn khi tooling đã đảm bảo ranh giới.

Áp dụng vào dự án

  1. Viết Road Runner Rules của riêng bạn theo kiểu always/never/only và giữ danh sách dưới mười mục. Đặt nó trong repo, không phải trên wiki.
  2. Khai báo thứ tự layer một lần (ví dụ reset, tokens, layouts, components, utilities) và thôi sửa xung đột bằng !important hay selector dài hơn.
  3. Rà soát các component có margin bên ngoài và width cố định. Chuyển khoảng cách sang các layout primitive như component stack hoặc grid.
  4. Thể hiện variant bằng prop có type render ra một data attribute, và giữ mọi rule cho variant và context trong stylesheet của chính component.
  5. Khi một ticket ghi "sửa trang này", hãy viết lại thành "thêm variation này cho component này" trước khi ai đó bắt đầu code.
  6. Trước khi thêm một framework hay UI kit, liệt kê những yêu cầu nó giải quyết mà bạn không thể đáp ứng bằng nền tảng web và vài utility.

Tự kiểm tra

  1. Vì sao Godbolt nói người dùng của architect là developer? Vì sản phẩm chính của architect là hệ thống mà người khác làm việc bên trong: quy ước, template, tooling build và test. Feature code cho người dùng cuối chỉ là một phần công việc.

  2. Năm vấn đề khi style #sidebar h2 là gì? Cuộc đua specificity, phải reset màu bị kế thừa, phụ thuộc vị trí, kế thừa từ nhiều selector, và selector ngày càng sâu cho nội dung lồng bên trong.

  3. Single source of truth khác single responsibility thế nào? Single responsibility nói một class phục vụ một element trong một component. Single source of truth nói mọi rule có thể thay đổi element đó, kể cả rule theo context, đều nằm trong file của component đó.

  4. Vì sao component phải tránh margin bên ngoài và background? Để bất kỳ component nào cũng có thể đặt vào bất kỳ ô layout nào và thẳng hàng với các component bên cạnh; khoảng cách và nền là việc của layout.

  5. Điều gì khiến một grid là "semantic" theo nghĩa của Red Hat? Layout là một giá trị có tên trong một data attribute trên phần tử cha, nên các phần tử con không cần class grid hay wrapper row và column.

  6. Tính năng CSS nào của năm 2026 thay thế trực tiếp nhất việc dùng độ dài selector để phân thắng thua? Cascade layers, vì chúng biến thứ tự ưu tiên thành vấn đề của thứ tự layer được khai báo thay vì specificity.

Đọc thêm