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

공식 문서 기반의 추가 정리

나중에 같은 증상을 다시 만나지 않으려고 문서에 적힌 기준으로 구현을 한 번 더 정리했다.

HTML 문서의 focus, form, history 동작은 브라우저가 관리하는 상태다. 앱 코드가 보이는 컴포넌트만 바꿔도 현재 focus와 history entry가 자동으로 의도대로 정리된다고 볼 수 없다. 모바일 키보드나 Safari 외부 이동에서 차이가 커지는 이유도 이 경계에 있다.

사용자 조작을 재현할 때는 클릭 결과만 보지 않고 focus가 어디에 남았는지, history가 한 번 추가됐는지, 복귀 시 문서가 새로 만들어졌는지까지 확인하는 편이 낫다.

다음에 비밀번호 마스킹과 포커스 충돌 같은 문제를 보면 아래 순서부터 확인하려고 한다.

  • focus 획득·해제 이벤트 순서
  • 키보드 표시 전후 viewport 높이
  • selection·cursor 위치 보존
  • 자동 완성과 수동 입력의 구분

브라우저·WebView·설치형 PWA를 같은 환경으로 묶지 말고 지원 범위별 fallback을 둬야 한다.

공식 참고 자료

  • W3C HTML 5.2 — 폼·포커스·autocomplete·탐색 동작
  • W3C WCAG 2.1 — 확대·reflow·orientation·입력 접근성