💬

Popover API — 브라우저 네이티브 툴팁이 바꾸는 것들

JavaScript 이벤트 리스너, ARIA 수동 동기화, 상태 관리를 HTML 속성 2개로 대체

툴팁을 만들어 본 사람은 안다. 겉보기엔 단순한데, 실제 사용자한테 배포하면 문제가 쏟아진다.

Tab으로 트리거에 진입해도 안 나온다. 스크린 리더가 두 번 읽거나 아예 무시한다. 마우스를 빠르게 움직이면 깜빡인다. Esc가 안 먹힌다. 포커스가 사라진다.

시간이 지나면 이벤트 리스너가 쌓이고, hover/focus 별도 처리, 외부 클릭 특수 케이스, ARIA 속성 수동 동기화... 코드가 비대해진다.

라이브러리를 쓴 이유

포지셔닝만으로도 의존성이 정당화될 정도로 복잡하다. 스크롤 컨테이너, transform, 반응형 레이아웃에서 툴팁 위치를 잡는 건 쉬운 일이 아니다.

근데 진짜 문제는 접근성이다. 마우스 사용자는 즉시성을, 키보드 사용자는 예측 가능성을 원한다. 둘 다 만족시키려면 지연과 엣지 케이스가 생긴다. 스크린 리더에서 읽히거나, 안 읽히거나, 두 번 읽히는 등 비일관적 동작.

ARIA 속성 하나 놓치면 접근성 트리에서 혼란이 발생한다. 코드가 잘못된 게 아니라 플랫폼에 적절한 도구가 없었던 거다.

Popover API 적용

<button popovertarget="tip-1">도움말</button>
<div id="tip-1" popover="manual" role="tooltip">
  여기에 툴팁 내용
</div>

이게 전부다. 이벤트 리스너 없음. 상태 추적 없음. JavaScript에서 ARIA 업데이트 없음.

포커스하면 표시되고, Esc 누르면 사라진다.

뭐가 달라지나

열기/닫기 — 브라우저가 HTML만으로 처리한다. 트리거와 툴팁 사이 관계가 마크업에 명시적으로 존재.

Esc 키 — 키 리스너를 추가한 적 없는데 작동한다. 브라우저가 popover 해제 가능성을 자체적으로 이해.

aria-expanded — popover 열림/닫힘에 따라 자동 업데이트. 수동 관리가 필요 없으니 오래된 상태가 남을 위험도 없다.

포커스 복원 — popover가 닫히면 트리거로 포커스가 자동 복원된다. 포커스 복원 코드를 추가한 게 아니라 제거했다.

코드 감소보다 중요한 건 책임의 전환이다. 이전엔 JavaScript가 "존재시킨" 툴팁이, 이제는 브라우저가 마크업에서의 역할을 이해하고 포커스 모델, 접근성 트리, 네이티브 해제 규칙에 참여한다.

Invoker Commands

popovertarget="id"가 버튼을 popover에 연결하고, popovertargetaction으로 동작을 지정한다.

  • show — 열기만

  • hide — 닫기만

  • toggle (기본값) — 열려 있으면 닫고, 닫혀 있으면 열기

하나의 툴팁에 여러 트리거를 연결할 수 있다.

아직 부족한 영역

타이밍 제어 — 네이티브 popover는 즉시 열고 닫힌다. 마우스를 빠르게 움직이거나 트리거를 스치면 깜빡임이 생긴다. hover/focus와 열림 사이의 딜레이는 아직 JavaScript가 필요하다.

CSS에서 interest/invoker 관련 작업이 진입/퇴장 딜레이를 CSS로 직접 표현하는 방향으로 진행 중이긴 하다.

호버 인텐트 — 브라우저는 호버가 의도적인지, 포인터가 지나가는 중인지 판단할 수 없다. 숨김 전 짧은 딜레이, 포인터가 툴팁으로 이동할 때 해제 취소 같은 동작은 JavaScript로 처리해야 한다.

popover="manual"의 포커스 — manual에서는 auto와 달리 포커스 자동 복원이 안 된다. 블러로 닫을 때 트리거로 포커스를 명시적으로 반환해야 한다.

라이브러리가 여전히 유효한 경우

대규모 디자인 시스템에서는 중앙화된 동작, 일관된 기본값을 위해 라이브러리가 합리적이다. 중첩 스크롤 컨테이너, 커스텀 플리핑 로직 같은 복잡한 포지셔닝이 필요하면 Floating UI 같은 도구가 여전히 유리하다.

CSS anchor positioning이 이 문제의 상당 부분을 커버하기 시작했다. 순수 CSS로 트리거 기준 상대 배치, 뷰포트 인식 배치, 엣지 플리핑이 가능해지고 있다. Interop에 포함되어 완전한 크로스 브라우저 지원 전망이 있다.

기본값이 네이티브 API로 전환 중이다. 가장 단순한 툴팁이 동시에 가장 정확한 툴팁이 될 수 있는 첫 번째 시점.

기존 방식 vs Popover API

항목 기존 (JavaScript) Popover API
열기/닫기 이벤트 리스너 + 상태 변수 HTML 속성만
Esc 키 처리 keydown 리스너 수동 추가 브라우저 자동
aria-expanded JS에서 수동 동기화 자동 동기화
포커스 복원 닫을 때 수동 복원 코드 자동 복원 (auto)
타이밍/딜레이 JS로 제어 아직 JS 필요
호버 인텐트 JS로 판별 아직 JS 필요
포지셔닝 Floating UI 등 라이브러리 CSS anchor positioning (진행 중)

최소 구현 코드

<button popovertarget="tip-1">도움말</button> <div id="tip-1" popover="manual" role="tooltip"> 여기에 툴팁 내용 </div>

JavaScript 0줄. 이벤트 리스너 0개. ARIA 수동 업데이트 0건.

popovertargetaction 옵션

toggle 기본값. 열려 있으면 닫고, 닫혀 있으면 연다.
show 열기만. 이미 열려 있으면 아무 일도 안 한다.
hide 닫기만. 이미 닫혀 있으면 아무 일도 안 한다.

popover="auto" vs popover="manual"

동작 auto manual
외부 클릭으로 닫기 자동 안 됨
Esc로 닫기 자동 자동
다른 popover 열면 닫힘 자동 안 됨
포커스 자동 복원 자동 수동 필요

CSS Anchor Positioning — 다음 단계

포지셔닝 라이브러리가 해결하던 문제를 CSS가 커버하기 시작했다. 트리거 기준 상대 배치, 뷰포트 인식 배치, 엣지 플리핑이 순수 CSS로 가능해지고 있다. Interop에 포함되어 크로스 브라우저 지원 전망이 있지만, 아직 신규 기능이라 프로덕션 사용은 상황에 따라 판단이 필요하다.

핵심 포인트

1

popover 속성 + popovertarget 속성만으로 열기/닫기, Esc 키, 키보드 내비게이션이 자동 동작

2

aria-expanded가 popover 열림/닫힘에 따라 자동 동기화 — ARIA 버그 범주 자체가 제거

3

타이밍 제어, 호버 인텐트 판별은 여전히 JavaScript 필요 — 핵심 상호작용만 브라우저 담당

4

CSS anchor positioning이 포지셔닝 문제도 커버 시작 — Interop 포함으로 크로스 브라우저 지원 전망

사용 사례

단순 툴팁 — JavaScript 0줄로 접근성 완벽한 네이티브 툴팁 드롭다운 메뉴 — popovertargetaction="toggle"로 열기/닫기 + Esc 자동 해제 디자인 시스템 마이그레이션 — 기존 커스텀 툴팁에서 네이티브 API로 점진적 전환