Vanilla JS
You don't need React, Angular, or Vue to use OGrid. The @alaarab/ogrid-js package gives you a sortable, filterable, editable data grid with the shared OGrid core through a simple class-based API — just a container element and an options object.
It's a good fit if you're working on an internal tool, a dashboard that can't take on a framework, or you're embedding a grid inside an existing non-framework app.
Install
npm install @alaarab/ogrid-js
Or drop it into an HTML file directly using a CDN (no build tools needed):
<script type="module">
import { OGrid } from 'https://cdn.jsdelivr.net/npm/@alaarab/ogrid-js/dist/esm/index.js';
</script>
Build a real example
Let's make an order management dashboard. It tracks orders by status, amount, and customer — sortable, filterable, and editable inline.
<div id="orders-grid" style="height: 500px;"></div>
<script type="module">
import { OGrid } from '@alaarab/ogrid-js';
import '@alaarab/ogrid-js/styles';
const grid = new OGrid(document.getElementById('orders-grid'), {
columns: [
{ columnId: 'orderId', name: 'Order #', sortable: true },
{ columnId: 'customer', name: 'Customer', sortable: true },
{
columnId: 'status',
name: 'Status',
sortable: true,
filterable: { type: 'multiSelect', options: ['Pending', 'Shipped', 'Delivered', 'Cancelled'] },
},
{
columnId: 'amount',
name: 'Amount',
type: 'numeric',
sortable: true,
editable: true,
valueFormatter: (v) => `$${Number(v).toLocaleString()}`,
},
{
columnId: 'date',
name: 'Order Date',
type: 'date',
sortable: true,
},
],
data: [
{ orderId: 'ORD-1001', customer: 'Acme Corp', status: 'Shipped', amount: 4200, date: '2024-03-01' },
{ orderId: 'ORD-1002', customer: 'Globex Inc', status: 'Pending', amount: 1850, date: '2024-03-03' },
{ orderId: 'ORD-1003', customer: 'Initech', status: 'Delivered', amount: 9300, date: '2024-02-28' },
{ orderId: 'ORD-1004', customer: 'Umbrella LLC', status: 'Cancelled', amount: 620, date: '2024-03-05' },
{ orderId: 'ORD-1005', customer: 'Acme Corp', status: 'Delivered', amount: 7100, date: '2024-02-20' },
],
getRowId: (row) => row.orderId,
pageSize: 20,
cellSelection: true,
statusBar: true,
});
// React to edits in real time
grid.on('cellValueChanged', ({ item, columnId, newValue }) => {
console.log(`Order ${item.orderId}: ${columnId} updated to ${newValue}`);
// sync to your backend here
});
</script>
Live demo
What you get out of the box
The JS package shares the same data model and most feature concepts as the framework packages, but browser-level interaction parity is still tracked package by package.
- Sorting — click column headers
- Filtering — multi-select dropdown on the Status column
- Inline editing — double-click any Amount cell to edit
- Cell selection — click and drag to select ranges
- Keyboard navigation — arrow keys, Tab, Home/End, Ctrl+Arrow
- Clipboard — Ctrl+C/V/X works like a spreadsheet
- Undo/redo — Ctrl+Z/Y
- Status bar — row count and selection aggregations
- Pagination — page controls at the bottom
Constructor
const grid = new OGrid(containerElement, options);
The container element defines where the grid renders. OGrid fills it completely — give it an explicit height (CSS or inline).
Key options
| Option | Type | Default | Description |
|---|---|---|---|
columns | IColumnDef<T>[] | required | Column definitions |
data | T[] | — | Client-side row data |
dataSource | IDataSource<T> | — | Server-side data source (use instead of data) |
getRowId | (item: T) => RowId | required | Returns a unique identifier per row |
pageSize | number | 20 | Rows per page |
sort | { field, direction } | — | Initial sort state |
filters | IFilters | — | Initial filters |
editable | boolean | false | Enable cell editing |
cellSelection | boolean | false | Spreadsheet-style range selection |
rowSelection | 'single' | 'multiple' | — | Row selection mode |
sideBar | boolean | ISideBarDef | — | Column visibility + filter panel |
layoutMode | 'fill' | 'content' | 'fill' | 'fill' fills the container; 'content' sizes to data |
pinnedColumns | Record<string, 'left' | 'right'> | — | Pin columns to left or right edge |
Listening to events
Use .on() to subscribe to grid events:
grid.on('cellValueChanged', ({ item, columnId, oldValue, newValue }) => {
console.log(`${columnId} changed: ${oldValue} → ${newValue}`);
// save to backend
});
grid.on('selectionChange', ({ selectedItems, selectedRowIds }) => {
console.log(`${selectedItems.length} rows selected`);
updateActionBar(selectedItems, selectedRowIds);
});
grid.on('sortChange', ({ sort }) => {
if (!sort) return;
console.log(`Sorted by ${sort.field} ${sort.direction}`);
});
| Event | Payload | When it fires |
|---|---|---|
cellValueChanged | { item, columnId, oldValue, newValue } | A cell edit is committed |
sortChange | { sort } | User changes sort |
filterChange | { filters } | User changes filters |
pageChange | { page } | User navigates pages |
selectionChange | { selectedItems, selectedRowIds } | Row selection changes |
Programmatic control
The grid API lives on grid.api:
// Swap in new data (re-renders efficiently)
grid.api.setRowData(freshData);
// Export what's currently displayed (respects active filters/sort)
grid.api.exportToCsv('orders.csv');
// Set sort programmatically
grid.api.setSort('amount', 'desc');
// Get current column state (widths, visibility, order)
const state = grid.api.getColumnState();
See the full JS API Reference for all available methods.
Theming
The default theme uses CSS custom properties. Override any of them to match your design system:
:root {
--ogrid-primary: #0066cc; /* buttons, active page */
--ogrid-selection: #0078d4; /* active cell outline */
--ogrid-bg: #ffffff; /* background */
--ogrid-fg: #242424; /* text color */
--ogrid-border: #e0e0e0; /* borders */
--ogrid-bg-subtle: #f3f2f1; /* header background */
--ogrid-bg-hover: #f5f5f5; /* row hover */
}
Dark mode works automatically if you rely on prefers-color-scheme, or you can opt into it explicitly:
[data-theme='dark'] {
--ogrid-bg: #1a1a24;
--ogrid-fg: #e0e0e0;
--ogrid-border: #333340;
}
Or skip the built-in theme entirely and write your own CSS targeting ogrid-* class names.
Cleanup
When your grid is no longer needed (navigating away, SPA route change, etc.), call destroy() to remove all DOM elements and event listeners:
grid.destroy();
What's next?
- JS API Reference — full method and options reference
- Features — feature guides, with JS notes where behavior differs from framework packages
- Theming Guide — deep dive into CSS customization