CSS Anchor Positioning으로 뷰포트 충돌 자동 해결하기 — `@position-try`와 `position-try-fallbacks` 위치 폴백 전략
툴팁 하나 만들면서 Floating UI 설치하고, useFloating 훅 연결하고, flip() 미들웨어 끼우고, ResizeObserver까지 붙였던 경험이 있으신지요. 솔직히 처음 그 과정을 겪었을 때 "이게 CSS 레벨에서 처리됐어야 하는 거 아닌가?" 하는 생각을 지울 수가 없었습니다. 그리고 드디어 그 불만이 해소됐습니다.
CSS Anchor Positioning은 한 요소를 다른 요소에 CSS만으로 묶어 배치하고, 뷰포트 경계에 충돌할 때 대안 위치를 자동으로 시도하는 네이티브 CSS API입니다. 2026년 현재 Chrome, Edge, Firefox, Safari 주요 4사 브라우저가 모두 지원하는 Baseline 2026 기술이 됐습니다. caniuse 전체 기준 브라우저 커버리지가 약 91%에 달하는 지금이 실무에 녹여볼 적기입니다.
이 글은 그 API의 핵심인 두 가지 — @position-try at-rule과 position-try-fallbacks 속성 — 에 집중합니다. 이 둘을 조합하면 요소가 뷰포트 경계를 벗어나는 순간 CSS 엔진이 직접 대안 위치를 순서대로 시도해 자동으로 해결해 줍니다. JavaScript 이벤트 한 줄 없이요.
핵심 개념
앵커와 포지션드 요소의 관계
CSS Anchor Positioning은 두 역할로 구성됩니다. 기준점이 되는 앵커(anchor) 와 그에 묶여 배치되는 포지션드 요소(positioned element) 입니다. 연결은 CSS 두 줄로 끝납니다.
/* 앵커 지정 */
.trigger {
anchor-name: --my-anchor;
}
/* 앵커에 묶이는 요소 */
.tooltip {
position: absolute;
position-anchor: --my-anchor;
position-area: top center; /* 앵커 기준 9분할 그리드에서 위치 선택 */
}
anchor-name의 이름 형식: CSS 변수(--color: red)처럼 이름 앞에--를 붙입니다. 예:--my-anchor,--btn,--dropdown-trigger.
position-area란? 앵커를 중심으로 한 3×3 격자(9칸)를 상상하면 됩니다.top center는 앵커 바로 위 가운데,bottom right는 앵커 아래 오른쪽입니다. 구 스펙의inset-area가 리네임된 속성입니다.
이 상태만으로도 앵커에 붙어 있는 요소가 만들어집니다. 하지만 버튼이 화면 상단 가장자리에 있다면? 툴팁은 뷰포트 밖으로 잘려나갑니다. 여기서 @position-try와 position-try-fallbacks가 등장합니다.
@position-try — 커스텀 폴백 위치 규칙 정의하기
@position-try는 이름을 붙인 대안 위치 규칙 세트를 정의하는 at-rule입니다. 위치, 크기, 마진 등을 묶어서 "이 포지션을 시도해봐라"는 하나의 옵션으로 만듭니다.
@position-try --tooltip-bottom {
position-area: bottom center;
width: 220px;
margin-block-start: 8px;
}
@position-try --tooltip-right {
position-area: right span-top;
margin-inline-start: 8px;
}단순히 "위치를 바꿔라"가 아니라 "이 방향일 때 이 크기와 간격으로 표시해라"를 통째로 정의할 수 있는 게 포인트입니다. 처음 이 부분을 발견했을 때 꽤 유용하게 쓸 수 있겠다 싶었습니다.
position-try-fallbacks — 브라우저가 순서대로 시도하는 폴백 목록
position-try-fallbacks는 폴백 시도 순서를 정의하는 속성입니다. 나열된 옵션들을 브라우저가 앞에서부터 하나씩 시험해보고, 요소가 containing block을 벗어나지 않는 첫 번째 옵션을 자동으로 적용합니다.
.tooltip {
position: absolute;
position-anchor: --my-anchor;
position-area: top center;
position-try-fallbacks:
flip-block, /* 1순위: 블록 축(상↔하) 반전 */
flip-inline, /* 2순위: 인라인 축(좌↔우) 반전 */
flip-block flip-inline, /* 3순위: 두 축 모두 반전 */
--tooltip-bottom; /* 4순위: 커스텀 규칙 적용 */
}세 키워드의 차이를 한번이라도 헷갈렸다면 이 표를 북마크해두면 됩니다.
| 키워드 | 반전 방향 | 사용 예 |
|---|---|---|
flip-block |
상↔하 (블록 축) | 위에 공간 없으면 아래로 |
flip-inline |
좌↔우 (인라인 축) | 오른쪽 공간 없으면 왼쪽으로 |
flip-start |
start→end 대각선 반전 | 아랍어·히브리어 등 우→좌 쓰기 환경 |
flip-block flip-inline |
두 축 동시 반전 | 모서리에 걸릴 때 대각선으로 |
flip-start는 LTR/RTL 혼용 레이아웃에서 특히 유용합니다. 아랍어·히브리어처럼 우→좌 쓰기 환경을 지원하는 다국어 서비스라면 flip-start 하나로 방향을 통일할 수 있습니다.
Containing Block이란? 포지션드 요소의 크기·위치 기준이 되는 부모 박스입니다.
position: absolute일 때는position: relative(또는 absolute/fixed/sticky)가 설정된 가장 가까운 조상 요소가 됩니다. 뷰포트 자체가 containing block 역할을 하기도 합니다.
position-try-order — "순서"가 아닌 "가장 넓은 공간" 기준으로 선택하기
position-try-order는 position-try-fallbacks의 동작 방식을 변경하는 보조 속성입니다. 기본값 normal은 선언 순서대로 폴백을 시도하지만, 이 속성을 바꾸면 가장 공간이 넓은 방향을 먼저 선택하도록 동작이 바뀝니다.
.dropdown {
position-try-fallbacks: flip-block, flip-inline, flip-start;
position-try-order: most-height; /* 가장 높이가 확보되는 방향 우선 */
}| 값 | 의미 |
|---|---|
normal |
선언 순서대로 시도 (기본값) |
most-width |
가장 넓은 가로 공간 확보 방향 우선 |
most-height |
가장 높은 세로 공간 확보 방향 우선 |
most-block-size |
블록 방향 공간이 가장 큰 옵션 우선 |
most-inline-size |
인라인 방향 공간이 가장 큰 옵션 우선 |
항목 수가 많은 드롭다운 목록이라면 most-height를 활용하면 브라우저가 알아서 가장 펼치기 좋은 방향을 골라줍니다. 단, Firefox는 현재 position-try-order를 지원하지 않으므로, 순서 기반 position-try-fallbacks만으로도 충분히 대응 가능한지 먼저 검토해 보시면 좋습니다.
실전 적용
예시 1: JavaScript 없는 자동 플립 툴팁
가장 기본적이면서 체감 효과가 큰 케이스입니다. 실제로 처음 이 패턴을 적용했을 때, Floating UI 없이도 화면 모서리에서 툴팁이 자연스럽게 방향을 바꾸는 걸 보고 꽤 놀랐습니다.
아래는 대응하는 HTML 마크업과 CSS입니다.
<button class="anchor-btn">도움말</button>
<div class="tooltip" role="tooltip">
버튼에 대한 추가 설명입니다.
</div>/* 앵커 버튼 */
.anchor-btn {
anchor-name: --btn;
}
/* 툴팁 */
.tooltip {
position: absolute;
position-anchor: --btn;
position-area: top center;
width: max-content;
max-width: 240px;
padding: 6px 10px;
background: #1e1e2e;
color: #fff;
border-radius: 6px;
/* 폴백: 위→아래→왼쪽→오른쪽 순서로 시도 */
position-try-fallbacks:
flip-block,
flip-inline,
flip-block flip-inline;
}주의: 위 CSS는 위치 배치 로직에 집중한 예시입니다. 실제 서비스에서는
opacity/visibility처리나 Popover API와 결합해 표시·숨김을 처리하는 것을 권장합니다. Popover API를 쓰면 키보드 접근성과 포커스 관리까지 브라우저가 담당해 줍니다.
| 시나리오 | 적용되는 폴백 | 결과 |
|---|---|---|
| 버튼이 화면 중앙 | 없음 (기본 top center) |
버튼 위에 표시 |
| 버튼이 화면 상단 | flip-block |
버튼 아래에 표시 |
| 버튼이 화면 우측 상단 모서리 | flip-block flip-inline |
버튼 왼쪽 아래에 표시 |
CSS 엔진이 뷰포트 경계를 감지하고 위 목록을 순서대로 시험해 안전한 위치를 자동으로 고릅니다. 스크롤이나 리사이즈에도 실시간으로 재계산됩니다.
예시 2: 컨텍스트 메뉴 — 오른쪽 공간 부족 시 왼쪽으로 전환
트리거 위치에 따라 왼쪽·오른쪽으로 전환해야 하는 UI에 어울리는 패턴입니다. 컨텍스트 메뉴에 이 방식을 처음 적용했을 때, 기존에 JavaScript로 작성한 위치 계산 코드를 상당 부분 덜어낼 수 있었습니다.
/* 커스텀 폴백: 왼쪽에 표시 */
@position-try --menu-left {
position-area: left span-bottom;
width: 180px;
}
/* 컨텍스트 메뉴 */
.context-menu {
position: absolute;
position-anchor: --trigger;
position-area: right span-bottom; /* 기본: 트리거 오른쪽 */
width: 180px;
position-try-fallbacks: --menu-left; /* 오른쪽 공간 없으면 왼쪽으로 */
}
span-bottom이란?position-area의 스팬 문법입니다.right span-bottom은 "앵커 오른쪽에서 시작해 아래 방향으로 확장"을 의미합니다. 드롭다운이 앵커 기준 오른쪽 아래로 자연스럽게 펼쳐집니다.
한 가지 짚고 넘어가야 할 점이 있습니다. "우클릭 메뉴"처럼 클릭 이벤트 자체를 감지하거나 커서 위치를 앵커로 잡는 동작은 CSS만으로 불가능합니다. 우클릭 이벤트 처리와 앵커 위치 지정은 JavaScript가 필요합니다. 이 패턴에서 CSS가 담당하는 역할은 "메뉴가 열렸을 때 뷰포트 경계를 자동으로 회피하는 위치 폴백"으로 이해하시면 정확합니다.
Popover API의 popovertarget 속성으로 버튼과 메뉴를 연결하고 anchor 어트리뷰트로 암묵적 앵커를 설정하면 CSS의 anchor-name 없이도 연결이 가능합니다. 백엔드 개발자나 React/Vue 사용자라면 HTML 어트리뷰트 방식이 더 친숙하게 느껴질 수 있는데, 더 알고 싶다면 Frontend Masters의 Popover Context Menus with Anchor Positioning 글이 좋은 출발점이 됩니다.
예시 3: 폴백 감지로 화살표 방향 변경 — Anchored Container Queries
⚠️ Chrome 143+ 전용 실험적 기능입니다. 아래 코드를 바로 프로덕션에 적용하기보다 점진적 향상 전략과 함께 도입을 검토해 보시면 좋습니다.
저도 처음엔 이 부분에서 꽤 오래 막혔는데, "어떤 폴백이 적용됐는지를 CSS가 알 수 없다"는 한계가 문제였습니다. 툴팁이 아래로 뒤집어졌을 때 화살표 방향도 같이 바꾸고 싶어도 방법이 없었거든요.
Chrome 143에서 Anchored Container Queries가 이 문제를 해결했습니다.
.tooltip {
position: absolute;
position-anchor: --btn;
position-area: top center;
position-try-fallbacks: flip-block, flip-inline;
/* 이 요소를 앵커 인식 쿼리 컨테이너로 지정 */
container-type: anchored;
}
/* 기본 상태: 화살표가 아래를 가리킴 */
.tooltip::after {
content: "▼";
}
/* flip-block 폴백이 적용됐을 때: 화살표 반전 */
@container anchored(fallback: flip-block) {
.tooltip::after {
content: "▲";
}
}CSS Anchor Positioning Level 2 스펙에서 도입된 기능으로 진행 방향은 명확합니다. 다만 container-type: anchored와 @container anchored(fallback: ...) 문법은 Chrome 릴리즈 진행에 따라 변경될 수 있으니, 도입 전 Chrome 릴리즈 노트를 직접 확인해 보시면 좋습니다.
장단점 분석
장점
| 항목 | 내용 |
|---|---|
| 성능 | 레이아웃 엔진 수준 처리. ResizeObserver 루프·rAF 불필요 |
| 코드 간결성 | Floating UI 설치·설정·이벤트 바인딩 코드를 상당 부분 제거 가능 |
| 자동 처리 범위 | 스크롤, 리사이즈, 뷰포트 오버플로를 브라우저가 직접 처리 |
| 관심사 분리 | 위치 로직이 CSS에만 존재. JS가 UI 위치를 몰라도 됨 |
| 접근성 통합 | Popover API 결합 시 키보드 탐색·포커스 관리를 브라우저가 보장 |
단점 및 주의사항
| 항목 | 내용 | 대응 방안 |
|---|---|---|
| 브라우저 지원 | Safari 26은 최신 macOS/iOS 필요 | @supports로 점진적 향상 |
position-try-order |
Firefox 미지원 | 순서 기반 폴백으로 대체 |
| Anchored CQ | Chrome 143+ 전용 | @supports로 분기, 미지원 환경엔 기본 스타일 유지 |
| 복잡한 시나리오 | Shadow DOM 크로스-오리진, 가상 스크롤 등 | Floating UI와 조건부 분기 병행 |
| 스펙 변화 | position-try-order는 Working Draft 상태 |
핵심 기능 위주로 먼저 적용 |
Working Draft란? W3C 표준화 프로세스의 초기 단계로, 스펙이 아직 확정되지 않아 변경될 가능성이 있습니다. 반면
position-try-fallbacks자체는 이미 안정적으로 구현된 기능입니다.
실무에서 가장 흔한 실수
-
기본 위치를 "망가진 상태"로 두는 것 — 폴백이 없는 구형 브라우저에서 요소가 뷰포트 밖으로 나가버립니다.
position-area의 기본값은 어느 환경에서도 최소한 "볼 수 있는" 위치여야 합니다. -
position-try-options이름으로 코드를 작성하는 것 — Chrome 129에서position-try-fallbacks로 공식 리네임됐습니다. 오래된 튜토리얼을 참고할 때 헷갈리기 쉬운 부분입니다. -
@supports분기 없이 바로 적용하는 것 — 아직 지원하지 않는 환경을 위해 Floating UI 초기화 코드를 유지해두는 것이 안전합니다. 아래 점진적 향상 패턴을 참고하시면 됩니다.
/* 구형 브라우저를 위한 기본 위치 */
.tooltip {
top: auto;
left: 50%;
transform: translateX(-50%);
}
/* CSS Anchor Positioning 지원 환경에서만 적용 */
@supports (anchor-name: --x) {
.tooltip {
position: absolute;
position-anchor: --trigger;
position-area: top center;
transform: none; /* 앵커 포지셔닝 활성화 시 transform 해제 */
position-try-fallbacks: flip-block, flip-inline;
}
}마치며
@position-try와 position-try-fallbacks는 "뷰포트 경계 처리는 JavaScript의 몫"이라는 오랜 전제를 CSS 레벨에서 무너뜨린 변화입니다. 기존 코드를 전부 교체하는 것보다, 새로 만드는 툴팁이나 드롭다운 컴포넌트부터 한 번씩 적용해 보시면 좋습니다. 브라우저 지원이 점점 넓어지고 있는 만큼, 지금 익숙해져 두면 분명 도움이 됩니다.
지금 바로 시작해볼 수 있는 3단계:
-
DevTools에서 앵커 관계 시각화 — Chrome DevTools Elements 패널에서
anchor-name이 붙은 요소를 선택해 보시면 앵커 관계와position-try폴백 시도 과정을 시각적으로 확인할 수 있습니다. -
기존 툴팁에
flip-block추가 — 프로젝트의 툴팁 하나를 골라@supports (anchor-name: --x)블록 안에position-anchor+position-try-fallbacks: flip-block만 추가해 보시면 추가 코드 없이 뷰포트 충돌이 얼마나 자연스럽게 처리되는지 바로 확인됩니다. -
조건부 임포트로 번들 최적화 —
CSS.supports('anchor-name', '--x')로 지원 여부를 감지해true일 때는 Floating UI를 로드하지 않도록 조건부 임포트를 설정해 보시면 번들 사이즈 절감 효과도 함께 경험할 수 있습니다.
참고 자료
- MDN: @position-try CSS at-rule | MDN Web Docs
- MDN: position-try-fallbacks | MDN Web Docs
- MDN: Fallback options and conditional hiding for overflow | MDN Web Docs
- MDN: position-try-order | MDN Web Docs
- Introducing the CSS anchor positioning API | Chrome for Developers
- Detect fallback positions with anchored container queries | Chrome for Developers
- position-try-fallbacks | CSS-Tricks
- CSS Anchor Positioning Guide | CSS-Tricks
- Anchor Positioning Updates for Fall 2025 | OddBird
- css-anchor-positioning Polyfill | OddBird GitHub
- W3C CSS Anchor Positioning Module Level 1 | W3C
- Popover Context Menus with Anchor Positioning | Frontend Masters
- The Basics of Anchor Positioning | Ahmad Shadeed
- Understanding CSS Anchor Positioning and Fallback Detection | JavaScript in Plain English