본문 바로가기

프로그래밍

CSS Anchor Positioning + Popover로 셀렉트·드롭다운·툴팁 무자바스크립트 구현 가이드

반응형

JS 없이도 동작하는 인터랙션이 왜 필요할까

 

현업 화면에서 셀렉트, 드롭다운, 툴팁은 작지만 자주 쓰는 요소입니다. 이 작은 요소를 위해 번들 크기와 이벤트 핸들러를 늘리는 일은 유지보수 부담으로 돌아옵니다.

 

CSS Anchor Positioning과 Popover는 클릭/포커스 같은 상호작용과 위치 배치를 CSS만으로 처리하도록 돕습니다. 자바스크립트를 줄이면 초기 로드가 가벼워지고 접근성 속성 관리도 단순해집니다.

 

Why now 관점에서, 최신 브라우저들이 두 기능을 빠르게 채택해 폴리필 의존도가 줄었습니다. 실무에서는 “간단한 열림/닫힘 + 기준 요소에 맞춘 위치” 요구가 대부분이어서 이 조합이 비용 대비 효율적입니다.

 

> 주의: 팀이 IE11 같은 구형 브라우저를 반드시 지원한다면 대체 전략을 준비해야 합니다. 지원 범위를 먼저 합의하고 도입 여부를 결정하세요.

 

핵심 개념을 사례로 이해하기

 

CSS Anchor Positioning은 특정 요소를 기준점(anchor)으로 지정해, 다른 박스를 그 위치에 맞춰 배치하는 기능입니다. 셀렉트 버튼을 기준으로 옵션 박스를 아래쪽에 정렬하는 식의 요구를 CSS 속성만으로 표현합니다.

 

Popover는 브라우저가 열림/닫힘 상태를 관리하는 내장 오버레이입니다. 버튼에 연결해 클릭이나 포커스로 토글할 수 있어, 드롭다운·툴팁처럼 “보였다 숨겼다”가 필요한 UI에 적합합니다.

 

실제 화면을 떠올려 보세요. “도시 선택” 버튼을 누르면 옵션 목록이 버튼 아래에 뜨고, 바깥을 클릭하면 닫히며, 포커스 이동도 자연스럽게 이어집니다.

 

이 흐름을 JS 없이 Anchor + Popover 조합으로 구성합니다.

 

- Anchor: 기준 요소에 anchor-name을 붙입니다.
- Position: 오버레이에 position-anchor, inset-area 등으로 상대 위치를 지정합니다.
- Popover: popover 속성으로 열림/닫힘과 포커스를 브라우저가 관리합니다.

 

> 팁: 툴팁처럼 자동 배치가 필요한 경우, 아래 영역이 부족하면 위로 옮기는 폴백을 설계하세요. 단순 레이아웃은 CSS만으로 충분하지만, 복잡한 충돌 감지는 일부 케이스에서 보조 스크립트가 필요할 수 있습니다.

 

셀렉트·드롭다운·툴팁 핵심 체크

 

버튼을 기준점(앵커)으로 삼고, 팝오버를 그 위치에 붙입니다. HTML 속성과 간단한 CSS만으로 열림/닫힘과 정렬을 처리합니다.

 

- 버튼: id와 anchor-name 지정
- 팝오버: popover="auto", popover-target로 연결
- 위치: position-anchor, inset-area로 방향 설정
- 접근성: button 사용, 팝오버 안에 포커스 가능한 항목
- 레이어/크기: z-index, min/max-width로 잘림 방지
- 충돌: top/bottom 전환용 보조 클래스 준비

 

아래 예시는 JS 없이 “도시 선택” 드롭다운을 만드는 구조입니다. 버튼 아래로 8px 간격을 둡니다.

 

<!-- 트리거(앵커) -->
<button id="cityBtn" anchor-name="--city-anchor" aria-haspopup="listbox" aria-controls="cityList">
  도시 선택
</button>

<!-- 팝오버(오버레이) -->
<div id="cityList" popover="auto" role="listbox">
  <button role="option">서울</button>
  <button role="option">부산</button>
  <button role="option">대구</button>
</div>

<style>
  #cityList {
    position-anchor: --city-anchor;
    inset-area: block-end;          /* 아래쪽 배치 */
    margin-block-start: 8px;        /* 간격 */
    min-width: anchor-size(inline); /* 버튼 너비 맞춤 */
    z-index: 10;
    border: 1px solid #ddd; background: #fff; border-radius: 8px; padding: 4px;
  }
  #cityBtn { popover-target: #cityList; }
  .above { inset-area: block-start; margin-block-end: 8px; } /* 위 배치용 */
</style>

 

핵심은 버튼의 anchor-name과 팝오버의 position-anchor를 맞추고, popover-target으로 열림/닫힘을 위임하는 것입니다. 충돌이 잦으면 .above 같은 대체 방향 클래스를 조건부로 적용하세요.

 

> 안티패턴: display:none을 수동 토글하면 aria 갱신을 빼먹기 쉽습니다. 브라우저가 상태를 관리하는 popover 속성을 우선 사용하세요.

 

앵커·팝오버 체크리스트

 

Anchor/Popover는 쉬워 보여도 접근성·레이어·충돌을 자주 놓칩니다. 아래 항목으로 기본 동작을 먼저 고정하세요.

 

- 접근성: 트리거는 button 사용. 팝오버에는 role(listbox/menu/tooltip) 지정, aria-controls/label 연결.
- 레이어: 부모에 overflow: hidden이면 잘립니다. 루트로 분리하거나 z-index와 기준 박스(containing block) 점검.
- 충돌: 아래 공간이 없으면 위로 여는 클래스 준비. 모바일 전환 시 max-height와 내부 스크롤 추가.
- 포커스: 열릴 때 첫 포커스 대상 지정, 닫힐 때 트리거로 포커스 복귀.
- 스크롤/리사이즈: 위치 재계산은 브라우저가 돕지만, 고정 헤더와 겹치면 여백 토큰으로 보정.

 

> 안티패턴: div로 클릭 토글을 만들고 키보드 포커스를 무시하면 접근성 오류가 납니다.

 

툴팁·드롭다운·셀렉트는 기대 동작이 다릅니다. 컴포넌트별 규칙을 분리해 설계하세요.

 

- 툴팁: role="tooltip". 포커스 가능한 요소에만 표시. 터치 환경은 클릭/롱프레스로 대체.
- 드롭다운: 상황에 따라 role="menu" 또는 listbox. Esc, 바깥 클릭 닫힘 확인.
- 셀렉트 대체: 화살표·Enter·Space 이동 규칙 정의. 선택 값을 버튼 라벨에 반영.

 

선택 기준은 요구 복잡도와 지원 브라우저 범위입니다. 단순 토글·정렬은 CSS 조합이 유리하고, 가상 스크롤·검색·세밀한 애니메이션은 소규모 JS 보조가 알맞습니다.

 

트레이드오프는 유지보수와 제어력의 균형입니다. CSS만 쓰면 빠르지만, 비표준 키보드 흐름·맞춤 애니메이션은 JS 개입이 필요합니다.

 

지금 할 일: 작은 컴포넌트부터 무자바스크립트로 전환

 

먼저 가장 단순한 UI 하나를 고르세요. 도시 선택 드롭다운처럼 열림/닫힘만 필요한 요소가 적합합니다.

 

다음 순서로 적용하면 위험 없이 시도할 수 있습니다.

 

- 트리거 버튼에 id와 anchor-name 추가, aria-haspopup/controls 연결
- 팝오버 박스에 popover="auto"와 position-anchor, inset-area 지정
- z-index와 max-height로 잘림 대비, 모바일에서 내부 스크롤 허용
- 키보드 탭 순서 확인, 닫힐 때 포커스가 버튼으로 돌아오는지 점검

 

실행 후에는 스크롤·리사이즈·입력 보조기기에서 동작을 확인하세요. 브라우저 지원 범위가 애매하면, 동일 마크업에 JS 토글을 얹는 폴백을 준비해 단계적으로 배포합니다.

 

> 팁: 레이아웃 컨테이너에 overflow: hidden이 있으면 팝오버가 잘립니다. 상위 컨텍스트를 조정하거나 루트 레이어로 이동하는 대안을 검토하세요.

 

포커스·호버로 여는 툴팁

 

툴팁은 클릭 없이 포커스·호버에 따라 열리고 닫히는 작은 안내창입니다. Anchor와 Popover를 같이 쓰면 자바스크립트 없이 트리거 옆에 정확히 붙일 수 있습니다.

 

도움말 아이콘을 앵커로 두고, 설명 박스를 수동 팝오버로 선언한 뒤 :hover와 :focus-within으로 표시를 토글합니다. 모바일을 고려해 포커스 중심으로 작동하도록 두세요.

 

<!-- 앵커: 도움말 아이콘 -->
<button id="helpBtn" anchor-name="--help" aria-describedby="tip1">
  ?
</button>

<!-- 팝오버: 툴팁 -->
<div id="tip1" popover="manual" role="tooltip" position-anchor="--help"
     style="inset-area: right; margin-inline-start: 8px; max-width: 220px;">
  필수 입력 항목입니다.
</div>

<style>
/* 포커스/호버 시 표시 */
#helpBtn:is(:hover, :focus-visible) + #tip1 { popover-open: show; }

/* 기본은 숨김 */
#tip1 { popover-open: hide; z-index: 1000; }
</style>

 

버튼 오른쪽에 8px 간격으로 배치되고, 포커스가 벗어나면 닫힙니다. role="tooltip"과 aria-describedby로 스크린 리더 연결을 제공합니다.

 

- 앵커: anchor-name으로 기준 설정
- 위치: position-anchor + inset-area로 방향 선택
- 토글: popover="manual" + CSS 상태 선택자
- 접근성: aria-describedby와 role="tooltip" 연결

 

> 주의: 부모가 overflow: hidden이면 잘릴 수 있습니다. 레이어 z-index와 배치 컨텍스트를 먼저 확인하세요.

반응형