Featuring Design System
Selection & Editing

Row Selection

TanStack 네이티브 rowSelection(행 id → boolean) 위의 체크박스 선택 — 셀 선택과는 독립적인 별개 기능입니다.

개요

행 선택은 TanStack Table의 네이티브 rowSelection 상태(Record<string, boolean>, 행 id → boolean 맵)를 기반으로 동작하는 체크박스 선택 모델입니다.

셀 선택과의 차이

이 페이지의 행 선택과 셀 선택(./selection)완전히 별개 기능입니다.

행 선택 (이 페이지)셀 선택 (./selection)
상태 모델RowSelectionState — 행 id → booleanDataGridSelectionState — anchor/focused/ranges 사각형
진입점enableRowSelection + createSelectColumnuseDataGridSelection
UI체크박스 컬럼Excel식 사각형 하이라이트
조합독립적으로 함께 켤 수 있음독립적으로 함께 켤 수 있음

언제 어느 것을? 행 단위 선택 후 벌크 액션(삭제·그룹 저장·내보내기)에는 행 선택을, 셀 데이터 복사·편집 진입·스프레드시트식 상호작용에는 셀 선택을 사용합니다. 둘을 동시에 켜도 됩니다(KitchenSink 참고).


기본 사용법

enableRowSelection: trueuseDataGrid에 주고, createSelectColumn을 컬럼 목록 맨 앞에 끼웁니다.

const data = [
{ id: '1', name: '김민준', followers: 128000 },
{ id: '2', name: '이서연', followers: 84500 },
{ id: '3', name: '박도윤', followers: 233000 },
{ id: '4', name: '최하은', followers: 45200 },
{ id: '5', name: '정시우', followers: 67800 },
];

// 행 선택 체크박스 컬럼 — 아래는 createSelectColumn이 하는 일을 인라인으로 풀어쓴 최소 버전입니다.
//   (실제 프로젝트에서는 공용 createSelectColumn 팩토리로 한 곳에서 소유하세요.)
const columns = [
{
  id: '_select',
  size: 44,
  enableSorting: false,
  meta: { columnRole: 'control' },
  header: ({ header, table }) => (
    <DataGrid.HeaderCell
      header={header}
      $css={{ cursor: 'pointer' }}
      onClick={() => table.toggleAllRowsSelected()}
    >
      <Center $css={{ width: '100%', height: '100%' }}>
        <Checkbox
          aria-label="전체 선택"
          size="sm"
          checked={table.getIsAllRowsSelected()}
          indeterminate={table.getIsSomeRowsSelected() && !table.getIsAllRowsSelected()}
          onChange={table.getToggleAllRowsSelectedHandler()}
          onClick={(e) => e.stopPropagation()}
        />
      </Center>
    </DataGrid.HeaderCell>
  ),
  cell: (info) => (
    <DataGrid.Cell
      cell={info.cell}
      $css={{ cursor: 'pointer' }}
      onClick={() => info.row.getCanSelect() && info.row.toggleSelected()}
    >
      <Center $css={{ width: '100%', height: '100%' }}>
        <Checkbox
          aria-label={info.row.original.name + ' 선택'}
          size="sm"
          disabled={!info.row.getCanSelect()}
          checked={info.row.getIsSelected()}
          onChange={info.row.getToggleSelectedHandler()}
          onClick={(e) => e.stopPropagation()}
        />
      </Center>
    </DataGrid.Cell>
  ),
},
{
  accessorKey: 'name',
  header: ({ header }) => <DataGrid.HeaderCell header={header}>채널</DataGrid.HeaderCell>,
  cell: (info) => <DataGrid.Cell cell={info.cell}>{info.getValue()}</DataGrid.Cell>,
},
{
  accessorKey: 'followers',
  header: ({ header }) => <DataGrid.HeaderCell header={header}>팔로워</DataGrid.HeaderCell>,
  cell: (info) => <DataGrid.Cell cell={info.cell}>{info.getValue().toLocaleString()}</DataGrid.Cell>,
},
];

function RowSelectGrid() {
const [rowSelection, setRowSelection] = useState({});
const table = useDataGrid({
  data,
  columns,
  getCoreRowModel: getCoreRowModel(),
  getRowId: (row) => row.id,
  state: { rowSelection },
  onRowSelectionChange: setRowSelection,
  enableRowSelection: true,
});
const selectedCount = table.getSelectedRowModel().rows.length;
return (
  <VStack $css={{ gap: '$spacing-200' }}>
    <Typo variant="$caption-1" $css={{ color: '$text-3' }}>
      선택됨: {selectedCount} / {data.length}
    </Typo>
    <DataGrid.Root table={table} size="md">
      <DataGrid.Header />
      <DataGrid.Body />
    </DataGrid.Root>
  </VStack>
);
}

render(<RowSelectGrid />);

createSelectColumn은 헤더에 전체선택 체크박스, 각 행 셀에 행별 체크박스를 렌더합니다. 체크박스뿐 아니라 셀 전체가 토글 히트영역이므로, 셀 어디를 눌러도 선택이 토글됩니다.

getRowId 필수rowSelection의 키가 행 id입니다. getRowId를 지정하지 않으면 TanStack이 행 인덱스를 id로 쓰고, 정렬·필터 후 행 순서가 바뀌면 선택이 엉뚱한 행을 가리킵니다. 항상 (r) => r.id처럼 안정적인 고유 키를 주세요.


전체선택 — 페이지 vs 전 행

페이지네이션(getPaginationRowModel)을 사용할 때 헤더 전체선택의 의미가 두 가지로 갈립니다.

selectAllScope선택 범위내부 핸들러
'all' (기본)모든 행getToggleAllRowsSelectedHandler
'page'현재 페이지만getToggleAllPageRowsSelectedHandler

createSelectColumnselectAllScope 옵션으로 고릅니다.

// 클라이언트 페이지네이션 — 현재 페이지만 전체선택
createSelectColumn<Row>({ selectAllScope: 'page' })

// 서버 페이지네이션 또는 단순 데이터 — 전 행 전체선택 (기본값)
createSelectColumn<Row>({ selectAllScope: 'all' })

헤더 indeterminate

헤더 체크박스는 자동으로 indeterminate 상태를 관리합니다. createSelectColumn 내부 로직이 다음을 계산합니다.

  • 모두 선택checked
  • 일부 선택indeterminate(isSomeSelected && !isAllSelected)
  • 없음 → unchecked

selectAllScope에 따라 전 행 버전(getIsAllRowsSelected / getIsSomeRowsSelected) 또는 페이지 버전(getIsAllPageRowsSelected / getIsSomePageRowsSelected)을 사용합니다. 별도로 계산할 필요가 없습니다.


Controlled

state.rowSelection + onRowSelectionChange를 소비자가 직접 소유하면 외부 UI와 그리드가 단일 상태를 공유합니다.

const [rowSelection, setRowSelection] = useState<RowSelectionState>({
  '1': true,  // 행 id '1'이 초기 선택 상태
  '3': true,
});

const table = useDataGrid<Row>({
  data,
  columns,
  getRowId: (r) => r.id,
  state: { rowSelection },
  onRowSelectionChange: setRowSelection,
  enableRowSelection: true,
});

// 선택된 행 id 읽기
const selectedIds = Object.keys(rowSelection).filter((id) => rowSelection[id]);

// 선택된 행 모델 읽기
const selectedRows = table.getSelectedRowModel().rows;

// 외부에서 전체 선택 해제
const clearAll = () => setRowSelection({});

setRowSelection({})만으로 그리드 체크박스가 즉시 동기화됩니다. state가 유일한 source of truth입니다.


조건부 선택 가능 — enableRowSelection 술어

enableRowSelection에 함수를 주면 행마다 선택 가능 여부를 결정합니다.

const table = useDataGrid<Row>({
  // ...
  enableRowSelection: (row) => row.original.status !== 'inactive',
});

row.getCanSelect()false인 행은 createSelectColumn이 체크박스를 disabled로 렌더하고, 헤더 전체선택도 선택 가능한 행만 대상으로 동작합니다.


행 상태 스타일링

패키지는 선택된 행에 강조 색을 강요하지 않습니다. <DataGrid.Row>DataGridRowStatestyle / className state-callback으로 노출하므로, 소비자가 원하는 스타일을 자유롭게 입힙니다.

<DataGrid.Body>
  {(row) => (
    <DataGrid.Row
      row={row}
      onClick={() => row.toggleSelected()}
      // style state-callback — isSelected 에 따라 동적 스타일
      style={(state) => ({
        cursor: 'pointer',
        backgroundColor: state.isSelected
          ? 'var(--global-colors-primary-10)'
          : 'transparent',
        borderLeft: state.isSelected
          ? '3px solid var(--global-colors-primary-60)'
          : '3px solid transparent',
      })}
    >
      {row.getVisibleCells().map((cell) => (
        <Fragment key={cell.id}>
          {flexRender(cell.column.columnDef.cell, cell.getContext())}
        </Fragment>
      ))}
    </DataGrid.Row>
  )}
</DataGrid.Body>

$cssstyle의 차이 — rainbow-sprinkles 기반 $css는 atomic CSS라 콜백을 받지 않습니다. 선택 상태처럼 런타임 boolean으로 분기하는 동적 스타일은 style state-callback을 사용하세요.

DOM에는 선택 상태가 다음 속성으로도 노출됩니다.

  • data-selected — 행이 선택된 경우 빈 문자열 속성 부착
  • data-some-selected — 하위 행 일부가 선택된 경우 (트리 데이터)
  • aria-selected — 선택 시 "true", 미선택 시 속성 없음

CSS만으로 선택 행을 스타일링할 수도 있습니다.

[data-row][data-selected] {
  background-color: var(--global-colors-primary-10);
}

createSelectColumn 옵션

createSelectColumn_shared 공용 팩토리로 ColumnDef<T>를 반환합니다. 컬럼 설정은 다음 옵션으로 조정합니다.

Prop

Type


DataGridRowState

<DataGrid.Row>style/className state-callback에 전달되는 상태 객체입니다.

Prop

Type


다음 단계

  • 셀 선택(Selection model) — anchor/focused/ranges 사각형 모델. 행 선택과 별개이며 함께 사용할 수 있습니다.
  • Columns — 컬럼 정의, control 컬럼 패턴(meta.columnRole: 'control').
  • Pagination — 페이지 전환과 selectAllScope의 관계.
  • 라이브 스토리 — Storybook에서 Overview · Controlled · 조건부 선택 · 페이지 vs 전 행 · 행 상태 스타일링 예제를 직접 실행할 수 있습니다.