심화 가이드
접근성 지원

다중 선택

개요

다중 선택 패턴은 읽기 전용 콤보박스 트리거와 다중 선택 리스트박스 팝업을 결합합니다. 이를 통해 키보드 탐색과 스크린 리더를 지원하고 접근성이 높은 다중 선택 드롭다운을 만듭니다.

사용법

다중 선택 패턴은 사용자가 익숙한 옵션 중에서 서로 관련된 여러 항목을 골라야 할 때 적합합니다.

다음과 같은 경우에 이 패턴 사용을 고려하세요.

  • 여러 항목을 선택해야 하는 경우: 여러 선택지를 적용할 수 있는 태그, 범주, 필터, 레이블에 사용합니다.
  • 옵션 목록이 고정된 경우(20개 미만): 사용자가 검색하지 않고 옵션을 훑어볼 수 있습니다.
  • 콘텐츠 필터링: 여러 조건을 동시에 활성화할 수 있습니다.
  • 속성 지정: 여러 값을 지정하는 것이 적합한 레이블, 권한, 기능에 사용합니다.
  • 관련된 선택지: 여러 팀원 선택처럼 옵션이 논리적으로 함께 동작하는 경우에 사용합니다.

다음과 같은 경우에는 이 패턴을 피하세요.

  • 단일 선택만 필요한 경우: 더 간단한 단일 선택 드롭다운인 선택 패턴을 사용하세요.
  • 목록이 20개를 초과하고 검색이 필요한 경우: 다중 선택 기능을 갖춘 자동 완성 패턴을 사용하세요.
  • 대부분 또는 모든 옵션을 선택하는 경우: 체크리스트 패턴이 옵션을 더 잘 보여줍니다.
  • 선택지가 서로 독립적인 이진 옵션인 경우: 개별 체크박스가 선택지를 더 명확하게 전달합니다.

주요 기능

다중 선택 패턴은 콤보박스와 리스트박스 디렉티브를 결합해서 다음과 같이 접근성을 갖춘 드롭다운을 제공합니다.

  • 키보드 탐색: 방향키로 옵션을 탐색하고 Space로 선택 상태를 전환하며 Escape로 닫습니다.
  • 스크린 리더 지원: aria-multiselectable을 비롯한 ARIA 어트리뷰트를 기본 제공합니다.
  • 선택 개수 표시: 여러 항목을 선택하면 "항목 + 2개 더"와 같은 간결한 형태로 표시합니다.
  • 시그널 기반 반응성: Angular 시그널로 상태를 반응형으로 관리합니다.
  • 스마트 위치 지정: CDK Overlay가 뷰포트 경계와 스크롤을 처리합니다.
  • 선택 상태 유지: 선택한 뒤에도 체크 표시로 선택된 옵션을 보여줍니다.

예제

기본 다중 선택

사용자가 옵션 목록에서 여러 항목을 선택해야 할 때가 있습니다. 읽기 전용 콤보박스와 다중 선택이 활성화된 리스트박스를 함께 사용하면 익숙한 다중 선택 기능과 함께 접근성을 지원할 수 있습니다.

ngListbox의 multi 어트리뷰트로 다중 선택을 활성화합니다. Space를 눌러 옵션의 선택 상태를 전환하며, 추가로 선택할 수 있도록 팝업이 열린 상태로 유지됩니다. 첫 번째 선택 항목과 나머지 선택 항목의 수를 함께 표시합니다.

커스텀 표시를 적용한 다중 선택

사용자가 선택지를 알아볼 수 있도록 옵션에 아이콘이나 색상 같은 시각적 표시가 필요할 때가 많습니다. 옵션 안에 커스텀 템플릿을 사용하면 풍부한 서식을 적용하면서 표시값은 간결하게 요약할 수 있습니다.

각 옵션은 레이블 옆에 아이콘을 표시합니다. 표시값은 첫 번째 선택 항목의 아이콘과 텍스트, 추가 선택 항목의 수로 갱신됩니다. 선택된 옵션에는 체크 표시를 해서 결과를 시각적으로 명확하게 보여줍니다.

선택 제어

폼에서는 선택할 수 있는 개수를 제한하거나 사용자의 선택을 검사해야 할 때가 있습니다. 코드로 선택을 제어하면 접근성을 유지하면서 이러한 제약을 적용할 수 있습니다.

이 예제는 선택을 두 항목으로 제한합니다. 한도에 도달하면 선택되지 않은 옵션을 비활성화해서 추가 선택을 막고, 콤보박스의 표시값에 선택 결과를 반영합니다.

테스트

다중 선택 패턴은 @angular/aria/combobox/testing의 ComboboxHarness와 @angular/aria/listbox/testing의 ListboxHarness를 함께 사용해서 테스트할 수 있습니다. 하네스로 다중 선택 컴포넌트를 테스트하는 예제는 다음과 같습니다.

import {ComponentFixture, TestBed} from '@angular/core/testing';
import {HarnessLoader} from '@angular/cdk/testing';
import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed';
import {ComboboxHarness} from '@angular/aria/combobox/testing';
import {ListboxHarness} from '@angular/aria/listbox/testing';
import {MyMultiselectComponent} from './my-multiselect'; // 테스트할 컴포넌트

describe('MyMultiselectComponent', () => {
  let fixture: ComponentFixture<MyMultiselectComponent>;
  let loader: HarnessLoader;

  beforeEach(async () => {
    TestBed.configureTestingModule({
      imports: [MyMultiselectComponent],
    });

    fixture = TestBed.createComponent(MyMultiselectComponent);
    await fixture.whenStable();
    loader = TestbedHarnessEnvironment.loader(fixture);
  });

  it('should allow selecting multiple options', async () => {
    const select = await loader.getHarness(ComboboxHarness);

    // 드롭다운을 엽니다.
    await select.open();

    // 팝업에서 리스트박스 하네스를 가져옵니다.
    const listbox = await select.getPopupWidget(ListboxHarness);
    expect(await listbox.isMulti()).toBe(true);

    const options = await listbox.getOptions();

    // 첫 번째와 두 번째 옵션을 선택합니다.
    await options[0].click();
    await options[1].click();

    // 두 옵션이 모두 선택되었는지 확인합니다.
    expect(await options[0].isSelected()).toBe(true);
    expect(await options[1].isSelected()).toBe(true);

    // 드롭다운을 닫습니다.
    await select.close();

    // 값이 변경되었는지 확인합니다(예: 쉼표로 구분한 목록이나 개수).
    expect(await (await select.host()).text()).toContain('Option 1, Option 2');
  });
});

API 레퍼런스

자세한 API 문서는 다음 API 레퍼런스를 참고하세요.