CSS Positioning: Hướng Dẫn Từ Cơ Bản Đến Nâng Cao
Khám phá tất cả các giá trị position trong CSS — static, relative, absolute, fixed, sticky — cùng stacking context, z-index và các ví dụ thực tế.

Giới thiệu
CSS Positioning là một trong những kỹ năng nền tảng quan trọng nhất mà bất kỳ web developer nào cũng phải nắm vững. Nó quyết định cách các phần tử HTML được đặt trên trang web — và nếu bạn không hiểu rõ, bạn sẽ dễ dàng rơi vào tình huống “phần tử này đi đâu mất rồi?”
Trong bài viết này, chúng ta sẽ đi từ cơ bản đến nâng cao, khám phá tất cả 5 giá trị của thuộc tính position trong CSS:
static— mặc địnhrelative— tương đốiabsolute— tuyệt đốifixed— cố địnhsticky— bám dính
Kèm theo đó là các khái niệm quan trọng như stacking context, z-index, và containing block.
Position: Static (Mặc định)
Mọi phần tử HTML đều có position: static theo mặc định. Điều này có nghĩa là phần tử nằm trong normal document flow — các phần tử xếp chồng lên nhau theo thứ tự xuất hiện trong HTML.
/* Đây là giá trị mặc định, không cần khai báo */
.element {
position: static;
}
Đặc điểm của static:
- Phần tử nằm đúng vị trí trong luồng tài liệu
- Các thuộc tính
top,right,bottom,left,z-indexkhông có tác dụng - Không thể dùng làm container cho phần tử con
absolute
Position: Relative
position: relative giữ phần tử trong normal flow nhưng cho phép bạn dịch chuyển nó tương đối so với vị trí ban đầu.
.relative-box {
position: relative;
top: 20px;
left: 30px;
}
Cách hoạt động
Khi bạn đặt position: relative và sử dụng top/right/bottom/left, phần tử sẽ dịch chuyển khỏi vị trí gốc nhưng vẫn chiếm không gian ban đầu. Các phần tử xung quanh không bị ảnh hưởng.
<div class="container">
<div class="box box-1">Box 1 (static)</div>
<div class="box box-2">Box 2 (relative)</div>
<div class="box box-3">Box 3 (static)</div>
</div>
.box-2 {
position: relative;
top: 20px;
left: 20px;
background: #3b82f6;
}
Kết quả: Box 2 dịch xuống 20px và sang phải 20px, nhưng Box 3 vẫn nằm ngay dưới vị trí gốc của Box 2 — không dịch lên.
Ứng dụng quan trọng nhất
relative tạo containing block cho phần tử con absolute — đây là ứng dụng số 1. Chúng ta sẽ nói rõ ở phần absolute.
Position: Absolute
position: absolute loại bỏ phần tử khỏi normal flow và định vị nó dựa trên containing block gần nhất có position khác static.
.absolute-box {
position: absolute;
top: 0;
right: 0;
}
Cách hoạt động
Khi một phần tử có position: absolute:
- Nó không còn chiếm không gian trong layout
- Các phần tử khác sẽ “đè lên” vị trí cũ của nó
- Nó được định vị dựa trên ancestor gần nhất có position khác static
<div class="card">
<div class="badge">NEW</div>
<p>Nội dung card...</p>
</div>
.card {
position: relative; /* Tạo containing block */
width: 300px;
padding: 1.5rem;
border: 1px solid #e5e7eb;
border-radius: 0.5rem;
}
.badge {
position: absolute;
top: -10px;
right: -10px;
background: #ef4444;
color: white;
padding: 0.25rem 0.5rem;
border-radius: 9999px;
font-size: 0.75rem;
font-weight: 600;
}
Badge nằm ở góc trên cùng bên phải của card — một pattern cực kỳ phổ biến.
Containing Block là gì?
Containing block quyết định vị trí và kích thước của phần tử absolute. Nó được xác định như sau:
- Nếu phần tử cha có
position: relative | absolute | fixed | sticky, nó là containing block - Nếu không, trình duyệt tìm lên ancestors tiếp theo
- Nếu không có ancestor nào, viewport là containing block
/* Absolute dựa trên viewport → nằm ở góc phải màn hình */
.no-relative-parent {
position: absolute;
top: 0;
right: 0;
}
/* Absolute dựa trên .container → nằm ở góc phải của container */
.container {
position: relative;
}
.no-relative-parent {
position: absolute;
top: 0;
right: 0;
}
Kích thước với Absolute
Absolute elements có thể dùng top: 0; bottom: 0; left: 0; right: 0 để căng ra toàn bộ containing block:
.overlay {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
}
Đây là cách tạo overlay phủ toàn bộ container.
Position: Fixed
position: fixed cũng loại bỏ phần tử khỏi normal flow, nhưng nó được định vị tương đối với viewport — không phải containing block.
.fixed-header {
position: fixed;
top: 0;
left: 0;
right: 0;
height: 60px;
background: white;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
z-index: 1000;
}
Đặc điểm
- Luôn cố định tại một vị trí trên màn hình, dù trang có scroll
- Không chiếm không gian trong normal flow → phần tử dưới nó bị đè
- Không bị ảnh hưởng bởi scroll của parent — chỉ scroll viewport
- Thường dùng cho: header, sidebar, back-to-top button, chat widget
Lưu ý khi dùng Fixed Header
/* Cần padding-top cho body để tránh bị header che */
body {
padding-top: 60px; /* Bằng chiều cao header */
}
CSS Transform thay đổi Fixed behavior
Một chi tiết quan trọng: nếu ancestor của fixed element có transform, perspective, hoặc filter khác none, thì nó không còn fixed theo viewport nữa — nó fixed theo ancestor đó.
.parent {
transform: translateX(0); /* Vô tình thay đổi behavior của fixed child! */
}
.child {
position: fixed; /* Sẽ không fixed theo viewport! */
top: 0;
}
Đây là một “tính năng” gây ra nhiều bug khó chịu. Nếu fixed element không hoạt động như mong đợi, hãy kiểm tra ancestors có transform không.
Position: Sticky
position: sticky là sự kết hợp giữa relative và fixed — phần tử hoạt động như relative cho đến khi scroll đến một ngưỡng nhất định, sau đó nó “bám dính” như fixed.
.sticky-nav {
position: sticky;
top: 0; /* Ngưỡng: bám khi scroll chạm top */
background: white;
z-index: 100;
}
Cách hoạt động
Sticky element có 4 giai đoạn:
- Relative phase: Nằm trong normal flow, chưa dính
- Sticky phase: Khi scroll đến ngưỡng (
top: 0), nó dính vào viewport - Container boundary: Khi container của nó scroll ra khỏi viewport, sticky element cũng đi theo
- Release: Khi container hoàn toàn rời khỏi viewport, sticky element được thả
<section class="section">
<h2 class="section-title">Phần 1</h2>
<p>Nội dung dài...</p>
</section>
<section class="section">
<h2 class="section-title">Phần 2</h2>
<p>Nội dung dài...</p>
</section>
.section-title {
position: sticky;
top: 0;
background: #3b82f6;
color: white;
padding: 0.75rem 1rem;
margin: 0;
}
Kết quả: mỗi tiêu đề section sẽ dính ở đầu viewport khi bạn scroll qua section đó, và bị đẩy đi bởi tiêu đề của section tiếp theo.
Điều kiện để Sticky hoạt động
- Phải có
top,right,bottom, hoặcleft— ít nhất một ngưỡng - Container phải có chiều cao lớn hơn sticky element (để có không gian scroll)
- Container không được có
overflow: hidden(trên container hoặc ancestors)
/* ❌ Không hoạt động — overflow: hidden trên parent */
.parent {
overflow: hidden;
}
.sticky {
position: sticky;
top: 0;
}
/* ✅ Hoạt động */
.parent {
overflow: visible; /* hoặc không có overflow */
}
.sticky {
position: sticky;
top: 0;
}
Z-Index và Stacking Context
z-index kiểm soát thứ tự xếp chồng của các phần tử trên trục Z (trước/sau). Nhưng không đơn giản như “số lớn hơn là ở trên”.
Khi nào Z-Index hoạt động?
Z-index chỉ có tác dụng trên các phần tử có position khác static:
/* ✅ Z-index hoạt động */
.relative { position: relative; z-index: 1; }
.absolute { position: absolute; z-index: 2; }
.fixed { position: fixed; z-index: 3; }
.sticky { position: sticky; z-index: 4; }
/* ❌ Z-index không hoạt động */
.static { position: static; z-index: 9999; }
Stacking Context là gì?
Stacking context là một không gian 3D riêng biệt, nơi các phần tử con được xếp chồng độc lập với phần tử bên ngoài.
Một stacking context mới được tạo khi:
/* Position + z-index */
.element {
position: relative;
z-index: 1;
}
/* Opacity nhỏ hơn 1 */
.element { opacity: 0.9; }
/* Transform */
.element { transform: scale(1); }
/* Filter */
.element { filter: blur(0); }
/* will-change */
.element { will-change: transform; }
/* mix-blend-mode */
.element { mix-blend-mode: multiply; }
/* contain: layout | paint | strict */
.element { contain: layout; }
Vấn đề kinh điển với Stacking Context
<div class="parent-1" style="position: relative; z-index: 1;">
<div class="child" style="position: relative; z-index: 9999;">
Tôi có z-index 9999!
</div>
</div>
<div class="parent-2" style="position: relative; z-index: 2;">
<p>Nhưng tôi nằm trên bạn</p>
</div>
Dù .child có z-index 9999, nó vẫn nằm dưới .parent-2 vì nó thuộc stacking context của .parent-1 (z-index: 1), mà .parent-2 có z-index: 2 cao hơn.
Quy tắc vàng: Z-index chỉ có ý nghĩa trong cùng một stacking context.
Bảng So Sánh 5 Giá trị Position
| Giá trị | Normal Flow | Định vị dựa trên | Chiếm không gian | Scroll behavior |
|---|---|---|---|---|
static | ✅ Có | — | ✅ Có | Theo luồng |
relative | ✅ Có | Vị trí gốc | ✅ Có | Theo luồng |
absolute | ❌ Không | Containing block | ❌ Không | Theo container |
fixed | ❌ Không | Viewport (⚠️ trừ khi có transform) | ❌ Không | Cố định màn hình |
sticky | ✅ Có (ban đầu) | Viewport + container | ✅ Có | Dính khi scroll |
Ví dụ Thực Tế
1. Modal Overlay
<div class="modal-overlay">
<div class="modal">
<button class="modal-close">×</button>
<h2>Xác nhận</h2>
<p>Bạn có chắc muốn xoá?</p>
<div class="modal-actions">
<button class="btn-cancel">Huỷ</button>
<button class="btn-confirm">Xoá</button>
</div>
</div>
</div>
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 9999;
}
.modal {
position: relative;
background: white;
padding: 2rem;
border-radius: 0.75rem;
max-width: 400px;
width: 90%;
}
.modal-close {
position: absolute;
top: 0.5rem;
right: 0.75rem;
background: none;
border: none;
font-size: 1.5rem;
cursor: pointer;
opacity: 0.5;
transition: opacity 0.2s;
}
.modal-close:hover {
opacity: 1;
}
2. Sticky Table Header
table {
border-collapse: collapse;
width: 100%;
}
thead th {
position: sticky;
top: 0;
background: #1f2937;
color: white;
padding: 0.75rem 1rem;
z-index: 10;
}
tbody tr:nth-child(even) {
background: #f9fafb;
}
tbody td {
padding: 0.5rem 1rem;
border-bottom: 1px solid #e5e7eb;
}
3. Tooltip với Position Absolute
<button class="tooltip-trigger">
Hover vào tôi
<span class="tooltip-text">Đây là tooltip giải thích</span>
</button>
.tooltip-trigger {
position: relative;
padding: 0.5rem 1rem;
background: #3b82f6;
color: white;
border: none;
border-radius: 0.25rem;
cursor: pointer;
}
.tooltip-text {
position: absolute;
bottom: calc(100% + 8px);
left: 50%;
transform: translateX(-50%);
background: #1f2937;
color: white;
padding: 0.5rem 0.75rem;
border-radius: 0.25rem;
font-size: 0.75rem;
white-space: nowrap;
opacity: 0;
pointer-events: none;
transition: opacity 0.2s;
}
.tooltip-trigger:hover .tooltip-text {
opacity: 1;
}
/* Mũi tên tooltip */
.tooltip-text::after {
content: '';
position: absolute;
top: 100%;
left: 50%;
transform: translateX(-50%);
border: 6px solid transparent;
border-top-color: #1f2937;
}
4. Fixed Back-to-Top Button
.back-to-top {
position: fixed;
bottom: 2rem;
right: 2rem;
width: 48px;
height: 48px;
background: #3b82f6;
color: white;
border: none;
border-radius: 50%;
cursor: pointer;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
z-index: 1000;
transition: transform 0.2s, opacity 0.2s;
}
.back-to-top:hover {
transform: translateY(-4px);
}
.back-to-top.hidden {
opacity: 0;
pointer-events: none;
}
5. Sticky Section Navigator
.section-nav {
position: sticky;
top: 80px; /* 80px cho fixed header */
max-height: calc(100vh - 80px);
overflow-y: auto;
width: 250px;
padding: 1rem;
border-left: 2px solid #e5e7eb;
}
.section-nav a {
display: block;
padding: 0.5rem 1rem;
color: #6b7280;
text-decoration: none;
border-radius: 0.25rem;
transition: background 0.2s, color 0.2s;
}
.section-nav a:hover,
.section-nav a.active {
background: #eff6ff;
color: #3b82f6;
}
Best Practices
1. Luôn đặt Position cho Container
Khi dùng position: absolute, hãy luôn đặt position: relative cho container nếu bạn muốn absolute dựa trên container đó:
/* ✅ Luôn làm rõ container */
.card {
position: relative;
}
.card-badge {
position: absolute;
top: 0;
right: 0;
}
2. Tránh Z-index Quá Cao
/* ❌ Z-index vô tội vạ */
.header { z-index: 99999; }
.modal { z-index: 999999; }
.toast { z-index: 9999999; }
/* ✅ Quản lý z-index có hệ thống */
:root {
--z-dropdown: 100;
--z-sticky: 200;
--z-header: 300;
--z-overlay: 400;
--z-modal: 500;
--z-toast: 600;
}
.header { z-index: var(--z-header); }
.modal { z-index: var(--z-modal); }
.toast { z-index: var(--z-toast); }
3. Cẩn Thận với Transform
Nếu bạn dùng transform trên parent, hãy nhớ rằng nó ảnh hưởng đến fixed children và tạo stacking context mới:
/* ⚠️ Transform vô tình thay đổi fixed behavior */
.page-wrapper {
transform: translateZ(0); /* GPU acceleration */
/* Tất cả fixed children bên trong sẽ không còn fixed theo viewport! */
}
4. Sticky và Overflow
/* ❌ Sticky không hoạt động */
.parent {
overflow-x: hidden;
}
.child {
position: sticky;
top: 0;
}
/* ✅ Chỉ overflow: visible để sticky hoạt động */
.parent {
overflow: visible;
}
5. Performance
position: fixedvàposition: stickykích hoạt GPU compositing — thường nhanh- Tránh thay đổi
top/lefttrong JavaScript animation — dùngtransform: translate()thay thế position: absolutelà hiệu quả cho overlay/loading states
Debugging Tips
Khi positioning không hoạt động như mong đợi:
- Kiểm tra DevTools → Computed → Position có đúng không?
- Kiểm tra containing block: ancestor gần nhất có position khác static?
- Kiểm tra stacking context: ancestor có tạo stacking context mới không?
- Kiểm tra overflow: ancestors có
overflow: hiddenkhông? - Kiểm tra transform: ancestor có
transformkhông (ảnh hưởng fixed)?
/* Debug: highlight tất cả containing blocks */
* {
outline: 1px solid rgba(59, 130, 246, 0.1);
}
*:is([style*="position: relative"], [style*="position: absolute"],
[style*="position: fixed"], [style*="position: sticky"]) {
outline: 2px solid #3b82f6;
}
Kết luận
CSS Positioning là một trong những công cụ mạnh mẽ nhất trong tay web developer. Hiểu rõ 5 giá trị position và cách chúng tương tác với nhau sẽ giúp bạn:
- ✅ Tạo layout phức tạp mà không cần JavaScript
- ✅ Xây dựng UI components chuyên nghiệp (modal, tooltip, dropdown, badge)
- ✅ Debug nhanh chóng khi gặp vấn đề về vị trí phần tử
- ✅ Viết CSS sạch hơn, ít hack hơn
Hãy nhớ: relative để làm container, absolute để đặt con, fixed cho UI toàn trang, sticky cho navigation section. Và đừng quên stacking context — nó là nguyên nhân số 1 gây ra các vấn đề z-index khó hiểu!
Bài viết được đăng bởi Devs2.org. Nếu bạn thấy bài viết hữu ích, hãy chia sẻ với đồng nghiệp của bạn!