Featuring Design System
Columns

Columns

ColumnDef 기반 헤드리스 컬럼 모델 — 컬럼 종류, 컨트롤 컬럼, 가시성·크기·순서 제어, meta 표면.

개요

그리드의 컬럼은 TanStack Table의 ColumnDef<TData> 위에서 동작하는 헤드리스 모델입니다. 패키지가 toolbar·resize 핸들·순서 변경 버튼을 자동으로 그려주지 않습니다. 헤더 셀·바디 셀의 모양은 column.header / column.cell 함수 안에서 소비자가 DataGrid.HeaderCellDataGrid.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.ResizeHandleHeaderCell 안에 명시적으로 부착합니다. 패키지가 자동으로 달지 않습니다(N-final 원칙).

header: ({ header }) => (
	<DataGrid.HeaderCell header={header}>
		채널
		<DataGrid.ResizeHandle header={header} />
	</DataGrid.HeaderCell>
),

ResizeHandle props

Prop

Type

키보드 동작 정리:

동작
ArrowRight+keyboardSteppx
ArrowLeftkeyboardSteppx
Shift+Arrow±5×keyboardSteppx
Home컬럼 정의의 기본 size로 reset

enableResizing: false인 컬럼은 핸들이 data-resize-disabled 속성으로 비활성되고 폭이 고정됩니다. 핸들은 DOM에 존재하지만 키보드·포인터 입력을 차단합니다.

리사이즈 mode는 useDataGridcolumnResizeMode 옵션으로 제어합니다(기본 '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 유니온, cell vs column 모드, $css·render·state callback.
  • SortinggetToggleSortingHandler, SortArrow, 멀티 컬럼 정렬.
  • Row selection — 체크박스 컬럼 레시피와 columnRole: 'control' 배선.
  • Cell mergingmeta.colSpan / meta.rowSpan 상세 규칙과 pin·가상화 제약.
  • View statemeta.skeleton, loading, skeletonRows 옵션.
  • 라이브 예시 → Storybook: Columns