GODRICH
#css애니메이션#버튼#로딩#전환효과#스크롤#대시보드#실전ui#디자인트렌드

인증번호 입력 UI 만들 때 겪는 9가지 상황 — 폼 검증까지 챙긴

인증번호 입력 UI는 6자리 숫자를 한 칸씩 나눠 받는 폼 부품으로, 다음 칸 이동부터 붙여넣기·오류 처리까지 한 번에 챙겨야 동작이 매끄러워집니다. 아홉 개 중 하나는 화면에 아무것도 안 보여도 실제로는 코드를 통째로 채워 넣습니다.

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

아홉 개는 인기순이 아니라 인증번호 입력 폼이 실제로 겪는 순서로 놓았습니다. 입력이 시작되는 흐름(01~04) 다음 확인과 보안(05~06), 재전송과 자동입력(07~08), 마지막으로 접근성(09)입니다. 화면마다 자리 하나씩 붙잡고 그 순간에 필요한 속성과 이벤트만 뽑아 실었습니다. 인증번호 말고 폼 전체의 위젯을 두루 살피려면 입력창 디자인 9가지가 같은 input 판을 넓게 펴고, 주소를 고르는 식의 선택 입력은 지도 ui 디자인 9종이 눌러 핀을 떨어뜨리는 형태로 다룹니다.

01다음 칸 자동 이동

어떤 움직임인가

숫자를 입력하면 커서가 자동으로 다음 칸으로 넘어갑니다.

다음 칸 자동 이동
새 창에서 열기

01 · 다음 칸 자동 이동 — 측정 누적면적 0.907% · 움직인 프레임 14/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

6칸자동 포커스숫자만

인증번호 입력 화면은 언제 쓰나

회원가입 인증이나 로그인 2단계 인증처럼 6자리를 연속으로 받는 화면에 씁니다.

boxes.forEach(function (b, i) {
  b.addEventListener('input', function () {
    b.value = b.value.replace(/[^0-9]/g, '').slice(-1);
    if (b.value && boxes[i + 1]) boxes[i + 1].focus({ preventScroll: true });
  });
});

02백스페이스 이전 칸 포커스

무엇이 보이나

빈 칸에서 백스페이스를 누르면 이전 칸으로 돌아가 지웁니다.

백스페이스 이전 칸 포커스
새 창에서 열기

02 · 백스페이스 이전 칸 포커스 — 측정 누적면적 0.888% · 움직인 프레임 18/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

백스페이스이전 칸연쇄 삭제

어디에 쓰나

인증번호를 잘못 눌렀을 때 처음부터 다시 지우지 않아도 됩니다.

boxes.forEach(function (b, i) {
  b.addEventListener('keydown', function (e) {
    if (e.key === 'Backspace' && !b.value && boxes[i - 1]) {
      boxes[i - 1].focus({ preventScroll: true });
    }
  });
});

03코드 전체 붙여넣기 분배

화면에서 일어나는 일

메일이나 메시지 앱에서 복사한 6자리 코드를 붙여넣으면 한 번에 칸마다 나눠 들어갑니다.

코드 전체 붙여넣기 분배
새 창에서 열기

03 · 코드 전체 붙여넣기 분배 — 측정 누적면적 0.432% · 움직인 프레임 8/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

clipboard6칸 분배숫자 외 무시

쓰는 자리

숫자가 아닌 글자는 걸러내고 남는 자리는 그대로 둡니다.

pin.addEventListener('paste', function (e) {
  var t = (e.clipboardData || window.clipboardData)
    .getData('text').replace(/[^0-9]/g, '');
  if (!t) return;
  e.preventDefault();
  t.slice(0, 6).split('').forEach(function (ch, k) {
    if (boxes[k]) boxes[k].value = ch;
  });
});

04모바일 숫자 키패드 강제

어떤 움직임인가

모바일에서는 문자 키패드 대신 숫자 키패드가 바로 뜹니다.

모바일 숫자 키패드 강제
새 창에서 열기

04 · 모바일 숫자 키패드 강제 — 측정 누적면적 0.8% · 움직인 프레임 14/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

inputmode numericpattern [0-9]*텐키

언제 쓰나

inputmode와 pattern 속성만으로 되고 별도 JS가 필요 없습니다.

<input
  type="text"
  inputmode="numeric"
  pattern="[0-9]*"
  maxlength="1"
  autocomplete="one-time-code"
  aria-label="인증번호 1번째 자리"
>

05마스킹 표시 토글

무엇이 보이나

눈 아이콘을 누르면 입력된 숫자가 점 대신 그대로 보입니다.

마스킹 표시 토글
새 창에서 열기

05 · 마스킹 표시 토글 — 측정 누적면적 0.702% · 움직인 프레임 16/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

마스킹토글점 대체

어디에 쓰나

은행 앱 계좌 비밀번호나 결제 pin 코드 입력처럼 값을 확인하고 싶을 때 씁니다.

var masked = true;
toggle.addEventListener('click', function () {
  masked = !masked;
  boxes.forEach(function (b) {
    b.type = masked ? 'password' : 'text';
  });
  toggle.textContent = masked ? '가려진 숫자 보기' : '다시 가리기';
});

06오류 시 흔들리고 초기화

화면에서 일어나는 일

잘못된 코드를 넣으면 칸 전체가 흔들리고 자동으로 비워집니다.

오류 시 흔들리고 초기화
새 창에서 열기

06 · 오류 시 흔들리고 초기화 — 측정 누적면적 5.167% · 움직인 프레임 15/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

흔들림 300ms자동 비움빨간 테두리

쓰는 자리

재입력을 유도하면서 화면에는 상태 문구를 따로 남겨 스크린 리더에도 실패 사실이 전달됩니다.

confirmBtn.addEventListener('click', function () {
  var code = boxes.map(function (b) { return b.value; }).join('');
  if (code.length < 6 || code !== realCode) {
    pin.classList.add('is-error');
    boxes.forEach(function (b) { b.value = ''; });
  }
});

07재전송 타이머

어떤 움직임인가

재전송 버튼은 60초 카운트다운 뒤에야 다시 누를 수 있습니다.

재전송 타이머
새 창에서 열기

07 · 재전송 타이머 — 측정 누적면적 1.097% · 움직인 프레임 11/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

60초비활성카운트다운

언제 쓰나

SMS 인증번호를 짧은 시간에 여러 번 요청하는 클릭을 막습니다.

var left = 60;
function tick() {
  resend.textContent = left > 0 ? left + '초 후 재전송' : '재전송';
  resend.disabled = left > 0;
  if (left > 0) { left--; setTimeout(tick, 1000); }
}
tick();

08SMS OTP 자동채움

무엇이 보이나

문자로 온 인증번호를 OS가 감지해 자동으로 채워 넣습니다.

SMS OTP 자동채움
새 창에서 열기

08 · SMS OTP 자동채움 — 측정 누적면적 0.749% · 움직인 프레임 9/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

autocompleteone-time-codeOS 감지

어디에 쓰나

autocomplete="one-time-code" 속성 하나로 iOS와 안드로이드 양쪽에서 동작합니다.

<input
  type="text"
  inputmode="numeric"
  autocomplete="one-time-code"
  maxlength="1"
>

09모션 축소 시 정적 포커스

화면에서 일어나는 일

모션을 줄인 설정에서는 애니메이션 없이 다음 칸에 바로 포커스가 옮겨갑니다.

모션 축소 시 정적 포커스
새 창에서 열기

09 · 모션 축소 시 정적 포커스 — 측정 누적면적 1.001% · 움직인 프레임 17/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

prefers-reduced-motion즉시 이동정적 포커스

쓰는 자리

전정기관에 민감한 사용자를 배려하면서 자동 이동이라는 기능 자체는 그대로 남깁니다.

@media (prefers-reduced-motion: reduce) {
  .pin__ghost, .pin__caret, .pin__signal {
    animation: none !important;
    transition: none !important;
  }
  .pin.is-demo .pin__ghost { opacity: 1; }
}

어디서 깨지는가 — 함정

자동 채움이 6칸을 놓칠 때

칸마다 autocomplete="one-time-code"를 걸면 대부분의 최신 브라우저에서 문자 인증코드를 자동으로 채워주지만, 일부 사파리·구형 안드로이드 브라우저는 6칸으로 나뉜 자동 채움을 종종 놓칩니다. web.dev의 OTP 폼 패턴 문서는 화면에는 6칸을 그대로 두되 자동 채움 자체는 화면 밖에 숨긴 input 하나에만 받고, 값이 들어오면 JS로 6칸에 나눠 넣는 절충을 권합니다. 이 zip의 08 항목은 그 절충 없이 칸마다 속성만 걸어 두었는데, 실제 서비스에 붙이기 전에 자동 채움이 기기 몇 대에서 실제로 성공하는지 직접 눌러 보고 넘어가는 편이 안전합니다. 입력이 여러 칸으로 나뉘는 대신 옵션 중 하나를 고르는 자리라면 세그먼트 컨트롤 선택 UI 쪽이 같은 자리의 다른 답입니다. 압축은 39kzjrs9로 열리며, 09까지 아홉 항목 전부가 그 안에 완결본으로 들어 있습니다. 붙여넣기 이벤트도 마찬가지 함정이 있어서, 칸마다 걸면 여러 번 겹쳐 실행될 수 있습니다. 03 항목처럼 감싸는 컨테이너 하나에만 paste 리스너를 걸어야 코드가 한 번만 분배됩니다.

접근성

동작 줄이기와 스크린 리더

아홉 개 모두 운영체제의 동작 줄이기 옵션에 반응합니다. 01·04·09는 캐럿과 신호가 사라지고 칸은 즉시 채워진 상태로만 보이며, 05는 눈 아이콘 깜빡임 없이 마스킹 상태만 바뀌고, 06은 흔들림 없이 오류 문구만 화면에 남습니다. 칸마다 aria-label로 몇 번째 자리인지 읽어주고, 06의 오류 문구는 role="status"로 스크린 리더가 따로 안내를 받습니다. 폼 검증을 시각 효과로 강조하고 싶다면 모달 애니메이션 아홉 가지에서 오류 흔들림과 페이드를 참고할 수 있고, 사이트 운영 방향은 소개 페이지에 정리해 두었습니다.

FAQ

핀번호 입력에 꼭 6칸으로 나눠야 합니까?

아니요. <input maxlength="6"> 하나로도 받을 수 있지만, 자리마다 칸을 나누면 남은 숫자가 몇 개인지 한눈에 보이고 자동 이동·백스페이스 같은 동작을 자리별로 다르게 다루기 쉬워집니다. 인증번호 앞 단계에 신분증 사진을 받는 화면이 붙는다면 본인인증 화면 UI 9가지가 그 앞장을 맡습니다.

autocomplete="one-time-code"는 어느 브라우저에서 됩니까?

사파리·크롬·엣지 최신 버전은 autocomplete="one-time-code"를 인식해 문자로 온 코드를 채워 줍니다. 정확한 브라우저·OS 조합은 caniuse.com에서 확인하고, 함정 항목에 적은 숨은 input 방식을 함께 테스트하는 편이 안전합니다.

여섯 자리가 아니라 네 자리 코드에도 씁니까?

됩니다. 이 zip의 리액트 컴포넌트는 length 값만 바꾸면 칸 수가 그대로 따라가므로 4자리 은행 PIN이나 8자리 백업 코드에도 같은 구조를 재사용할 수 있습니다.

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

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