Columns
ColumnDef 기반 헤드리스 컬럼 모델 — 컬럼 종류, 컨트롤 컬럼, 가시성·크기·순서 제어, meta 표면.
개요
그리드의 컬럼은 TanStack Table의 ColumnDef<TData> 위에서 동작하는 헤드리스 모델입니다.
패키지가 toolbar·resize 핸들·순서 변경 버튼을 자동으로 그려주지 않습니다. 헤더 셀·바디 셀의 모양은
column.header / column.cell 함수 안에서 소비자가 DataGrid.HeaderCell과 DataGrid.Cell로 직접 렌더합니다.
컬럼의 성격(데이터인지 chrome인지)도 라이브러리가 accessor 유무 같은 것으로 추론하지 않습니다.
chrome 컬럼을 선택·복사 범위에서 빼고 싶으면 meta.columnRole: 'control'로 명시 표시해야 합니다.
덕분에 computed/display 데이터 컬럼이 소리 없이 누락되는 일이 없습니다.
컬럼 종류
컬럼을 정의하는 방식은 세 가지입니다.
accessorKey — 키 직접 매핑
row 객체의 키로 값을 뽑는 가장 흔한 방식입니다.
const columns: ColumnDef<Influencer>[] = [
{
accessorKey: 'name',
size: 180,
header: ({ header }) => <DataGrid.HeaderCell header={header}>채널</DataGrid.HeaderCell>,
cell: (info) => (
<DataGrid.Cell cell={info.cell}>
{info.getValue() as string}
</DataGrid.Cell>
),
},
];accessorFn + id — 파생값
여러 필드를 합성하거나 변환이 필요한 컬럼입니다. accessorFn은 키가 없으므로 id가 필수입니다.
accessor가 있으므로 정렬·필터 대상이 됩니다.
{
id: 'cpm',
accessorFn: (row) =>
row.followers > 0 ? row.costPerPost / (row.followers / 10_000) : 0,
size: 150,
header: ({ header }) => (
<DataGrid.HeaderCell header={header} $css={{ justifyContent: 'flex-end' }}>
1만 팔로워당 단가
</DataGrid.HeaderCell>
),
cell: (info) => (
<DataGrid.Cell cell={info.cell}>
{Math.round(info.getValue() as number)}
</DataGrid.Cell>
),
},display 컬럼 — accessor 없음
accessor가 없는 순수 표시 컬럼입니다. id + header + cell만 지정합니다.
정렬·필터 대상이 아니며, 액션 버튼·행 번호·확장 토글 같은 chrome에 씁니다.
{
id: 'actions',
size: 120,
enableSorting: false,
header: ({ header }) => <DataGrid.HeaderCell header={header}>액션</DataGrid.HeaderCell>,
cell: (info) => (
<DataGrid.Cell cell={info.cell}>
<Button.Root size="sm" variant="tertiary">
<Button.Text>편집</Button.Text>
</Button.Root>
</DataGrid.Cell>
),
},라이브 예시 → Storybook: ColumnKinds
컨트롤 컬럼 — meta.columnRole
meta.columnRole: 'control'을 지정하면 그 컬럼은 셀 선택·복사·키보드 내비게이션에서 제외됩니다.
행 번호, 선택 체크박스, 확장 토글, 액션 버튼처럼 데이터가 아닌 chrome 컬럼에 씁니다.
명시적 opt-out 모델 — 표시하지 않으면 기본값 'data'(선택 가능)입니다. chrome 컬럼만 'control'로 표시합니다.
// 행 번호 컬럼 — 선택/복사/내비에서 제외
{
id: '_rownum',
size: 56,
enableSorting: false,
enableResizing: false,
meta: { columnRole: 'control' },
header: ({ header }) => (
<DataGrid.HeaderCell header={header} $css={{ justifyContent: 'center', color: '$text-3' }}>
#
</DataGrid.HeaderCell>
),
cell: (info) => (
<DataGrid.Cell cell={info.cell} $css={{ justifyContent: 'center', color: '$text-3' }}>
{info.row.index + 1}
</DataGrid.Cell>
),
},
// 액션 컬럼 — display 컬럼이어도 명시 표시가 필요
{
id: 'actions',
size: 110,
enableSorting: false,
meta: { columnRole: 'control' },
header: ({ header }) => <DataGrid.HeaderCell header={header}>액션</DataGrid.HeaderCell>,
cell: (info) => (
<DataGrid.Cell cell={info.cell} onPointerDown={(e) => e.stopPropagation()}>
<Button.Root size="sm" variant="tertiary">
<Button.Text>편집</Button.Text>
</Button.Root>
</DataGrid.Cell>
),
},셀 선택이 활성화된 그리드에서 데이터 셀을 클릭·드래그하면 선택 범위가 잡히지만,
columnRole: 'control' 컬럼은 클릭·드래그·복사 범위·화살표 내비게이션 모두에서 건너뜁니다.
Row selection 페이지의 createSelectColumn 레시피(패키지 export가 아닌 소비자 코드)도 내부에서 columnRole: 'control'을 세팅합니다.
라이브 예시 → Storybook: ControlColumns
가시성
컬럼 가시성은 TanStack 표준 API로만 제어합니다. 패키지 코드 변경 없이 소비자 toolbar에서 토글합니다.
column.getIsVisible() // 현재 표시 여부
column.getToggleVisibilityHandler() // onChange 핸들러 (checkbox에 바로 전달)
column.toggleVisibility() // 직접 호출 방식
column.getCanHide() // false면 숨길 수 없음(enableHiding: false)컬럼 정의에서 enableHiding: false를 지정하면 그 컬럼은 숨길 수 없습니다(getCanHide() → false).
핵심 컬럼(예: 이름)을 항상 표시하고 싶을 때 씁니다.
// 가시성 toolbar 예시
{table.getAllLeafColumns().map((col) => (
<Label.Root key={col.id} size="sm">
<Checkbox
checked={col.getIsVisible()}
disabled={!col.getCanHide()}
onChange={col.getToggleVisibilityHandler()}
/>
<Label.Text>{col.id}</Label.Text>
</Label.Root>
))}가시성 상태를 외부에서 소유하려면 컨트롤드 state.columnVisibility + onColumnVisibilityChange로 흘립니다.
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({ email: false });
const table = useDataGrid<Row>({
// ...
state: { columnVisibility },
onColumnVisibilityChange: setColumnVisibility,
});그리드는 getVisibleLeafColumns 기준으로 렌더되므로 토글 즉시 반영됩니다.
라이브 예시 → Storybook: ColumnVisibility
크기와 리사이즈
컬럼 폭은 size(초기 폭) · minSize · maxSize로 정의합니다. 단위는 픽셀(px)입니다.
{
accessorKey: 'name',
size: 200, // 초기 폭
minSize: 120, // 드래그 최소 폭
maxSize: 360, // 드래그 최대 폭
// ...
}드래그 리사이즈는 DataGrid.ResizeHandle을 HeaderCell 안에 명시적으로 부착합니다.
패키지가 자동으로 달지 않습니다(N-final 원칙).
header: ({ header }) => (
<DataGrid.HeaderCell header={header}>
채널
<DataGrid.ResizeHandle header={header} />
</DataGrid.HeaderCell>
),ResizeHandle props
Prop
Type
키보드 동작 정리:
| 키 | 동작 |
|---|---|
ArrowRight | +keyboardSteppx |
ArrowLeft | −keyboardSteppx |
Shift+Arrow | ±5×keyboardSteppx |
Home | 컬럼 정의의 기본 size로 reset |
enableResizing: false인 컬럼은 핸들이 data-resize-disabled 속성으로 비활성되고 폭이 고정됩니다.
핸들은 DOM에 존재하지만 키보드·포인터 입력을 차단합니다.
리사이즈 mode는 useDataGrid의 columnResizeMode 옵션으로 제어합니다(기본 'onChange' — 실시간).
const table = useDataGrid<Row>({
// ...
columnResizeMode: 'onChange',
});핸들에는 role="separator" + aria-valuenow / aria-valuemin / aria-valuetext가 자동으로 붙어
스크린리더에 현재 폭을 노출합니다.
라이브 예시 → Storybook: ColumnSizing
순서
컬럼 순서는 TanStack state.columnOrder(컬럼 id 문자열 배열) 한 곳이 SoT입니다. 컨트롤드로 들고
onColumnOrderChange로 갱신합니다.
const [columnOrder, setColumnOrder] = useState<ColumnOrderState>([
'name', 'category', 'platform', 'engagementRate',
]);
const table = useDataGrid<Row>({
// ...
state: { columnOrder },
onColumnOrderChange: setColumnOrder,
});table.setColumnOrder(nextOrder) 또는 setColumnOrder state setter로 배열을 교체하면
헤더·바디가 그 순서대로 다시 렌더됩니다.
드래그앤드롭 기반 헤더 reorder는 onCellPointerDown veto 패턴과 함께 사용합니다.
자세한 내용은 Storybook Reorder 스토리를 참고하세요.
라이브 예시 → Storybook: ColumnOrdering
column.meta 표면
ColumnMeta<TData, TValue>는 declaration merging으로 TanStack에 추가된 필드입니다.
각 필드는 해당 기능 페이지에서 자세히 다룹니다.
Prop
Type
다음 단계
- Composition & DI — HeaderCell·Cell 유니온,
cellvscolumn모드,$css·render·state callback. - Sorting —
getToggleSortingHandler,SortArrow, 멀티 컬럼 정렬. - Row selection — 체크박스 컬럼 레시피와
columnRole: 'control'배선. - Cell merging —
meta.colSpan/meta.rowSpan상세 규칙과 pin·가상화 제약. - View state —
meta.skeleton,loading,skeletonRows옵션. - 라이브 예시 → Storybook: Columns