FreeGridv0.6.0한국어

Docs

Getting started

FreeGrid is a framework-free TypeScript grid with thin React and Vue wrappers. Install the core package, or the wrapper for your framework.

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

Give the grid a container with a height, import the stylesheet once, and pass columns and rows. Only the rows on screen are rendered, so the data size barely matters.

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,
});

Use it from a CDN

No build step? Load the bundle from npm through unpkg with a script tag. It exposes a global FreeGrid and includes all seven languages. @0 follows the newest 0.x release; pin an exact version such as @freegridjs/core@0.3.0 in production. The same files are on jsDelivr: replace unpkg.com with 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>

Column definitions

Columns are plain objects. The column id is id if you set one, otherwise field; sorting, filters and column state all refer to columns by that 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}` },
];
NameTypeDescription
fieldstringProperty of the row object shown in this column.
idstringColumn id. Defaults to field; required when there is no field.
headerstringHeader text. Defaults to field.
type'text' | 'number' | 'date' | 'boolean'Drives alignment, formatting, sorting and the default filter.
formatstring | Intl optionsNumber: '#,##0.00', '0.0%', 'currency:KRW', 'integer'. Date: 'date', 'datetime', 'time', 'short', 'medium', 'long'.
width / minWidth / maxWidthnumberPixel widths. Default width 120, min 40.
flexnumberShare of the leftover width. Resizing turns it into a fixed width.
pinned'left' | 'right'Keep the column visible while scrolling horizontally.
hidebooleanStart hidden. Users can show it from the column chooser.
align'left' | 'center' | 'right'Cell alignment. Numbers default to right.
sortablebooleanDefault true.
resizablebooleanDefault true.
filter'text' | 'number' | 'date' | 'set' | falseFilter UI in the column menu. Default depends on type.
comparator(a, b, rowA, rowB) => numberCustom sort order.
valueGetter(row) => unknownComputed value, e.g. quantity × price. Used for display, sort, filter and summaries.
renderer(params) => string | HTMLElementCustom cell content. Strings are inserted as HTML — escape untrusted data.
cellClassstring | (params) => stringExtra CSS class on cells.
childrenColumnDef[]Makes this a column group header.
mergeboolean | (a, b, rowA, rowB) => booleanMerge consecutive rows with the same value into one tall cell.
wrapHeaderTextbooleanLet a long header label wrap.
aggFunc'sum' | 'avg' | 'min' | 'max' | 'count' | (values, rows) => valueAggregate shown in group rows.
chooserbooleanfalse keeps the column out of the column chooser.
lockVisiblebooleanUsers cannot hide this column.
editablebooleanAllow editing and pasting (needs field).
editor{ type: 'select', options } | { type: 'textarea' } | (params) => editorEditor for this column. options can be a function of the row (dependent selects).
requiredbooleanReject blank values.
validate(value, row) => true | stringReturn a message to reject a value.

Grid options

NameTypeDescription
columnsColumnDef[]Required. Column definitions.
dataobject[]Rows.
getRowId(row) => stringStable id; keeps selection across setData.
rowSelection'single' | 'multiple'Adds a checkbox column and click selection.
rowHeight / headerHeightnumberPixels. Default follows the font size (36 / 40 at 13px).
themestring'light', 'dark', 'board', 'erp', 'excel', 'classic' or your own.
fontSize / fontFamilynumber / stringGrid font. Row heights follow the size.
localestring | LocalePackUI language, e.g. 'ko' or an imported pack. Default: the browser's language.
sideBarboolean | { open }Dock the Columns panel on the right.
summarySummaryOptionsSummary rows. See Summary rows.
rangeSelectionbooleanExcel-style cell ranges by drag, Shift+click, Shift+arrows, Ctrl+A. Default true.
statusBarbooleanRow counts plus count/sum/average/min/max of the selected cells.
fillHandlebooleanDrag the selection corner to fill editable cells. Default true.
pinnedTopRows / pinnedBottomRowsRow[]Rows fixed above or below the scrolling rows.
findBar / wrapHeaderTextbooleanCtrl+F find bar (default true); wrap all header labels.
groupBystring[]Group rows by these column ids, in order.
groupDefaultExpandednumberLevels open at start: 0 none (default), n levels, -1 all. Also applies to tree data.
groupPanelbooleanBar above the header where headers can be dropped to group.
treeData{ getParentId(row) }Nest rows under their parent id. Needs getRowId.
detailRenderer / detailHeight / isRowMaster(params) => Element | string / number / (row) => booleanExpandable panel under a row (default height 240); limit which rows can expand.
rowDragbooleanHandle column for dragging rows into a new order (off while sorted, filtered or grouped).
paginationboolean | { pageSize?, pageSizes? }Show one page at a time with a pager (default 100 rows per page).
datasource{ getRows({ start, end, sort, filters, quickFilter }) }Load rows from your server in blocks; must resolve to { rows, total? }.
blockSize / maxBlocksnumberRows per request (100) and blocks kept in memory (100).
sideBarboolean | { open?, panels?: ('columns' | 'filters')[] }Docked panels for columns and filters.
keymapRecord<string, action | false>Remap or disable keys, e.g. { Enter: false, 'Ctrl+E': 'edit' }.
contextMenufalse | (params) => itemsfalse keeps the browser menu; a function customizes the right-click menu.

Data and row ids

Rows are ordinary objects. Without getRowId rows are identified by position, so replacing the data clears the selection. With it, the selection survives setData as long as the ids still exist.

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

Sorting

Click a header to cycle ascending, descending and unsorted. Shift-click (or Ctrl/⌘-click) adds the column as another sort key; a small number shows its order. Empty values always sort last. Text uses the active language's collation, so Korean, accents and numbers inside text sort naturally.

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) }

Filtering

Each column's ⋮ menu holds a filter that matches its type: text (contains, equals, starts/ends with, blank…), number (=, ≠, <, ≤, >, ≥, between, blank), date (equals, before, after, between) and set (a searchable checklist of the column's values). Text matching ignores case. A filled filter icon marks filtered columns.

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));

Pinning and column groups

Pinned columns stay visible while the middle section scrolls horizontally. Users can pin and unpin from the column menu. Columns with children become group headers, nested as deep as you like; a group split across pinned sections shows its header in each.

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' },
];

Column customization

Users can drag headers to reorder, drag header edges to resize, and use the column menu to autosize, hide, pin or reset columns. Choose columns opens a chooser with search, select all, the group tree and drag-to-reorder; the same panel can be docked on the right with sideBar. Store getColumnState() to remember each user's layout.

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();

Summary rows

Summary rows show totals, averages and other aggregates. Put them at the top or bottom; keep them pinned outside the scroll area, or let them scroll with the data. They recalculate when data, filters or (with scope: 'selected') the selection change, and use each column's format unless a cell sets its own.

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 use numeric values and skip blanks; min/max also compare dates.
  • count is the number of rows in scope.
  • A function receives the column's values and the rows; return a number to have it formatted, or a string to show as is.
  • The row label appears in the first visible column without an aggregate in that row.

Editing

Mark a column editable: true and users can edit its cells: double-click, press Enter or F2, or just start typing. Enter saves and moves down, Tab saves and moves to the next editable cell, Esc cancels. Booleans toggle with Space or a double-click.

The editor follows the column type: text, numbers (1,234 is accepted), dates and booleans. Use a select editor for a fixed list. When its options is a function of the row, you get dependent selects such as cities by country. required and validate keep bad values out; the editor stays open with a message until the value is fixed.

Every edit, paste, insert and removal is tracked and can be undone with Ctrl+Z. Changed cells get a small corner mark. When you're ready to save, read getModifiedRows() or the column-level getChanges(), send them to your server, then call 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()

Spreadsheet tools

Set merge: true on a column and consecutive rows with the same value become one tall cell, which is handy for reports grouped by region or team. Sort by that column first. statusBar: true adds a bar that shows row counts and the count, sum, average, minimum and maximum of the selected cells. Drag the small square at the corner of a selection to fill editable cells: numbers and dates continue their step, text ending in a number counts up, and anything else repeats.

Use pinnedTopRows and pinnedBottomRows for rows that should never scroll away, such as targets or references. Ctrl+F opens a find bar inside the grid; find() does the same from code.

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();

Grouping, trees and details

Pass groupBy with one or more column ids and rows are gathered under group rows that show the value and the number of rows. Give a column an aggFunc such as 'sum' or 'avg' and every group row shows that total. Groups follow the sort direction of their column. Open them with the arrow, a double-click, or Enter, Space and the left and right arrow keys. With groupPanel: true, people can drag a header into the bar above the grid to group by it themselves.

For data that already forms a hierarchy, such as an org chart or a folder list, use treeData with a getParentId function. Filtering keeps the parents of every match, so results stay in context. detailRenderer adds a panel under a row that can hold anything, including another grid. rowDrag: true adds a handle for putting rows in order by hand; it pauses while the rows are sorted, filtered or grouped.

// 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,
});

Pages and server data

With pagination the grid shows one page at a time and puts a pager under the rows. People can jump to the first, previous, next or last page, and pick the number of rows per page from pageSizes. Sorting or filtering goes back to the first page.

When the data lives on a server, pass a datasource instead of data. The grid asks getRows only for the rows on screen, in blocks of blockSize, and draws grey placeholders until they arrive. Sorting, filters and the quick search are sent along so the server can apply them. Return total when you know it. Leave it out for infinite scroll, which keeps loading until a block comes back short. Add pagination as well, and every page becomes one request. Grouping and adding or removing rows only work with data in the browser.

// 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
});

Export and import

exportXlsx() saves the rows as a real Excel file. Numbers stay numbers with the column's format, dates stay dates, column groups become merged header rows, and the header is frozen with filter buttons. exportCsv() writes CSV with a byte order mark, so Excel opens Korean and other non-Latin text correctly. Text that looks like a formula is prefixed so a spreadsheet never runs it. Both export what is on screen by default: the current sort and filters, and the visible columns. Choose other rows or columns with the options.

importFile() reads an .xlsx or CSV file. The first row is matched to your columns by header or field name, and every value is checked like a paste, so a number column only takes numbers and a date column takes dates such as 2026-03-05 or 2026.3.5. Imported rows are added at the end and can be undone, or they can replace the data. With sideBar, people get a Filters panel next to the Columns panel and can filter several columns at once.

// 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]] }]);

Copy, paste and right-click

Select cells like in Excel: drag across them, Shift-click, use Shift with the arrow keys, or press Ctrl+A. Ctrl+C copies the range in a format Excel and Google Sheets understand: numbers stay numbers, dates become YYYY-MM-DD. Ctrl+V pastes cells copied from Excel starting at the selected cell. A single copied value fills the whole selected range.

Pasting only changes columns with editable: true, so ids and computed columns stay safe. Values are converted to the column type: 1,234, ₩12,000, (5) and 12% become numbers; 2026. 3. 5. and 2026년 3월 5일 become dates. Values that can't be converted are skipped and counted in the paste event.

Right-click a cell for Copy, Copy with headers, Paste, autosize, hide and pin. Right-click a header for the column menu. Turn ranges off with rangeSelection: false, or bring back the browser menu with 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');

The menu's Paste item reads the clipboard, which some browsers block or ask about. Ctrl+V always works because it uses the browser's own paste event.

Row selection

With rowSelection the grid adds a checkbox column. Click selects a row, Ctrl/⌘-click toggles, Shift-click selects a range, the header checkbox selects every filtered row, and Space toggles the focused row.

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();

Keyboard

Click a cell or tab into the grid, then:

KeyAction
← ↑ → ↓Move the focused cell.
Tab / Shift+TabNext / previous cell, wrapping rows; leaves the grid at the ends.
Home / EndFirst / last column.
Ctrl+Home / Ctrl+EndFirst / last cell.
PageUp / PageDownOne screen up / down.
Shift + ← ↑ → ↓ / Home / End / PageExtend the cell range.
Ctrl+ASelect every cell.
Ctrl+C / Ctrl+VCopy to / paste from Excel (TSV).
Ctrl+FFind in the grid.
Shift+F10 / ContextMenuOpen the right-click menu.
Enter / F2 / typingStart editing an editable cell.
Enter / Tab / Esc (editing)Save and move down / save and move right / cancel.
DeleteClear editable cells.
Ctrl+Z / Ctrl+YUndo / redo.
SpaceToggle the focused row’s selection.
EscClose the column menu or chooser.

Formatting and renderers

Numbers and dates are formatted with the browser's Intl APIs in the grid language, so 1234.5 becomes 1,234.5 in English and 1.234,5 in German. Use renderer for anything else; it gets value, formatted, row, rowIndex and 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;
} }

A renderer that returns a string is inserted as HTML. Escape anything that comes from users, or return an element.

Themes and fonts

Built-in themes: light, dark, and four simple Korean-style themes — board (bulletin board: thick top rule, horizontal lines), erp (every cell bordered, compact), excel (gridlines, green focus) and classic (navy header). Font size and family are one option each; row heights follow the size unless you set them.

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

Make your own theme

/* 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;
}
VariableKindControls
--fg-font / --fg-font-sizefontFont family and size.
--fg-bg / --fg-fg / --fg-mutedcolorBackground, text and secondary text.
--fg-header-bg / --fg-header-fg / --fg-header-weightcolor / weightHeader colors and weight.
--fg-border / --fg-row-bordercolorOuter border and row lines.
--fg-col-linebox-shadowVertical cell lines, e.g. 'inset -1px 0 #ddd'. Default none.
--fg-row-odd / --fg-row-hover / --fg-row-selectedcolorZebra, hover and selected rows.
--fg-accent / --fg-focuscolorButtons, sort icons and the focused cell.
--fg-radius / --fg-cell-pxlengthCorner radius and cell padding.
--fg-header-text-align / --fg-top-borderkeyword / borderCenter header labels; thick top rule.

Languages

English is built in. Korean, Simplified Chinese, Japanese, Spanish, French and German are separate imports so you only ship what you use. Without a locale option the grid picks the browser language when that pack is registered, otherwise English.

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 exports <FreeGrid>. Every grid option is a prop and every event is an on… prop. The ref is the grid API. Changing data, columns, theme, locale, font or summary updates the grid; keep those objects stable with 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 exports <FreeGrid> for Vue 3.3+. Events are kebab-case (@cell-click, @selection-change…), and the component ref exposes getApi().

<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 reference

createGrid(element, options) returns this API. Calls after destroy() do nothing.

MethodReturnsDescription
setData(rows)voidReplace all rows.
getData()Row[]Current rows (unfiltered, unsorted).
setColumns(defs)voidReplace column definitions.
getColumnState() / applyColumnState(state)ColumnState[]Save and restore order, width, visibility and pinning.
resetColumnState()voidBack to the column definitions.
setColumnVisible(colId, visible)voidShow or hide a column.
autoSizeColumns(colIds?)voidFit widths to header and visible cells.
openColumnChooser() / closeColumnChooser()voidFloating column chooser.
toggleSideBar(open?)voidOpen or close the docked Columns panel.
setSort(items) / getSort()SortItem[]e.g. [{ colId: 'total', dir: 'desc' }].
setFilter(colId, model | null) / getFilterModel()FilterModelSet or clear one column filter.
setQuickFilter(text)voidSearch all visible columns.
getSelectedRows() / selectAll() / deselectAll()Row[]Selection helpers. selectAll selects the filtered rows.
setSummary(options | null) / getSummaryValues()object[]Change summary rows; read raw totals.
getRange() / setRange(range | null)GridRangeRead or set the selected cell range.
copySelection({ headers? })stringCopy the range as Excel TSV and return it.
pasteText(text){ changed, skipped }Paste TSV/Excel text at the range or focused cell.
startEdit(rowIndex, colId) / stopEdit(cancel?)boolean / voidOpen or close the editor.
addRows(rows, index?) / removeRows(rows | fn) / removeSelectedRows()Row[]Insert or remove rows (tracked, undoable).
getModifiedRows() / getChanges() / hasChanges()objectCreated/updated/deleted rows and column-level diffs.
commitChanges() / revertChanges()voidAccept changes as the new baseline, or undo all of them.
undo() / redo() / canUndo() / canRedo()void / booleanUndo history for edits, pastes and row changes.
fillRange(target)voidFill from the selection into a larger range (series aware).
find(text) / findNext() / findPrevious() / clearFind()number / voidHighlight and step through matching cells.
setPinnedRows({ top?, bottom? })voidChange pinned rows.
setGroupBy(colIds) / getGroupBy()void / string[]Change or read the grouping.
expandAll() / collapseAll() / setGroupExpanded(key, open)voidOpen or close groups and tree rows. Group keys look like 'country:KR|city:Seoul'; tree keys are row ids.
setDetailExpanded(row, open) / isDetailExpanded(row)void / booleanOpen, close or check a detail panel.
moveRow(row, toIndex)booleanMove a row in the source data (undoable). False while sorting, filtering or grouping.
setPage(n) / getPage() / getPageCount()void / numberMove between pages (0-based).
setPageSize(n) / getPageSize()void / numberChange rows per page.
refreshRows()voidServer data: drop cached rows and load again.
exportXlsx(filename?, options?) / getXlsx(options?)void / Uint8ArrayExcel file of the rows. Options: columns, rows (filtered | all | selected), headers, sheetName.
exportCsv(filename?, options?) / getCsv(options?)void / stringCSV with a BOM so Excel reads Korean; same options plus separator.
importFile(file, { mode?, sheet? })Promise<{ rows, skipped }>Read .xlsx or CSV; headers are matched to columns; append (default) or replace.
toggleSideBar(open?, panel?)voidOpen or close the side bar, optionally on 'columns' or 'filters'.
versionstringExported constant with the package version, e.g. import { version }.
getDisplayedRowCount()numberRows after filtering.
scrollToRow(index)voidScroll a displayed row into view.
setTheme(name)voidSwitch theme.
setFont({ size?, family? })voidChange font; family null restores the theme font.
setLocale(codeOrPack)voidSwitch UI language.
on(event, handler)() => voidSubscribe; returns an unsubscribe function.
destroy()voidRemove the grid and its listeners.

Events

EventPayloadWhen
cellClick / cellDoubleClick{ row, rowIndex, colId, value, event }A data cell was clicked.
selectionChange{ selected: Row[] }Selected rows changed.
sortChange{ sort }Sort changed.
filterChange{ filters }A column filter changed.
columnResize{ colId, width }Resize or autosize finished.
columnMove{ colId, toIndex }A column was dragged to a new position.
columnVisible{ colId, visible }A column was shown or hidden.
rangeChange{ range }The cell range changed.
cellValueChanged{ row, rowIndex, colId, oldValue, newValue }A paste changed a cell.
paste{ changed, skipped }A paste finished.
editStart / editStop{ rowIndex, colId, committed? }An editor opened or closed.
validationFailed{ rowIndex, colId, value, message }An edited value was rejected.
rowsAdded / rowsRemoved{ rows }Rows were inserted or removed.
groupChange / groupToggle{ groupBy } / { key, expanded }Grouping changed; a group or tree row opened or closed.
detailToggle{ row, expanded }A detail panel opened or closed.
rowDragEnd{ row, fromIndex, toIndex }A row was dragged to a new position.
pageChange{ page, pageSize, pageCount }The page or page size changed.
rowsLoaded / rowsLoadFailed{ start, end, total? } / { start, end, error }A server block arrived or failed.
focusChange{ rowIndex, colId }The focused cell moved.