심화 가이드
접근성 지원

그리드

개요

그리드는 방향키, Home, End, Page Up/Down으로 2차원 데이터나 상호작용 엘리먼트를 탐색할 수 있게 합니다. 데이터 표, 달력, 스프레드시트, 관련 상호작용 엘리먼트를 그룹으로 묶는 레이아웃 패턴에 사용할 수 있습니다.

사용법

그리드는 데이터나 상호작용 엘리먼트를 행과 열로 구성하고, 사용자가 여러 방향으로 키보드 탐색을 해야 하는 경우에 적합합니다.

그리드를 사용하기 좋은 경우:

  • 편집하거나 선택할 수 있는 셀이 있는 상호작용 데이터 표를 만들 때
  • 달력이나 날짜 선택기를 만들 때
  • 스프레드시트와 비슷한 인터페이스를 구현할 때
  • 상호작용 엘리먼트(버튼, 체크박스)를 묶어서 페이지의 Tab 키 이동 지점을 줄일 때
  • 2차원 키보드 탐색이 필요한 인터페이스를 만들 때

그리드를 피해야 하는 경우:

  • 간단한 읽기 전용 표를 표시할 때(의미에 맞는 HTML <table>을 사용하세요)
  • 열이 하나인 목록을 표시할 때(리스트박스를 사용하세요)
  • 계층형 데이터를 표시할 때(트리를 사용하세요)
  • 표 형태가 아닌 폼을 만들 때(표준 폼 컨트롤을 사용하세요)

주요 기능

  • 2차원 탐색: 방향키로 모든 방향의 셀 사이를 이동합니다.
  • 포커스 모드: roving tabindex 또는 activedescendant 포커스 전략을 선택합니다.
  • 선택 지원: 단일/다중 선택 모드로 셀 선택 기능을 활성화할 수 있습니다.
  • 순환 동작: 그리드 경계에서의 탐색 동작을 설정합니다(continuous, loop, nowrap).
  • 범위 선택: 보조 키나 드래그로 여러 셀을 선택합니다.
  • 비활성화 상태: 전체 그리드나 개별 셀을 비활성화합니다.
  • RTL 지원: 오른쪽에서 왼쪽으로 쓰는 언어의 탐색을 자동으로 지원합니다.

예제

데이터 표 그리드

사용자가 방향키로 셀 사이를 이동해야 하는 상호작용 표에 그리드를 사용하세요. 이 예제는 키보드 탐색을 지원하는 기본 데이터 표입니다.

표 엘리먼트에는 ngGrid 디렉티브를, 각 행에는 ngGridRow를, 각 셀에는 ngGridCell을 적용하세요.

달력 그리드

달력은 그리드의 대표적인 사용 사례입니다. 이 예제는 사용자가 방향키로 날짜를 탐색하는 월간 달력입니다.

사용자는 셀에 포커스가 있을 때 Enter나 Space를 눌러 날짜를 활성화할 수 있습니다.

레이아웃 그리드

상호작용 엘리먼트를 묶고 Tab 키 이동 지점을 줄이려면 레이아웃 그리드를 사용하세요. 이 예제는 알약 모양 버튼으로 구성된 그리드입니다.

각 버튼을 Tab 키로 이동하는 대신 방향키로 탐색하며, 버튼 중 하나만 Tab 키로 포커스를 받습니다.

선택 모드와 포커스 모드

[enableSelection]="true"로 선택을 활성화하고 포커스와 선택이 어떻게 연동되는지 설정하세요.

<table
  ngGrid
  [enableSelection]="true"
  [selectionMode]="'explicit'"
  [multi]="true"
  [focusMode]="'roving'"
>
  <tr ngGridRow>
    <td ngGridCell>Cell 1</td>
    <td ngGridCell>Cell 2</td>
  </tr>
</table>

선택 모드:

  • follow: 포커스가 있는 셀을 자동으로 선택합니다.
  • explicit: 사용자가 Space를 누르거나 클릭해서 셀을 선택합니다.

포커스 모드:

  • roving: tabindex를 사용해서 셀로 포커스를 이동합니다(간단한 그리드에 더 적합합니다).
  • activedescendant: 포커스는 그리드 컨테이너에 두고 aria-activedescendant로 활성 셀을 나타냅니다(가상 스크롤에 더 적합합니다).

테스트

Angular Aria는 그리드 컴포넌트를 테스트할 수 있는 컴포넌트 하네스를 제공합니다. 컴포넌트 테스트에서 하네스를 사용하는 예제는 다음과 같습니다.

import {ComponentFixture, TestBed} from '@angular/core/testing';
import {HarnessLoader} from '@angular/cdk/testing';
import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed';
import {GridHarness} from '@angular/aria/grid/testing';
import {MyGridComponent} from './my-grid'; // 테스트할 컴포넌트

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

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

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

  it('should read cell values and focus cells', async () => {
    const grid = await loader.getHarness(GridHarness);

    // 모든 셀의 텍스트를 행별로 구성한 2차원 배열로 가져옵니다.
    const cellTexts = await grid.getCellTextByIndex();
    expect(cellTexts).toEqual([
      ['Cell 1.1', 'Cell 1.2'],
      ['Cell 2.1', 'Cell 2.2'],
    ]);

    // 텍스트로 특정 셀을 가져옵니다.
    const cells = await grid.getCells({text: 'Cell 1.1'});
    expect(cells.length).toBe(1);
    const cell = cells[0];

    // 셀 상태를 확인합니다.
    expect(await cell.isSelected()).toBe(true);
    expect(await cell.isActive()).toBe(true);

    // 셀에 포커스를 둡니다.
    await cell.focus();
    expect(await cell.isFocused()).toBe(true);
  });
});

API 레퍼런스

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