본문으로 건너뛰기

topgrid 시작 가이드

topgrid 는 TanStack Table v8 기반의 React 그리드 라이브러리다. 핵심 그리드와 셀 렌더러, 정렬/필터, Excel/CSV/PDF 내보내기, Vue 어댑터를 제공하는 무료 MIT 패키지 7종을 쓸 수 있고, 다단 헤더·변경 추적·Excel-style 범위 편집 같은 고급 기능은 상용 Pro 패키지로 확장한다. (전체 31 패키지 구성은 아키텍처 참고.)

  • npm scope: @topgrid/*
  • 권장 환경: React 18/19 + TypeScript + Tailwind CSS
  • 정확한 export·시그니처 전체는 API 레퍼런스 참고

1. 어떤 패키지가 필요한가

사용 사례권장 패키지
단순 데이터 표시 (정렬/필터 없음)@topgrid/grid-core + @topgrid/grid-renderers
정렬 + 필터 + 검색+ @topgrid/grid-features
Excel/CSV/PDF 다운로드+ @topgrid/grid-export
다단 헤더 (월/일/요일 등)+ @topgrid/grid-pro-header (Pro)
인라인 편집 + 변경 추적 + 저장+ @topgrid/grid-pro-tracking (Pro)
Excel-style 범위 선택 + 키보드+ @topgrid/grid-pro-range (Pro)
Master-Detail (펼치기) + 우클릭 메뉴+ @topgrid/grid-pro-master (Pro)
Group / Sum / Avg 집계+ @topgrid/grid-pro-agg (Pro)
차트·시각화 (셀 스파크라인 ~ 엔터프라이즈 17종, React/Vue)+ 차트 패키지 → 차트 (Pro)
  • 무료 MIT 7 패키지 (grid-core / grid-core-headless / grid-renderers / grid-features / grid-sizing / grid-export / grid-vue) 는 자유 사용.
  • Pro 패키지는 라이선스 키가 필요하다. 미설정 시 그리드 우상단에 "Unlicensed @topgrid/grid" watermark 가 표시되며, 기능 자체는 동작한다.

2. Hello World — 5분 안에 그리드 표시

2.1 설치

# Vite + React 프로젝트 가정
npm create vite@latest my-app -- --template react-ts
cd my-app
npm install

# topgrid + peer deps
npm install @topgrid/grid-core @topgrid/grid-renderers \
@tanstack/react-table @tanstack/react-virtual

# Tailwind CSS (renderer 의 className 사용을 위해)
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p

Peer dependency:

react ^18.0.0 || ^19.0.0
react-dom ^18.0.0 || ^19.0.0
@tanstack/react-table ^8.0.0
@tanstack/react-virtual ^3.0.0 (가상화 사용 시)

2.2 Tailwind 설정

모든 셀 렌더러는 Tailwind className 으로만 스타일링된다. 패키지 안의 클래스가 빌드에 포함되도록 content 에 패키지 경로를 추가한다.

// tailwind.config.js
export default {
content: [
'./src/**/*.{js,ts,jsx,tsx}',
'./node_modules/@topgrid/**/*.{js,mjs}', // ★ topgrid 클래스 인식
],
theme: { extend: {} },
plugins: [],
};
/* src/index.css */
@tailwind base;
@tailwind components;
@tailwind utilities;

2.3 첫 그리드

권장: 고수준 createColumns — TanStack ColumnDef 지식 없이 { id, name, type } 로 선언하면, type 에 맞는 셀 렌더러가 자동 배선된다(facade @topgrid/grid 가 기본 렌더러를 wiring).

import { Grid, createColumns } from '@topgrid/grid'; // facade = 렌더러 자동 배선

interface User {
id: number;
name: string;
email: string;
age: number;
}

const columns = createColumns<User>([
{ id: 'name', name: '이름', type: 'text', width: '150' },
{ id: 'email', name: '이메일', type: 'text', width: '250' },
{ id: 'age', name: '나이', type: 'number', align: 'right', width: '80' },
]);

const data: User[] = [
{ id: 1, name: '김철수', email: 'chulsoo@example.com', age: 30 },
{ id: 2, name: '이영희', email: 'younghee@example.com', age: 28 },
{ id: 3, name: '박민수', email: 'minsoo@example.com', age: 35 },
];

export default function App() {
return (
<div className="p-8">
<h1 className="text-2xl font-bold mb-4">사용자 목록</h1>
<Grid<User>
data={data}
columns={columns}
getRowId={(u) => String(u.id)} // ★ 안정적 행 식별 (아래 주의 참고)
enableSort
/>
</div>
);
}
npm run dev

→ 브라우저에 그리드가 표시되고, 컬럼 헤더 클릭 시 정렬이 동작한다.

★ getRowId 를 지정하라. 미지정 시 행 식별이 배열 인덱스로 떨어져, 선택(selection)·행 재정렬·셀 변경 플래시가 정렬/필터 후 엉뚱한 행을 추적한다(가장 흔한 함정). dev 모드에서 이 조건이면 경고가 출력된다. getRowId={(row) => row.<고유키>} 권장.

정렬 prop 은 enableSort (enableSorting 아님 — TanStack 옵션명과 다름).

저수준 경로: TanStack 의 ColumnDef<User>[]columns 에 직접 넘겨 완전 제어할 수도 있다 (accessorFn·커스텀 cell 등). 이때만 @tanstack/react-table 지식이 필요하다.


3. 점진적 확장

3.1 페이지네이션

GridPagination 은 그리드와 별도 컴포넌트로 배치한다.

import { Grid, GridPagination, useGridState } from '@topgrid/grid-core';

function App() {
const grid = useGridState<User>({
initialPagination: { pageIndex: 0, pageSize: 10 },
});

return (
<>
<Grid<User>
data={data}
columns={columns}
enablePagination
state={{ pagination: grid.pagination }}
onPaginationChange={grid.setPagination}
/>
<GridPagination
table={grid.table}
mode="client"
pageSizeOptions={[10, 20, 50]}
showTotalCount
/>
</>
);
}

3.2 필터 + 검색 (@topgrid/grid-features)

import { TextFilter, GlobalSearchInput, textFilterFn } from '@topgrid/grid-features';

const columns: ColumnDef<User>[] = [
{
id: 'name',
accessorKey: 'name',
header: ({ column }) => (
<div className="flex items-center gap-1">
이름
<TextFilter column={column} defaultOperator="contains" />
</div>
),
filterFn: textFilterFn,
},
// ...
];

// 그리드 위에 글로벌 검색
<GlobalSearchInput table={table} placeholder="전체 검색..." />

다중 정렬이 필요하면 useMultiSort 를 사용한다. 이 훅은 @topgrid/grid-features 에서 export 된다 (정렬 우선순위 배지 SortBadge 와 초기화 버튼 SortClearButton@topgrid/grid-core 에 있다).

3.3 셀 렌더러 (@topgrid/grid-renderers)

import { NumberCell, StatusBadgeCell, LinkCell } from '@topgrid/grid-renderers';

const columns: ColumnDef<User>[] = [
{
id: 'name',
accessorKey: 'name',
header: '이름',
cell: (info) => (
<LinkCell value={String(info.getValue())} onClick={() => goToDetail(info.row.original.id)} />
),
},
{
id: 'age',
accessorKey: 'age',
header: '나이',
cell: (info) => <NumberCell value={info.getValue() as number} unit="" />,
},
{
id: 'status',
accessorKey: 'status',
header: '상태',
cell: (info) => (
<StatusBadgeCell
value={info.getValue() as string}
colorMap={{ active: 'green', inactive: 'gray', pending: 'yellow' }}
/>
),
},
];

표시 셀 11종 + 인라인 편집 셀 EditableCell 의 prop 계약은 API 레퍼런스 §2 참고.

3.4 Excel 내보내기 (@topgrid/grid-export)

import { exportToExcel } from '@topgrid/grid-export';

<button onClick={() => exportToExcel(table, { fileName: '사용자목록.xlsx' })}>
엑셀 다운로드
</button>

버튼 하나로 Excel/CSV/PDF 를 모두 노출하려면 <GridExportButton> 컴포넌트를 쓴다. 범위(전체/필터/선택)·다중 시트·수식 보존·Vue 사용법은 데이터 내보내기 가이드 참고.

jspdf optional deps: exportToPdf 는 jspdf 의 동적 import 4종 (fflate / html2canvas / dompurify / canvg) 을 사용한다. Excel/CSV 만 쓰는데 빌드가 이 모듈을 못 찾으면 번들러 설정으로 stub 한다.

// next.config.ts 또는 vite.config.ts
resolve: {
fallback: { fflate: false, html2canvas: false, dompurify: false, canvg: false },
}

3.5 컬럼 고정 + 가상화 (대량 데이터)

<Grid<User>
data={data} // 1만+ 행
columns={columns}
enableColumnPinning
defaultColumnPinning={{ left: ['name'], right: ['actions'] }}
enableVirtualization
estimatedRowHeight={40}
virtualOverscan={5}
/>

가상화는 enableVirtualizationestimatedRowHeight 를 함께 명시해야 적용된다.


4. Pro 기능

4.1 라이선스 키 설정 (@topgrid/grid-license)

import { setLicenseKey } from '@topgrid/grid-license';

useEffect(() => {
setLicenseKey(import.meta.env.VITE_TOPGRID_LICENSE_KEY ?? '');
}, []);

미설정/만료 시 그리드 우상단에 "Unlicensed @topgrid/grid" watermark 가 표시된다 (사용은 가능). 라이선스 상태는 useLicenseStatus() 로 조회한다.

4.2 다단 헤더 (@topgrid/grid-pro-header)

import { createColumnGroup } from '@topgrid/grid-pro-header';

const columns: ColumnDef<User>[] = [
createColumnGroup<User>({
header: '개인 정보',
columns: [
{ id: 'name', accessorKey: 'name', header: '이름' },
{ id: 'age', accessorKey: 'age', header: '나이' },
],
}),
createColumnGroup<User>({
header: '연락처',
columns: [
{ id: 'email', accessorKey: 'email', header: '이메일' },
{ id: 'phone', accessorKey: 'phone', header: '전화' },
],
}),
];

4.3 인라인 편집 + 변경 추적 (@topgrid/grid-pro-tracking)

ChangeTrackingGrid 는 baseline 대비 추가/수정/삭제를 추적하고, ref 로 노출되는 ChangeTrackingAPI 로 변경분을 모아 저장한다.

import { ChangeTrackingGrid, type ChangeTrackingAPI } from '@topgrid/grid-pro-tracking';
import { EditableCell } from '@topgrid/grid-renderers';
import { useRef, useState } from 'react';

function EditablePage() {
const trackingRef = useRef<ChangeTrackingAPI<User>>(null);
const [editingCell, setEditingCell] = useState<{ rowId: string; colId: string } | null>(null);

const editableColumns: ColumnDef<User>[] = [
{
id: 'name',
accessorKey: 'name',
header: '이름',
cell: (info) => {
const isEditing =
editingCell?.rowId === info.row.id && editingCell?.colId === 'name';
return (
<EditableCell
value={String(info.getValue() ?? '')}
editType="text"
isEditing={isEditing}
onStartEdit={() => setEditingCell({ rowId: info.row.id, colId: 'name' })}
onCommit={(newValue) => {
trackingRef.current?.updateRow(info.row.id, { name: newValue });
setEditingCell(null);
}}
onCancel={() => setEditingCell(null)}
/>
);
},
},
// ...
];

const handleSave = async () => {
const cs = trackingRef.current?.getChangeSet();
if (!cs) return;
await api.batchSave({ added: cs.added, updated: cs.updated, removed: cs.removed });
trackingRef.current?.resetChanges();
};

return (
<>
<ChangeTrackingGrid<User>
ref={trackingRef}
data={data}
columns={editableColumns}
getRowId={(row) => String(row.id)}
/>
<button onClick={handleSave}>저장</button>
<button onClick={() => trackingRef.current?.resetChanges()}>취소</button>
</>
);
}
  • 변경된 행은 색상으로 구분된다: 추가=green / 수정=yellow / 삭제=red.
  • resetChanges() 는 baseline 으로 복원한다. 저장 직후 화면 데이터도 갱신하려면 데이터 state 를 함께 갱신한 뒤 resetChanges() 를 호출한다.

첫 글자 유실 방지: 키 입력으로 편집을 시작할 때 첫 글자가 사라지지 않도록 EditableCellinitialDraft prop 으로 첫 글자를 전달한다.

4.4 Excel-style 범위 선택 + 키보드 (@topgrid/grid-pro-range)

가장 간단한 길은 all-in-one RangeSelectGrid 다.

import { RangeSelectGrid } from '@topgrid/grid-pro-range';

<RangeSelectGrid<User>
data={data}
columns={columns}
enableClipboard // Ctrl+C/V
enableKeyboardNav // 방향키 + Tab + Enter
enableDragFill // Excel-style 채우기 핸들
onCellChange={(rowIdx, colIdx, newValue) => {
// 데이터 업데이트
}}
/>

저수준 제어가 필요하면 useCellRange (드래그 범위) / useKeyboardNav (방향키·Tab) / useClipboard (Ctrl+C/V) / useKeyboardEdit (Delete·일괄 입력) 훅을 직접 조합한다. 시그니처는 API 레퍼런스 §6 참고.


4.5 Vue 3 — 피벗 · 서버사이드 (Pro)

Nuxt/Vue 3 앱은 동일한 프레임워크 무관 코어(grid-pro-{pivot,serverside}-core) 위에 얹은 Vue 전용 패키지로 피벗·서버사이드를 쓴다. React 의존 0, 라이선스는 @topgrid/grid-license-core로 게이트하며 컨트롤러는 onMounted에서만 생성되어 Nuxt SSR 안전하다.

# 피벗(Vue) / 서버사이드(Vue) — 필요한 것만
pnpm add @topgrid/grid-pro-pivot-vue @topgrid/grid-pro-serverside-vue vue @tanstack/vue-table

피벗 — useVuePivot 컴포저블 + VuePivotGrid 렌더 셸

<script setup lang="ts">
import { ref } from 'vue';
import { useVuePivot, VuePivotGrid, setLicenseKey } from '@topgrid/grid-pro-pivot-vue';

setLicenseKey('YOUR-LICENSE-KEY'); // 앱 entry 1회 (미설정 시 워터마크)

const data = ref([
{ region: '서울', quarter: 'Q1', amt: 120 },
{ region: '부산', quarter: 'Q1', amt: 90 },
// …
]);
const config = ref({
rows: ['region'],
columns: ['quarter'],
values: [{ field: 'amt', aggregationFn: 'sum' }],
});

// 헤드리스: 반응형 PivotModel (직접 렌더)
const model = useVuePivot(data, config);
</script>

<template>
<!-- 편의 렌더 셸: 다단 헤더 + 값 셀을 vue-table 로 렌더 -->
<VuePivotGrid :data="data" :config="config" />
<!-- 또는 model 로 직접 렌더 / VuePivotPanel(4존 DnD)로 config 편집 -->
</template>

서버사이드(SSRM) — useVueServerSideData

<script setup lang="ts">
import { useVueServerSideData, isRowPlaceholder, setLicenseKey } from '@topgrid/grid-pro-serverside-vue';
import type { ServerSideDatasource } from '@topgrid/grid-pro-serverside-vue';

setLicenseKey('YOUR-LICENSE-KEY');

const datasource: ServerSideDatasource<Row> = {
async getRows({ startRow, endRow, sortModel, filterModel }) {
const res = await fetch('/api/rows', {
method: 'POST',
body: JSON.stringify({ startRow, endRow, sortModel, filterModel }),
});
return await res.json(); // { rows, lastRow }
},
};

// 반응형 data/totalCount + ensureRange·setSorting·refresh
const { data, totalCount, ensureRange, setSorting, refresh } =
useVueServerSideData(datasource, { blockSize: 100, rowCount: 100_000 });
// 가상화 라이브러리의 visible range → ensureRange(first, last)
// isRowPlaceholder(row) 로 로딩 스켈레톤 표시
</script>

5. 자주 사용하는 패턴

5.1 서버 사이드 페이지네이션

<GridPagination
table={table}
mode="server" // ★ 서버 모드
totalCount={data?.totalCount ?? 0}
pageIndex={page}
pageSize={pageSize}
onPageChange={setPage}
onPageSizeChange={setPageSize}
/>

5.2 URL 동기화 + localStorage 영속화

import { useGridState, useUrlSync, useStoragePersist } from '@topgrid/grid-core';

const grid = useGridState<User>({});
useUrlSync(grid, { paramPrefix: 'users_' }); // ?users_sort=name&users_page=2
useStoragePersist(grid, { storageKey: 'my-grid' }); // 사용자 설정 유지

5.3 행 클릭 → 상세 이동

<Grid<User> data={data} columns={columns} onRowClick={(row) => router.push(`/users/${row.id}`)} />

5.4 셀별 조건부 색상

<Grid<User>
data={data}
columns={columns}
cellClassName={(ctx) => {
if (ctx.columnId === 'age' && (ctx.value as number) >= 60) {
return 'bg-red-50 text-red-700';
}
return '';
}}
/>

5.5 셀 이벤트 — clean 컨텍스트 (GridCellContext)

grid-core 1.0 부터 onCellClick / onCellKeyDown / getCellTooltip / cellClassName 의 첫 인자는 TanStack Cell 이 아니라 깨끗한 GridCellContext = { rowId, columnId, value, row } 다(ADR-006 D3). TanStack API 를 import·이해할 필요 없이 셀 데이터를 바로 읽는다.

import { Grid } from '@topgrid/grid';

<Grid<User>
data={data}
columns={columns}
onCellClick={(ctx) => {
// ctx = { rowId, columnId, value, row }
console.log(ctx.columnId, ctx.value, ctx.row.name);
}}
getCellTooltip={(ctx) =>
ctx.columnId === 'email' ? `메일 보내기: ${ctx.value}` : null
}
/>
  • ctx.value = 셀 값, ctx.row = 원본 행 객체, ctx.rowId = 안정적 행 id(= getRowId 결과; §2.3), ctx.columnId = 컬럼 id. onCellClick/onCellKeyDown(ctx, event) 2-arg 다.
  • 0.x → 1.0 마이그레이션: cell.getValue()ctx.value, cell.column.idctx.columnId, cell.row.idctx.rowId, 2번째 row 인자→ctx.row. (상세=grid-core CHANGELOG 1.0.0.)

커스텀 컬럼 렌더러(createColumnscell: (info) => …)에서 받는 TanStack CellContext 를 같은 clean 형태로 바꾸려면 toGridCell(info) 어댑터를 쓴다(여전히 export 됨).

5.6 floating 필터 직접 그리기 — GridFilterColumn

grid-core 1.0 부터 renderFloatingFilter 의 인자는 TanStack Column 이 아니라 clean GridFilterColumn = { id, value, setValue } 다(ADR-006 D3). TanStack API 없이 바로 동기화한다.

import { Grid } from '@topgrid/grid';

<Grid<User>
data={data}
columns={columns}
enableFilter
renderFloatingFilter={(column) => (
// column = { id, value, setValue }
<input
value={(column.value as string) ?? ''}
onChange={(e) => column.setValue(e.target.value)}
placeholder={`${column.id} 필터`}
/>
)}
/>

0.x 의 column.getFilterValue()column.value, column.setFilterValue(x)column.setValue(x), column.id 동일. (raw TanStack Column 을 변환할 일이 있으면 toGridFilterColumn 어댑터가 그대로 있다.)


6. 기존 그리드에서 마이그레이션

xxxx / XX Grid 등 기존 그리드 솔루션의 사용처를 topgrid 로 옮기는 경우, 컬럼 정의와 주요 prop 이 1:1 또는 유사하게 대응된다. 대표 매핑:

기존 (XX Grid)topgrid
<XxGridReact rowData={...} columnDefs={...} /><Grid<TData> data={...} columns={...} />
columnDefs: ColDef[]columns: ColumnDef<TData>[]
{ field: 'x' }{ id: 'x', accessorKey: 'x' }
{ headerName: '제목' }{ header: '제목' }
valueFormatter: (p) => fmt(p.value)cell: (info) => fmt(info.getValue())
cellStyle: { textAlign: 'center' }meta: { align: 'center' } + cellClassName
pagination: true별도 <GridPagination mode="client" /> 컴포넌트
rowSelection="single"enableRowSelection + TanStack rowSelection state
ag-theme-* CSS classTailwind 기반 — 별도 CSS import 불필요
기존 (xxxx)topgrid
cellEditEnded<EditableCell onCommit={...}>
formatItem (셀 배경)cellClassName={(ctx) => ...}
allowMerging="ColumnHeaders"createColumnGroup (다단 헤더)
frozenColumns={n}enableColumnPinning + defaultColumnPinning.left
excelExportexportToExcel(table, options) 또는 exportRowsToExcel(...)

권장 전략은 페이지 단위 점진적 마이그레이션이다. 사용처가 공통 wrapper 컴포넌트를 거치는 경우, wrapper 내부만 topgrid 기반으로 교체하면 사용처 코드 변경 없이 일괄 적용할 수도 있으나, 모든 사용처가 동시에 영향을 받으므로 시각 회귀 테스트가 필요하다.


7. 트러블슈팅

증상원인해결
그리드가 빈 화면data 없음 또는 columns 누락둘 다 전달했는지 확인
스타일 깨짐 (Tailwind 클래스 미적용)tailwind.config.js content 누락./node_modules/@topgrid/**/*.{js,mjs} 추가 (§2.2)
Module not found: @tanstack/table-corepeer dep 미설치@tanstack/react-table @tanstack/react-virtual 설치
Module not found: fflate / html2canvas / ...grid-export 의 jspdf optional deps번들러 resolve.fallback 으로 stub (§3.4)
컬럼 정렬 안 됨enableSorting 누락<Grid enableSorting> 추가
가상화 적용 안 됨enableVirtualization 또는 estimatedRowHeight 부재둘 다 명시 (§3.5)
셀 편집 시 첫 글자 손실initialDraft 미전달EditableCellinitialDraft 전달 (§4.3)
Pro watermark 항상 표시라이선스 키 미설정/무효setLicenseKey() 호출 + 키 유효성 확인

8. 번들 최적화

// ✅ 권장 — 개별 패키지 import (tree-shaking)
import { Grid } from '@topgrid/grid-core';
import { EditableCell } from '@topgrid/grid-renderers';

// ❌ 비권장 — meta facade 는 전체 번들 유입 가능
import { Grid, EditableCell } from '@topgrid/grid';

9. 다음 단계

목적문서
패키지별 정확한 export + 시그니처 전체API 레퍼런스
셀 렌더러 11종 + EditableCell 상세API 레퍼런스 §2
Next.js (App/Pages Router) · SSR 연동Next.js / SSR
차트 (스파크라인 ~ 엔터프라이즈 17종, React/Vue)차트
아키텍처 · 패키지 구조아키텍처

Pro 기능을 도입 검토 중이시라면 — 도메인당 라이선스(개발자 무제한), 30일 무료 평가 키로 직접 검증하세요. 가격 · 도입 문의 →