CSS Anchor Positioning: anchor-size()·position-visibility로 JS 없이 드롭다운·툴팁 크기 자동 연동하기
드롭다운 너비를 버튼에 맞추려고 ResizeObserver를 달고 element.offsetWidth를 읽어 style.width를 주입하던 코드, 한 번쯤 짜봤거나 유지보수해본 경험이 있을 겁니다. 툴팁이 스크롤 밖으로 나간 앵커 옆에 혼자 둥둥 떠 있는 버그를 잡으려고 requestAnimationFrame 폴링을 붙이던 것도요. 저도 그런 코드를 꽤 오래 유지했는데, CSS Anchor Positioning API가 Baseline 2026으로 분류되면서 이런 상황이 많이 달라졌습니다.
anchor-size()와 position-visibility를 함께 쓰면, JS 없이도 앵커 크기에 반응하고 상황에 따라 스스로 숨고 나타나는 UI 컴포넌트를 선언적으로 구현할 수 있습니다.
2026년 1월 기준 Chrome 125+, Firefox 147+, Safari 26+가 모두 지원하면서 전 세계 브라우저 트래픽의 약 91%를 커버합니다. "아직 브라우저 지원이 문제겠지"라는 걱정은 이제 내려놔도 됩니다. 이 글에서는 CSS Anchor Positioning API 중에서도 덜 다뤄지는 두 기능인 anchor-size()와 position-visibility에 집중해서, 실제로 어떤 문제를 해결해주는지 코드와 함께 풀어냅니다.
핵심 개념
전제: anchor-name과 position-anchor
anchor-size()와 position-visibility를 쓰려면 먼저 앵커 등록과 참조 방식을 이해해야 합니다. CSS Anchor Positioning에서 기준이 되는 요소에 anchor-name으로 이름을 붙이고, 위치 지정할 요소에서 position-anchor로 그 이름을 참조하는 것이 기본 구조입니다.
.trigger {
anchor-name: --my-anchor; /* 기준 요소로 등록 */
}
.popup {
position: absolute;
position-anchor: --my-anchor; /* 이 앵커를 기준으로 삼겠다고 선언 */
}이 연결 구조 위에 anchor()(위치 좌표), anchor-size()(치수), position-visibility(조건부 표시)가 얹힙니다. Anchor Positioning API 전반적인 내용은 MDN Using CSS anchor positioning에 잘 정리되어 있습니다.
Baseline 2026이란? MDN과 웹 커뮤니티에서 사용하는 브라우저 호환성 분류 체계입니다. "Baseline 2026"은 2026년 기준 모든 주요 브라우저(Chrome, Firefox, Safari, Edge)에서 안정적으로 지원된다는 의미입니다. 이 등급에 도달하면 폴리필 없이 광범위하게 사용할 수 있다는 신호로 읽으면 됩니다.
anchor-size() — 앵커 치수를 <length>로 꺼내 쓰는 함수
CSS Anchor Positioning API에 포함된 함수로, 앵커 요소의 물리적·논리적 치수를 길이 값으로 반환합니다. 처음에 저도 anchor()와 혼동했는데, "위치 좌표는 anchor(), 치수는 anchor-size()"로 기억하니 바로 정리됐습니다.
사용할 수 있는 속성이 정해져 있어서, width, height, min-*, max-*, inset, margin 안에서만 유효합니다. 인셋 속성(top, left 등)에 anchor-size()를 쓰면 무효 선언이 되는데, 처음에 이걸 몰라서 한참 헤맨 경험이 있습니다.
/* 기본 문법 */
anchor-size(<anchor-name>? <anchor-size>, <length-percentage>?)
/* <anchor-size> 키워드 */
/* width | height | block | inline | self-block | self-inline */두 번째 인자는 폴백 값입니다. 앵커가 존재하지 않거나 참조가 끊길 때 적용됩니다. 가상화 리스트처럼 앵커 요소가 DOM에서 언마운트될 수 있는 환경이라면 이 값이 중요해집니다.
/* 앵커가 없거나 참조가 끊길 경우 200px로 폴백 */
.dropdown {
width: anchor-size(width, 200px);
}가장 흥미로운 특성은 크로스 축 참조입니다. width: anchor-size(height)처럼 다른 축의 치수를 그대로 가져올 수 있어서, 앵커 높이를 읽어 대상의 너비에 적용하는 것도 됩니다. 인자를 아예 생략하면 해당 속성의 축에 맞는 치수가 자동 선택됩니다.
/* 인자 생략 → width 속성에서는 앵커의 width를 자동 참조 */
.dropdown {
width: anchor-size(); /* = anchor-size(width) */
}position-visibility — 앵커 상태에 따른 조건부 표시
앵커 요소의 가시성이나 대상 요소의 오버플로 상태에 따라 위치 지정 요소를 조건부로 숨겨주는 속성입니다. 세 가지 값이 있는데, 솔직히 처음엔 차이가 헷갈렸습니다. 각각 어떤 문제를 해결하는지 시나리오로 보면 바로 잡힙니다.
| 값 | 동작 | 언제 쓰는지 |
|---|---|---|
always |
항상 표시 | 고정 UI, 디버그용 레이블 |
anchors-visible |
앵커가 뷰포트 밖으로 완전히 벗어나거나 완전히 가려지면 숨김 | 스크롤 목록의 툴팁·컨텍스트 메뉴 |
no-overflow |
대상 자신이 위치 지정 컨테이너(또는 뷰포트)를 조금이라도 벗어나기 시작하면 즉시 숨김 | 고정 크기 카드·테이블 내부 팝오버 |
anchors-visible의 '완전히': 앵커가 뷰포트 밖으로 1px이라도 걸쳐 있으면 아직 표시 상태입니다. 완전히 사라져야만 숨겨지므로, 스크롤 중 UI가 갑자기 깜빡이는 현상 없이 자연스럽게 전환됩니다.
명세상 기본값은 anchors-visible이지만, Chromium 계열 브라우저를 포함해 명세가 비교적 최근까지 개정되어 온 만큼 과도기적인 구현 차이가 있습니다. 중요한 컴포넌트라면 값을 항상 명시적으로 선언하는 편이 훨씬 안전합니다.
실전 적용
예시 1: 버튼 너비에 딱 맞는 드롭다운 메뉴
실무에서 가장 자주 맞닥뜨리는 상황입니다. 트리거 버튼 offsetWidth를 읽어 드롭다운에 동적으로 주입하던 코드, CSS만으로 처리됩니다.
<div class="nav-item">
<button class="trigger">메뉴</button>
<ul class="dropdown">
<li>항목 1</li>
<li>항목 2</li>
</ul>
</div>.trigger {
anchor-name: --dropdown-anchor;
}
.dropdown {
position: absolute;
position-anchor: --dropdown-anchor;
/* 위치: 버튼 바로 아래, 왼쪽 정렬 */
top: anchor(bottom);
left: anchor(left);
/* 크기: 버튼과 동일한 너비, 앵커가 완전히 사라지면 숨김 */
width: anchor-size(width);
position-visibility: anchors-visible;
}| 속성 | 역할 |
|---|---|
anchor-name: --dropdown-anchor |
버튼을 앵커로 등록 |
position-anchor: --dropdown-anchor |
드롭다운이 참조할 앵커 지정 |
width: anchor-size(width) |
버튼 너비를 그대로 가져옴 |
position-visibility: anchors-visible |
버튼이 스크롤로 완전히 사라지면 드롭다운도 숨김 |
한 가지 주의할 점이 있습니다. .dropdown이 overflow: hidden인 컨테이너 안에 있으면 드롭다운이 잘려 보입니다. 이 경우 popover 어트리뷰트를 활용하거나 드롭다운을 클리핑 컨테이너 밖으로 꺼내는 것이 일반적인 해결 방법입니다. 단점 섹션에서 조금 더 다룹니다.
예시 2: 크로스 축 참조로 정사각형 뱃지 유지
아이콘 크기가 동적으로 바뀌는 환경에서 뱃지를 항상 정사각형으로 유지해야 할 때 크로스 축 참조가 빛을 발합니다. 앵커의 height를 읽어 뱃지의 width와 height에 동시에 적용합니다.
.icon {
anchor-name: --icon;
}
.badge {
position: absolute;
position-anchor: --icon;
top: anchor(top);
right: anchor(right);
translate: 50% -50%;
/* 크로스 축: 아이콘 높이 → 뱃지의 너비·높이 동시 적용 */
width: anchor-size(height);
height: anchor-size(height);
border-radius: 50%;
}크로스 축 참조:
width속성에서anchor-size(height)를 쓰는 것처럼, 속성 축과 다른 축의 치수를 참조하는 패턴입니다.anchor()함수와 달리anchor-size()는 이 방향 제약이 없습니다.
예시 3: position-visibility 세 값의 상황별 선택
상황마다 어떤 값을 골라야 하는지 코드로 직접 보면 가장 빠르게 감이 잡힙니다.
/* 패턴 A: 항상 표시 — 스티키 헤더 위 고정 안내 레이블 */
.sticky-label {
position-anchor: --sticky-header;
position-visibility: always;
}
/* 패턴 B: 앵커 연동 숨김 — 스크롤 목록의 컨텍스트 툴팁 */
.list-tooltip {
position-anchor: --list-item;
position-visibility: anchors-visible;
/* 아이템이 스크롤로 완전히 사라져야 툴팁도 사라짐 */
}
/* 패턴 C: 오버플로 즉시 숨김 — 카드 내부 팝오버 */
.card-popover {
position-anchor: --card-trigger;
position-visibility: no-overflow;
/* 카드 경계를 1px이라도 넘으면 즉시 숨김 */
}예시 4: @position-try와 조합한 계층적 폴백
공간이 부족할 때 먼저 위치를 뒤집어 보고, 뒤집어도 해결이 안 되면 아예 숨기는 두 단계 폴백 패턴입니다.
@position-try --flip-up {
top: auto;
bottom: anchor(top);
}
.tooltip {
position-anchor: --question-mark;
top: anchor(bottom);
/* 1단계: 아래에 공간 없으면 위로 뒤집기 */
position-try-fallbacks: --flip-up;
/* 2단계: 위로 뒤집어도 앵커가 사라지면 숨기기 */
position-visibility: anchors-visible;
/* 앵커 높이의 4배를 최대 높이로 제한 — 내용이 과도하게 길어지는 것을 방지 */
max-height: calc(anchor-size(height) * 4);
}calc() 안에서 anchor-size()를 쓰면 앵커 치수를 기준으로 상대적인 크기 제한을 걸 수 있습니다. 고정 픽셀 값 대신 앵커 크기에 비례하는 제한이 필요할 때 유용한 패턴입니다.
이 패턴의 강점은 "위치 조정 → 그래도 안 되면 숨기기"라는 계층 구조를 브라우저가 알아서 처리해 준다는 점입니다. JS로 짜면 스크롤 이벤트마다 계산해야 할 로직이 CSS 선언 몇 줄로 끝납니다.
장단점 분석
번들 크기 0이라는 표현이 다소 자극적으로 들릴 수 있는데, 실제로 Floating UI를 제거하고 번들 분석을 돌려보면 숫자가 바뀌긴 합니다. Floating UI 자체가 이미 최적화가 잘 된 라이브러리이긴 하지만, 기본 사용 사례에서 CSS 네이티브로 대체하면 외부 의존성 자체가 사라진다는 게 장기 유지보수 측면에서 의미 있는 변화입니다.
장점
| 항목 | 내용 |
|---|---|
| 성능 | 브라우저 레이아웃 엔진 직접 처리. getBoundingClientRect() 루프, requestAnimationFrame 폴링 불필요. 저사양 모바일에서 스크롤 성능·배터리 효율에 측정 가능한 차이 |
| 의존성 제거 | Floating UI·Popper.js·Tippy.js 없이 기본 사용 사례 처리 가능 |
| 선언적 폴백 | @position-try로 뷰포트 충돌 시 자동 위치 전환, 코드가 훨씬 단순 |
| 유지보수 | 스크롤·리사이즈·레이아웃 이동에 브라우저가 자동 대응, JS 이벤트 리스너 관리 불필요 |
단점 및 주의사항
| 항목 | 내용 | 대응 방안 |
|---|---|---|
| 레거시 브라우저 | Baseline 2026 이전(구 Safari, Firefox < 147) 미지원 | @supports (anchor-name: --x)로 분기, 레거시에만 Floating UI 3.0 로드 |
overflow: hidden 컨테이너 |
앵커·대상이 동일한 클리핑 컨테이너 안에 있으면 대상이 잘림 | popover 어트리뷰트 활용하거나 DOM상 컨테이너 밖으로 이동 |
| Shadow DOM 크로스 경계 | Shadow DOM 경계를 넘는 앵커-대상 참조에 제약 | 동일 Shadow root 내에서만 사용하거나 JS 보조 |
| 가상화 리스트 | 앵커가 DOM에서 언마운트될 수 있어 참조 깨짐 | anchor-size(width, 200px) 형식으로 폴백 값 지정, 가상화 환경에서는 JS 병행 |
no-overflow Top Layer 미지원 |
<dialog>, popover 등 Top Layer 요소에 no-overflow 미적용 (W3C 이슈 #10454) |
anchors-visible로 대체하거나 해결 전까지 JS 보조 |
Top Layer:
<dialog>나popover어트리뷰트를 가진 요소는 브라우저가 다른 모든 요소 위에 렌더링하는 특별한 레이어에 배치됩니다. 이 레이어에서는 현재no-overflow동작에 미구현 이슈가 있습니다.
실무에서 가장 흔한 실수
position-visibility기본값을 믿고 명시하지 않는 것: 명세 개정 과정에서 과도기적인 브라우저 구현 차이가 생겼습니다. 원하는 값을 항상 명시적으로 선언하는 편이 안전합니다.overflow: hidden부모 안에 그대로 넣는 것: 가장 많이 막히는 부분입니다. 팝오버·드롭다운은popover어트리뷰트를 활용하거나, DOM 구조상 클리핑 컨테이너 밖으로 꺼내는 것이 일반적인 해결책입니다.anchor()와anchor-size()를 혼동하는 것:top: anchor-size(height)처럼 인셋 속성에anchor-size()를 쓰거나,width: anchor(bottom)처럼 크기 속성에anchor()를 쓰면 무효 선언이 됩니다. "위치 좌표는anchor(), 치수는anchor-size()"로 구분하면 됩니다.
마치며
anchor-size()와 position-visibility를 실제로 프로젝트에 적용하고 나면 체감하는 변화가 하나 있는데, 레이아웃 관련 JS가 조금씩 줄어든다는 겁니다. 물론 가상화 리스트나 Shadow DOM 경계처럼 여전히 JS가 더 나은 상황도 분명히 존재합니다. 이 두 속성이 모든 것을 대체하는 건 아니지만, "이 정도는 CSS로 되더라" 하는 범위가 이제 꽤 넓어졌습니다.
지금 바로 시작해볼 수 있는 3단계:
package.json에서 Floating UI나 Popper.js 사용 현황을 먼저 확인해볼 수 있습니다. 어떤 컴포넌트에서 쓰이는지 파악하면 CSS 전환 후보를 추릴 수 있습니다.- 후보로 고른 드롭다운이나 툴팁 하나를
@supports (anchor-name: --x) { }블록 안에서 CSS Anchor Positioning 버전으로 작성해볼 수 있습니다. 지원 브라우저에서는 새 구현이, 나머지에서는 기존 JS 로직이 그대로 동작하므로 안전하게 실험해볼 수 있습니다. - 스크롤 컨테이너가 있는 목록 UI에
position-visibility: anchors-visible을 추가해보시면 좋습니다. 아이템이 스크롤로 사라질 때 툴팁이 자연스럽게 따라 숨겨지는 동작을 바로 확인할 수 있습니다.
참고 자료
- anchor-size() CSS function | MDN Web Docs
- position-visibility CSS property | MDN Web Docs
- Using CSS anchor positioning | MDN Web Docs
- Fallback options and conditional hiding for overflow | MDN Web Docs
- Introducing the CSS anchor positioning API | Chrome for Developers Blog
- The CSS anchor positioning API | Chrome for Developers Docs
- anchor-size() | CSS-Tricks Almanac
- position-visibility | CSS-Tricks Almanac
- CSS Anchor Positioning Guide | CSS-Tricks
- First Public Working Draft: CSS Anchor Positioning Module Level 2 | W3C News
- position-visibility: no-overflow does not support Top Layer elements | w3c/csswg-drafts #10454