GODRICH
#css-animation#button#loader#transition#scroll#ui#xu-huong-thiet-ke

9 mẫu form đăng ký & đăng nhập copy-paste được

Một mẫu form đăng ký tưởng chỉ có ô email, mật khẩu — nhưng là chín quyết định nhỏ: chất liệu thẻ, layout, tiến trình từng bước.

Tự chạy · bấm ô để tới mục đó · cả 9 trong một zip

Thứ tự chín mục dưới đây không theo độ phổ biến mà theo đúng trình tự mà một người thực sự trải qua khi đăng nhập hoặc đăng ký. Thẻ đập vào mắt trước (01, 02); bạn gõ tên hoặc email (03); gõ mật khẩu và xem độ mạnh (04); chọn một nút mạng xã hội (05) hoặc đi qua nhiều bước đăng ký (06); chờ link trong email (07); phiên hết hạn nên phải đăng nhập lại (08); và kết thúc bằng việc đồng ý điều khoản (09).

# Tên Cái gì chuyển động JS
01 Thẻ đăng nhập kiểu kính mờ vệt sáng transform, thẻ backdrop-filter: blur không
02 Đăng nhập chia đôi màn hình panel translateX khi vào không
03 Ô nhập đăng ký với nhãn nổi nhãn translateY + scale không
04 Thanh đo độ mạnh mật khẩu width/màu thanh, dòng trạng thái 14 dòng
05 Hàng nút đăng nhập mạng xã hội nút translateY khi hover không
06 Thanh tiến trình các bước đăng ký width đường nối chuyển tiếp không
07 Thẻ đã gửi magic link phong bì translateY nảy, đếm ngược 18 dòng
08 Modal đăng nhập lại khi hết phiên modal scale+opacity, nền blur không
09 Checkbox đồng ý điều khoản dấu check vẽ bằng stroke-dashoffset không

01Thẻ đăng nhập kiểu kính mờ (glassmorphism)

Bạn thấy gì

Một thẻ kính mờ trong suốt làm nhòe gradient phía sau, trong khi một vệt sáng mềm trôi chậm qua bề mặt để làm nổi bật chất liệu kính.

Thẻ đăng nhập kiểu kính mờ (glassmorphism)
Mở trong tab mới

01 · Thẻ đăng nhập kiểu kính mờ (glassmorphism) — Diện tích đo được 19.275% · khung hình chuyển động 18/23Nguyên tác GODRICH · SCSS và React nằm trong tệp zip · Xem mật khẩu

backdrop-filter: blur반투명 테두리@keyframes drift-a

Dùng ở đâu

Dùng cho modal đăng nhập trên trang landing sản phẩm, hay màn hình đầu tiên của SaaS theme tối. backdrop-filter: blur chỉ trông giống kính khi thực sự có thứ gì đó chuyển động phía sau, nên hai vệt sáng luôn trôi bằng transform: translate.

.glass-card {
  background: rgba(255, 255, 255, .14);
  border: 1px solid rgba(255, 255, 255, .35);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
}
.glass-scene__blob {
  border-radius: 50%;
  filter: blur(2px);
  animation: drift 6s $easing infinite;
}
@keyframes drift {
  0%, 100% { transform: translate(-30px, -20px) scale(1); }
  50%      { transform: translate(40px, 30px) scale(1.15); }
}

02Đăng nhập chia đôi màn hình (split-screen)

Chuyện gì diễn ra trên màn hình

Màn hình chia đôi — một bên là hình ảnh hoặc khẩu hiệu, bên kia là form — cả hai panel trượt vào một đoạn ngắn từ hai phía khi trang tải xong. Hợp với trang đăng nhập của sản phẩm chú trọng hình ảnh thương hiệu, hay màn hình xác thực quảng bá luôn app di động.

Đăng nhập chia đôi màn hình (split-screen)
Mở trong tab mới

02 · Đăng nhập chia đôi màn hình (split-screen) — Diện tích đo được 7.366% · khung hình chuyển động 10/23Nguyên tác GODRICH · SCSS và React nằm trong tệp zip · Xem mật khẩu

display: flex 반반@keyframes enter-leftlinear-gradient 패널

Chỗ dùng

Lý do khoảng cách trượt chỉ 16% chiều rộng chứ không trượt hẳn ra ngoài màn hình được giải thích ở phần "Bẫy nằm ở đâu" bên dưới.

.split { display: flex; overflow: hidden; }
.split__panel--image { animation: enter-left 2s $easing infinite; }
.split__panel--form  { animation: enter-right 2s $easing infinite; }
@keyframes enter-left {
  0%, 100%  { transform: translateX(-16%); }
  35%, 90%  { transform: translateX(0); }
}

03Ô nhập đăng ký với nhãn nổi (floating label)

Chuyển động thế nào

Nhãn nằm trong ô nhập sẽ bay lên và thu nhỏ ngay khi được focus hoặc có giá trị, nên ô vẫn có nhãn dù placeholder đã biến mất. Dùng cho form đăng ký thu thập email, tên, số điện thoại, hay form di động cần tiết kiệm chiều cao.

Ô nhập mẫu form đăng ký với nhãn nổi (floating label)
Mở trong tab mới

03 · Ô nhập mẫu form đăng ký với nhãn nổi (floating label) — Diện tích đo được 1.618% · khung hình chuyển động 11/23Nguyên tác GODRICH · SCSS và React nằm trong tệp zip · Xem mật khẩu

:placeholder-showntransform: translateY + scale@keyframes ring-a

Dùng khi nào

Chìa khóa là input:not(:placeholder-shown) — đặt placeholder=" " (một khoảng trắng) thì trạng thái này chỉ đúng khi ô trống, nên nhãn bay lên mà không cần JS.

.float-field__label {
  transform-origin: left center;
  transition: transform $duration $easing, color $duration $easing;
}
.float-field__input:focus + .float-field__label,
.float-field__input:not(:placeholder-shown) + .float-field__label {
  transform: translateY(-135%) scale(.78);
  color: $color;
}

04Thanh đo độ mạnh mật khẩu

Bạn thấy gì

Thanh đo đổi màu từ đỏ sang cam rồi xanh lá khi bạn gõ, dựa trên độ dài và kiểu ký tự, còn nhãn bên cạnh báo yếu/trung bình/mạnh theo thời gian thực. Dùng cho ô nhập mật khẩu khi đăng ký và màn hình đổi mật khẩu.

Thanh đo độ mạnh mật khẩu
Mở trong tab mới

04 · Thanh đo độ mạnh mật khẩu — Diện tích đo được 1.076% · khung hình chuyển động 19/23Nguyên tác GODRICH · SCSS và React nằm trong tệp zip · Xem mật khẩu

input 이벤트커스텀 프로퍼티 --strengtharia-live

Dùng ở đâu

Bốn điều kiện độ dài, chữ hoa thường, số, ký tự đặc biệt được kiểm riêng — ba điều kiện sau bằng test() với regex, điều kiện độ dài bằng phép so sánh — rồi điểm số đó đổi cả width lẫn màu của thanh cùng lúc.

.pw-meter__fill { transition: width $duration $easing, background-color $duration $easing; }
inp.addEventListener('input', function(){
  var v = inp.value, s = 0;
  if (v.length >= 6) s++;
  if (/[a-z]/.test(v) && /[A-Z]/.test(v)) s++;
  if (/[0-9]/.test(v)) s++;
  if (/[^A-Za-z0-9]/.test(v)) s++;
  var pct = v.length ? [15,40,70,100,100][s] : 0;
  fill.style.width = pct + '%';
});

05Hàng nút đăng nhập mạng xã hội

Chuyện gì diễn ra trên màn hình

Các nút đăng nhập kiểu Google, Apple, Kakao xếp thành một hàng và hơi nhô lên, đổ bóng đậm hơn ngay khi con trỏ chạm vào. Dùng cho màn hình ưu tiên đăng nhập mạng xã hội đặt trước form email, hay dịch vụ muốn xếp nhiều cách đăng nhập cạnh nhau.

Hàng nút đăng nhập mạng xã hội
Mở trong tab mới

05 · Hàng nút đăng nhập mạng xã hội — Diện tích đo được 4.935% · khung hình chuyển động 21/23Nguyên tác GODRICH · SCSS và React nằm trong tệp zip · Xem mật khẩu

transform: translateY 호버stagger animation-delayfocus-visible 링

Chỗ dùng

Chỉ transform: translateY chuyển động, không dùng top, nên layout không phải tính lại.

.social-btn {
  transition: transform $duration $easing, box-shadow $duration $easing;
}
.social-btn:hover, .social-btn:focus-visible {
  transform: translateY(-3px);
  box-shadow: $shadow-press;
}

06Thanh tiến trình các bước đăng ký

Chuyển động thế nào

Ba chấm bước 1-2-3 nối với nhau bằng một đường kẻ, và đường kẻ đó chạy đầy mỗi khi bạn chuyển sang bước tiếp theo. Dùng cho đăng ký nhiều bước chia nhỏ thông tin cần nhập, hay onboarding qua nhiều bước trước khi thanh toán.

Thanh tiến trình các bước đăng ký
Mở trong tab mới

06 · Thanh tiến trình các bước đăng ký — Diện tích đo được 0.337% · khung hình chuyển động 20/23Nguyên tác GODRICH · SCSS và React nằm trong tệp zip · Xem mật khẩu

커스텀 프로퍼티 --progresswidth 전환step-end 타이밍

Dùng khi nào

Ở bản React chỉ cần một prop step, đường nối phía trước sẽ tự chuyển width, nên component cha chỉ cần truyền đang ở bước nào.

.stepper__fill {
  width: 0%;
  background: $color;
  border-radius: $r-xs;
}
.stepper.is-demo .stepper__fill--1 { animation: fill-1 $dur-loop $easing infinite; }
.stepper.is-demo .stepper__step--1 .stepper__dot,
.stepper.is-demo .stepper__step--2 .stepper__dot {
  animation: dot-fill $dur-loop step-end infinite;
}

07Thẻ đã gửi magic link

Bạn thấy gì

Với kiểu đăng nhập không mật khẩu bằng link, ngay sau khi gửi email biểu tượng phong bì nảy lên và nút gửi lại bị khóa cho đến khi đếm ngược về 0. Dùng cho luồng đăng nhập không mật khẩu (magic link) và màn hình chờ xác thực email.

Thẻ đã gửi magic link
Mở trong tab mới

07 · Thẻ đã gửi magic link — Diện tích đo được 1.242% · khung hình chuyển động 23/23Nguyên tác GODRICH · SCSS và React nằm trong tệp zip · Xem mật khẩu

setInterval 카운트다운disabled 토글@keyframes bounce

Dùng ở đâu

Phong bì nảy là vòng lặp @keyframes luôn chạy, còn đếm ngược là 18 dòng setInterval thật, trừ mỗi giây một lần và mở khóa nút khi về 0.

var n = 30;
var timer = setInterval(function () {
  n--;
  cnt.textContent = n;
  if (n <= 0) {
    clearInterval(timer);
    btn.disabled = false;
    btn.textContent = 'Resend link';
  }
}, 1000);

08Modal yêu cầu đăng nhập lại khi hết phiên

Chuyện gì diễn ra trên màn hình

Khi phiên hết hạn sau một khoảng thời gian, nền bị làm mờ và modal phóng to xuất hiện, báo rằng bạn phải đăng nhập lại mới quay lại được màn hình trước đó.

Modal yêu cầu đăng nhập lại khi hết phiên
Mở trong tab mới

08 · Modal yêu cầu đăng nhập lại khi hết phiên — Diện tích đo được 26.689% · khung hình chuyển động 7/23Nguyên tác GODRICH · SCSS và React nằm trong tệp zip · Xem mật khẩu

backdrop-filter: blurfade + scale 진입포커스 이동

Chỗ dùng

Dùng cho thông báo tự động đăng xuất trên dashboard quản trị, hay ứng dụng web mở lại sau thời gian không dùng.

.modal-scene__backdrop {
  background: rgba(23, 20, 26, 0);
  backdrop-filter: blur(0px);
  transition: background $duration $easing, backdrop-filter $duration $easing;
}
.modal-scene__modal {
  transform: translate(-50%, -50%) scale(.7);
  opacity: 0;
  transition: transform $duration $easing, opacity $duration $easing;
}

09Checkbox đồng ý điều khoản

Chuyển động thế nào

Chạm vào ô vuông sẽ vẽ dấu check theo từng nét, đồng thời viền cả thẻ đổi màu cùng lúc với dòng chữ đồng ý điều khoản nằm ngay bên cạnh. Dùng cho bước đồng ý điều khoản dịch vụ, quyền riêng tư ở cuối đăng ký, và checkbox đồng ý nhận marketing.

Checkbox đồng ý điều khoản
Mở trong tab mới

09 · Checkbox đồng ý điều khoản — Diện tích đo được 0.647% · khung hình chuyển động 7/23Nguyên tác GODRICH · SCSS và React nằm trong tệp zip · Xem mật khẩu

stroke-dashoffset 드로잉:has(:checked):focus-visible outline

Dùng khi nào

Một <input type="checkbox"> thật vẫn nằm trong DOM nhưng ẩn đi, còn pathLength="1" trên path SVG biến stroke-dashoffset thành một giá trị duy nhất từ 0 đến 1, nên trình đọc màn hình vẫn đọc đúng checkbox thật bên dưới.

.consent__check {
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
  transition: stroke-dashoffset $duration $easing;
}
.consent__input:checked ~ .consent__box .consent__check {
  stroke-dashoffset: 0;
}
.consent-card:has(.consent__input:checked) {
  border-color: $color;
}

Bẫy nằm ở đâu

Khoảng cách trượt ở mục 02

Mục 02 (split-screen) ban đầu để khoảng cách trượt vào là 100% chiều rộng (trượt hẳn ra ngoài màn hình), nhưng ảnh tĩnh tự động (poster) lại toàn bắt trúng khoảnh khắc panel mới vào được nửa chừng, khiến thẻ trông như bị cắt chữ. Poster chọn khung hình có mức thay đổi pixel lớn nhất giữa hai lần chụp, mà với animation trượt vào thì khoảnh khắc "thay đổi lớn nhất" luôn rơi vào giữa lúc panel còn treo lơ lửng nửa trong nửa ngoài. Giảm khoảng cách trượt xuống 16% thì dù poster bắt trúng khung nào, chữ và nút vẫn hiện đầy đủ — không nhất thiết phải cho thứ gì trượt hẳn ra ngoài khung hình mới bán được cảm giác "vừa xuất hiện". Modal hết phiên ở mục 08 cũng vì lý do này mà giữ đoạn chuyển tiếp trong 15% của cả chu kỳ. Mật khẩu giải nén chín tệp này là fc9jedg2, nằm ngay trong câu này chứ không đóng khung hay tô màu nổi bật.

Khả năng tiếp cận

Trạng thái tĩnh khi giảm chuyển động

Cả chín mục đều tắt vòng lặp và chuyển tiếp khi prefers-reduced-motion: reduce, dừng lại gần trạng thái nghỉ. 01 dừng vệt sáng và chỉ còn vẻ kính tĩnh; 02 giữ cả hai panel đứng yên ở giữa; 03 chuyển trạng thái nhãn ngay lập tức; 04 nhảy thẳng đến giá trị đích không qua chuyển tiếp; 06 chỉ dừng vòng lặp và giữ nguyên trạng thái nghỉ (chưa lấp đầy) của đường nối; 05 áp dụng ngay hiệu ứng nhô lên khi hover không cần easing; 07 dừng nảy phong bì và chỉ còn số đếm ngược; 08 hiện hoặc ẩn modal không qua chuyển tiếp; 09 vẽ dấu check ngay lập tức. Mục 03 và 09 vẫn dùng phần tử form thật (input), nên việc di chuyển bằng phím Tab và trình đọc màn hình hoạt động như trên bất kỳ form gốc nào — mục 09 không tự đặt role riêng mà dùng đúng vai trò checkbox mặc định của trình duyệt. Mức hỗ trợ trình duyệt cho backdrop-filter có trong tài liệu MDN. Muốn xem thêm ý tưởng animation cho checkbox, radio, có thể xem bộ sưu tập animation checkbox radio tùy chỉnh và bộ sưu tập hiệu ứng focus ô nhập.

FAQ

Nhãn nổi ở mục 03 có thực sự không cần JavaScript nào không?

Đúng vậy. Đặt placeholder=" " (một khoảng trắng) khiến :placeholder-shown chỉ đúng khi ô còn trống, nên chỉ hai selector input:not(:placeholder-shown) + label và input:focus + label làm hết việc nâng và hạ nhãn. Bản React cũng dùng lại đúng CSS này, không có useState.

Checkbox ở mục 09 có thể dùng để submit form thật không?

Có. Nó chỉ bị ẩn về mặt hiển thị bằng opacity: 0 — <input type="checkbox"> thật vẫn còn đó, nên đặt trong <form> kèm thuộc tính name thì giá trị vẫn được gửi đi như mọi checkbox khác. Biểu tượng dấu check nhìn thấy chỉ là phần trang trí đi theo trạng thái của checkbox thật đó.

Đếm ngược ở mục 07 sẽ ra sao nếu tab chạy nền?

Hầu hết trình duyệt làm chậm (throttle) setInterval ở tab chạy nền, nên đếm ngược có thể chạy chậm hơn 30 giây thật. Nếu cần thời gian chính xác, cách an toàn hơn là lưu lại thời điểm bắt đầu rồi tính lại số giây còn lại từ chênh lệch với Date.now(), thay vì tin vào số lần tick.

Nhập mật khẩu file nén

Mật khẩu nằm ngay trong bài viết này. Đọc tới đó bạn sẽ thấy.