Cheondi
개발 · 웹

비밀번호 마스킹과 포커스가 충돌했다

웹 입력창의 비밀번호 표시 전환 때 커서와 자동 완성이 흔들리는 문제를 DOM 상태로 해결한 기록입니다.

  • #web
  • #password
  • #input

비밀번호 보기 버튼을 누르면 입력 내용은 유지되지만 커서가 맨 끝으로 이동하거나 모바일 Safari에서 키보드가 닫혔다. input의 typepasswordtext 사이에서 바꾸는 순간 브라우저가 요소 상태를 다시 계산했다. 자동 완성 값과 앱 상태가 어긋나는 경우도 있었다.

사용자에게는 눈 아이콘 하나였지만 DOM 선택 범위와 포커스, 접근성 상태가 함께 움직였다.

선택 범위를 보존했다

버튼 클릭 전에 현재 시작과 끝 위치를 저장하고 타입 변경 뒤 포커스와 선택을 복구했다.

function togglePassword(input, visible) {
  const start = input.selectionStart;
  const end = input.selectionEnd;
  input.type = visible ? 'text' : 'password';
  input.focus({ preventScroll: true });
  input.setSelectionRange(start, end);
}

일부 브라우저에서는 다음 프레임에 복구해야 했고, 입력이 활성 상태일 때만 실행했다.

버튼이 입력 포커스를 빼앗지 않게 했다

마우스나 터치의 기본 동작으로 버튼이 먼저 포커스를 가져가면 모바일 키보드가 닫혔다. pointerdown에서 필요한 기본 동작만 막고 클릭 접근성과 키보드 조작은 유지했다.

버튼에는 현재 상태에 맞는 aria-labelaria-pressed를 제공했다. 비밀번호 값을 읽어 별도 텍스트 요소에 복사하지 않았다.

자동 완성 계약을 유지했다

type을 바꾸며 input 요소 자체를 새로 만들면 브라우저의 비밀번호 관리자 연결이 끊길 수 있었다. 같은 요소를 유지하고 name, autocomplete="current-password" 같은 의미 정보를 바꾸지 않았다.

로그에는 글자 수나 내용 대신 포커스 여부, 마스킹 상태, 브라우저 실행 모드만 남겼다.

작은 상호작용도 실제 기기에서 봐야 했다

데스크톱 Chrome에서는 문제없이 보여 수정이 끝난 줄 알기 쉬웠다. 모바일 Safari, Android 브라우저, PWA에서 입력 중간 커서, 한글 입력, 자동 완성, 키보드 다음 버튼을 각각 확인했다.

이 경험 뒤 입력 UI를 값 문자열만 다루는 컴포넌트로 보지 않는다. 선택 범위, 포커스, 브라우저 자동 완성, 접근성까지 사용자가 이어서 입력할 수 있는 하나의 상태였다.