9 css button design ideas — dán một file là chạy
css button design là quyết định màu, hình dáng nút và cách nó phản ứng với chuột, chỉ bằng vài dòng code.
Tự chạy · bấm ô để tới mục đó · cả 9 trong một zip
- 01 Nút gradient kẹo chảy
- 02 Nút quét vệt sáng
- 03 Nút blob mềm
- 04 Nút viền tự vẽ
- 05 Nút trượt mũi tên
- 06 Nút hai màu quét
- 07 Nút gợn vòng tròn
- 08 Nút nhấn 3D
- 09 Nút pill có loader
Chín kiểu không xếp theo độ nổi tiếng mà theo vòng đời thật của một nút trên màn hình. Đầu tiên là nhóm bắt mắt — gradient kẹo (01), vệt sáng (02), blob mềm (03). Tiếp theo là nhóm trả lời khi hover — viền tự vẽ (04), mũi tên trượt (05), quét hai màu (06). Rồi đến khoảnh khắc nhấn — vòng gợn (07), nút 3D (08) — và cuối cùng là chờ đợi sau cú nhấn, pill loader (09).
01Nút gradient kẹo chảy
Một tấm vải màu kẹo rộng gấp ba nút được trải bên dưới và mỗi nhịp bị đẩy sang trái đúng một chu kỳ sọc. Thứ duy nhất di chuyển là một translateX nên không bao giờ tính lại bố cục, và vì chu kỳ đúng 240px nên bạn không bao giờ thấy điểm nối của hoa văn. Hợp với nút đăng ký ở màn hình đầu của landing.
.btn__bg {
position: absolute;
top: 0; bottom: 0; left: 0;
width: 720px;
background: repeating-linear-gradient(to right,
$color 0 58px, $candy-b 64px 118px, $candy-c 124px 178px, $color 184px 240px);
}
.stage.is-demo .btn__bg { animation: gradient-flow $duration $easing infinite; }
@keyframes gradient-flow {
0% { transform: translateX(0); }
100% { transform: translateX(-240px); }
}
02Nút quét vệt sáng
Một dải sáng trắng nghiêng lướt qua nút tối một lần rồi để lại vệt long lanh như ánh đèn lướt trên thủy tinh. Dải sáng là phần tử giả ::after, đặt nghiêng bằng skewX(-20deg) và chỉ di chuyển bằng translateX nên nút rộng hay hẹp vẫn chạy đúng. Dành cho nút thanh toán, nút mua xứng đáng được nhìn thêm một lần.
.btn::after {
content: "";
position: absolute;
top: 0; bottom: 0; left: -60%;
width: 60%;
background: linear-gradient(115deg,
transparent 0%, rgba(255, 255, 255, .85) 50%, transparent 100%);
transform: skewX(-20deg) translateX(-10%);
}
@keyframes sweep {
0%, 10% { transform: skewX(-20deg) translateX(-10%); }
55% { transform: skewX(-20deg) translateX(310%); }
100% { transform: skewX(-20deg) translateX(310%); }
}
03Nút blob mềm
Hai hình tròn bạc hà nấp sau nhãn phồng ra hai bên làm khung nút giãn ra thụt vào như thạch. Hai hình tròn cùng đúng màu nút nên mép của chúng hòa làm một, và độ phồng chỉ dùng một scale kèm chút dịch ngang. Rất hợp với nút thêm vào giỏ ở shop đồ trẻ em.
.btn::before, .btn::after {
content: "";
position: absolute;
top: 50%;
width: 56px; height: 56px;
border-radius: 50%;
background: $color;
transform: translateY(-50%) scale(.3);
}
.stage.is-demo .btn::before { animation: blob-l $duration $easing infinite; }
@keyframes blob-l {
0%, 12% { transform: translateY(-50%) scale(.3); }
40% { transform: translateY(-50%) scale(1.3) translateX(-8px); }
100% { transform: translateY(-50%) scale(.3); }
}
04Nút viền tự vẽ
Viền nút tự vẽ mình vào từng nét như viết tay. Gắn thuộc tính pathLength="100" cho hình chữ nhật SVG thì dù độ dài thật là bao nhiêu cũng thành thước 100 vạch, nên chỉ cần hạ stroke-dashoffset từ 100 về 0 là xong bản vẽ. Sau đó một lớp nước màu nhạt đổ đầy, trọn một chu kỳ.
.btn__rect {
fill: none;
stroke: $color;
stroke-width: 3;
stroke-dasharray: 100;
stroke-dashoffset: 100;
}
.stage.is-demo .btn__rect { animation: draw $duration $easing infinite; }
@keyframes draw {
0%, 12% { stroke-dashoffset: 100; }
55% { stroke-dashoffset: 0; }
100% { stroke-dashoffset: 100; }
}
05Nút trượt mũi tên
Hai ô nằm cạnh nhau — một ô chỉ có nhãn, một ô có nhãn kèm mũi tên — và cả dải trượt đúng nửa chiều rộng. Khi hover chữ bị đẩy sang trái còn mũi tên lướt vào chỗ của nó từ bên phải, và vì dải cố định độ rộng 200% nên nhãn dài ngắn thế nào cũng không lệch. Hợp với nút xem thêm dẫn sang nội dung kế tiếp.
.btn__track { display: flex; width: 200%; height: 100%; }
.btn__cell { flex: 0 0 50%; display: flex; align-items: center; }
.stage.is-demo .btn__track { animation: slide-in $duration $easing infinite; }
@keyframes slide-in {
0%, 18% { transform: translateX(0); }
55%, 72% { transform: translateX(-50%); }
88% { transform: translateX(-4%); }
100% { transform: translateX(0); }
}
06Nút hai màu quét
Nút tách thành kem và đào, lớp màu dưới trải ra từ trái bằng scaleX làm cả mặt nút lật màu như nước loang. Chữ cũng có hai bản chồng nhau và đổi bằng opacity nên ngay khoảnh khắc chuyển màu vẫn đọc được. Hợp với nút bắt đầu trên thẻ giá.
.btn__wash {
position: absolute;
inset: 0;
background: $color;
transform: scaleX(0);
transform-origin: left;
}
@keyframes wash {
0%, 12% { transform: scaleX(0); }
48%, 70% { transform: scaleX(1); }
100% { transform: scaleX(0); }
}
.stage.is-demo .btn__t-on-dark { animation: swap-in $duration $easing infinite; }
07Nút gợn vòng tròn
Đúng lúc nhấn, thay cho gợn sóng là một vòng viền loang ra ngoài như tiếng chuông. Vòng 3px nằm ở ::after, phóng to bằng scale trong khi opacity xóa dần, nên chỉ cần đổi một giá trị màu là đặt lên được mọi nút icon. Trái tim đồng thời phồng lên rồi co nhẹ, rất hợp nút like và bookmark.
.btn::after {
content: "";
position: absolute;
inset: 0;
border-radius: 50%;
border: 3px solid $color;
opacity: 0;
}
@keyframes ring {
0% { transform: scale(1); opacity: .9; }
34% { transform: scale(1.8); opacity: 0; }
50% { transform: scale(1); opacity: .9; }
84% { transform: scale(1.8); opacity: 0; }
}
08Nút nhấn 3D
Một lớp cứng 8px nằm dưới nút khiến nó trông như phím bấm vật lý, và khi nhấn mặt nút lún đúng 8px bằng translateY trong khi lớp đó biến mất. Vòng lặp demo còn thêm cú nảy nhẹ khi thả tay; khi dùng thật :active chỉ cần hai dòng. Dành cho nút bắt đầu game và các nút to trong app trẻ em.
$depth: 8px;
.btn {
box-shadow: 0 $depth 0 darken($color, 22%),
0 #{$depth + 6px} #{$sp-3} rgba(23, 20, 26, .22);
}
.btn:active { transform: translateY($depth); }
@keyframes press {
0%, 20% { transform: translateY(0); }
35%, 55% { transform: translateY($depth); }
70% { transform: translateY(-3px); }
85%,100% { transform: translateY(0); }
}
09Nút pill có loader
Ngay lúc gửi file, nhãn mờ dần, nút khóa sang màu chàm đậm và trong chiếc pill một thanh ngắn trượt qua lại hai lần. Thanh chỉ di chuyển bằng translateX, hết thời gian chờ nhãn trở lại. Khi dùng thật chỉ cần bật tắt lớp is-busy trong lúc hàm gửi chạy.
.btn__bar {
position: absolute;
top: 0; bottom: 0;
width: 60px;
border-radius: $r-pill;
background: $subject-cream;
transform: translateX(-60px);
}
@keyframes bar-run {
0% { transform: translateX(-60px); }
100% { transform: translateX(168px); }
}
@keyframes busy-bg {
0%, 15% { background: $color; }
25%, 85% { background: $busy; }
95%,100% { background: $color; }
}
Ở đâu nó dễ hỏng — pastel mềm máy không nhìn thấy
Mục 01 lúc đầu được làm ngược với những gì bạn đang thấy: bản thân nền gradient được dời bằng background-position, bảng màu là bộ pastel hòa mờ. Nhìn bằng mắt thì rõ ràng là đang chảy, nhưng đo khung hình bằng máy ra cường độ 0.0 và số khung di chuyển bằng 0. Giữa hai khung gradient tiến 10px, mà mức đổi màu trong quãng đó nằm dưới ngưỡng đo nên không pixel nào được tính là đã đổi. Vẽ lại bằng dải sọc có biên rõ và di chuyển cả lớp bằng translateX mới qua được phép đo. Khoảng chạy của vệt sáng ở mục 02 đặt gấp nhiều lần băng rộng cũng vì lý do đó. Mật khẩu giải nén của chín thư mục là 3vxxw8yh, gõ đúng như vậy thì mới mở được. Chu kỳ sọc của gradient (240px) phải khớp chính xác với khoảng di chuyển cũng là điều mục này chứng minh ngay trên màn hình — lệch đúng 4px là hoa văn giật một lần mỗi vòng.
Khả năng tiếp cận
Cả chín kiểu đều rơi về animation: none dưới prefers-reduced-motion: reduce, và mỗi kiểu có một khung hình tĩnh vẫn tự giải thích được mình là gì. Mục 01 dừng ở vị trí thấy được dải sọc, mục 04 dừng với viền vẽ xong hoàn chỉnh, mục 05 dừng ở ô có cả nhãn và mũi tên. Thanh của mục 09 đỗ giữa đường để màu và hình vẫn nói "đang tải". Mọi nút hiện viền :focus-visible khi dùng bàn phím. Cách bật/tắt cài đặt này được ghi trong trang prefers-reduced-motion của MDN.
FAQ
Không biết JavaScript có dùng được không?
Có, cả chín kiểu chạy thuần bằng CSS (SCSS), không một dòng JavaScript. Ngay mục 09 khi dùng thật cũng chỉ cần thêm bớt lớp is-busy trên nút. Nếu muốn thêm các bước trước đó như ripple và hover, bài 9 hiệu ứng nút CSS có sẵn.
Đổi màu theo thương hiệu thì sửa ở đâu?
Biến $color trên đầu SCSS của mỗi mục là màu gốc. Mục 01 có thêm hai màu phụ ngoài $color, còn mục 08 tự sinh cạnh đậm bằng darken($color, 22%). Ở mục 06 $color là màu đào lớp dưới, nên muốn đổi màu trên thì sửa luôn dòng nền.
Trong zip có gì?
Mỗi mục gồm index.html, style.scss, style.css đã biên dịch, và cặp .tsx + .module.scss đặt thẳng vào dự án React. Duration, easing, color được kéo ra thành props nên đổi một giá trị là thành biến thể. Vật liệu nút khác tìm được qua tìm kiếm trên site.