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 Model và Flow 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
widthvàheightmặ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ử.
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.
/* 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á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.
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:
- Phần tử gốc của tài liệu (
<html>). -
Phần tử có
position: absolute/relativeđi kèmz-indexkhácauto. - Phần tử có
position: fixedhoặcsticky. - Phần tử có
opacity < 1. -
Phần tử có
transform,filter,backdrop-filter,perspectivekhácnone. - Con của container Flex/Grid có
z-indexkhácauto. - Phần tử có thuộc tính
will-changekích hoạt các thuộc tính trên.
[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ó:
<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>
.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.
- 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.
- 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.
(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 |
!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.
/* Đ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?