GODRICH

로그인 화면 디자인 9가지 — 복붙 가능한 카드 UI

로그인 화면 디자인은 이메일·비밀번호 칸과 버튼 몇 개로 끝나는 것 같지만, 실제로는 카드 재질·화면 분할·단계 진행·오류 알림까지 아홉 가지 결정이 쌓여 만들어집니다. 그중 하나는 팝업 모달 대신 이미 열린 화면 위에 흐림 효과만 얹어 "세션 만료"를 알립니다.

자동 재생 · 눌러서 해당 항목으로 · 전부 한 zip

아홉 개는 인기 순서가 아니라 사용자가 로그인·회원가입 화면에서 실제로 겪는 순서로 놓았습니다. 카드가 처음 눈에 띄고(01, 02), 이름·이메일을 입력하고(03), 비밀번호를 입력하면서 강도를 확인하고(04), 소셜 로그인 버튼 중 하나를 고르거나(05) 여러 단계를 거쳐 가입을 진행하고(06), 메일함에서 링크를 기다리고(07), 세션이 끊겨 다시 로그인해야 하고(08), 마지막으로 약관에 동의하며 끝납니다(09).

번호 이름 무엇이 움직이나 JS
01 글래스모피즘 로그인 카드 블롭 transform 이동, 카드 backdrop-filter: blur 없음
02 스플릿 스크린 로그인 좌우 패널 translateX 진입 없음
03 플로팅 라벨 회원가입 입력창 라벨 translateY + scale 없음
04 비밀번호 강도 표시바 막대 width·배경색, 상태 텍스트 11줄
05 소셜 로그인 버튼 줄 버튼 translateY 호버 없음
06 회원가입 단계 진행바 연결선 width 전환 없음
07 매직 링크 발송 완료 카드 봉투 translateY 바운스, 카운트다운 18줄
08 세션 만료 재로그인 모달 모달 scale+opacity, backdrop blur 없음
09 약관 동의 체크박스 체크 stroke-dashoffset 드로잉 없음

01글래스모피즘 로그인 카드

반투명 유리 재질 카드가 배경 그라데이션을 흐릿하게 비추고, 카드 뒤 빛 얼룩이 천천히 움직이며 유리 질감을 살립니다. 제품 랜딩 페이지의 로그인 모달이나 다크 테마 SaaS 서비스의 첫 화면에 씁니다. backdrop-filter: blur는 카드 자체가 아니라 카드 뒤에서 실제로 움직이는 것이 있어야 유리처럼 보이므로, 블롭 두 개를 transform: translate로 계속 움직입니다.

backdrop-filter: blur반투명 테두리@keyframes drift-a
.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스플릿 스크린 로그인

화면을 반으로 나눠 한쪽엔 이미지·문구, 다른 쪽엔 입력 폼을 놓고, 처음 열릴 때 두 패널이 양쪽에서 살짝 밀려 들어옵니다. 브랜드 이미지가 중요한 서비스의 로그인 페이지나 앱 다운로드를 함께 홍보하는 인증 화면에 어울립니다. 진입 거리를 화면 폭의 100%가 아니라 16% 정도로만 잡은 이유는 함정 섹션에 있습니다.

display: flex 반반@keyframes enter-leftlinear-gradient 패널
.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플로팅 라벨 회원가입 입력창

입력칸 안에 있던 라벨이 포커스되거나 값이 채워지면 위로 떠오르며 작아져서, placeholder가 사라져도 무슨 칸인지 계속 보입니다. 이메일·이름·전화번호를 받는 회원가입 폼, 자리를 아껴야 하는 모바일 폼에 씁니다. input:not(:placeholder-shown)이 핵심으로, placeholder=" "(스페이스 한 칸)를 넣어두면 값이 없을 때만 그 상태가 아니게 되어 JS 없이도 라벨이 뜹니다.

:placeholder-showntransform: translateY + scale@keyframes ring-a
.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;
}

04비밀번호 강도 표시바

입력 중인 비밀번호 길이와 문자 조합에 따라 막대가 빨강→주황→초록으로 채워지고, 옆 텍스트가 "약함/보통/강함"을 실시간으로 알려줍니다. 회원가입 비밀번호 입력칸, 비밀번호 변경 화면에 씁니다. 길이·대소문자 혼합·숫자·특수문자 네 가지를 각각 test()로 확인해 점수를 매기고, 그 점수로 막대 너비와 색을 한 번에 바꿉니다.

input 이벤트커스텀 프로퍼티 --strengtharia-live
.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 + '%';
});

05소셜 로그인 버튼 줄

구글·애플·카카오 같은 소셜 로그인 버튼이 한 줄로 늘어서 있고, 커서를 올리면 살짝 떠오르며 그림자가 진해집니다. 이메일 가입 앞에 놓는 소셜 로그인 우선 화면, 여러 로그인 수단을 나란히 보여줘야 하는 서비스에 씁니다. transform: translateY만 옮기고 top을 쓰지 않아 레이아웃이 다시 계산되지 않습니다.

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

06회원가입 단계 진행바

1·2·3 단계 점이 연결선으로 이어져 있고, 다음 단계로 넘어갈 때마다 연결선이 채워지는 애니메이션이 재생됩니다. 정보를 나눠 받는 다단계 회원가입, 결제 전 여러 단계를 거치는 온보딩에 씁니다. React 판에서는 step prop 하나만 바뀌면 그 앞 연결선의 width가 전환되도록 만들어, 부모가 지금 몇 단계인지만 넘기면 됩니다.

커스텀 프로퍼티 --progresswidth 전환step-end 타이밍
.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;
}

07매직 링크 발송 완료 카드

비밀번호 없이 링크로 로그인하는 방식에서, 메일을 보낸 직후 봉투 아이콘이 통통 튀고 재전송 버튼은 카운트다운이 끝날 때까지 눌리지 않습니다. 비밀번호 없는 로그인(매직 링크)을 쓰는 서비스, 이메일 인증 대기 화면에 씁니다. 봉투 바운스는 CSS @keyframes가 항상 돌지만, 카운트다운은 실제 setInterval 18줄로 1초마다 숫자를 줄이다 0이 되면 버튼을 풀어줍니다.

setInterval 카운트다운disabled 토글@keyframes bounce
var n = 30;
var timer = setInterval(function () {
  n--;
  cnt.textContent = n;
  if (n <= 0) {
    clearInterval(timer);
    btn.disabled = false;
    btn.textContent = 'Resend link';
  }
}, 1000);

08세션 만료 재로그인 모달

일정 시간이 지나 세션이 끊기면 배경이 흐려지고 모달이 확대되며 나타나, 다시 로그인해야 이전 화면으로 돌아갈 수 있음을 알려줍니다. 관리자 대시보드의 자동 로그아웃 알림, 장시간 방치 후 다시 여는 웹앱에 씁니다.

backdrop-filter: blurfade + scale 진입포커스 이동
.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;
}

09약관 동의 체크박스

네모 칸을 누르면 체크 표시가 선을 그리듯 나타나고, 옆 "약관에 동의합니다" 문구와 함께 카드 테두리 색이 바뀝니다. 회원가입 마지막 단계의 이용약관·개인정보 동의, 마케팅 수신 동의 체크에 씁니다. 진짜 <input type="checkbox">를 화면에서만 숨기고 SVG pathpathLength="1"stroke-dashoffset을 0~1 사이 값 하나로 다뤄, 스크린리더는 숨겨진 원래 체크박스를 그대로 읽습니다.

stroke-dashoffset 드로잉:has(:checked):focus-visible outline
.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;
}

어디서 깨지는가 (함정)

02번 스플릿 스크린은 처음에 진입 거리를 화면 폭의 100%(완전히 화면 밖)로 만들었더니, 자동 재생 미리보기의 정지 화면(포스터)이 하필 패널이 절반만 들어온 순간을 붙잡아 "글자가 잘린 카드"처럼 보였습니다. 포스터는 프레임 사이에서 픽셀이 가장 크게 바뀌는 순간을 고르는데, 슬라이드 진입 애니메이션은 그 "가장 크게 바뀌는 순간"이 항상 패널이 반쯤 걸친 상태라서 생기는 문제입니다. 진입 거리를 16%로 줄이자 어느 프레임을 붙잡아도 텍스트와 버튼이 다 보였습니다 — 카드나 버튼 등장 애니메이션을 만들 때 "화면 밖에서 등장"을 항상 100% 이동으로 잡을 필요는 없다는 걸 이번에 확인했습니다. 08번 세션 만료 모달도 같은 이유로 전환 구간을 전체 주기의 15% 안쪽으로 짧게 잡았습니다. zip 압축을 풀 때 필요한 비밀번호는 fc9jedg2으로, 굳이 상자로 강조하지 않고 이 문장 안에 그대로 적어 두었습니다.

접근성

아홉 개 전부 prefers-reduced-motion: reduce에서 반복 애니메이션과 전환을 끄고 최종 상태 근처에서 멈춥니다. 01은 블롭이 멈추고 유리 질감만 남고, 02는 두 패널이 중앙에 멈추고, 03은 라벨 전환만 즉시 일어나고, 04는 막대가 전환 없이 바로 값으로 바뀌고, 06은 반복 재생이 멈추고 연결선이 처음 상태로 남고, 05는 호버 lift가 즉시 적용되고, 07은 봉투 바운스가 멈추고 카운트다운 숫자만 남고, 08은 모달이 전환 없이 바로 보이거나 사라지고, 09는 체크 선이 전환 없이 바로 그려집니다. 03·09번처럼 실제 폼 요소(input)를 그대로 쓴 항목은 키보드 탭 이동과 스크린리더가 기본으로 동작합니다 — 09번의 role은 브라우저 기본 체크박스 역할을 그대로 쓰고 별도로 지정하지 않았습니다. backdrop-filter 브라우저 지원 범위는 MDN 문서에서 확인할 수 있습니다. 체크박스·라디오 쪽 애니메이션을 더 보고 싶다면 체크박스·라디오 커스텀 애니메이션 모음과 포커스 상태를 다루는 입력창 포커스 효과 모음도 함께 보면 좋습니다.

FAQ

03번 플로팅 라벨은 정말 JS가 하나도 없습니까?

네. placeholder=" "(스페이스 한 칸)를 넣어두면 :placeholder-shown이 "값이 비어 있을 때"만 참이 되고, input:not(:placeholder-shown) + labelinput:focus + label이라는 CSS 선택자 두 개만으로 라벨이 뜨고 내려갑니다. React 판에서도 useState 없이 같은 CSS를 그대로 씁니다.

09번 체크박스는 실제로 폼 제출에 쓸 수 있습니까?

네. 화면에서는 opacity: 0으로 숨겼을 뿐 실제 <input type="checkbox">가 그대로 있어서, <form> 안에 넣으면 name 속성만 붙여 다른 체크박스처럼 값이 전송됩니다. 시각적인 체크 아이콘은 그 진짜 체크박스의 상태를 따라가는 장식일 뿐입니다.

07번 카운트다운은 탭을 백그라운드에 두면 어떻게 됩니까?

브라우저 대부분이 백그라운드 탭에서 setInterval 주기를 늦추므로(스로틀링) 카운트다운이 실제 30초보다 느리게 줄어들 수 있습니다. 정확한 시각이 중요하면 시작 시각을 저장해 두고 Date.now() 차이로 남은 초를 다시 계산하는 방식이 더 안전합니다.

압축 비밀번호를 입력하세요

비밀번호는 이 글 본문 안에 있습니다. 읽다 보면 나옵니다.