9 Tailwind v4 Animations — dán 1 dòng là chạy
Tailwind v4 animations là các animate- được khai báo ngay trong khối @theme của CSS, không cần tailwind.config.js.
Tự chạy · bấm ô để tới mục đó · cả 9 trong một zip
- 01 @theme keyframes tùy chỉnh
- 02 Tiện ích entrance tw-animate-css
- 03 Tiện ích fade + slide
- 04 scroll-timeline giá trị tùy ý
- 05 Icon group-hover
- 06 Animation theo data-state (shadcn)
- 07 Guard motion-safe
- 08 Tiện ích spin
- 09 Tiện ích stagger
Chín tiện ích không được xếp theo độ phổ biến mà theo đúng trình tự học cú pháp Tailwind v4: trước tiên là cách đăng ký animation tùy chỉnh (01–03), rồi cách giả lập an toàn một tính năng còn lệch trình duyệt (04), cách phản ứng theo trạng thái (05–06), một lớp bảo vệ khả năng tiếp cận (07), và cuối cùng là cách mở rộng utility có sẵn thay vì viết lại (08–09). Bên dưới chỉ còn đúng đoạn class Tailwind cần dán, còn bản đầy đủ kèm SCSS thuần nằm trong file zip.
01@theme keyframes tùy chỉnh
Khai báo biến --animate-pop-spin cùng @keyframes pop-spin ngay trong khối @theme của CSS — không cần tailwind.config.js — thì cái tên đó tự trở thành class animate-pop-spin. Badge xoay rồi bật ra, dùng khi có mục mới hoặc số đếm thông báo vừa đổi.
<style>
@theme {
--animate-pop-spin: pop-spin 900ms cubic-bezier(.34,1.56,.64,1) both;
@keyframes pop-spin {
0% { opacity: 0; transform: scale(.5) rotate(-8deg); }
60% { opacity: 1; transform: scale(1.08) rotate(3deg); }
100% { opacity: 1; transform: scale(1) rotate(0deg); }
}
}
</style>
<span class="animate-pop-spin motion-reduce:animate-none rounded-full bg-[#fff7e6] px-4 py-2 font-bold">NEW</span>
02Tiện ích entrance tw-animate-css
Dùng nguyên tổ hợp animate-in fade-in slide-in-from-top-8 duration-500 của plugin tw-animate-css, card bắt đầu ở vị trí cao hơn 32px và trong suốt rồi hạ dần về chỗ cũ trong lúc rõ nét lên. Hợp cho lúc modal hoặc toast xuất hiện lần đầu.
npm install -D tw-animate-css
<!-- Chỉ cần thêm @import "tw-animate-css"; vào app.css là xong -->
<div
class="animate-in fade-in slide-in-from-top-8 duration-500
motion-reduce:animate-none
rounded-2xl bg-[#17141a] p-6"
>
<p class="text-[#fff7e6] font-bold">Deployed</p>
<p class="text-[#fff7e6]/70 text-sm">The new version just went live</p>
</div>
03Tiện ích fade + slide
Không cần plugin nào, chỉ định nghĩa một biến --animate-fade-slide trong @theme để gộp opacity và translateY vào chung một keyframe, đoạn văn nổi lên từ dưới 16px bằng đúng một class. Hợp cho đoạn mở hero hoặc tiêu đề section vừa lộ ra.
<style>
@theme {
--animate-fade-slide: fade-slide 400ms ease-out both;
@keyframes fade-slide {
from { opacity: 0; transform: translateY(16px); }
to { opacity: 1; transform: translateY(0); }
}
}
</style>
<p class="animate-fade-slide text-center font-bold text-2xl">One line is all it takes</p>
04scroll-timeline giá trị tùy ý
Tailwind chưa có utility riêng cho scroll-timeline nên phải gắn qua thuộc tính tùy ý [animation-timeline:scroll()], nhưng mức hỗ trợ còn lệch giữa các trình duyệt nên demo này dựng lại đúng tiến độ đó bằng một vòng lặp an toàn thay vì cuộn thật. Hợp cho thanh tiến độ đọc bài dài hoặc chỉ báo cuộn trên landing page.
<!-- Chỉ dùng trên trình duyệt hỗ trợ: kiểm tra phạm vi hỗ trợ trên caniuse.com trước -->
<div class="h-2 rounded-full bg-black/10">
<div
class="h-full rounded-full bg-[#2f6df6] origin-left
animate-[fill_1s_linear] [animation-timeline:scroll()] [animation-range:0%_100%]"
></div>
</div>
05Icon group-hover
Gắn group lên phần tử cha và group-hover:translate-x-1 lên icon, hover bất kỳ chỗ nào trên card cũng chỉ đẩy mũi tên bên trong dịch phải 4px. Hợp cho CTA dạng card hoặc mũi tên ở cuối một dòng danh sách.
<a class="group inline-flex items-center gap-3 rounded-xl bg-white/8 px-6 py-4" href="/more">
<span class="text-[#fff7e6] font-semibold">Tìm hiểu thêm</span>
<svg class="size-4 text-[#2f6df6] transition-transform duration-200
group-hover:translate-x-1 group-focus-visible:translate-x-1" viewBox="0 0 24 24">
<path d="M4 12h14M13 6l6 6-6 6" fill="none" stroke="currentColor" stroke-width="2" />
</svg>
</a>
06Animation theo data-state (shadcn)
Như các component của shadcn, JS chỉ đổi thuộc tính data-state="open", còn CSS dùng biến thể data-[state=open]: để đặt scale-y-100 và opacity-100. Điểm mấu chốt là không đụng vào width, height hay grid-template-rows — những thuộc tính buộc trình duyệt tính lại layout mỗi khung hình.
<button onclick="p.dataset.state = p.dataset.state === 'open' ? 'closed' : 'open'"
aria-expanded="false" class="w-full rounded-xl bg-[#17141a] px-5 py-4 text-left text-[#fff7e6]">
Thông tin vận chuyển
</button>
<div id="p" data-state="closed"
class="origin-top scale-y-0 opacity-0 transition-all duration-200
data-[state=open]:scale-y-100 data-[state=open]:opacity-100">
<p>Giao hàng trong 1-2 ngày sau khi thanh toán.</p>
</div>
07Guard motion-safe
Không cần viết riêng @media, chỉ cần đặt motion-safe:-translate-y-1 cạnh motion-reduce:shadow-md, người dùng đã giảm chuyển động chỉ thấy bóng đổ nhạt đi còn cú nhấc lên chưa từng được gắn vào từ đầu. Hợp cho phản hồi hover của CTA chính như nút lưu.
<button
class="rounded-xl bg-[#fff7e6] px-8 py-4 font-bold shadow-md transition-all duration-200
motion-safe:hover:-translate-y-1 motion-safe:hover:shadow-lg
motion-reduce:hover:shadow-md"
>
Lưu thay đổi
</button>
08Tiện ích spin
Giữ nguyên animate-spin có sẵn (1 giây) và chỉ thêm --animate-spin-slow: spin 3s linear infinite; vào @theme, logo và vòng loading cùng dùng một phép xoay nhưng khác tốc độ. Không cần dựng thêm @keyframes mới nào.
<style>
@theme {
--animate-spin-slow: spin 3s linear infinite;
}
</style>
<div class="flex items-center gap-8">
<span class="animate-spin size-14 rounded-full border-4 border-black/10 border-t-[#2f6df6]"></span>
<span class="animate-spin-slow size-14 rounded-full border-4 border-black/10 border-t-[#17141a]"></span>
</div>
09Tiện ích stagger
Mỗi item trong danh sách chỉ đổi [animation-delay:calc(var(--i)*80ms)] với --i tăng dần 0, 1, 2, cùng một class animate-fade-slide sẽ nổi lên lần lượt cách nhau 80ms. Hợp cho danh sách thông báo hoặc card dashboard tải lần lượt.
<ul class="flex flex-col gap-2">
<li style="--i:0" class="animate-fade-slide [animation-delay:calc(var(--i)*80ms)] rounded-lg bg-white/8 px-4 py-2">
Có bình luận mới
</li>
<li style="--i:1" class="animate-fade-slide [animation-delay:calc(var(--i)*80ms)] rounded-lg bg-white/8 px-4 py-2">
Bạn có thêm người theo dõi
</li>
<li style="--i:2" class="animate-fade-slide [animation-delay:calc(var(--i)*80ms)] rounded-lg bg-white/8 px-4 py-2">
Thanh toán đã hoàn tất
</li>
</ul>
Bẫy nằm ở đâu khi chuyển sang v4?
Bẫy hay gặp nhất xảy ra ngay lúc nâng cấp từ v3: giữ nguyên theme.extend.keyframes và theme.extend.animation trong tailwind.config.js rồi chỉ đổi tên class sang animate-pop-spin thì không có gì chạy cả. Tailwind v4 là CSS-first nên file config đó bị bỏ qua hoàn toàn — định nghĩa phải chuyển hẳn vào khối @theme trong CSS như tài liệu chính thức yêu cầu. Ở mục 06 cũng dễ bị cám dỗ dùng grid-template-rows: 0fr → 1fr để mở khay accordion, nhưng thuộc tính đó buộc trình duyệt tính lại kích thước track mỗi khung hình, phá nguyên tắc chỉ động transform và opacity của cả bài, nên riêng file zip của mục 06 gấp bằng scaleY cùng transform-origin: top thay thế. Mục 08 spin cũng suýt vướng lỗi tương tự: dựng thêm một @keyframes riêng cho vòng thứ hai chỉ làm phình CSS đã biên dịch, còn cách đúng là để cả hai vòng dùng chung spin và chỉ đổi animation-duration — file nén giữ đúng bản rút gọn đó, khóa bằng mtsjkxrf, tám ký tự chỉ gồm chữ thường và số.
Khả năng tiếp cận
Chín tiện ích đều phản hồi prefers-reduced-motion nhưng chia làm hai nhóm rõ rệt. Nhóm dùng animate-*/@keyframes (01, 02, 03, 04, 06, 08, 09) tắt hẳn animation qua motion-reduce:animate-none hoặc tương đương và dừng thẳng ở trạng thái cuối — badge hiện sẵn, card đã rõ nét, progress-bar đã đầy. Nhóm 05 và 07 không tắt gì cả vì động tác hover của chúng chưa từng được gắn ngoài khối motion-safe: ngay từ đầu. Panel ở mục 06 vẫn giữ opacity để trạng thái mở/đóng còn đọc được dù chuyển động đã tắt. Phạm vi hỗ trợ trình duyệt của media query này nằm ở trang prefers-reduced-motion của MDN.
Các bài về Tailwind và CSS animation khác nằm trong chuyên mục CSS, còn trang giới thiệu nói rõ site này bán gì.
FAQ
Cấu hình animation kiểu tailwind.config.js của v3 còn dùng được trong v4 không?
Không. v4 theo hướng CSS-first nên hoàn toàn không đọc file config, theme.extend.keyframes và animation bị âm thầm bỏ qua. Phải chuyển định nghĩa sang biến --animate-* cùng @keyframes bên trong khối @theme thì class mới sinh ra.
Không cài plugin tw-animate-css thì có tạo được animation lúc xuất hiện không?
Có. Như mục 03, chỉ cần khai báo một biến --animate-* tùy chỉnh trong @theme là đủ, không cần plugin nào. Plugin chỉ đáng cài khi cần nhiều biến thể hướng khác nhau như slide-in-from-*, lúc đó viết tay sẽ tốn dòng hơn hẳn.
Component React xử lý toggle data-state như thế nào?
Thư mục react/ trong file nén giữ trạng thái mở bằng useState rồi gán thẳng vào thuộc tính data-state. Phần SCSS chỉ nhìn vào selector [data-state="open"] nên phía component chỉ cần đổi đúng một giá trị trạng thái.