FreeGridv0.7.0English

사용법

시작하기

FreeGrid는 프레임워크에 의존하지 않는 TypeScript 그리드이고, React와 Vue용 얇은 래퍼를 제공합니다. 코어 패키지나 사용하는 프레임워크용 패키지를 설치하세요.

npm i @freegridjs/core
# React
npm i @freegridjs/core @freegridjs/react
# Vue
npm i @freegridjs/core @freegridjs/vue

높이가 있는 컨테이너를 준비하고, 스타일시트를 한 번 import한 다음 컬럼과 데이터를 넘기면 됩니다. 화면에 보이는 행만 그리기 때문에 데이터가 많아도 느려지지 않습니다.

import { createGrid } from '@freegridjs/core';
import '@freegridjs/core/styles.css';

const grid = createGrid(document.getElementById('grid'), {
  columns: [
    { field: 'name', header: 'Name', pinned: 'left' },
    { field: 'country', header: 'Country', filter: 'set' },
    { field: 'joined', header: 'Joined', type: 'date' },
    { field: 'total', header: 'Total', type: 'number', format: '#,##0' },
  ],
  data: rows,
});

CDN으로 쓰기

빌드 도구가 없다면 npm 패키지를 unpkg CDN에서 script 태그로 불러오세요. 전역 FreeGrid 객체가 생기고 7개 언어가 모두 들어 있습니다. @0은 0.x 중 최신 버전을 가리키니, 운영 환경에서는 @freegridjs/core@0.3.0처럼 버전을 고정해서 쓰세요. jsDelivr에도 같은 파일이 있습니다. 주소의 unpkg.com을 cdn.jsdelivr.net/npm으로 바꾸면 됩니다.

<link rel="stylesheet" href="https://unpkg.com/@freegridjs/core@0/dist/cdn/freegrid.css">
<script src="https://unpkg.com/@freegridjs/core@0/dist/cdn/freegrid.min.js"></script>

<div id="grid" style="height: 480px"></div>
<script>
  const grid = FreeGrid.createGrid(document.getElementById('grid'), {
    columns: [{ field: 'name' }, { field: 'total', type: 'number', format: '#,##0' }],
    data: [{ name: 'Kim', total: 1200 }, { name: 'Lee', total: 3400 }],
    locale: 'ko', // all 7 languages are bundled
  });
</script>

컬럼 정의

컬럼은 일반 객체입니다. 컬럼 ID는 id를 지정하면 그 값, 아니면 field입니다. 정렬, 필터, 컬럼 상태는 모두 이 ID로 컬럼을 가리킵니다.

const columns = [
  { field: 'id', header: 'ID', type: 'number', format: '0', width: 80, pinned: 'left' },
  { field: 'name', header: 'Name', flex: 1, minWidth: 160 },
  { field: 'price', header: 'Price', type: 'number', format: 'currency:KRW' },
  { field: 'qty', header: 'Qty', type: 'number' },
  // computed column
  { id: 'amount', header: 'Amount', type: 'number', format: '#,##0', valueGetter: row => row.price * row.qty },
  { field: 'status', header: 'Status', filter: 'set', cellClass: p => `status-${p.value}` },
];
이름타입설명
fieldstring이 컬럼에 표시할 행 객체의 속성 이름.
idstring컬럼 ID. 기본값은 field이며 field가 없으면 필수.
headerstring헤더 텍스트. 기본값은 field.
type'text' | 'number' | 'date' | 'boolean'정렬 방향, 서식, 정렬 방식, 기본 필터를 결정합니다.
formatstring | Intl options숫자: '#,##0.00', '0.0%', 'currency:KRW', 'integer'. 날짜: 'date', 'datetime', 'time', 'short', 'medium', 'long'.
width / minWidth / maxWidthnumber픽셀 너비. 기본 너비 120, 최소 40.
flexnumber남는 너비를 비율로 나눠 가집니다. 사용자가 너비를 조절하면 고정 너비가 됩니다.
pinned'left' | 'right'가로 스크롤해도 컬럼이 보이도록 고정합니다.
hideboolean숨긴 상태로 시작합니다. 컬럼 선택에서 다시 표시할 수 있습니다.
align'left' | 'center' | 'right'셀 정렬. 숫자는 기본 오른쪽 정렬.
sortableboolean기본값 true.
resizableboolean기본값 true.
filter'text' | 'number' | 'date' | 'set' | false컬럼 메뉴의 필터 종류. 기본값은 type에 따라 정해집니다.
comparator(a, b, rowA, rowB) => number사용자 정의 정렬 순서.
valueGetter(row) => unknown계산된 값(예: 수량×단가). 표시, 정렬, 필터, 요약에 모두 쓰입니다.
renderer(params) => string | HTMLElement셀 내용을 직접 그립니다. 문자열은 HTML로 삽입되니 외부 데이터는 이스케이프하세요.
cellClassstring | (params) => string셀에 추가할 CSS 클래스.
childrenColumnDef[]지정하면 컬럼 그룹(다단 헤더)이 됩니다.
mergeboolean | (a, b, rowA, rowB) => boolean같은 값이 연속되면 한 칸으로 합칩니다.
wrapHeaderTextboolean긴 헤더를 줄바꿈합니다.
aggFunc'sum' | 'avg' | 'min' | 'max' | 'count' | (values, rows) => value그룹 행에 표시할 집계.
chooserbooleanfalse면 컬럼 선택 목록에서 제외합니다.
lockVisibleboolean사용자가 이 컬럼을 숨길 수 없습니다.
editableboolean편집과 붙여넣기를 허용합니다(field 필요).
editor{ type: 'select', options } | { type: 'textarea' } | (params) => editor편집기. options를 행에 따라 바뀌는 함수로 주면 종속 선택이 됩니다.
requiredboolean빈 값을 거부합니다.
validate(value, row) => true | string메시지를 돌려주면 값을 거부합니다.

그리드 옵션

이름타입설명
columnsColumnDef[]필수. 컬럼 정의.
dataobject[]행 데이터.
getRowId(row) => string고유 ID. setData 후에도 선택을 유지합니다.
rowSelection'single' | 'multiple'체크박스 컬럼과 클릭 선택을 켭니다.
rowHeight / headerHeightnumber픽셀. 기본값은 글꼴 크기에 맞춰집니다(13px일 때 36 / 40).
themestring'light', 'dark', 'board', 'erp', 'excel', 'classic' 또는 직접 만든 테마.
fontSize / fontFamilynumber / string그리드 글꼴. 행 높이가 크기에 맞춰집니다.
localestring | LocalePackUI 언어. 'ko' 또는 import한 언어팩. 기본값은 브라우저 언어.
sideBarboolean | { open }오른쪽에 컬럼 패널을 붙입니다.
summarySummaryOptions요약 행. 요약 행 절 참고.
rangeSelectionboolean드래그, Shift+클릭, Shift+방향키, Ctrl+A로 엑셀처럼 셀 범위 선택. 기본 true.
statusBarboolean행 수와 선택 셀의 개수·합계·평균·최소·최대를 표시합니다.
fillHandleboolean선택 영역 모서리를 끌어 편집 가능한 셀을 채웁니다. 기본 true.
pinnedTopRows / pinnedBottomRowsRow[]스크롤과 상관없이 위/아래에 고정되는 행.
findBar / wrapHeaderTextbooleanCtrl+F 찾기 창(기본 true), 모든 헤더 줄바꿈.
groupBystring[]이 컬럼 ID 순서대로 행을 묶습니다.
groupDefaultExpandednumber처음에 펼칠 단계 수: 0 없음(기본), n단계, -1 전부. 트리 데이터에도 적용됩니다.
groupPanelboolean헤더 위에 막대를 두어 열 머리글을 끌어와 묶을 수 있게 합니다.
treeData{ getParentId(row) }부모 ID 아래로 행을 중첩합니다. getRowId가 필요합니다.
detailRenderer / detailHeight / isRowMaster(params) => Element | string / number / (row) => boolean행 아래에 펼쳐지는 패널(기본 높이 240)과 펼칠 수 있는 행 조건.
rowDragboolean행을 끌어 순서를 바꾸는 손잡이 컬럼(정렬·필터·그룹 중에는 꺼짐).
paginationboolean | { pageSize?, pageSizes? }한 쪽씩 보여 주고 아래에 쪽 이동 막대를 둡니다(기본 100행).
datasource{ getRows({ start, end, sort, filters, quickFilter }) }서버에서 행을 블록 단위로 불러옵니다. { rows, total? }을 돌려줘야 합니다.
blockSize / maxBlocksnumber요청당 행 수(100)와 메모리에 둘 블록 수(100).
sideBarboolean | { open?, panels?: ('columns' | 'filters')[] }컬럼·필터 패널을 옆에 붙입니다.
pivot{ rows, columns?, values: { colId, aggFunc? }[], totals? }데이터를 피벗 테이블로 보여 줍니다. pivotPanel: true면 피벗 패널을 추가합니다.
keymapRecord<string, action | false>키 재지정/비활성화. 예: { Enter: false, 'Ctrl+E': 'edit' }.
contextMenufalse | (params) => itemsfalse면 브라우저 기본 메뉴, 함수로 우클릭 메뉴를 바꿉니다.

데이터와 행 ID

행은 일반 객체입니다. getRowId가 없으면 행을 순서로 구분하기 때문에 데이터를 교체하면 선택이 해제됩니다. getRowId를 주면 같은 ID의 행은 setData 후에도 선택이 유지됩니다.

const grid = createGrid(el, {
  columns,
  data: rows,
  getRowId: row => String(row.id), // keeps selection and focus stable across setData
});

grid.setData(await fetch('/api/orders').then(r => r.json()));
grid.getData();               // rows as passed in
grid.getDisplayedRowCount();  // rows after filters

정렬

헤더를 클릭하면 오름차순, 내림차순, 정렬 해제 순으로 바뀝니다. Shift 클릭(또는 Ctrl/⌘ 클릭)은 정렬 기준을 추가하고, 작은 숫자로 순서를 표시합니다. 빈 값은 항상 맨 뒤로 갑니다. 문자열은 현재 언어 기준으로 정렬하므로 한글, 악센트, 문자 속 숫자도 자연스럽게 정렬됩니다.

grid.setSort([{ colId: 'country', dir: 'asc' }, { colId: 'total', dir: 'desc' }]);
grid.getSort();
grid.setSort([]); // clear

// custom order
{ field: 'grade', comparator: (a, b) => ORDER.indexOf(a) - ORDER.indexOf(b) }

필터

컬럼의 ⋮ 메뉴에 타입별 필터가 있습니다. 텍스트(포함, 같음, 시작/끝 문자, 빈 값…), 숫자(=, ≠, <, ≤, >, ≥, 범위, 빈 값), 날짜(같음, 이전, 이후, 범위), 값 목록(컬럼 값을 검색해 체크) 필터입니다. 텍스트는 대소문자를 구분하지 않습니다. 필터가 걸린 컬럼에는 채워진 필터 아이콘이 표시됩니다.

grid.setFilter('name', { type: 'text', op: 'contains', value: 'kim' });
grid.setFilter('total', { type: 'number', op: 'between', value: 1000, valueTo: 5000 });
grid.setFilter('joined', { type: 'date', op: 'after', value: '2025-01-01' });
grid.setFilter('country', { type: 'set', values: ['Japan', 'South Korea'] });
grid.setFilter('name', null);   // clear one
grid.setQuickFilter('kim seoul'); // every word must match some visible column
grid.on('filterChange', e => console.log(e.filters));

고정과 다단 헤더

고정한 컬럼은 가운데 영역을 가로로 스크롤해도 계속 보입니다. 사용자는 컬럼 메뉴에서 고정과 해제를 할 수 있습니다. children이 있는 컬럼은 그룹 헤더가 되고 원하는 깊이까지 중첩할 수 있습니다. 그룹이 고정 영역에 걸쳐 나뉘면 각 영역에 그룹 헤더가 표시됩니다.

const columns = [
  { field: 'name', header: 'Name', pinned: 'left' },
  { header: 'Revenue 2026', children: [
    { header: 'First half', children: [{ field: 'q1' }, { field: 'q2' }] },
    { header: 'Second half', children: [{ field: 'q3' }, { field: 'q4' }] },
  ] },
  { field: 'total', header: 'Total', pinned: 'right' },
];

컬럼 커스터마이징

헤더를 드래그해 순서를 바꾸고, 헤더 경계를 드래그해 너비를 조절하고, 컬럼 메뉴에서 너비 자동 맞춤, 숨기기, 고정, 초기화를 할 수 있습니다. 컬럼 선택은 검색, 전체 선택, 그룹 트리, 드래그 순서 변경이 있는 창을 엽니다. 같은 패널을 sideBar로 오른쪽에 붙일 수도 있습니다. getColumnState()를 저장해 두면 사용자별 레이아웃을 기억할 수 있습니다.

const grid = createGrid(el, {
  columns: [
    { field: 'id', lockVisible: true },       // cannot be hidden
    { field: 'internalCode', chooser: false }, // not listed in the chooser
    ...rest,
  ],
  data,
  sideBar: { open: false }, // docked Columns panel (true = closed, { open: true } = open)
});

grid.openColumnChooser();
grid.setColumnVisible('email', false);
grid.autoSizeColumns();             // all columns
grid.autoSizeColumns(['name']);     // one column

// save / restore the user's layout
localStorage.setItem('layout', JSON.stringify(grid.getColumnState()));
grid.applyColumnState(JSON.parse(localStorage.getItem('layout')));
grid.resetColumnState();

요약 행

요약 행에는 합계, 평균 같은 집계를 표시합니다. 위치는 상단 또는 하단이고, 스크롤 영역 밖에 고정하거나 데이터와 함께 스크롤되게 할 수 있습니다. 데이터나 필터가 바뀌면 다시 계산하고, scope: 'selected'일 때는 선택이 바뀔 때도 다시 계산합니다. 셀에 서식을 따로 지정하지 않으면 컬럼 서식을 따릅니다.

createGrid(el, {
  columns,
  data,
  summary: {
    position: 'bottom',   // 'top' | 'bottom'
    pinned: true,         // true: fixed; false: scrolls with the rows
    scope: 'filtered',    // 'filtered' | 'all' | 'selected'
    rows: [
      { label: 'Total', columns: { qty: 'sum', amount: 'sum', id: 'count' } },
      { label: 'Average', columns: { amount: { agg: 'avg', format: '#,##0.0' } } },
      { columns: {
        amount: { agg: 'max', prefix: 'max' },
        country: values => `${new Set(values).size} countries`, // custom
      } },
    ],
  },
});

grid.setSummary({ position: 'top', rows: [...] }); // change at runtime
grid.setSummary(null);                              // remove
grid.getSummaryValues();                            // [{ qty: 1234, amount: 99000, id: 50 }, …]
  • sum, avg, min, max는 숫자 값만 계산하고 빈 값은 건너뜁니다. min과 max는 날짜도 비교합니다.
  • count는 집계 대상 행 수입니다.
  • 함수는 컬럼 값 배열과 행 배열을 받습니다. 숫자를 돌려주면 서식이 적용되고, 문자열은 그대로 표시됩니다.
  • label은 그 행에서 집계가 없는 첫 번째 보이는 컬럼에 표시됩니다.

편집

컬럼에 editable: true를 지정하면 사용자가 셀을 수정할 수 있습니다. 더블클릭하거나 Enter나 F2를 누르거나, 그냥 입력을 시작하면 됩니다. Enter는 저장하고 아래로, Tab은 저장하고 다음 편집 칸으로 이동하며, Esc는 취소합니다. 참/거짓 값은 Space나 더블클릭으로 바뀝니다.

편집기는 컬럼 타입을 따릅니다. 텍스트, 숫자(1,234도 입력 가능), 날짜, 참/거짓입니다. 정해진 목록에서 고르게 하려면 select 편집기를 쓰세요. options를 행을 받는 함수로 주면 국가에 따른 도시처럼 종속 선택이 됩니다. required와 validate로 잘못된 값을 막을 수 있고, 값을 고칠 때까지 편집기에 안내 메시지가 남아 있습니다.

편집, 붙여넣기, 행 추가와 삭제는 모두 기록되어 Ctrl+Z로 되돌릴 수 있습니다. 바뀐 셀에는 모서리에 작은 표시가 생깁니다. 저장할 때는 getModifiedRows()나 컬럼 단위의 getChanges()를 서버로 보낸 뒤 commitChanges()를 호출하세요.

const COUNTRIES = { KR: ['Seoul', 'Busan'], JP: ['Tokyo', 'Osaka'] };

const grid = createGrid(el, {
  columns: [
    { field: 'id', type: 'number' },                                   // read-only
    { field: 'name', editable: true, required: true },
    { field: 'qty', type: 'number', editable: true, validate: v => v >= 0 || 'Must be 0 or more' },
    { field: 'country', editable: true, editor: { type: 'select', options: Object.keys(COUNTRIES) } },
    { field: 'city', editable: true, editor: { type: 'select', options: row => COUNTRIES[row.country] } },
    { field: 'memo', editable: true, editor: { type: 'textarea' } },
    { field: 'active', type: 'boolean', editable: true },             // Space / double-click toggles
  ],
  data,
});

grid.addRows([{ name: 'New' }], 0);
grid.removeSelectedRows();
grid.undo(); grid.redo();

// send only what changed
const { created, updated, deleted } = grid.getModifiedRows();
await save({ created, updated, deleted, cells: grid.getChanges() });
grid.commitChanges(); // or grid.revertChanges()

스프레드시트 기능

컬럼에 merge: true를 주면 같은 값이 이어지는 행이 한 칸으로 합쳐집니다. 지역별, 팀별 보고서에 유용하며, 먼저 그 컬럼으로 정렬해 두세요. statusBar: true를 켜면 하단에 행 수와 선택한 셀의 개수, 합계, 평균, 최소, 최대가 표시됩니다. 선택 영역 모서리의 작은 사각형을 끌면 편집 가능한 셀이 채워집니다. 숫자와 날짜는 간격을 이어가고, 숫자로 끝나는 글자는 숫자가 하나씩 늘어나며, 나머지는 반복됩니다.

목표치나 기준값처럼 스크롤해도 계속 보여야 하는 행은 pinnedTopRows, pinnedBottomRows로 고정하세요. Ctrl+F를 누르면 그리드 안에 찾기 창이 열리고, 코드에서는 find()로 같은 일을 할 수 있습니다.

createGrid(el, {
  columns: [
    { field: 'region', merge: true },                 // identical neighbours become one tall cell
    { field: 'team', merge: (a, b, ra, rb) => a === b && ra.region === rb.region },
    { field: 'q1', type: 'number', editable: true, wrapHeaderText: true },
  ],
  data: rowsSortedByRegion,
  statusBar: true,     // "1,000 rows · Count 12  Sum 48,000  Average 4,000 …"
  fillHandle: true,    // drag the corner: 1, 2 → 3, 4, 5 …; Mon → copies; "item 7" → "item 8"
  pinnedTopRows: [{ region: 'Target', q1: 50000 }],
});

grid.find('Seoul');    // highlights matches, focuses the first; Ctrl+F opens the same as a bar
grid.findNext();

그룹핑·트리·상세 행

groupBy에 컬럼 ID를 넣으면 같은 값을 가진 행이 그룹 행 아래로 모이고, 그룹 행에 값과 행 수가 표시됩니다. 컬럼에 aggFunc를 'sum'이나 'avg'로 지정하면 그룹 행마다 합계나 평균이 나옵니다. 그룹 순서는 그 컬럼의 정렬 방향을 따릅니다. 화살표를 누르거나 더블클릭하면 펼쳐지고, 키보드로는 Enter, Space, 좌우 방향키를 씁니다. groupPanel: true를 켜면 사용자가 열 머리글을 그리드 위 막대로 끌어와 직접 묶을 수 있습니다.

조직도나 폴더 목록처럼 이미 계층이 있는 데이터는 treeData와 getParentId로 나타냅니다. 필터를 걸어도 일치하는 행의 상위 행이 함께 남아 위치를 알 수 있습니다. detailRenderer는 행 아래에 펼쳐지는 패널을 만들며, 그 안에 다른 그리드를 넣을 수도 있습니다. rowDrag: true를 켜면 손잡이로 행 순서를 직접 바꿀 수 있고, 정렬·필터·그룹이 걸려 있는 동안에는 잠시 꺼집니다.

// Row grouping with totals
createGrid(el, {
  columns: [
    { field: 'country' },
    { field: 'name' },
    { field: 'sales', type: 'number', aggFunc: 'sum' },   // shown in each group row
  ],
  data: rows,
  groupBy: ['country'],      // or api.setGroupBy(['country', 'city'])
  groupPanel: true,          // drag headers above the grid to group
  groupDefaultExpanded: 1,   // 0 = collapsed, -1 = everything open
});

// Tree data: rows point at their parent
createGrid(el, {
  columns: [{ field: 'name' }, { field: 'budget', type: 'number' }],
  data: orgRows,
  getRowId: r => r.id,
  treeData: { getParentId: r => r.parent },
});

// Master / detail and row drag
createGrid(el, {
  columns, data,
  detailHeight: 160,
  detailRenderer: ({ row }) => `<p>Orders of ${row.name}</p>`,   // or return an element
  rowDrag: true,
});

페이지와 서버 데이터

pagination을 켜면 한 번에 한 쪽씩 보여 주고 아래에 쪽 이동 막대가 붙습니다. 처음·이전·다음·마지막 쪽으로 이동할 수 있고, pageSizes를 주면 쪽당 행 수도 고를 수 있습니다. 정렬하거나 필터를 바꾸면 첫 쪽으로 돌아갑니다.

데이터가 서버에 있다면 data 대신 datasource를 넘기세요. 그리드는 화면에 보이는 행만 blockSize 단위로 getRows에 요청하고, 도착할 때까지 회색 자리 표시를 그립니다. 정렬, 필터, 빠른 검색 조건이 함께 전달되므로 서버에서 처리하면 됩니다. 전체 개수를 알면 total을 돌려주고, 모르면 빼 두세요. 그러면 무한 스크롤이 되어 짧은 블록이 올 때까지 계속 불러옵니다. 여기에 pagination까지 켜면 한 쪽이 요청 한 번이 됩니다. 그룹핑과 행 추가·삭제는 브라우저에 데이터가 있을 때만 쓸 수 있습니다.

// Pages in the browser
createGrid(el, { columns, data, pagination: { pageSize: 50, pageSizes: [20, 50, 100] } });

// Rows from your server, 100 at a time, as people scroll
createGrid(el, {
  columns,
  datasource: {
    async getRows({ start, end, sort, filters, quickFilter }) {
      const res = await fetch('/api/orders?' + new URLSearchParams({
        start, end, sort: JSON.stringify(sort), filters: JSON.stringify(filters), q: quickFilter,
      }));
      const { rows, total } = await res.json();
      return { rows, total };        // leave out total for infinite scroll
    },
  },
  blockSize: 100,
  // pagination: { pageSize: 50 },   // add this for server-side pages
});

내보내기와 가져오기

exportXlsx()는 행을 진짜 엑셀 파일로 저장합니다. 숫자는 컬럼 서식을 유지한 숫자로, 날짜는 날짜로 들어가며, 다단 헤더는 병합된 헤더 행이 되고, 머리글은 틀 고정과 필터 단추까지 붙습니다. exportCsv()는 BOM을 붙여 저장하므로 엑셀에서 열어도 한글이 깨지지 않습니다. 수식처럼 보이는 글자는 앞에 표시를 붙여 스프레드시트에서 실행되지 않게 합니다. 기본으로는 지금 보이는 정렬, 필터, 컬럼 그대로 내보내고, 옵션으로 다른 행이나 컬럼을 고를 수 있습니다.

importFile()은 .xlsx나 CSV 파일을 읽습니다. 첫 행을 헤더 이름이나 field 이름으로 컬럼에 맞추고, 값은 붙여넣기와 똑같이 검사합니다. 숫자 컬럼에는 숫자만, 날짜 컬럼에는 2026-03-05나 2026.3.5 같은 날짜만 들어갑니다. 가져온 행은 끝에 추가되며 실행 취소할 수 있고, 데이터를 통째로 바꿀 수도 있습니다. sideBar를 켜면 컬럼 패널 옆에 필터 패널이 생겨 여러 컬럼의 필터를 한곳에서 걸 수 있습니다.

// Download what people see (current sort and filters)
grid.exportXlsx('orders.xlsx');
grid.exportCsv('orders.csv', { separator: ';' });

// Pick columns and rows
grid.exportXlsx('picked.xlsx', { columns: ['name', 'total'], rows: 'selected', sheetName: 'Picked' });

// Read a file chosen by the user
input.addEventListener('change', async () => {
  const { rows, skipped } = await grid.importFile(input.files[0]);   // { mode: 'replace' } to swap
});

// Filters in a side panel next to the columns panel
createGrid(el, { columns, data, sideBar: { open: true, panels: ['filters', 'columns'] } });

// Without a grid
import { writeXlsx, readXlsx } from '@freegridjs/core';
const bytes = writeXlsx([{ name: 'Report', rows: [['Name', 'Total'], ['Kim', 1200]] }]);

피벗 테이블과 차트

setPivot()은 행을 요약 표로 바꿉니다. rows에 넣은 필드가 행이 되고, 둘 이상이면 앞쪽 필드는 소계가 붙은 그룹이 됩니다. columns에 넣은 필드는 위쪽에 열 그룹으로 펼쳐지고, 값마다 합계 열이 붙습니다. 값은 기본으로 더하며, 필요하면 'avg', 'min', 'max', 'count'를 고르세요. 소계는 원래 행에서 다시 계산하므로 평균도 정확합니다. 피벗 표도 일반 보기처럼 정렬, 필터, 내보내기, 차트가 모두 되고, setPivot(null)이면 원래 행으로 돌아옵니다. 사이드바에 'pivot' 패널을 넣으면 사용자가 직접 피벗을 구성할 수 있습니다.

셀을 드래그해 선택한 뒤 우클릭해서 차트를 고르세요. 첫 번째 글자 컬럼이 항목 이름이 되고, 숫자 컬럼이 계열이 됩니다. 차트는 그리드 위 패널에 열리며 막대, 누적 막대, 꺾은선, 영역, 원형으로 바꿀 수 있고, 셀 값이 바뀌면 다시 그려집니다. 코드에서는 createChart()로 같은 일을 하고, container를 주면 페이지 어디에든 차트를 넣을 수 있습니다.

// Sales by country (rows) and year (columns), with totals
grid.setPivot({
  rows: ['region', 'country'],        // region becomes a group with subtotals
  columns: ['year'],
  values: [{ colId: 'sales', aggFunc: 'sum' }, { colId: 'margin', aggFunc: 'avg' }],
});
grid.setPivot(null);                   // back to the normal rows

// Let people build it themselves
createGrid(el, { columns, data, sideBar: { open: true, panels: ['pivot', 'columns'] } });

// Charts: the selected range, or everything on screen
const chart = grid.createChart({ type: 'bar', title: 'Sales' });
chart.setType('line');
grid.createChart({ type: 'pie', container: document.querySelector('#chart') });

// Without a grid
import { renderChart } from '@freegridjs/core';
renderChart(el, { type: 'line', categories: ['Jan', 'Feb'], series: [{ name: 'Sales', values: [120, 180] }] });

복사·붙여넣기·우클릭

엑셀처럼 셀을 선택할 수 있습니다. 드래그하거나, Shift 클릭, Shift+방향키, Ctrl+A를 쓰세요. Ctrl+C는 선택 범위를 엑셀과 구글 시트가 이해하는 형식으로 복사합니다. 숫자는 숫자 그대로, 날짜는 YYYY-MM-DD로 들어갑니다. Ctrl+V는 엑셀에서 복사한 셀을 선택한 셀부터 붙여넣습니다. 값 하나를 복사했다면 선택한 범위 전체를 그 값으로 채웁니다.

붙여넣기는 editable: true로 지정한 컬럼만 바꾸므로 ID나 계산 컬럼은 안전합니다. 값은 컬럼 타입에 맞게 변환됩니다. 1,234, ₩12,000, (5), 12%는 숫자로, 2026. 3. 5., 2026년 3월 5일은 날짜로 바뀝니다. 변환할 수 없는 값은 건너뛰고 paste 이벤트에 개수를 알려줍니다.

셀을 우클릭하면 복사, 헤더 포함 복사, 붙여넣기, 너비 자동 맞춤, 숨기기, 고정 메뉴가 열립니다. 헤더를 우클릭하면 컬럼 메뉴가 열립니다. 범위 선택은 rangeSelection: false로 끌 수 있고, contextMenu: false로 브라우저 기본 메뉴를 쓸 수 있습니다.

const grid = createGrid(el, {
  columns: [
    { field: 'id', type: 'number' },                        // read-only
    { field: 'name', editable: true },                      // paste allowed
    { field: 'qty', type: 'number', editable: true },       // "1,234", "(5)", "12%" are parsed
    { field: 'due', type: 'date', editable: true },         // "2026-03-05", "2026. 3. 5." …
  ],
  data,
  rangeSelection: true, // default: drag / Shift+click / Shift+arrows / Ctrl+A
  contextMenu: p => [
    ...p.defaultItems,                                      // Copy, Paste, Pin, Hide, …
    'separator',
    { label: 'Open order', action: () => open(p.row) },
  ],
});

grid.on('cellValueChanged', e => save(e.row));              // persist edits
grid.on('paste', e => console.log(e.changed, 'cells changed'));

grid.setRange({ start: { rowIndex: 0, colId: 'name' }, end: { rowIndex: 9, colId: 'qty' } });
grid.copySelection({ headers: true }); // TSV, also written to the clipboard
grid.pasteText('Kim	1,234
Lee	56');

메뉴의 "붙여넣기"는 클립보드를 읽어야 해서 브라우저가 막거나 권한을 물을 수 있습니다. Ctrl+V는 브라우저의 붙여넣기 이벤트를 쓰므로 항상 동작합니다.

행 선택

rowSelection을 주면 체크박스 컬럼이 추가됩니다. 클릭하면 그 행만 선택되고, Ctrl/⌘ 클릭은 선택을 토글하고, Shift 클릭은 범위를 선택합니다. 헤더 체크박스는 필터된 행 전체를 선택하고, Space는 포커스된 행을 토글합니다.

const grid = createGrid(el, { columns, data, rowSelection: 'multiple', getRowId: r => r.id });
grid.on('selectionChange', e => console.log(e.selected.length));
grid.getSelectedRows();
grid.selectAll();   // selects the filtered rows
grid.deselectAll();

키보드

셀을 클릭하거나 Tab으로 그리드에 들어간 뒤 다음 키를 쓸 수 있습니다.

키동작
← ↑ → ↓포커스 셀 이동.
Tab / Shift+Tab다음/이전 셀. 줄 끝에서 다음 줄로, 처음·끝에서는 그리드 밖으로.
Home / End첫/마지막 컬럼.
Ctrl+Home / Ctrl+End첫/마지막 셀.
PageUp / PageDown한 화면 위/아래.
Shift + ← ↑ → ↓ / Home / End / Page셀 범위 확장.
Ctrl+A모든 셀 선택.
Ctrl+C / Ctrl+V엑셀로 복사 / 엑셀에서 붙여넣기(TSV).
Ctrl+F그리드에서 찾기.
Shift+F10 / ContextMenu우클릭 메뉴 열기.
Enter / F2 / typing편집 가능한 셀 편집 시작.
Enter / Tab / Esc (editing)저장 후 아래로 / 저장 후 오른쪽으로 / 취소.
Delete편집 가능한 셀 비우기.
Ctrl+Z / Ctrl+Y실행 취소 / 다시 실행.
Space포커스 행 선택 토글.
Esc컬럼 메뉴나 선택 창 닫기.

서식과 렌더러

숫자와 날짜는 브라우저 Intl API로 그리드 언어에 맞게 표시됩니다. 예를 들어 1234.5는 영어에서 1,234.5, 독일어에서 1.234,5입니다. 그 밖의 표시는 renderer로 직접 그립니다. 렌더러는 value, formatted, row, rowIndex, column을 받습니다.

{ field: 'total', type: 'number', format: '#,##0' }       // 1,234
{ field: 'rate', type: 'number', format: '0.0%' }          // 12.5%
{ field: 'price', type: 'number', format: 'currency:KRW' } // ₩12,000
{ field: 'price', type: 'number', format: { maximumSignificantDigits: 3 } } // Intl options
{ field: 'joined', type: 'date', format: 'long' }          // 2026년 3월 5일 (ko)

// element renderer
{ field: 'status', renderer: p => {
    const b = document.createElement('span');
    b.className = `badge badge-${p.value}`;
    b.textContent = p.formatted;
    return b;
} }

렌더러가 문자열을 돌려주면 HTML로 삽입됩니다. 사용자 입력 데이터는 이스케이프하거나 요소(element)를 돌려주세요.

테마와 글꼴

기본 테마는 light, dark와 한국식 심플 테마 네 가지입니다. board는 게시판형(상단 굵은 선, 가로선만), erp는 관리자·ERP형(모든 셀 테두리, 촘촘한 행), excel은 엑셀형(격자선, 녹색 포커스), classic은 진한 헤더형(남색 헤더)입니다. 글꼴 크기와 종류는 옵션 하나씩이면 바뀌고, 행 높이를 따로 지정하지 않으면 글꼴 크기에 맞춰 조정됩니다.

createGrid(el, { columns, data, theme: 'erp', fontSize: 12, fontFamily: 'Pretendard, sans-serif' });
grid.setTheme('board');
grid.setFont({ size: 14 });
grid.setFont({ family: null }); // back to the theme font

나만의 테마 만들기

/* any class named fg-theme-<name> is a theme */
.fg-theme-brand {
  --fg-font: "Pretendard", sans-serif;
  --fg-font-size: 13px;
  --fg-bg: #fff; --fg-fg: #1d2433; --fg-muted: #6b7385;
  --fg-header-bg: #f2f5fa; --fg-header-fg: #1d2433;
  --fg-border: #d9dfe8; --fg-row-border: #edf0f5;
  --fg-col-line: inset -1px 0 #edf0f5;
  --fg-row-odd: #fff; --fg-row-hover: #f5f8ff; --fg-row-selected: #e6eeff;
  --fg-accent: #3056d3; --fg-focus: #3056d3;
  --fg-header-text-align: center; --fg-header-label-flex: 1 1 auto;
}
변수종류조절 대상
--fg-font / --fg-font-sizefont글꼴과 크기.
--fg-bg / --fg-fg / --fg-mutedcolor배경, 글자, 보조 글자 색.
--fg-header-bg / --fg-header-fg / --fg-header-weightcolor / weight헤더 색과 굵기.
--fg-border / --fg-row-bordercolor바깥 테두리와 행 구분선.
--fg-col-linebox-shadow세로 셀 구분선. 예: 'inset -1px 0 #ddd'. 기본 없음.
--fg-row-odd / --fg-row-hover / --fg-row-selectedcolor줄무늬, 마우스 오버, 선택 행 색.
--fg-accent / --fg-focuscolor버튼, 정렬 아이콘, 포커스 셀 색.
--fg-radius / --fg-cell-pxlength모서리 둥글기와 셀 좌우 여백.
--fg-header-text-align / --fg-top-borderkeyword / border헤더 가운데 정렬, 상단 굵은 선.

다국어

영어는 기본으로 들어 있습니다. 한국어, 중국어 간체, 일본어, 스페인어, 프랑스어, 독일어는 따로 import하므로 쓰는 언어만 번들에 포함됩니다. locale을 지정하지 않으면, 브라우저 언어의 언어팩이 등록돼 있을 때 그 언어를 쓰고 없으면 영어를 씁니다.

import ko from '@freegridjs/core/locales/ko';
createGrid(el, { columns, data, locale: ko });

// or register once and use codes
import { registerLocale } from '@freegridjs/core';
import ja from '@freegridjs/core/locales/ja';
registerLocale(ja);
grid.setLocale('ja');

// override a few messages
createGrid(el, { columns, data, locale: { ...ko, noRows: '조회 결과가 없습니다' } });

React

@freegridjs/react는 <FreeGrid> 컴포넌트를 제공합니다. 그리드 옵션은 모두 props이고, 이벤트는 on… props입니다. ref로 그리드 API를 받습니다. data, columns, theme, locale, 글꼴, summary를 바꾸면 그리드에 반영됩니다. 이 객체들은 useMemo로 안정적으로 유지하세요.

import { FreeGrid } from '@freegridjs/react';
import type { GridApi } from '@freegridjs/core';
import '@freegridjs/core/styles.css';

export function Orders({ rows }) {
  const grid = useRef<GridApi>(null);
  const columns = useMemo(() => [...], []); // keep props stable
  return (
    <FreeGrid
      ref={grid}
      columns={columns}
      data={rows}
      rowSelection="multiple"
      theme="erp"
      locale="ko"
      summary={summary}
      onCellClick={e => console.log(e.row)}
      onSelectionChange={e => setCount(e.selected.length)}
      style={{ height: 520 }}
    />
  );
}
// grid.current?.setSort([{ colId: 'total', dir: 'desc' }])

Vue

@freegridjs/vue는 Vue 3.3 이상용 <FreeGrid> 컴포넌트를 제공합니다. 이벤트는 kebab-case(@cell-click, @selection-change…)이고, 컴포넌트 ref의 getApi()로 API를 받습니다.

<script setup lang="ts">
import { FreeGrid } from '@freegridjs/vue';
import '@freegridjs/core/styles.css';

const grid = ref();
const sort = () => grid.value.getApi().setSort([{ colId: 'total', dir: 'desc' }]);
</script>

<template>
  <FreeGrid
    ref="grid"
    :columns="columns"
    :data="rows"
    row-selection="multiple"
    theme="erp"
    locale="ko"
    :height="520"
    :summary="summary"
    @cell-click="e => console.log(e.row)"
    @selection-change="e => (count = e.selected.length)"
  />
</template>

API 레퍼런스

createGrid(element, options)가 아래 API를 돌려줍니다. destroy() 이후의 호출은 무시됩니다.

메서드반환값설명
setData(rows)void모든 행을 교체합니다.
getData()Row[]현재 행(필터·정렬 전).
setColumns(defs)void컬럼 정의를 교체합니다.
getColumnState() / applyColumnState(state)ColumnState[]순서, 너비, 표시 여부, 고정 상태를 저장/복원합니다.
resetColumnState()void컬럼 정의 상태로 되돌립니다.
setColumnVisible(colId, visible)void컬럼을 표시하거나 숨깁니다.
autoSizeColumns(colIds?)void헤더와 보이는 셀에 맞춰 너비를 조절합니다.
openColumnChooser() / closeColumnChooser()void떠 있는 컬럼 선택 창.
toggleSideBar(open?)void오른쪽 컬럼 패널을 열거나 닫습니다.
setSort(items) / getSort()SortItem[]예: [{ colId: 'total', dir: 'desc' }].
setFilter(colId, model | null) / getFilterModel()FilterModel컬럼 하나의 필터를 설정하거나 해제합니다.
setQuickFilter(text)void보이는 모든 컬럼에서 검색합니다.
getSelectedRows() / selectAll() / deselectAll()Row[]선택 관련. selectAll은 필터된 행을 선택합니다.
setSummary(options | null) / getSummaryValues()object[]요약 행 변경, 집계 원값 조회.
getRange() / setRange(range | null)GridRange선택된 셀 범위를 읽거나 설정합니다.
copySelection({ headers? })string범위를 엑셀 TSV로 복사하고 돌려줍니다.
pasteText(text){ changed, skipped }범위나 포커스 셀에 TSV/엑셀 텍스트를 붙여넣습니다.
startEdit(rowIndex, colId) / stopEdit(cancel?)boolean / void편집기를 열거나 닫습니다.
addRows(rows, index?) / removeRows(rows | fn) / removeSelectedRows()Row[]행 추가/삭제(변경 추적, 실행 취소 가능).
getModifiedRows() / getChanges() / hasChanges()object추가/수정/삭제 행과 컬럼 단위 변경 내역.
commitChanges() / revertChanges()void변경을 확정하거나 모두 되돌립니다.
undo() / redo() / canUndo() / canRedo()void / boolean편집·붙여넣기·행 변경 실행 취소.
fillRange(target)void선택 영역에서 더 큰 범위로 채웁니다(연속값 인식).
find(text) / findNext() / findPrevious() / clearFind()number / void일치하는 셀을 강조하고 이동합니다.
setPinnedRows({ top?, bottom? })void고정 행을 바꿉니다.
setGroupBy(colIds) / getGroupBy()void / string[]그룹 기준을 바꾸거나 읽습니다.
expandAll() / collapseAll() / setGroupExpanded(key, open)void그룹과 트리 행을 펼치거나 접습니다. 그룹 키는 'country:KR|city:Seoul' 형태, 트리 키는 행 ID입니다.
setDetailExpanded(row, open) / isDetailExpanded(row)void / boolean상세 패널을 열고 닫거나 상태를 확인합니다.
moveRow(row, toIndex)boolean원본 데이터에서 행을 옮깁니다(실행 취소 가능). 정렬·필터·그룹 중에는 false.
setPage(n) / getPage() / getPageCount()void / number쪽을 이동합니다(0부터 시작).
setPageSize(n) / getPageSize()void / number쪽당 행 수를 바꿉니다.
refreshRows()void서버 데이터: 캐시를 비우고 다시 불러옵니다.
exportXlsx(filename?, options?) / getXlsx(options?)void / Uint8Array행을 엑셀 파일로. 옵션: columns, rows(filtered | all | selected), headers, sheetName.
exportCsv(filename?, options?) / getCsv(options?)void / string엑셀에서 한글이 깨지지 않도록 BOM을 붙인 CSV. 같은 옵션에 separator 추가.
importFile(file, { mode?, sheet? })Promise<{ rows, skipped }>.xlsx나 CSV를 읽어 헤더를 컬럼에 맞춥니다. 추가(기본) 또는 교체.
toggleSideBar(open?, panel?)void사이드바를 열고 닫습니다. 'columns'나 'filters' 패널을 지정할 수 있습니다.
setPivot(config | null) / getPivot()void / PivotConfig | null피벗을 켜거나 바꾸거나 일반 보기로 돌아갑니다.
createChart({ type?, range?, container?, title? }){ el, setType, update, destroy }범위나 전체 보기를 SVG 차트로: 'bar', 'stackedBar', 'line', 'area', 'pie'.
versionstring패키지 버전 상수. import { version }으로 가져옵니다.
getDisplayedRowCount()number필터 후 행 수.
scrollToRow(index)void해당 행으로 스크롤합니다.
setTheme(name)void테마를 바꿉니다.
setFont({ size?, family? })void글꼴을 바꿉니다. family에 null을 주면 테마 기본 글꼴로 돌아갑니다.
setLocale(codeOrPack)voidUI 언어를 바꿉니다.
on(event, handler)() => void이벤트 구독. 해제 함수를 돌려줍니다.
destroy()void그리드와 이벤트 리스너를 제거합니다.

이벤트

이벤트전달 값발생 시점
cellClick / cellDoubleClick{ row, rowIndex, colId, value, event }데이터 셀을 클릭했습니다.
selectionChange{ selected: Row[] }선택된 행이 바뀌었습니다.
sortChange{ sort }정렬이 바뀌었습니다.
filterChange{ filters }컬럼 필터가 바뀌었습니다.
columnResize{ colId, width }너비 조절이나 자동 맞춤이 끝났습니다.
columnMove{ colId, toIndex }컬럼을 드래그해 위치를 옮겼습니다.
columnVisible{ colId, visible }컬럼이 표시되거나 숨겨졌습니다.
rangeChange{ range }셀 범위가 바뀌었습니다.
cellValueChanged{ row, rowIndex, colId, oldValue, newValue }붙여넣기로 셀 값이 바뀌었습니다.
paste{ changed, skipped }붙여넣기가 끝났습니다.
editStart / editStop{ rowIndex, colId, committed? }편집기가 열리거나 닫혔습니다.
validationFailed{ rowIndex, colId, value, message }입력값이 거부됐습니다.
rowsAdded / rowsRemoved{ rows }행이 추가되거나 삭제됐습니다.
groupChange / groupToggle{ groupBy } / { key, expanded }그룹 기준이 바뀌었거나 그룹·트리 행이 펼쳐지거나 접혔습니다.
detailToggle{ row, expanded }상세 패널이 열리거나 닫혔습니다.
rowDragEnd{ row, fromIndex, toIndex }행을 끌어 위치를 옮겼습니다.
pageChange{ page, pageSize, pageCount }쪽이나 쪽당 행 수가 바뀌었습니다.
rowsLoaded / rowsLoadFailed{ start, end, total? } / { start, end, error }서버 블록을 받았거나 실패했습니다.
pivotChange{ pivot }피벗이 켜지거나 바뀌거나 꺼졌습니다.
focusChange{ rowIndex, colId }포커스 셀이 이동했습니다.