SOYOYU
기술 SEO

커스텀 dropdown은 거의 항상 잘못된 선택입니다 — select와 combobox 코드 리뷰

native <select>는 모바일에서 압도적으로 좋은 UX를 줍니다. 커스텀 dropdown으로 갈아탈 정당한 이유는 검색 가능, 멀티 선택, 가상화 셋 정도뿐입니다. ARIA combobox의 진짜 비용을 봅니다.

전승엽2026년 8월 22일9 min read
A11y 코드 리뷰시맨틱 HTMLaccessibility treeAI 에이전트웹 접근성select 태그comboboxARIAKWCAG

A11y 코드 리뷰 #012 · 오늘 도감에 등록할 태그 — <select><option>, 그리고 ARIA combobox 패턴

이번 카드는 디자이너가 가장 싫어하는 태그에 관한 글입니다. native select는 디자인 자유도가 낮지만 — 모바일 UX, a11y, AI 에이전트 호환성 셋 다에서 압도적입니다. 커스텀 dropdown의 진짜 비용을 코드로 짚습니다.

TL;DR

  • native <select>데스크탑·모바일 모두에서 잘 작동하는 거의 유일한 dropdown입니다.
  • 커스텀 dropdown(ARIA combobox)으로 갈아탈 정당한 케이스는 — 검색 가능, 멀티 선택, 옵션 수 1000개 이상으로 가상화 필요 셋 정도입니다.
  • ARIA combobox 패턴은 복잡한 구현입니다 — listbox role, aria-activedescendant, 키보드 단축키, 검색 필터, 포커스 관리. 직접 만들면 자주 깨집니다.
  • Headless UI Listbox, Radix Select 같은 검증된 라이브러리가 최선의 fallback. 다만 모바일에서 native select만큼 좋지는 않습니다.
  • AI 에이전트가 옵션을 선택하는 작업에서 — native select는 한 번에 성공, 커스텀 dropdown은 얼마나 잘 만들었는지에 따라 성공률이 갈립니다.

1. 어디서나 본 그 dropdown

다음 네 패턴은 한국 사이트에서 자주 보는 dropdown 안티패턴입니다.

div + click 토글로 만든 가짜 dropdown

function CustomDropdown({ options, value, onChange }: Props) {
  const [open, setOpen] = useState(false);
  return (
    <div className="dropdown">
      <div onClick={() => setOpen(!open)}>{value || "선택"}</div>
      {open && (
        <div className="options">
          {options.map((opt) => (
            <div onClick={() => { onChange(opt); setOpen(false); }}>
              {opt.label}
            </div>
          ))}
        </div>
      )}
    </div>
  );
}

select인데 option에 ARIA 추가

<select>
  <option role="option" aria-selected={value === "a"}>A</option>
</select>

드롭다운인데 label 없음

<select>
  <option value="">-- 선택 --</option>
  <option value="kr">한국</option>
</select>

모바일에서 깨지는 컨트롤된 select

<select value={value} onChange={...}>
  {options.map(opt => <option value={opt.id}>{opt.label}</option>)}
</select>

첫 번째 패턴 — 마우스로는 동작하지만 키보드·스크린리더·AI 에이전트 모두 어렵습니다. 두 번째 — native에 ARIA 중복은 No ARIA is better than Bad ARIA. 세 번째 — 라벨 누락. 네 번째 — 모바일에서는 잘 동작하지만 옵션이 많거나 검색 필요하면 한계.


2. 한 가지 질문

이 dropdown이 정말 커스텀이어야 하나요? 디자인 자유도 외에 native select가 못 하는 일이 있나요?

답이 면 ARIA combobox로 갑니다. 아니오면 native select입니다. 90%의 경우 아니오입니다.


3. select와 combobox — 점진적 해부

3.1 가장 단순한 native select

<label for="country">국가</label>
<select id="country" name="country" autocomplete="country">
  <option value="">선택하세요</option>
  <option value="kr">대한민국</option>
  <option value="us">미국</option>
  <option value="jp">일본</option>
</select>

이 8줄이 주는 것:

  • 데스크탑: 클릭 시 native dropdown
  • iOS: 휠 피커 (사용자에게 친숙)
  • Android: full-screen 또는 modal dropdown
  • 키보드: 방향키로 옵션 이동, 첫 글자 누르면 그 글자로 시작하는 옵션 점프, Enter 또는 Esc로 닫기
  • 스크린리더: "국가, 콤보 상자, 대한민국, 4개 중 1번째"

직접 만들면 키보드 단축키만 30줄 이상이 필요합니다.

3.2 optgroup으로 그룹화

<select id="city" autocomplete="address-level2">
  <optgroup label="수도권">
    <option value="seoul">서울</option>
    <option value="incheon">인천</option>
    <option value="gyeonggi">경기</option>
  </optgroup>
  <optgroup label="강원·충청">
    <option value="gangwon">강원</option>
    <option value="chungbuk">충북</option>
  </optgroup>
</select>

optgroup으로 옵션을 그룹화. 스크린리더가 그룹 이름을 함께 발화합니다 — "수도권, 서울".

3.3 멀티 선택 — multiple 속성

<label for="tags">관심 태그 (여러 개 선택 가능)</label>
<select id="tags" name="tags" multiple size={5}>
  <option>SEO</option>
  <option>GEO</option>
  <option>AEO</option>
  <option>A11y</option>
  <option>웹 성능</option>
</select>

multiple로 멀티 선택. 다만 — 데스크탑은 Ctrl/Cmd + 클릭, 모바일은 일반 탭. 사용자에게 친숙하지 않은 패턴입니다. 멀티 선택은 보통 체크박스 그룹 또는 커스텀 multi-combobox로 가는 게 일반적.

3.4 ARIA combobox 패턴 — 진짜 비용

ARIA combobox(검색 가능 dropdown)는 다음 구조가 표준입니다.

<label id="city-label">도시</label>
<div role="combobox"
     aria-controls="city-listbox"
     aria-expanded="true"
     aria-haspopup="listbox"
     aria-labelledby="city-label">
  <input type="text" aria-autocomplete="list" />
</div>
<ul id="city-listbox" role="listbox">
  <li role="option" aria-selected="false" id="opt-seoul">서울</li>
  <li role="option" aria-selected="true" id="opt-busan">부산</li>
</ul>

이 구조에 더해서:

  • aria-activedescendant키보드 포커스를 가상으로 관리 (실제 포커스는 input에)
  • 방향키로 listbox 옵션 사이 이동
  • Enter로 선택
  • Escape로 닫기
  • Home/End로 처음·끝 옵션
  • 첫 글자 입력으로 검색 필터
  • 옵션 클릭 시 input에 값 반영

전체 구현이 최소 200줄입니다. 직접 만들면 한 가지 이상 깨집니다.

3.5 Listbox vs Combobox 차이

<select>            → 시맨틱 combobox + listbox 합쳐진 것 (native)
role="listbox"      → 옵션 목록만, 검색 입력 없음
role="combobox"     → 입력 + 옵션 목록 (검색 가능)

용도 분기:

  • 적은 옵션, 검색 불필요<select> 또는 role="listbox"
  • 많은 옵션, 검색 필요 → ARIA combobox
  • 멀티 선택 + 검색 → multi-combobox (가장 복잡한 패턴)

4. 왜 문제인가 — 3중 영향

4a. 스크린리더 사용자

native select에서:

사용자가 select 포커스
→ NVDA: "국가, 콤보 상자, 대한민국 선택됨, 4개 중 1번째"
→ 사용자가 ↓ 누름
→ NVDA: "미국, 4개 중 2번째"

직관적이고 효율적. 사용자가 전체 옵션 수와 현재 위치를 항상 인지합니다.

커스텀 dropdown에서 — 어떻게 만들었는지에 따라 다릅니다. 잘 만들었으면 native와 비슷, 못 만들었으면 "버튼"만 발화되고 옵션 목록을 통째로 지나갑니다.

4b. 키보드·모바일 사용자

native select의 모바일 UX:

  • iOS: 화면 하단에 휠 피커 (사용자에게 익숙한 iOS native UI)
  • Android: 화면 중앙에 modal dropdown (Material Design)

커스텀 dropdown은 — 보통 화면 어딘가에 떠 있는 작은 박스. 모바일 화면을 가립니다. 닫기 버튼이 명확하지 않습니다. iOS 휠 피커의 친숙함이 사라집니다.

WebAIM 연구 결과 — 모바일 사용자의 옵션 선택 성공률이 native select는 96%, 평균 커스텀 dropdown은 78%. 18% 차이가 폼 완료율에 직접 영향.

4c. AI 에이전트

native select에서 에이전트의 옵션 선택:

1. select element 식별 (accessibility tree에 combobox role)
2. option들의 텍스트 매칭
3. select.value = "kr" 또는 클릭 + 클릭 시뮬레이션

직접적이고 안정적.

커스텀 dropdown에서:

1. dropdown 트리거 식별 (시맨틱 약함 — div onClick일 수 있음)
2. 클릭으로 열기
3. 옵션 목록이 portal로 다른 DOM 위치에 렌더될 수 있음 — 식별 어려움
4. 옵션 클릭
5. 검색 필요 시 input에 typing 시뮬레이션

각 단계가 추가 추측과 시도. 실패 가능성이 단계마다 누적됩니다.

선행 글 구글이 'AI 검색 최적화 가이드'를 냈는데, 정작 핵심은 접근성 트리였습니다에서 본 사실 — accessibility tree의 명시적 role이 에이전트 성공률의 기반.


5. SEO/GEO/AEO 영향

5.1 SEO — 폼 완료율 신호

dropdown의 모바일 친화성이 폼 완료율에 직접 영향. native select가 커스텀보다 완료율이 평균 18% 높다는 결과는 — 모바일 SEO 신호로 환산하면 적지 않은 차이.

5.2 GEO — 에이전트의 옵션 선택 성공률

GEO에서 AI가 우리 사이트에서 select를 처리할 수 있는가는 중요한 지표. 회원가입 폼의 국가·지역·관심 분야 선택, 결제 폼의 카드 종류 선택 — 모두 select 패턴입니다. native면 성공률이 높고, 커스텀이면 얼마나 잘 만들었는지에 종속됩니다.

5.3 AEO — 옵션 정보의 인덱싱

옵션 목록 안의 텍스트는 DOM의 정적 콘텐츠입니다. native select의 option은 검색엔진이 일반 콘텐츠로 인덱싱합니다. 커스텀 dropdown에서 옵션이 클릭 시점에 동적으로 렌더되면 — 인덱싱이 안 될 수 있습니다.

"이 사이트는 어떤 국가를 지원하나?" 같은 질문에 AI가 답할 때 — native select의 option 목록이 인용 후보. 동적 옵션은 안 보입니다.


6. React에서

6.1 native select with React

function CountrySelect({ value, onChange }: Props) {
  return (
    <>
      <label htmlFor="country">국가</label>
      <select
        id="country"
        name="country"
        autoComplete="country"
        value={value}
        onChange={(e) => onChange(e.target.value)}
      >
        <option value="">선택하세요</option>
        <option value="kr">대한민국</option>
        <option value="us">미국</option>
        <option value="jp">일본</option>
      </select>
    </>
  );
}

10줄. 모든 a11y 자동.

6.2 Radix Select — native와 비슷한 a11y

import * as Select from "@radix-ui/react-select";

<Select.Root value={value} onValueChange={onChange}>
  <Select.Trigger>
    <Select.Value placeholder="선택하세요" />
    <Select.Icon />
  </Select.Trigger>

  <Select.Portal>
    <Select.Content>
      <Select.Viewport>
        <Select.Item value="kr">
          <Select.ItemText>대한민국</Select.ItemText>
        </Select.Item>
        <Select.Item value="us">
          <Select.ItemText>미국</Select.ItemText>
        </Select.Item>
      </Select.Viewport>
    </Select.Content>
  </Select.Portal>
</Select.Root>

Radix Select는 데스크탑에서 native와 유사한 a11y를 줍니다. 다만 — 모바일에서는 native만큼 좋지 않습니다. iOS의 휠 피커가 안 뜨고, Android의 modal dropdown 효과가 없습니다.

언제 Radix Select가 정당한가:

  • 옵션이 검색 가능해야 할 때
  • 옵션에 아이콘이나 풍부한 콘텐츠가 필요할 때
  • 디자인이 brand guideline상 native와 달라야 할 때

위 셋이 아니면 — native select가 더 좋습니다.

6.3 Headless UI Listbox/Combobox

import { Combobox } from "@headlessui/react";

const cities = ["서울", "부산", "대구", "인천", "광주", "대전", "울산"];

function CitySearch() {
  const [query, setQuery] = useState("");
  const [selected, setSelected] = useState("");

  const filtered = query === ""
    ? cities
    : cities.filter((c) => c.includes(query));

  return (
    <Combobox value={selected} onChange={setSelected}>
      <Combobox.Input onChange={(e) => setQuery(e.target.value)} />
      <Combobox.Options>
        {filtered.map((city) => (
          <Combobox.Option key={city} value={city}>
            {city}
          </Combobox.Option>
        ))}
      </Combobox.Options>
    </Combobox>
  );
}

이게 검색 가능 dropdown의 표준 패턴. Headless UI Combobox가 ARIA combobox의 모든 키보드/포커스 관리를 처리. 직접 만들지 마세요.

6.4 react-aria의 useSelect

Adobe의 react-aria 라이브러리는 headless hook 형태로 select·combobox를 제공합니다. 가장 견고한 a11y이지만 학습 곡선이 큽니다. 대규모 디자인 시스템에 적합.


7. 한국 맥락 — 가볍게

KWCAG 관련 항목:

  • 2.1.1 키보드 사용 보장: 모든 dropdown이 키보드로 조작 가능해야 합니다.
  • 4.1.2 이름, 역할, 값: dropdown의 현재 선택값과 옵션들이 보조 기술에 노출돼야 합니다.

한국 사이트에서 자주 보는 패턴 — 주소 검색을 위한 시·구·동 단계별 select 3개. 각각이 native select면 — 키보드와 모바일 모두에서 잘 동작합니다. 커스텀으로 갈아탄 사이트들이 입력 이탈률이 더 높은 케이스를 자주 봅니다.


8. "디자이너가 native select를 싫어해요"

자주 듣는 반론들:

"브라우저별로 디자인이 달라요" — 그게 native의 의미입니다. 사용자는 자신의 브라우저 native UI에 익숙합니다. 강제로 통일시키면 사용자가 낯설어합니다.

"화살표 아이콘 디자인 못 바꿔요"appearance: none으로 native 화살표 제거하고 CSS 배경 이미지로 교체 가능. 디자인 자유도 80%까지는 가능합니다.

"옵션 목록 자체의 디자인이 필요해요" — 옵션 안에 아이콘·서브텍스트·이미지가 필요하면 — 그땐 정당하게 ARIA combobox로 갑니다. Radix Select 또는 Headless UI.

"검색이 필요해요" — 검색은 native가 못 합니다. Headless UI Combobox가 표준.

"native가 너무 구식 같아요" — 사용자가 익숙한 패턴이 가장 좋은 UX입니다. 구식이 아니라 친숙. 모바일 사용자의 96% 옵션 선택 성공률이 그 증거.


9. 체크리스트

  • dropdown이 진짜 커스텀이어야 하는 이유가 있는가 (검색·멀티·풍부한 옵션 콘텐츠 등)
  • 단순 옵션 선택은 native <select>로 만들었는가
  • select에 <label>이 연결돼 있는가
  • select에 적절한 autocomplete 표준값이 있는가 (country, address-level1 등)
  • 옵션이 많을 때 <optgroup>으로 그룹화돼 있는가
  • 커스텀 dropdown을 쓴다면 Headless UI/Radix/react-aria 같은 검증된 라이브러리인가
  • 모바일 디바이스에서 실제로 옵션 선택이 매끄러운가 (실제 디바이스 검증)
  • AI 에이전트가 옵션 선택 시뮬레이션에 성공하는가

마무리

이 글이 16편 시리즈의 열두 번째 도감 카드입니다. 다음 카드는 ARIA tab 패턴 — "탭처럼 보이는 div"가 어떻게 키보드와 AI를 무력화하는지 봅니다.

select의 단 한 줄 규칙 — dropdown은 디자인 자유도가 아닌 데이터의 의미가 정합니다. 단순 선택은 native select, 검색·멀티는 검증된 라이브러리, 직접 만들기는 거의 항상 잘못된 선택입니다.

폼 컴포넌트·디자인 시스템 a11y 컨설팅이 필요하시면 문의하기로 연락 주세요.


codex:
  primary_tag: select
  secondary_tags: [option, optgroup, combobox, listbox]
  aria_roles: [combobox, listbox, option]
  aria_attributes: [aria-controls, aria-expanded, aria-haspopup, aria-activedescendant, aria-autocomplete, aria-selected]
  related_patterns: [native-select, searchable-dropdown, multi-select, country-picker, address-input]
  kwcag: ["2.1.1", "4.1.2"]
  wcag: ["2.1.1", "4.1.2", "1.3.5"]
  ai_agent_impact: high
  seo_geo_impact: medium
  difficulty: intermediate
  test_envs:
    - "Mobile Safari iOS 17"
    - "Mobile Chrome Android 14"
    - "NVDA 2024.x + Chrome 125"
    - "VoiceOver macOS 14 + Safari 17"
  references:
    - "WAI-ARIA Authoring Practices, Combobox Pattern"
    - "WAI-ARIA Authoring Practices, Listbox Pattern"
    - "Adrian Roselli, Stop Using Drop-Downs"
    - "Headless UI Combobox Documentation"
    - "react-aria useSelect / useComboBox"

다른 글 읽기

기술 SEO

이미지 검색에 우리 제품 사진이 하나도 안 나오는 이유

구글 이미지·시각 검색(Google Lens)에 제품 사진이 안 뜨는 원인과 대응. Alt Text·파일명·이미지 사이트맵·주변 텍스트까지 이커머스 시각 검색 최적화 실무.

4 min read
기술 SEO

검색 결과에 내 사이트 설명이 이상하게 나오는 이유

메타 디스크립션을 써도 구글이 다르게 보여주는 이유와 대응 방법. 구글이 스니펫을 재작성하는 구조, 자주 하는 실수, 최소화할 수 있는 기술.

4 min read
기술 SEO

type=text는 거의 항상 잘못된 선택입니다 — input 타입별 코드 리뷰

type 속성 한 글자가 모바일 키보드를 바꾸고, AI 자동완성 정확도를 결정하고, 한국 전화번호 입력의 함정을 만듭니다. input 타입 13개의 실무 결정 트리를 정리합니다.

8 min read
기술 SEO

고성 송지호·봉수대 오토캠핑장 예약 사이트가 검색에 안 나오는 이유

송지호·봉수대 등 고성군 공공캠핑장 6곳 예약 방법 정리. 그리고 공식 예약 사이트가 구글 색인에 0건인 이유를 구글 오픈소스 파서까지 돌려 실측으로 진단합니다.

19 min read
기술 SEO

신규 페이지를 만들어도 구글이 며칠째 안 가져가는 이유

사이트맵을 제출해도 신규 페이지가 바로 색인되지는 않습니다. 발견·크롤·색인 단계를 구분하고 공식 도구로 원인을 점검하는 대응 순서.

5 min read
기술 SEO

Radix Dialog도 깨지는 그 케이스 — focus trap 코드 리뷰

포커스 트랩은 native <dialog>가 무료로 해주는 일이지만, 라이브러리·SPA 라우팅·autoFocus가 섞이면 미세하게 깨집니다. 깨지는 다섯 시나리오를 코드로 짚어봅니다.

10 min read

검색 최적화가 필요하신가요?

무료 상담을 통해 비즈니스에 맞는 최적화 전략을 확인하세요.