블로그 목록
frontend

CSS로 커스텀 셀렉트 만들기 — appearance: base-select로 드롭다운까지 스타일링

<select> 스타일링은 늘 애매한 지점이었다. 닫혀 있는 버튼 부분이야 어떻게든 손봐도, 클릭했을 때 열리는 **옵션 목록(드롭다운)**은 브라우저가 그리는 영역이라 CSS가 거의 안 먹혔다. 폰트도 안 맞고, 항목에 아이콘 하나 넣으려면 결국 <select>를 버리고 div로 흉내 낸 커스텀 컴포넌트나 라이브러리를 붙여야 했다.

문제는 그렇게 직접 만들면 키보드 조작, 포커스, 스크린리더 대응을 전부 손으로 다시 짜야 한다는 거다. 접근성을 제대로 챙긴 셀렉트는 생각보다 손이 많이 간다.

appearance: base-select

이걸 해결하려고 나온 게 커스터마이저블 셀렉트다. <select>와 버튼 역할을 하는 부분에 appearance: base-select를 주면, 네이티브 동작(키보드·접근성)은 그대로 두고 목록 전체를 CSS로 스타일링할 수 있는 모드로 바뀐다.

<select>
  <button>
    <selectedcontent></selectedcontent>
  </button>
  <option value="kr">🇰🇷 한국</option>
  <option value="jp">🇯🇵 일본</option>
  <option value="us">🇺🇸 미국</option>
</select>
select,
::picker(select) {
  appearance: base-select;
}

::picker(select) {
  border: 1px solid #ddd;
  border-radius: 12px;
  padding: 6px;
  background: #fff;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
}

option {
  border-radius: 8px;
  padding: 8px 12px;
}

option:hover,
option:focus {
  background: #eef2ff;
}

/* 선택된 항목 앞에 붙는 기본 체크 표시 커스텀 */
option::checkmark {
  content: '✓';
  color: #4f46e5;
}

핵심은 셋이다. ::picker(select)는 열렸을 때 뜨는 드롭다운 컨테이너를 가리키는 가상 요소라 여기에 그림자·라운드·패딩을 줄 수 있다. <option> 안에 이미지든 이모지든 자유롭게 넣을 수 있어서 국기·아바타 같은 걸 항목마다 붙이기 쉽다. <selectedcontent>는 닫힌 버튼에 현재 선택값을 그대로 복제해 보여주는 요소라, 선택된 옵션의 마크업이 버튼에도 똑같이 반영된다.

드롭다운은 top-layer에 뜨기 때문에 부모의 overflow: hidden이나 z-index에 잘리지 않는다. 예전에 커스텀 셀렉트 만들면서 제일 골치 아팠던 부분이 이거였는데, 그게 공짜로 해결된다.

정리

가장 큰 이득은 <select>의 키보드 조작과 접근성을 그대로 유지한 채 겉모습만 원하는 대로 바꾼다는 점이다. div로 새로 짜면 잃어버리던 것들을 안 버려도 된다. 아직 크롬 계열 위주라 미지원 브라우저에서는 기본 셀렉트로 떨어지지만, 어차피 동작은 되니까 점진적 향상으로 붙이기 딱 좋다. 커스텀 셀렉트 하나 만들자고 라이브러리를 끌어오던 시절이 슬슬 끝나가는 느낌이다.