06 — CSS Anchor Positioning (+ Popover API)

한 줄 답: Anchor Positioning은 position: absolute다른 요소에 종속시키는 CSS 네이티브 위치 계산이며, Popover API와 짝지어 JS 없는 툴팁·메뉴·드롭다운을 가능하게 한다. 2026년 baseline에 진입했다.


Why — 왜 이게 필요한가

20년간 “버튼을 클릭하면 그 옆에 메뉴가 뜬다” 라는 단순한 UI를 만드는 데 JavaScript가 필수였다:

// floating-ui 또는 popper.js
import { computePosition, flip, shift } from '@floating-ui/dom';
computePosition(button, menu, {
  placement: 'bottom-start',
  middleware: [flip(), shift({ padding: 5 })],
}).then(({x, y}) => {
  Object.assign(menu.style, { left: `${x}px`, top: `${y}px` });
});
// + 스크롤 리스너, resize 리스너, 화면 가장자리 처리 ...

문제:

  • 위치 계산을 매 프레임 JS로해야 한다 (스크롤·리사이즈).
  • 부모 transform, overflow로 containing block이 깨지면 수동 보정.
  • 화면 가장자리에서 반대편으로 뒤집기(flip), 밀어내기(shift)도 JS.
  • 접근성 (focus trap, ESC, outside click) 모두 직접 구현.

**Anchor Positioning + Popover API + <dialog>**는 이 모든 걸 CSS와 선언적 HTML로 가능하게 한다.


How — 어떻게 동작하나

1) 핵심 개념 — anchor와 anchored element

  • Anchor: 기준이 되는 요소. anchor-name: --foo로 명명.
  • Anchored: 위치가 anchor에 상대적인 요소. position-anchor: --foo로 연결.
  • anchor() 함수: anchor의 *변(top/right/bottom/left/center)*을 좌표로 가져온다.
  • position-area: 9개 영역 중 하나로 선언적 배치 (top, top-end, bottom-start …).
  • position-try: 공간 부족 시 대체 위치 후보들.

2) 가장 단순한 예 — 버튼 아래 툴팁

<button id="btn" popovertarget="tip">메뉴 열기</button>
<div id="tip" popover>드롭다운 내용</div>
#btn  { anchor-name: --btn; }
#tip  {
  position: absolute;
  position-anchor: --btn;
  position-area: bottom span-right;  /* 아래쪽, 오른쪽으로 펼침 */
  margin-block-start: 4px;
}

JS 0줄로 버튼 아래에 4px 간격 두고 드롭다운이 뜬다. popover 속성은 ESC·outside click 자동 처리.

3) anchor() 함수 사용

.tooltip {
  position: absolute;
  position-anchor: --btn;
 
  top: anchor(bottom);           /* anchor의 bottom 좌표를 top으로 */
  left: anchor(center);          /* anchor의 가로 center를 left로 */
  translate: -50% 4px;           /* 자기 너비 절반 왼쪽으로 + 4px 아래 */
}

anchor() 인자:

  • top, right, bottom, left
  • center (해당 축의 중앙)
  • start, end (logical)
  • self-start, self-end
  • <percentage> (anchor 박스의 0~100%)

4) position-area — 9 + 6 영역 선언

       │  start  │ center │  end   │
───────┼─────────┼────────┼────────┤
 start │  ┌── top-start ── top ── top-end ──┐
       │  │                                  │
center │  │              [anchor]            │
       │  │                                  │
   end │  └── bottom-start ─ bottom ─ bottom-end ┘
.menu { position-area: bottom span-all; }     /* anchor 아래, 양쪽으로 확장 */
.tip  { position-area: top center; }           /* anchor 위, 가로 중앙 */
.aside{ position-area: end start; }            /* logical: 끝쪽 시작 영역 */

position-area9칸 격자 + span 모디파이어선언적으로 위치를 표현. anchor() + 수동 translate 조합보다 훨씬 명확.

5) position-try — 공간 부족 시 대안

.menu {
  position: absolute;
  position-anchor: --btn;
  position-area: bottom;
  position-try-fallbacks: top, --shift;  /* 아래 공간 없으면 위로, 그래도 안 되면 --shift */
}
 
@position-try --shift {
  position-area: bottom;
  /* 화면 가장자리에서 옆으로 밀기 */
  margin-inline: 8px;
}

→ 옛 floating-ui의 flip(), shift() 미들웨어가 CSS 선언으로 바뀐 것.

6) Popover API와의 결합

<button popovertarget="menu">열기</button>
<div id="menu" popover="auto">
  <ul>...</ul>
</div>
  • popover 속성을 가진 요소는 top layer에 그려진다 — z-index, transform 조상 모두 무관.
  • popovertarget 클릭으로 토글.
  • popover="auto": ESC, 외부 클릭으로 닫힘. 한 번에 하나만 열림 (light dismiss).
  • popover="manual": 명시적 닫기만 (HTMLElement.hidePopover()).
  • 기본 접근성: focus 관리, role, aria-* 자동 (구현체별 차이 있음).

Anchor Positioning + Popover API + <dialog>2026 모달/메뉴/툴팁의 표준 조합.


What — 구체 사양

핵심 속성/함수 표

이름역할
anchor-name: --x이 요소를 anchor로 명명
position-anchor: --x이 요소를 anchor --x에 연결
anchor(<side>)anchor의 한 변 좌표 (top/left/inset 값으로)
anchor-size(<dim>)anchor의 너비/높이 (width/height 값으로)
position-area9칸 격자 + span 모디파이어
position-visibilityanchor가 안 보이면 어떻게 할지
position-try-fallbacks후보 위치들
@position-try --name { ... }후보 위치 정의

position-area 값 (9칸 + span)

position-area: <row> <column>;

row:   top | center | bottom | block-start | center | block-end
column: left | center | right | inline-start | center | inline-end
extra: span-start | span-end | span-all  (한 축 전체로 확장)

예시:

.x { position-area: top center; }              /* 위 가로 중앙 */
.x { position-area: bottom span-all; }          /* 아래 전체 폭 */
.x { position-area: block-end inline-start; }   /* logical */

anchor-size() — anchor 크기 활용

.dropdown {
  position-anchor: --btn;
  position-area: bottom;
  width: anchor-size(width);     /* 버튼과 같은 너비 */
  min-width: anchor-size(width); /* 최소 버튼 너비 */
}

Popover API 속성

HTML의미
popover 또는 popover="auto"light dismiss popover
popover="manual"수동 닫기만
popovertarget="id"트리거 버튼
popovertargetaction="toggle/show/hide"동작

CSS ::backdrop (popover/dialog 배경)

[popover]::backdrop { background: rgb(0 0 0 / 0.5); }
dialog::backdrop { backdrop-filter: blur(4px); }

호환성 (2026 baseline)

기능ChromeFirefoxSafari
Popover API114 (2023)125 (2024)17 (2023)
Anchor Positioning125 (2024)진행중 (2025)TP (2024)
position-area125진행중TP
@position-try125진행중TP

2026년 5월 baseline에 부분 진입. Firefox 안정화 일정에 따라 Polyfill (@oddbird/css-anchor-positioning) 병용 고려.

Progressive enhancement 패턴

@supports (anchor-name: --x) {
  /* Anchor positioning 사용 */
  .menu { position-anchor: --btn; position-area: bottom; }
}
 
@supports not (anchor-name: --x) {
  /* JS fallback (floating-ui 등) */
  .menu { /* JS가 top/left 박음 */ }
}

What-if — 잘못 쓰면 / 흔한 함정

1) position: absolute 빠뜨림

.menu { position-anchor: --btn; position-area: bottom; }
/* → position이 static이면 anchor 무시됨 */
 
/* 해결 */
.menu { position: absolute; position-anchor: --btn; position-area: bottom; }
/* 또는 popover 요소는 자동으로 top layer + absolute가 됨 */

2) Anchor와 anchored 사이의 scoping

/* anchor-name은 *문서 스코프*에서 충돌 가능 */
.card { anchor-name: --header; }
/* 여러 카드가 있으면 어느 .card가 anchor인지 모호 */

해결: anchor-scope로 격리.

.card-list { anchor-scope: --header; }  /* 이 안의 --header만 유효 */

→ Tab UI, 카드 리스트처럼 같은 이름의 anchor가 여러 개일 때 필수.

3) Anchor가 보이지 않을 때

.tooltip {
  position-visibility: anchors-visible;
  /* anchor가 viewport에서 가려지면 tooltip도 숨김 */
}

값:

  • always (기본)
  • anchors-visible: anchor가 보일 때만
  • no-overflow: 박스가 컨테이너를 삐져나가면 숨김

4) position-try 후보가 모두 실패

→ 명세는 마지막 후보 사용. 디자인 의도와 다를 수 있으므로 마지막 후보는 언제나 작동하는 안전한 위치로.

5) Popover 안의 폼이 submit 안 됨

popover="auto"light dismiss가 켜져 있어서 외부 클릭/ESC에 닫힌다. 폼 submit 버튼이 영역 안에 있으면 OK지만, 폼 검증 메시지 등이 밖에 나타나면 popover가 닫혀버린다.

popover="manual"로 바꾸거나, <dialog>.showModal()로 옮긴다.

6) Floating-ui와 혼용

.menu { position-anchor: --btn; }
computePosition(btn, menu, ...).then(({x, y}) => {
  menu.style.left = `${x}px`;  // ★ 충돌!
  menu.style.top  = `${y}px`;
});

→ CSS가 잡은 위치를 JS가 덮어쓰면 둘 다 동작하는 듯 하면서 가끔 깨진다. 둘 중 하나만 써라.


Insight — 흥미로운 이야기

”20년 묵은 문제의 해결”

2003년경 autocomplete, 드롭다운, 툴팁이 웹 UI의 핵심이 되면서 “버튼 옆에 박스 띄우기” 코드를 누구나 짰다. Bootstrap dropdown(2011), Popper.js(2014), Tippy.js(2017), Floating UI(2022) 가 모두 같은 문제를 풀고 있었다 — 부모 containing block 무관하게, 화면 가장자리 고려하면서 위치 잡기.

CSSWG는 2018년경부터 “이게 CSS 표준이어야 하지 않나” 를 논의했고, 2021년 Tab Atkins(Google)와 Jhey Tompkins가 초안을 냈다. 5년 만에 표준이 되어 baseline에 진입한다는 것은 W3C 기준 매우 빠른 속도다.

”Popover API의 의외의 효과”

Popover API는 원래 native autocomplete를 위한 제안이었다. 하지만 “DOM 어디서나 띄울 수 있고, top layer에 그려지며, light dismiss 처리가 공짜” 라는 특성이 너무 강력해서, 모든 종류의 floating UI(메뉴, 툴팁, 모달, 노티피케이션 토스트)에 쓰이기 시작했다.

이는 <dialog> + Popover + Anchor Positioning 이라는 세 명세의 시너지를 만든다 — 셋이 합쳐지면 모든 floating UI를 JS 없이 만들 수 있다.

<select>도 결국 anchor positioning을 쓴다”

2024년부터 Chrome은 <select>의 옵션 리스트 위치 계산을 내부적으로 anchor positioning으로 옮기는 작업을 진행 중이다. 모든 form control이 같은 위치 알고리즘을 쓰게 되면, 디자이너가 만든 커스텀 select기본 select가 동일한 동작을 한다.

이는 CSS Forms Customization과 연결된다 — 2025~2026년 표준화 중인 기본 form control 스타일링의 토대.

”왜 position-area라는 새 모델인가”

anchor() 함수만으로도 위치를 잡을 수 있는데 position-area가 따로 추가된 이유는 디자이너의 사고가 좌표가 아니라 영역이기 때문이다.

“위 가운데에 띄워줘” (디자이너) → position-area: top center vs “top = anchor의 top, left = anchor의 center, translate -50% -100%” (좌표 계산)

후자는 수학이고, 전자는 언어다. CSS는 후자에서 전자로 컴파일하는 도구를 표준으로 만든 것.


요약 + 다이어그램

Anchor Positioning은 position: absolute를 임의의 요소에 종속시키는 CSS 네이티브 위치 계산. anchor-name + position-anchor + position-area9칸 격자 선언만으로 위치 잡기. position-try로 공간 부족 시 자동 flip/shift. Popover API + <dialog> + Anchor = 2026 floating UI의 표준 세트. Firefox 안정화까지는 @oddbird/css-anchor-positioning polyfill 병용.

이 챕터 마무리: 이제 박스를 어떻게 흐름·위치·크기로 배치하는지 알았다. 다음 챕터 03-flexbox-grid — 이 토대 위에 2D 레이아웃 엔진을 얹는다.