Position: sticky là một trong những thuộc tính CSS mạnh mẽ, dùng trong nhiều trường hợp để nội dung được hoạt động xuyên suốt nhưng cũng rất dễ “không hoạt động” nếu hiểu sai bản chất
1. position: sticky hoạt động như thế nào?
Một element có position: sticky sẽ:
- Hoạt động như
position: relativecho đến khi - Một cạnh (
top/bottom/left/right) chạm tới offset đã chỉ định trong vùng scroll - Sau đó nó sẽ “dính” (stick) tại vị trí đó
📌 Quan trọng: Sticky luôn bị giới hạn trong phạm vi của parent (không vượt ra ngoài).
Ví dụ minh họa
.header {
position: sticky;
top: 0;
background: white;
z-index: 10;
}
Header này sẽ cuộn bình thường cho đến khi cạnh trên chạm vị trí 0px từ đỉnh viewport, sau đó sẽ “dính” lại ở đó khi bạn tiếp tục cuộn xuống.
2. Điều kiện bắt buộc để sticky hoạt động
2.1. Phải có ít nhất một offset
❌ Sai:
.sticky {
position: sticky;
}
✅ Đúng:
.sticky {
position: sticky;
top: 0;
}
Tailwind CSS:
<div class="sticky top-0">...</div>
2.2. Parent không được có overflow: hidden | auto | scroll
Nếu bất kỳ cha nào có overflow khác visible → sticky bị vô hiệu hóa.
❌ Sai:
.parent {
overflow: hidden;
}
✅ Fix:
- Xoá
overflow - Hoặc đưa sticky ra ngoài container đó
- Hoặc áp dụng
overflowcho một container bên trong
2.3. Parent phải đủ cao để có scroll
Sticky chỉ kích hoạt khi có scroll thật.
❌ Sai:
<div style="height: 100px">
<div class="sticky">Short parent</div>
</div>
✅ Đúng:
<div style="min-height: 200vh">
<div class="sticky">Tall parent</div>
</div>
2.4. Parent không được dùng transform
Nếu parent có:
transform: translateZ(0);transform: scale(1);- hoặc animation/transition với transform
→ sticky không hoạt động.
Giải pháp: Di chuyển transform ra khỏi parent, hoặc áp dụng cho element khác.
2.5. z-index có thể làm bạn tưởng sticky không chạy
Sticky vẫn hoạt động nhưng bị che bởi các element khác.
.sticky {
position: sticky;
top: 0;
z-index: 10;
background: white; /* Quan trọng để không bị trong suốt */
}
Tailwind CSS:
<div class="sticky top-0 z-10 bg-white">...</div>
3. Vì sao top: 30px không hoạt động nhưng bottom: 30px lại được?
Đây là bẫy rất phổ biến và gây nhầm lẫn nhiều nhất.
Nguyên nhân cốt lõi
Sticky chỉ hoạt động nếu cạnh được chỉ định CÓ KHẢ NĂNG chạm tới offset đó khi scroll.
top: 30px→ cạnh trên phải chạm 30px từ đỉnh vùng scrollbottom: 30px→ cạnh dưới phải chạm 30px từ đáy vùng scroll
👉 Trong layout của bạn:
- Cạnh trên không bao giờ chạm được 30px →
topkhông hoạt động - Nhưng cạnh dưới có thể chạm →
bottomhoạt động
4. Các tình huống gây ra hiện tượng này
4.1. Element đã nằm sẵn gần top parent
<div class="parent">
<div class="sticky" style="position: sticky; top: 30px">
Tôi đã nằm ở đây từ đầu
</div>
</div>
Phân tích:
- Nếu sticky ban đầu đã nằm ≤ 30px từ top
top: 30px→ không có thời điểm kích hoạt- Browser coi như
position: relative
Trong khi đó:
- Khi scroll, cạnh dưới vẫn có thể chạm đáy viewport
- →
bottomhoạt động bình thường
4.2. Parent không đủ cao
Sticky bị giới hạn trong parent.
- Khoảng cách từ sticky → đỉnh parent quá ngắn →
topkhông kích hoạt - Khoảng cách tới đáy parent vẫn còn →
bottomhoạt động
<div style="height: 200px"> <!-- Parent quá ngắn -->
<div style="position: sticky; top: 30px">
Không đủ khoảng cách để stick
</div>
</div>
4.3. Scroll container không phải window
.wrapper {
height: 100vh;
overflow-y: auto;
}
top được tính theo .wrapper, không phải theo màn hình.
Nếu sticky đã gần top của wrapper → top không ăn.
5. Minh họa trực quan
top không hoạt động
┌─────────────┐
│ viewport │
│ │
│ 30px ↓ │ ← Mốc sticky mong muốn
│ [sticky] │ ← Nhưng element đã nằm sẵn ở đây
│ content │
│ ⋮ │
└─────────────┘
Không có lúc nào cạnh trên chạm mốc 30px → Sticky không kích hoạt
bottom hoạt động
┌─────────────┐
│ content │
│ ⋮ │
│ [sticky] │ ← Element bắt đầu ở đây
│ │
│ 30px ↑ │ ← Khi scroll, cạnh dưới sẽ chạm mốc này
│ viewport │
└─────────────┘
Cạnh dưới có lúc chạm 30px → Sticky kích hoạt
6. Cách fix top: 30px không hoạt động
✅ Cách 1: Tạo khoảng cách ban đầu
.sticky {
margin-top: 40px; /* Đẩy xuống để có khoảng cách */
position: sticky;
top: 30px;
}
✅ Cách 2: Đặt sticky thấp hơn trong DOM
<div class="parent">
<div style="height: 100px"></div> <!-- Spacer -->
<div class="sticky" style="position: sticky; top: 30px">
Bây giờ có khoảng cách để stick
</div>
</div>
✅ Cách 3: Kiểm tra overflow của parent
.parent {
overflow: visible; /* Hoặc bỏ hoàn toàn */
}
✅ Cách 4: Debug nhanh bằng outline
.sticky {
position: sticky;
top: 30px;
outline: 2px solid red; /* Giúp nhìn rõ khi nào sticky kích hoạt */
}
Khi bạn scroll, nếu outline màu đỏ “dính” lại ở vị trí 30px từ trên xuống, nghĩa là sticky đã hoạt động.
7. Checklist debug sticky (90% fix được vấn đề)
Khi sticky không hoạt động, hãy check theo thứ tự:
- ✔ Có
top/bottom/left/rightđược khai báo? - ✔ Parent không có
overflow: hidden/auto/scroll? - ✔ Có scroll thật (parent đủ cao)?
- ✔ Parent không dùng
transform? - ✔ Có
z-indexvàbackgroundnếu cần? - ✔ Cạnh được chỉ định có khả năng chạm offset không?
Debug tool nhanh
/* Thêm vào element sticky để debug */
.sticky {
outline: 3px dashed red;
outline-offset: -3px;
}
/* Thêm vào parent để kiểm tra */
.parent {
outline: 2px solid blue;
}
8. Ví dụ thực tế: Sticky header
❌ Không hoạt động
<div style="overflow: hidden"> <!-- Lỗi: overflow -->
<header style="position: sticky; top: 0">
Menu
</header>
<main>Content...</main>
</div>
✅ Hoạt động đúng
<div> <!-- Không có overflow -->
<header style="position: sticky; top: 0; z-index: 10; background: white">
Menu
</header>
<main style="min-height: 200vh">
Content...
</main>
</div>
Với Tailwind CSS
<div>
<header class="sticky top-0 z-10 bg-white shadow">
<nav class="container mx-auto">Menu</nav>
</header>
<main class="min-h-[200vh]">
Content...
</main>
</div>
9. Ví dụ nâng cao: Sticky sidebar
<div class="flex gap-4">
<!-- Sidebar sticky -->
<aside class="w-64">
<div class="sticky top-4">
<nav>Navigation links...</nav>
</div>
</aside>
<!-- Main content -->
<main class="flex-1 min-h-[200vh]">
Long content...
</main>
</div>
Tailwind CSS:
<div class="flex gap-4">
<aside class="w-64">
<div class="sticky top-4">
<nav>Navigation</nav>
</div>
</aside>
<main class="flex-1 min-h-[200vh]">Content</main>
</div>
10. Lưu ý khi dùng với framework
Nuxt / Vue
<template>
<div class="container">
<!-- ❌ Tránh -->
<div style="overflow-x: hidden">
<header class="sticky top-0">Header</header>
</div>
<!-- ✅ Đúng -->
<header class="sticky top-0 z-10 bg-white">Header</header>
</div>
</template>
<style scoped>
.container {
/* Không dùng overflow ở đây */
}
</style>
React / Next.js
export default function Layout({ children }) {
return (
<div className="min-h-screen">
<header className="sticky top-0 z-10 bg-white shadow">
<nav>Menu</nav>
</header>
<main className="container mx-auto">
{children}
</main>
</div>
)
}
11. Kết luận
position: sticky không hề lỗi, chỉ là nó rất nghiêm ngặt về điều kiện kích hoạt.
Nguyên tắc vàng
“Cạnh này có thực sự chạm được offset đó khi scroll không?”
Nếu top không hoạt động nhưng bottom lại được, 99% là do cạnh trên không bao giờ chạm được offset đã chỉ định.
Lời khuyên cuối
- Nếu bạn đang dùng Nuxt / Tailwind / layout phức tạp, chỉ cần sai 1 class
overflow-hiddenlà sticky hỏng hoàn toàn - Luôn test sticky với outline hoặc background màu nổi để debug
- Đọc kỹ các điều kiện ở phần 2, đặc biệt là overflow và transform
- Khi gặp lỗi, check lại chiều cao parent và vị trí ban đầu của element
💡 Pro tip: Tạo một component/utility class sticky chuẩn cho project và tái sử dụng, tránh phải debug nhiều lần.
Chúc bạn code vui! 🚀