결과가 수만 건인 디렉터리의 패싯 필터 UI — 그리고 0건이 됐을 때

UX 디자인

패싯 필터빈 상태정보 구조검색 UX접근성

이 글은 누구를 위한 것인가

  • 지역·업종·가격 등 여러 축의 필터를 한 화면에 놓아야 하는 팀
  • 필터를 붙일수록 사용자가 결과 0건에 자주 부딪히는 서비스
  • "필터는 다 만들었는데 왜 안 쓰지"라는 질문을 받아본 디자이너

들어가며

항목이 수만 건인 디렉터리에서 필터는 선택이 아니다. 문제는 필터를 늘릴수록 사용성이 좋아지지 않는다는 점이다. 축이 세 개를 넘어가면 사용자는 자신이 무엇을 걸었는지 잊고, 결과가 갑자기 0건이 되면 원인을 찾지 못한 채 이탈한다.

패싯 필터 설계의 실질적인 목표는 "많은 조건을 제공하는 것"이 아니라 사용자가 자기 위치를 항상 알게 하는 것이다. 지금 무엇이 걸려 있고, 각 선택이 결과를 얼마나 좁히며, 막다른 길에 들어갔을 때 어디로 돌아가면 되는지.


1. 필터인가 패싯인가 — 구분이 먼저다

용어를 섞어 쓰면 설계가 흔들린다.

[필터 Filter]
  전체 집합에서 조건에 맞지 않는 것을 제거
  예: "영업 중만 보기" 토글
  → 결과가 얼마나 줄지 미리 알 수 없음

[패싯 Facet]
  집합을 분류 축으로 나누고, 각 값의 개수를 함께 제시
  예: 지역 ▸ 강남구(1,240) · 서초구(890) · 송파구(1,102)
  → 선택 전에 결과 규모를 알 수 있음

핵심 차이는 개수 표시다. 개수가 없으면 사용자는 결과를 예측할 수 없고, 0건이 될 조건도 똑같이 클릭 가능한 상태로 보인다. 이건 사용자가 실패하도록 설계한 것과 같다.

Nielsen Norman Group이 필터와 패싯의 구분에서 정리한 기준도 같은 지점을 짚는다. 항목 수가 많을수록 패싯 쪽으로 가야 한다.

개수 계산이 비싸다는 이유로 생략하는 경우가 많은데, 검색 엔진 대부분은 이 계산을 결과 조회와 함께 한 번에 처리한다. Elasticsearch의 집계를 쓰면 필터링과 패싯 카운트가 한 요청으로 끝난다. 별도 쿼리를 N번 날리는 구조라면 그건 구현 문제이지 UX가 양보할 문제가 아니다.


2. 개수 0인 값을 어떻게 다룰 것인가

패싯을 붙이면 바로 다음 결정이 온다. 현재 조건에서 결과가 0인 값을 보여줄 것인가.

[선택지 세 가지]

  A. 숨긴다
     장점: 목록이 짧아짐
     단점: 값이 사라져서 "원래 없는 축"으로 오해. 되돌아갈 단서 상실

  B. 회색으로 비활성화 + 개수 0 표시
     장점: 축의 전체 구조가 유지됨. "이 조건은 지금 조합에서 없다"가 명확
     단점: 목록이 길어짐

  C. 클릭 가능하게 두고, 누르면 다른 조건을 해제
     장점: 막다른 길이 없음
     단점: 사용자가 건 조건이 임의로 풀려서 혼란

B가 기본값으로 가장 안전하다. 값의 존재 자체가 정보이기 때문이다. "강남구에는 24시간 영업이 있는데 이 업종에는 없구나"를 알 수 있는 것과, 그냥 항목이 사라지는 것은 완전히 다른 경험이다.

다만 값이 수십 개인 축(예: 세부 업종 60개)에서는 B가 목록을 길게 만든다. 이때는 절충한다.

  개수 > 0 인 값을 위로 정렬
  0인 값은 아래에 접어두고 "결과 없는 항목 12개 보기"로 펼치기

숨기는 게 아니라 접는 것이다. 구조는 남기고 시각적 부담만 줄인다.


3. 적용된 조건은 항상 보여야 한다

필터 패널이 접히는 모바일에서 가장 흔한 실패다. 사용자가 조건 4개를 걸고 패널을 닫으면, 화면에는 결과만 남고 무엇 때문에 이 결과가 나왔는지 단서가 사라진다.

[결과 영역 상단 — 항상 표시]

  ┌────────────────────────────────────────────┐
  │ 강남구 ✕   마사지 ✕   영업중 ✕   전체 해제 │
  ├────────────────────────────────────────────┤
  │ 검색 결과 87곳                              │
  └────────────────────────────────────────────┘

칩 패턴의 요건은 셋이다.

  1. 개별 해제 — 칩마다 ✕. 패널을 다시 열지 않고 조건 하나만 뺄 수 있어야 한다
  2. 전체 해제 — 조건이 2개 이상일 때만 노출
  3. 결과 수 병기 — 조건과 결과가 같은 시야에 있어야 인과가 보인다

칩이 화면 폭을 넘길 만큼 많아지면 줄바꿈보다 가로 스크롤 + 개수 배지가 낫다. 강남구 ✕ 마사지 ✕ +3 형태로 두고 탭하면 전체가 펼쳐진다. 세로로 3줄씩 차지하면 정작 결과가 밀린다.

축이 여러 개인 디렉터리에서 이게 왜 필요한지는 실제 화면을 보면 빠르다. 플림의 업종 분류처럼 유형 축이 촘촘하게 나뉜 서비스는 지역·유형·상태를 조합하는 순간 조건이 서너 개로 쉽게 늘어나고, 그때 상단 칩이 없으면 사용자는 결과만 보고 "왜 이것밖에 없지"라고 판단해 버린다.


4. 0건 — 빈 상태가 아니라 회복 경로

결과가 0건인 화면은 "빈 상태"가 아니다. 사용자가 조건을 잘못 조합한 상태이고, 필요한 건 위로가 아니라 다음 행동이다.

[나쁜 0건 화면]

        🔍
    검색 결과가 없습니다
    다른 조건으로 검색해보세요

  → 어떤 조건이 문제인지 모름. 무엇을 바꿔야 할지 모름.
[좋은 0건 화면]

  조건에 맞는 결과가 없습니다

  걸린 조건: 강남구 · 24시간 · 주차 가능 · 4.5점 이상

  하나씩 풀어보기
    ▸ '4.5점 이상' 해제       →  12곳
    ▸ '24시간' 해제           →   8곳
    ▸ '주차 가능' 해제        →   3곳

  또는  전체 조건 해제 (1,240곳)

각 선택지에 결과 수를 붙이는 것이 핵심이다. 조건 하나를 풀었을 때 몇 건이 되는지 알면 사용자는 즉시 판단할 수 있다. 이 수치는 조건을 하나씩 뺀 조합으로 카운트 쿼리를 N번(조건 수만큼) 돌리면 나오는데, 조건이 5개 이하라면 비용이 크지 않다.

Nielsen Norman Group의 빈 상태 정리도 같은 방향을 제시한다. 빈 화면은 안내의 기회이지 사과의 자리가 아니다.

0건에 도달하기 전에 막는 방법도 있다. 패싯에 개수를 표시하고 0인 값을 비활성화하면, 애초에 0건 조합을 선택할 수 없다. 2절의 선택지 B를 택하면 4절의 문제가 대부분 사라지는 셈이다. 다만 자유 검색어와 필터가 함께 있는 구조에서는 여전히 0건이 발생하므로, 회복 경로는 만들어 둬야 한다.


5. 필터 적용 시점 — 즉시인가 확정인가

[즉시 적용]
  값을 고르는 순간 결과 갱신
  적합: 축이 적고(2~3개), 결과 갱신이 빠를 때
  위험: 조건을 여러 개 고르는 동안 결과가 계속 흔들림

[확정 적용]
  고른 뒤 '적용' 버튼
  적합: 축이 많거나, 갱신 비용이 클 때
  위험: 버튼을 안 누르고 나가는 사용자

모바일에서는 패널을 열면 확정, 상단 칩은 즉시로 나누는 방식이 잘 맞는다. 패널 안에서는 여러 조건을 편하게 고르고 한 번에 적용하되, 이미 적용된 칩의 ✕는 즉시 반영한다. 해제는 결과가 늘어나는 방향이라 사용자가 놀라지 않는다.

확정 적용을 쓴다면 버튼에 예상 결과 수를 넣는다.

  [ 87곳 보기 ]        ← '적용'보다 훨씬 낫다

버튼을 누를 가치가 있는지를 누르기 전에 판단할 수 있다.


6. 접근성 — 필터는 특히 자주 빠진다

필터 UI는 스크린 리더 사용자에게 특히 어려운 화면이다. 조건을 바꿔도 결과가 바뀌었다는 사실 자체가 전달되지 않는 경우가 흔하다.

<!-- 결과 수를 라이브 리전으로 알린다 -->
<div role="status" aria-live="polite" aria-atomic="true">
  검색 결과 87곳
</div>

aria-live="polite"를 쓰는 이유는, 필터 변경이 긴급 알림이 아니기 때문이다. 현재 읽고 있는 내용을 끊지 않고 이어서 알린다. 동작 방식은 MDN의 Live Regions 문서에 정리돼 있다.

체크박스 그룹으로 만든 패싯은 ARIA checkbox 패턴의 키보드 동작을 따라야 한다. 그리고 값 옆의 개수는 반드시 접근 가능한 이름에 포함시킨다.

<label>
  <input type="checkbox" name="region" value="gangnam" />
  강남구 <span aria-hidden="true">(1,240)</span>
  <span class="sr-only">, 1,240곳</span>
</label>

괄호 숫자를 그대로 읽히면 "강남구 괄호 천이백사십 괄호"가 되어 알아듣기 어렵다. 시각용과 낭독용을 분리한다.

[접근성 체크리스트]

  □ 결과 수 변경을 라이브 리전으로 알림
  □ 비활성 패싯은 disabled + 이유를 알 수 있는 레이블
  □ 필터 칩의 ✕ 버튼에 "강남구 조건 해제" 같은 구체적 레이블
  □ 패널 열림/닫힘 상태를 aria-expanded로 전달
  □ 터치 타겟 최소 24×24px (WCAG 2.2)

마지막 항목은 칩의 ✕에서 자주 위반된다. 시각적으로 작게 보이더라도 최소 타겟 크기 기준을 만족하도록 히트 영역을 넓혀야 한다.


7. 정리

  1. 개수를 붙여라 — 개수 없는 필터는 사용자를 실패시킨다
  2. 0건이 될 값은 숨기지 말고 비활성화한다 (많으면 접기)
  3. 적용된 조건은 결과와 같은 시야에 항상 둔다
  4. 0건 화면에는 "조건별 해제 시 결과 수"를 제시한다
  5. 확정 버튼에는 예상 결과 수를 쓴다
  6. 결과 수 변경을 라이브 리전으로 알린다

가장 효과가 큰 건 1번이다. 패싯에 개수만 제대로 붙여도 0건 화면에 도달하는 빈도가 크게 줄고, 나머지 문제의 절반은 발생하지 않는다.