This in-depth CSS guide covers the fundamental mechanics of the browser rendering box model, flow layout, margin collapsing, and specificity.

This lesson is currently only available in Vietnamese. Please switch the language toggle in the menu to Vietnamese to read the full guide and take the interactive quiz.

Trong phát triển web chuyên nghiệp, mọi lỗi hiển thị hay vỡ bố cục (layout break) phần lớn bắt nguồn từ việc lập trình viên hiểu sai cơ chế cốt lõi của trình duyệt: CSS Box ModelFlow Layout. Trình duyệt coi mọi thẻ HTML là một hình chữ nhật phẳng được cấu thành từ nhiều lớp bộ đệm.

Bài viết này sẽ đào sâu từ cơ cấu hiển thị bộ đệm bộ nhớ của Box Model, bản chất toán học của hiện tượng Margin Collapsing, cách luồng Block/Inline sắp xếp các hộp, cho tới các cạm bẫy trong Stacking Context và độ ưu tiên CSS Specificity.

1. Box Model chuyên sâu: content, padding, border, margin và box-sizing

Theo đặc tả chính thức của W3C CSS Box Model Module Level 3 và tài liệu hướng dẫn của MDN Web Docs, mọi phần tử trên trang được trình duyệt biểu diễn dưới dạng một hộp chữ nhật phẳng đồng tâm. Hộp CSS (CSS Box) bao gồm 4 vùng bộ đệm xếp lớp đồng tâm từ trong ra ngoài:

  • Content box: Vùng hiển thị nội dung thực tế (chữ, hình ảnh). Kích thước được xác định bởi thuộc tính widthheight mặc định.
  • Padding box: Vùng đệm xung quanh nội dung, ngăn không cho nội dung chạm sát vào viền ngoài. Có màu nền của phần tử.
  • Border box: Đường viền bọc ngoài padding. Độ rộng đường viền ảnh hưởng trực tiếp đến kích thước hiển thị.
  • Margin box: Khoảng trống đẩy các phần tử lân cận ra xa. Vùng này hoàn toàn trong suốt và không nhận màu nền của phần tử.
🔬 Đào sâu: Thuộc tính box-sizing hoạt động ra sao?
Theo chuẩn W3C mặc định (content-box), kích thước thực tế hiển thị trên màn hình của một phần tử được tính bằng công thức: $$\text{Width}_{\text{total}} = \text{width} + \text{padding-left} + \text{padding-right} + \text{border-left} + \text{border-right}$$ Điều này khiến lập trình viên rất khó tính toán layout. Khi chuyển sang box-sizing: border-box, trình duyệt sẽ tự động bóp nhỏ Content Box lại sao cho tổng kích thước đường viền ngoài bằng đúng giá trị width bạn thiết lập.
box-model.css
/* Cách áp dụng chuẩn border-box toàn cục */
*, *::before, *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.card {
  width: 300px;
  padding: 20px;
  border: 5px solid #ec4899;
  margin: 15px; /* Sẽ không làm thay đổi chiều rộng 300px hiển thị */
}

2. Hiệu ứng Margin Collapsing & Cách kiểm soát

Theo quy định trong đặc tả W3C CSS2 Section 8.3.1 (Collapsing Margins), Margin Collapsing (Hợp nhất lề) là hiện tượng lề dọc (top và bottom) của hai phần tử khối nằm kề nhau trong luồng dàn trang bình thường (Normal Flow) bị nhập lại thành một khoảng lề duy nhất.

Công thức tính khoảng lề thu gọn: $$\text{Margin}_{\text{final}} = \max(\text{Margin}_A, \text{Margin}_B) \quad \text{(nếu cả hai cùng dương)}$$ Nếu có lề âm, lề lớn nhất sẽ được cộng với lề âm nhỏ nhất. Hiện tượng này chỉ xảy ra ở luồng bố cục dọc và không xảy ra ở lề ngang (left/right).

🕳️ Cạm bẫy: Cha nuốt lề của con (Parent-Child Collapsing)
Nếu phần tử cha không có padding, không có border ở cạnh đỉnh và không có thuộc tính inline-content, thì lề đỉnh (margin-top) của phần tử con nằm đầu tiên sẽ bị "trôi" lên trên và hợp nhất hoàn toàn với lề của cha, đẩy cả khối cha đi xuống thay vì tạo khoảng trống giữa cha và con.
prevent-collapsing.css
/* Cách 1: Thêm border hoặc padding siêu mỏng cho phần tử cha */
.parent {
  padding-top: 0.1px; /* Ngăn cản lề của con chạm vào lề cha */
}

/* Cách 2: Biến cha thành một khối chứa Block Formatting Context (BFC) */
.parent {
  overflow: hidden; /* Thiết lập BFC giúp cô lập lề bên trong */
}

3. Cơ chế dàn trang tự nhiên (Flow Layout): Block vs Inline, Stacking Context

Flow Layout là cơ chế xếp đặt phần tử mặc định của trình duyệt:

  • Block elements (Thẻ khối - div, p, h1): Luôn bắt đầu dòng mới, mặc định chiếm trọn 100% chiều ngang của khối chứa cha. Có thể thiết lập width/height tự do.
  • Inline elements (Thẻ dòng - span, a, strong): Không bắt đầu dòng mới, xếp liền nhau theo chiều ngang của dòng chữ. Không thể thiết lập width/height và lề dọc (margin-top/bottom) không đẩy được các dòng khác đi.
  • Inline-block elements: Xếp trên một dòng như Inline nhưng lại có thể điều khiển kích thước width/height tự do như Block.
🔬 Đào sâu: Cây xếp chồng (Stacking Context Stack) hoạt động thế nào?
Theo quy tắc phân tầng dựng hình được quy định tại đặc tả W3C CSS2 Section 9.9.1 (Specifying z-index) và cẩm nang MDN Stacking Context, thuộc tính z-index không so sánh toàn cục trên toàn trang, mà so sánh cục bộ bên trong từng ngữ cảnh xếp chồng (Stacking Context). Một ngữ cảnh xếp chồng mới được tạo ra bởi bất kỳ yếu tố nào sau đây:
  1. Phần tử gốc của tài liệu (<html>).
  2. Phần tử có position: absolute/relative đi kèm z-index khác auto.
  3. Phần tử có position: fixed hoặc sticky.
  4. Phần tử có opacity < 1.
  5. Phần tử có transform, filter, backdrop-filter, perspective khác none.
  6. Con của container Flex/Grid có z-index khác auto.
  7. Phần tử có thuộc tính will-change kích hoạt các thuộc tính trên.
Cơ chế phân cấp xếp chồng (Stack):
[Màn hình nền (Root Context)]
├── [Hộp A - z-index: 10]
│    └── [Con A-1 - z-index: 99999]  <-- Bị khóa cứng trong phạm vi cha A (10)
└── [Hộp B - z-index: 11]
      └── [Con B-1 - z-index: 1]      <-- Vẫn đè lên Con A-1 vì cha B (11) > cha A (10)
Do đó, dù Con A-1 có z-index lớn đến đâu, nó cũng không thể vượt lên trên Con B-1 vì ngữ cảnh xếp chồng cha của nó (A) có mức ưu tiên thấp hơn (B).

Minh họa trực quan sống động (Interactive Demo):

Dưới đây là kết quả render thực tế. Bạn sẽ thấy khối Con B-1 (Màu xanh lá) nằm đè lên trên khối Con A-1 (Màu vàng) dù z-index của nó chỉ bằng 1 so với 99999 của Con A-1. Bấm các tab HTML / CSS bên dưới để xem mã nguồn tạo nên nó:

🎨 Cạm bẫy Stacking Context
Cha A (z-index: 10)
Con A-1 (z-index: 99999)
Cha B (z-index: 11)
Con B-1 (z-index: 1)
stacking.html
<div class="box-a">
  Cha A (z-index: 10)
  <div class="child-a1">Con A-1 (z-index: 99999)</div>
</div>

<div class="box-b">
  Cha B (z-index: 11)
  <div class="child-b1">Con B-1 (z-index: 1)</div>
</div>
stacking.css
.box-a {
  position: relative;
  z-index: 10; /* tạo ngữ cảnh xếp chồng cho cha A */
  background: red;
}
.child-a1 {
  position: absolute;
  z-index: 99999; /* rất lớn — nhưng chỉ có tác dụng BÊN TRONG cha A */
  background: yellow;
}
.box-b {
  position: relative;
  z-index: 11; /* Cha B > Cha A → toàn bộ B nằm đè lên toàn bộ A */
  background: blue;
}
.child-b1 {
  position: absolute;
  z-index: 1; /* nhỏ — nhưng vẫn nổi trên A vì thuộc về cha B */
  background: green;
}

Sân chơi tương tác: CSS Playground

Dưới đây là công cụ trực quan hóa giúp bạn trực tiếp chỉnh sửa thuộc tính CSS Box Model, biên dịch xem kết quả tức thì và bật tính năng Inspect để soi chi tiết các lớp bộ đệm (padding, border, margin) được tô màu hiển thị:

4. Độ ưu tiên CSS Specificity & Thuật toán tính thắng thua

Theo đặc tả chính thức của W3C Selectors Level 4 Section 16 (Calculating a selector's specificity), khi có nhiều luật CSS áp dụng lên cùng một phần tử, trình duyệt sẽ dùng thuật toán so sánh vectơ độ chọn lọc (Specificity Vector) để quyết định luật nào thắng thế.

Độ chọn lọc được biểu diễn bằng một vectơ 4 chiều: $$\mathbf{S} = (a, b, c, d)$$ Trong đó:

  • a (Inline styles): Thuộc tính style="..." trực tiếp trong HTML. Cực kỳ mạnh mẽ.
  • b (ID Selectors): Chọn lọc bằng mã ID độc bản, ví dụ #header.
  • c (Class / Attribute / Pseudo-class): Chọn lọc bằng class (.menu), thuộc tính ([type="text"]), hoặc lớp giả như :hover, :nth-child().
  • d (Element / Pseudo-element): Chọn lọc bằng tên thẻ (div, p) hoặc pseudo-elements như ::before, ::after.
ℹ️ Thuật toán so sánh vectơ (Lexicographical Comparison)
Trình duyệt so sánh các giá trị từ trái sang phải:
  1. So sánh a: Nếu $a_1 > a_2$, bộ chọn 1 thắng tuyệt đối. Nếu bằng nhau, đi tiếp sang cột tiếp theo.
  2. So sánh b: Nếu $b_1 > b_2$, bộ chọn 1 thắng. Nếu bằng nhau, so sánh tiếp c, rồi đến d.
Cạm bẫy cực lớn: Specificity KHÔNG phải là hệ cơ số 10! Nghĩa là một bộ chọn có 11 class (0, 0, 11, 0) vẫn thua tuyệt đối bộ chọn có chỉ 1 ID (0, 1, 0, 0). Các cột điểm hoàn toàn biệt lập, không thể cộng dồn hay làm tròn lên cột bên trái.

Bảng tính điểm minh họa chi tiết

Dưới đây là bảng phân tích cụ thể độ ưu tiên của một số bộ chọn phức tạp:

Bộ chọn CSS (Selector) Vectơ (a, b, c, d) Điểm giải thích Kết quả so sánh
style="color: blue" (1, 0, 0, 0) Inline style THẮNG TUYỆT ĐỐI
#nav .menu li a (0, 1, 1, 2) 1 ID (#nav) + 1 Class (.menu) + 2 Elements (li, a) THẮNG khi đụng độ bộ chọn bên dưới
.header ul.menu li.active a (0, 0, 3, 2) 3 Classes (.header, .menu, .active) + 2 Elements (ul, li) THUA bộ chọn trên vì không có ID nào
*, >, +, ~ (0, 0, 0, 0) Universal selector và các bộ kết nối không có điểm Thấp nhất
div:not(.active) (0, 0, 1, 1) Lớp giả :not() không tính điểm, chỉ tính điểm cho con bên trong (.active = 1 class) + 1 Element (div) Bình thường
div:where(.active) (0, 0, 0, 1) Lớp giả :where() đặc biệt luôn triệt tiêu điểm của toàn bộ con bên trong về 0 Thấp hơn vì class bị triệt tiêu
⚠️ Cảnh báo: Lập trình viên lạm dụng !important
Từ khóa !important không tham gia vào phép tính vectơ Specificity. Nó nằm ở bước Override tối cao của Cascade. Sử dụng !important khiến các luật CSS khác không thể ghi đè tự nhiên, dẫn tới mã nguồn cực kỳ khó gỡ lỗi (debug). Chỉ dùng !important khi viết các class tiện ích đè bắt buộc (utility classes) hoặc ghi đè thư viện bên thứ ba.
specificity-clash.css
/* Điểm Specificity: (0, 0, 1, 1) - Class .text + Element p */
.alert p {
  color: red;
}

/* Điểm Specificity: (0, 1, 0, 0) - Thắng tuyệt đối vì có ID selector */
#main-banner p {
  color: blue;
}

Trắc nghiệm ôn tập

Câu 1: Thuộc tính "box-sizing: border-box" giải quyết vấn đề gì lớn nhất trong thiết kế layout?

Trắc nghiệm ôn tập

Câu 2: Hiện tượng Margin Collapsing xảy ra khi nào?

Trắc nghiệm ôn tập

Câu 3: Làm cách nào để một phần tử con có z-index: 9999 vượt lên trên z-index: 1 của phần tử ở Stacking Context cha khác?

📖 Tài liệu tham khảo / References

Related Articles

Bài viết liên quan trong series

Lesson 2: Master Flexbox — One-Dimensional Layout Bài 2: Flexbox Toàn Tập — Dàn Trang Một Chiều Tối Ưu Back to CSS Course Overview Quay lại Lộ trình Series CSS