표시 영역 인식 드롭다운

표시된 작업 패널 또는 팝오버 버튼 그룹은 사용자가 최소한의 공간을 차지하면서 추가 기능에 액세스할 수 있는 유용한 패턴입니다. 이 오버레이 패턴은 표시 영역 제약 조건에 맞게 조정되는 동안 패널이 트리거 요소에 연결된 상태를 유지해야 하므로 레이아웃이 복잡합니다. 기존에는 위치를 계산하고 충돌을 처리하기 위해 복잡한 JavaScript 라이브러리 (예: Popper.js 또는 Floating UI)가 필요했습니다.

CSS 앵커 포지셔닝은 CSS에서 이러한 관계를 완전히 처리하는 선언적이고 성능에 최적화된 방법을 제공하므로 브라우저가 포지셔닝 및 오버플로 로직을 기본적으로 관리할 수 있습니다.

1. 버튼과 패널의 관계 정의

첫 번째 단계는 Popover API를 사용하여 오버레이 컨테이너를 여는 트리거 버튼을 만드는 것입니다.

필수 접근성 구분: 이 패턴은 실제 ARIA 메뉴가 아닌 팝오버 내에 표시되는 버튼 그룹을 명시적으로 모델링합니다. 해당 키보드 탐색 계약을 완전히 구현하지 않는 한 (예: 항목 간의 공간 화살표 키 탐색 처리) role="menu" 또는 role="menuitem"를 적용하지 마세요. 같은 이유로 트리거에 aria-haspopup를 추가하지 마세요. 이 값 (menu, listbox, tree, grid 또는 기존 true)은 타겟이 일치하는 역할을 노출한다는 약속이지만 이 패턴은 그렇지 않습니다.

<button popovertarget="action-panel">
  Open Actions
</button>
<!-- Use the Popover API (`popover="auto"`) for the overlay to ensure it is placed in the top layer and handled accessibly by the browser. -->
<div id="action-panel" popover="auto" class="panel">
  <button class="action-item" type="button">Edit</button>
</div>

이렇게 하면 버튼과 패널 사이에 암시적 앵커 연결이 생성되므로 버튼에 anchor-name를 사용하거나 패널에 position-anchor를 사용하지 않고도 버튼을 기준으로 패널을 기본적으로 배치할 수 있습니다.

2. position-area을 사용한 포지셔닝

수동 top/left 오프셋 대신 position-area를 사용하여 앵커를 기준으로 3x3 그리드에 타겟을 배치합니다.

.panel {
  /* 
     Position the panel below the anchor (block-end), 
     aligned to the start of the anchor and spanning to its end (span-inline-end).
  */
  position-area: block-end span-inline-end;
  
  /* Reset insets to allow the grid to take control */
  inset: auto;
}

RTL 및 다양한 쓰기 모드를 자동으로 지원하려면 물리적 키워드 (left, top)보다 논리적 키워드 (span-inline-end, block-start)를 사용하는 것이 좋습니다.

필수: position-area에서 물리적 키워드와 논리적 키워드를 혼합하지 마세요.

3. 에지 복원력 (대체) 구현

화면 가장자리에서 패널이 잘리지 않도록 하려면 기본 위치가 오버플로되는 경우 브라우저에서 시도해야 하는 '시도 전략'을 정의합니다.

.panel {
  /* 
     If the panel overflows the bottom, flip it to the top (flip-block).
     If it overflows the inline edges, flip it horizontally (flip-inline).
     If it overflows both (e.g., in a corner), flip on both axes (flip-block flip-inline).
  */
  position-try-fallbacks: flip-block, flip-inline, flip-block flip-inline;
}

드롭다운 또는 플라이아웃에 선택한 대체 위치에 따라 스타일을 변경해야 하는 방향 화살표 또는 테두리가 포함된 경우 위치 인식 도움말을 참고하세요. JavaScript 없이 대화상자와 팝오버를 선언적으로 호출하려면 선언적 대화상자 및 팝오버 제어를 참고하세요.

대체 전략

Popover의 대체 및 브라우저 지원

Popover API는 대부분 점진적 개선이지만, 최상위 레이어 승격, 가벼운 닫기, popovertarget 호출과 같은 정의 동작에는 CSS 전용에 상응하는 기능이 없습니다. 이전 브라우저에는 폴리필이 필요하며, 폴리필을 제공하지 않으려면 수동 대체가 필요합니다.

폴리필: 이전 브라우저에서 popover 속성을 지원하려면 조건부로 @oddbird/popover-polyfill를 로드합니다. 필수: HTMLElement.prototype에서 popover 속성을 확인하여 기능을 감지하고 네이티브 지원이 누락된 경우에만 폴리필을 로드합니다. 무조건 로드하지 마세요.

번들러 또는 가져오기 맵 사용:

// MANDATORY: Feature detect 'popover' on HTMLElement.prototype.
if (!("popover" in HTMLElement.prototype)) {
  import("@oddbird/popover-polyfill");
}

번들러가 없는 경우 <script type="module"> 내의 CDN에서 가져옵니다.

<script type="module">
  if (!("popover" in HTMLElement.prototype)) {
    import("https://unpkg.com/@oddbird/popover-polyfill@latest/dist/popover.min.js");
  }
</script>

스타일 지정 주의사항: 폴리필은 실제 :popover-open 의사 클래스를 정의할 수 없으므로 대신 .\:popover-open 클래스를 적용합니다. 필수: :is() 또는 :where()를 사용하여 두 가지를 결합합니다. 그렇지 않으면 :popover-open가 없는 브라우저에서 전체 규칙을 삭제합니다.

[popover]:is(:popover-open, .\:popover-open) {
  display: block;
}

또는 폴리필이 없는 기존 대체 기능의 경우 position: fixed를 사용하고 getBoundingClientRect()를 통해 좌표를 수동으로 계산하거나, 사용 사례에 적합한 경우 inset: auto를 사용하여 기본 위치 지정에 의존합니다.

앵커 위치 지정이 없는 브라우저를 지원하려면 적절한 위치를 설정해야 합니다. 기본적으로 팝오버는 화면 중앙에 배치되며, 이는 사용 사례에 적합할 수 있습니다.

일부 사용 사례의 경우 일부 앵커 위치 지정 사용 사례에 대한 지원을 추가하는 @oddbird/css-anchor-positioning 폴리필을 사용할 수 있습니다. 암시적 앵커를 지원하지 않으므로 트리거에 앵커 이름을 추가해야 합니다(MUST). 또한 폴리필은 팝오버에서 position-area를 지원하지 않으므로 원하는 인셋에 anchor()를 사용해야 합니다(MUST).

<!-- MANDATORY: Conditionally install the anchor positioning polyfill -->
<script type="module">
  if (!("anchorName" in document.documentElement.style)) {
    await import("https://unpkg.com/@oddbird/css-anchor-positioning");
  }
</script>
.panel {
  /* Mandatory: use explicit anchor name */
  position-anchor: --kebab-anchor;
  /* Mandatory: use insets rather than position-area for positioning */
  bottom: auto;
  right: auto;
  top: anchor(bottom);
  left: anchor(left);
  margin: 0;
}