tooltip 위치 계산하던 JS, 이제 지워도 된다

tooltip 하나 제대로 만들려고 getBoundingClientRect() 호출하고, ResizeObserver 붙이고, 뷰포트 경계 체크하고, 화살표 방향 뒤집는 로직까지 — 솔직히 이게 2026년에도 필요한 코드인가 싶었다. Chrome 143에서 나온 Anchored Container Queries를 보고 나서, 진짜로 지웠다.

Anchor Positioning이 해결한 것, 못 한 것

CSS Anchor Positioning은 Chrome 125부터 있었다. anchor-name으로 기준 요소를 지정하고, position-anchor로 연결하면 JS 없이 위치를 잡을 수 있다. position-try-fallbacks로 뷰포트 밖으로 나갈 때 자동 flip도 가능하고.

Floating UI(구 Popper.js)가 수년간 해오던 일을 브라우저 네이티브로 가져온 셈인데, 실제로 마이그레이션해보면 코드량 차이가 꽤 크다. Floating UI를 쓰는 전형적인 tooltip 컴포넌트는 useFloating 훅에 autoUpdate, flip, shift, offset 미들웨어를 조합하면 대략 30-40줄. 여기에 화살표 처리용 FloatingArrow까지 넣으면 50줄을 넘긴다. 같은 결과를 CSS Anchor Positioning으로 구현하면 CSS 10줄 내외로 끝난다.

문제는 flip 이후다. tooltip이 위에서 아래로 뒤집혔는데 화살표는 여전히 위를 가리키고 있다면? 기존에는 이걸 감지할 방법이 CSS만으로는 없었다. 결국 JS로 현재 위치를 읽어서 클래스를 토글하는 코드가 다시 들어왔다. Floating UI에서는 middlewareData.flip.overflows로 flip 여부를 알 수 있었지만, CSS만으로는 "지금 fallback이 적용됐는지"를 물어볼 수 없었던 거다.

container-type: anchored

Chrome 143이 이 빈틈을 메웠다.

.tooltip {
  anchor-name: --trigger;
  position-anchor: --trigger;
  position-try-fallbacks: flip-block;

  /* 이게 새로 추가된 부분 */
  container-type: anchored;
}

@container anchored(fallback: flip-block) {
  .tooltip .arrow {
    transform: rotate(180deg);
    top: auto;
    bottom: -6px;
  }
}

container-type: anchored를 선언하면 해당 요소가 "나 지금 어떤 fallback 쓰고 있는지 알고 있어"라는 상태가 된다. @container anchored(fallback: flip-block)으로 쿼리하면, 브라우저가 flip-block을 적용했을 때만 안쪽 스타일이 활성화된다.

JS 제로. Observer 제로. 클래스 토글 제로.

실전에서 써볼 만한 패턴

tooltip 말고도 "앵커에 상대적으로 붙는 UI" 전부에 해당되는 이야기다.

popover 모서리 라운딩. 앵커 바로 옆에 맞닿는 모서리는 border-radius: 0이 자연스럽다. flip 되면 맞닿는 모서리가 바뀌니까 @container anchored로 분기하면 된다. 전에는 이걸 위해 JS에서 위치를 읽고 data-placement 속성을 박아넣었는데, 그 코드가 통째로 필요 없어진다.

드롭다운 그림자 방향. 아래로 열리는 드롭다운은 box-shadow가 아래쪽으로 떨어져야 자연스럽고, 위로 열리면 그림자도 위로 가야 한다. 사소해 보이지만 이런 디테일을 JS 없이 처리할 수 있다는 게 의미 있다.

컨텍스트 메뉴 slide-in 방향. flip 방향에 따라 애니메이션 origin을 전환하는 것도 같은 원리다.

프로덕션 투입은 아직 이르다

현실적인 브라우저 지원부터 보자. Chrome 143+만 지원이다. Firefox는 anchor positioning 자체가 layout.css.anchor-positioning.enabled 플래그 뒤에 있고, Nightly에서만 부분 구현된 상태. Safari는 WebKit 블로그에 "investigating" 수준의 언급만 있었고, 올해 안에 들어올지도 미지수다.

그러면 어떻게 해야 하나. @supports(container-type: anchored) 분기를 잡아두고, 미지원 브라우저에서는 기존 JS(Floating UI든 직접 작성이든)를 유지하는 progressive enhancement가 현실적이다. 사내 도구나 Chrome-only Electron 앱이라면 바로 쓸 수 있겠지만, 퍼블릭 웹에서는 fallback 없이 배포하기엔 이르다.

반론도 있다. Floating UI가 이미 충분히 잘 되어 있는데, CSS로 굳이 옮길 필요가 있느냐는 입장. 실제로 Floating UI는 접근성 처리, 가상 요소 앵커링, 커스텀 미들웨어 같은 기능까지 갖추고 있어서 CSS Anchor Positioning이 완전히 대체하기는 어렵다. 하지만 번들 사이즈(Floating UI core + DOM = ~8KB gzipped)와 런타임 리소스 관점에서 보면, 브라우저 네이티브 구현이 이길 수밖에 없다. 특히 한 페이지에 tooltip이 수십 개 붙는 대시보드 같은 UI에서는 ResizeObserver 콜백이 수십 번 도는 것과 CSS 엔진이 자체 처리하는 것의 성능 차이가 무시할 수 없다.

방향은 확실하다. 위치 계산을 JS에서 CSS로 넘기는 흐름은 되돌아오지 않는다. @supports 분기 하나만 미리 잡아두면, 브라우저 지원이 넓어지는 순간 JS 코드를 삭제하는 커밋 하나로 끝난다.