리치 미디어 선택 도구 (맞춤설정 가능한 선택)

기존의 네이티브 <select> 요소는 스타일을 지정하기 어려웠으며 일반 텍스트 옵션만 포함할 수 있었습니다. appearance: base-select 속성은 <select> 요소의 맞춤설정 가능한 상태를 선택하는 선언적 CSS 전용 방법을 제공합니다. 이를 통해 개발자는 이미지, SVG, 복잡한 레이아웃과 같은 리치 HTML 콘텐츠를 <option> 요소 내에 포함하면서도 네이티브 키보드 접근성과 양식 통합을 유지할 수 있습니다. 이 패턴을 사용하여 무겁고 맞춤 제작된 선택 구성요소를 표준 네이티브 요소로 대체합니다.

구현 방법

Customizable Select API를 사용하여 리치 미디어 선택 도구를 구현하려면 다음 단계를 따르세요.

  1. 기본 스타일 선택: ::picker(select) 가상 요소를 사용하여 <select> 요소와 내부 선택 도구 모두에 appearance: base-select를 적용합니다. 이렇게 하면 <select> 내부 콘텐츠의 브라우저 HTML 파서가 변경됩니다.
  2. 버튼 콘텐츠 정의: <select> 내에서 표준 <button> 및 <selectedcontent> 요소를 사용하여 선택 도구가 닫혀 있을 때 표시되는 내용을 정의합니다. <selectedcontent> 요소는 선택한 옵션의 콘텐츠를 자동으로 미러링합니다. 버튼에 현재 선택된 리치 콘텐츠를 표시하려면 이 속성이 필요합니다.
  3. 옵션 내에서 리치 콘텐츠 사용: 이제 <option> 태그 내에 이미지, SVG, 기타 HTML 태그를 배치할 수 있습니다. 이 API 이전에는 브라우저가 <option> 태그에서 태그를 제거하고 일반 텍스트만 렌더링했습니다.
  4. 팝오버 스타일 지정: ::picker(select) 가상 요소를 타겟팅하여 드롭다운 옵션 목록의 스타일을 지정합니다. 최상위 레이어에서 렌더링되므로 z-index를 사용하지 않아도 됩니다. 옵션은 기본적으로 Anchor Positioning API를 사용하여 배치됩니다.

예시 코드: 리치 역할 선택 도구

<label for="role-picker">Select your role</label>
<select class="custom-select" name="role" id="role-picker">
  <button>
    <selectedcontent></selectedcontent> <!-- Mirrors the selected option's content automatically so you do not need JS to update the button -->
  </button>

  <!-- Define concise aria-label values on options whose mirrored rich content would otherwise read awkwardly as a concatenated string -->
  <option value="frontend" aria-label="Frontend Developer">
    <svg aria-hidden="true" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor">
      <rect x="2" y="3" width="20" height="14" rx="2" ry="2"></rect>
    </svg>
    <div class="option-text">
      <span class="option-title">Frontend Developer</span>
      <span class="option-desc">React, Vue, CSS</span>
    </div>
  </option>

  <option value="backend" aria-label="Backend Developer">
    <svg aria-hidden="true" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor">
      <rect x="2" y="12" width="20" height="14" rx="2" ry="2"></rect>
    </svg>
    <div class="option-text">
      <span class="option-title">Backend Developer</span>
      <span class="option-desc">Node.js, Python</span>
    </div>
  </option>
</select>
select.custom-select,
select.custom-select::picker(select) {
  appearance: base-select; /* MUST opt-in both the select and its picker to enable the customizable state, otherwise browser standard rendering applies */
}

select.custom-select {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  padding: 12px;
  background-color: #1e293b;
  border: 1px solid #334155;
  border-radius: 8px;
  color: #f1f5f9;
  cursor: pointer;
}

select.custom-select::picker(select) {
  background-color: #0f172a;
  border: 1px solid #334155;
  border-radius: 8px;
  padding: 8px;
  box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.5);
  width: anchor-size(width); /* Uses Anchor Positioning API to keep the dropdown precisely aligned to the button trigger width */
}

select.custom-select option {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px;
  border-radius: 4px;
  cursor: pointer;
}

select.custom-select option:hover {
  background-color: #1e293b;
}

/* Remove standard OS checkmark for base-select */
select.custom-select option::before {
  display: none;
}

/* MANDATORY: Provide multiple visual indicators (e.g., prominent background color and bold title font) to communicate the checked state cleanly */
select.custom-select option:checked {
  background-color: #3b82f6;
  color: #ffffff;
}
select.custom-select option:checked .option-title {
  font-weight: 700;
}

전략적 구현 및 권장사항

  • 이전에는 무거운 JavaScript UI 프레임워크로만 가능했던 복잡한 옵션 레이아웃 (아이콘, 설명, 이미지)이 필요한 경우 appearance: base-select를 사용하세요.
  • 성능 지연이 발생하는 경우 임시 요소를 사용하지 마세요. 브라우저는 기본 키보드 포커스를 기본적으로 처리합니다.
  • 최상위 레이어 렌더링을 고려합니다. 선택 도구는 최상위 레이어에서 렌더링되므로 페이지 콘텐츠의 상대적 z-index를 재정의합니다.
  • 권장사항: .custom-select selectedcontent .option-desc { display: none; }의 스타일을 지정하여 버튼 상태에서 보조 세부정보 (예: 설명)가 너무 많은 공간을 차지하는 경우 숨깁니다.
  • 레이아웃 동작을 테스트합니다. appearance: base-select를 설정하면 가장 긴 옵션 너비를 기준으로 선택 항목의 크기를 조정하는 기본 브라우저 동작이 삭제됩니다. 레이아웃 변경을 방지하려면 고정 너비를 설정하거나 flex/grid 제약 조건을 사용해야 할 수 있습니다.
  • <select>에 name 속성과 연결된 <label>가 있는지 확인합니다. 이렇게 하면 맞춤 UI를 사용하더라도 스크린 리더가 구성요소에 액세스할 수 있고 표준 양식 제출이 올바르게 작동합니다.
  • 옵션에 표준 value 속성을 사용하여 양식 제출이 이전과 동일하게 작동하도록 합니다.

대체 전략

맞춤설정 가능한 <select>의 대체 및 브라우저 지원

아직 appearance: base-select를 지원하지 않는 브라우저의 경우 <select> 요소가 표준 운영체제 드롭다운으로 원활하게 다운그레이드됩니다.

  • 텍스트가 아닌 콘텐츠 무시: 이전 브라우저는 <option> 태그 내의 HTML 태그 (예: <svg> 또는 <div>)를 제거하고 텍스트 노드만 렌더링합니다. <option>의 텍스트 콘텐츠가 그 자체로 읽기 쉽고 의미가 있는지 확인합니다.
  • HTML 구조 처리: 표준 파서는 <select> 내의 <button> 및 <selectedcontent> 태그를 무시하거나 잘못된 것으로 처리할 수 있습니다. 표준 텍스트를 읽을 수 있는 대체로 간주하는 경우 점진적 개선을 위해 무거운 JavaScript 폴리필이 반드시 필요한 것은 아닙니다.
document.addEventListener("DOMContentLoaded", () => {
  // Check if browser supports base-select value
  if (!CSS.supports("appearance", "base-select")) {
    // Custom select overrides are not supported natively.
  }
});