npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

data-grid-component

v3.10.0

Published

Standalone data grid web component

Readme

Data Grid Web Component

NPM Downloads

Autonomous open source grid component with RTL support. Designed for server side paginated content but also works for basic tables.

Demo:

Key features:

  • Server side support (FetchDataSource)
  • Inline editing
  • Sorting / filtering
  • i18n friendly
  • Easily themable (--dg-* tokens)

Docs:

How to use

Installation

$ npm install data-grid-component

Initialization

  • HTML way
<data-grid src="data.json" sortable filterable></data-grid>
<script type="module" src="./data-grid.js"></script>

From a CDN, use the distributed, versioned build (dist/data-grid.min.js) rather than relying on the CDN to minify the unbundled source:

<script type="module" src="https://cdn.jsdelivr.net/npm/data-grid-component@3/dist/data-grid.min.js"></script>

Still link the stylesheet (dist/data-grid.min.css) as usual.

Zero-config standalone script

dist/data-grid.standalone.min.js is a single classic script that registers the <data-grid> element and injects the grid stylesheet itself, so no <link rel="stylesheet"> is needed:

<data-grid src="data.json" sortable filterable></data-grid>
<script src="https://cdn.jsdelivr.net/npm/data-grid-component@3/dist/data-grid.standalone.min.js"></script>

The bundle is meant to be loaded once, without importing from it (it exposes no exports). Use the standalone or the classic CSS-included distribution, never both: the stylesheet it injects is the same one, so a duplicate is harmless but pointless.

Because the stylesheet is injected as an inline <style> at runtime, CSP style-src must allow it (unsafe-inline, or a nonce propagated from the loading <script nonce>); otherwise use the non-standalone distribution. Note that the grid already sets style attributes (computed geometry) on its elements, which style-src governs as well.

  • using the DOM API
const grid = document.createElement("data-grid");
grid.setAttribute("src", "/api/users"); // triggers a reload on an existing instance
document.body.appendChild(grid);
  • using the constructor with an import statement
import { DataGrid } from "data-grid-component";
const grid = new DataGrid({ src: "/api/users" });
document.body.appendChild(grid);

Declarative HTML

The supplied table provides the structure, the initial dataset and the declarative cell presentation; the grid owns subsequent rendering. caption, colgroup and the table's own classes and attributes are preserved.

<data-grid sortable filterable searchable page-size="10">
    <table>
        <thead>
            <tr>
                <th data-field="name" data-sort="asc">Name</th>
                <th data-field="email">Email</th>
                <th data-field="status" data-filter="select">Status</th>
            </tr>
        </thead>
        <tbody>
            <tr data-row-key="42">
                <td>User One</td>
                <td>[email protected]</td>
                <td data-value="active">Active</td>
            </tr>
        </tbody>
    </table>
</data-grid>

<th data-field> declares a column: title is the cell text, and data-sortable, data-filterable, data-filter, data-responsive, data-hidden, data-editable, data-editable-type, data-transform, data-format, data-align, data-width (preferred width) and data-min-width (never compress below) map to the matching column options. data-sort="asc"|"desc" seeds the initial sort (DOM order is the priority). The host still activates the global capabilities — data-sortable on a column only opts out, it never turns sorting on globally.

Authored child nodes in a declarative <th> are preserved too. For a sortable column they are cloned into the standard .dg-sort-label, so an application can provide its own icon or web component while the grid retains sorting, focus, ARIA and sizing:

<th data-field="theme" data-width="210" data-min-width="170">
    <i class="ti ti-circle-chevron-down" aria-hidden="true"></i>
    Theme
</th>

The <data-grid> host takes the reflected attributes listed above (select-visible-only, row-key, no-data, page-sizes, row-actions, ...): HTML covers structure, data and scalar configuration; functions, objects and behaviors (renderCell, validators, dataSource, actions) stay JavaScript.

Row actions can also come from the markup: a <th data-actions> column activates the capability, and each <td data-actions> cell is normalized into row.$actions descriptors:

<data-grid row-actions>
    <table>
        <thead>
            <tr>
                <th data-field="name">Name</th>
                <th data-actions>Actions</th>
            </tr>
        </thead>
        <tbody>
            <tr data-row-key="42">
                <td>User One</td>
                <td data-actions>
                    <a data-action="view" href="/users/42">View</a>
                    <button data-action="delete" data-confirm="Delete this user?">Delete</button>
                </td>
            </tr>
        </tbody>
    </table>
</data-grid>

When no dataSource/src is configured, the <tbody> rows become the local dataset: <td> maps to the columns by index, tr[data-row-key] is the authoritative row key. td[data-value] is the machine value (typed — numbers, booleans, null and JSON are parsed), while the cell content is the user representation, preserved across rerenders as long as the value is unchanged (so badges, <time>, <data> and formatting survive). Without data-value, the cell text is used as a plain string. Filter <select> labels are derived from the same data-value + cell text when present. With a src/dataSource, the source stays authoritative and the <tbody> is not consumed as data.

Cell templates

A direct-child <template data-cell="field"> declares the presentation of a column. The template is cloned for every cell; data-dg-text="value" receives the raw scalar value through textContent:

<data-grid src="/api/users">
  <table>...</table>
  <template data-cell="status">
    <span class="badge" data-dg-text="value"></span>
  </template>
</data-grid>

The rendered cell exposes the same value as td[data-value], so conditional styling stays in CSS:

td[data-value="active"] .badge { color: green; }

Precedence: renderCell > template > authored cell markup > format / transform. Templates bypass format and transform; use renderCell for formatted output. On an editable column the inline editor replaces the template in the cell: the template is the pre-edit presentation and data-value reflects the last rendered value; use renderEditor for a custom editing UI. No conditions, loops or attribute interpolation: the template is a renderer, not a template language. Objects and arrays render as empty text and expose no data-value. The template is cloned for every cell, so avoid id attributes; use classes and data-value for styling. Templates are trusted application HTML: values are never parsed, but the guard against script, style, on* attributes and executable URL protocols is not a sanitizer (data: is allowed for image/media src).

Rule of thumb: HTML = declarative configuration and simple presentation, JS = behavior. For custom rendering (renderCell), validators or a custom data source, use the JavaScript API instead.

Options

Options are set as constructor options or reflected HTML attributes. HTML attributes are an intentionally curated declarative subset of options (kebab-case -> camelCase, a bare attribute means true); complex or functional options (dataSource, actions, renderCell, ...) remain JavaScript-only. Some options only work if the proper plugin is loaded.

| Option | Type | Default | Description | |-----------------------|----------------------|----------------------|--------------------------------------------------------------| | src | String | "" | URL to a server-side endpoint | | params | Object | {} | Extra constant HTTP params per request | | dataSource | DataSource | - | Custom data source (defaults to fetch/array) | | loading | String | "eager" | eager or lazy | | columns | Column[] | [] | Available columns | | rowKey | String \| Function | "id" | Field or function for the stable row key | | rowLabel | String \| Function | - | Field or function for the accessible row label | | sortable | Boolean | false | Sort by column | | filterable | Boolean | false | Show the filter row | | selectable | Boolean | false | Select rows with checkboxes | | singleSelect | Boolean | false | Select a single row with radios | | selectVisibleOnly | Boolean | true | selectAll only selects the visible rows | | actions | Action[] | [] | Row actions (also resolved from $actions / meta.actions) | | rowActions | Boolean | false | Show the actions column even without static actions | | actionRenderer | Function | - | Global action renderer | | collapseActions | Boolean | false | Group actions in a native popover when supported | | bulkActions | BulkAction[] | [] | Bulk actions on the current selection | | resizable | Boolean | false | Resizable columns | | reorder | Boolean | false | Draggable column headers | | menu | Boolean | false | Pointer-positioned column menu; native fallback | | responsive | Boolean | false | Responsive columns | | responsiveToggle | Boolean | true | Show toggle column on small screens | | responsiveStartOpen | Boolean | false | Open responsive detail rows by default | | rowDetails | Function | - | Render expandable application content for a row | | rowDetailsStartOpen | Boolean | false | Open row details by default | | detailsDisplay | String | "inline" | inline / dialog row details (initial-only) | | autosize | Boolean | false | Measure widthless columns to give them a preferred width | | enterMovesDown | Boolean | false | Enter commits and moves the edit focus one row down | | autoheight | Boolean | true | Fill table height on the last page | | autohidePager | Boolean | false | Hide the pager when everything fits | | pager | String | "pages" | Result navigation: "pages" replaces rows, "more" appends chunks via loadMore() | | wrap | Boolean | false | Allow data cells to wrap over multiple lines | | snapColumns | Boolean | false | Snap horizontal scrolling near column starts | | pageSizes | Number[] | [10,25,50,100,250] | Available page size options | | showPageSize | Boolean | true | Show the page size select | | filterDelay | Number | 300 | Debounce delay (ms) for text column filters | | searchable | Boolean | false | Show the global search input | | searchPlaceholder | String\|null | null | Search hint; defaults to the translated search label | | searchDelay | Number | 300 | Debounce delay (ms) for the global search | | minSearchLength | Number | 0 | Minimum characters before a search is applied | | density | String | "default" | Row density: compact, default, comfortable | | saveState | Boolean | false | Persist query and column state (note below) | | errorMessage | String | "" | Message shown when a load fails | | noData | String | "" | Message shown when there is no data | | caption | String | "" | Table caption (accessible name) | | initialQuery | QueryState | - | Initial runtime query state | | initialResult | PageResult | - | Initial result to display without loading | | validate | Function | - | Grid-level editor validator | | debug | Boolean | false | Log actions in DevTools console | | dir | String | "ltr" | Direction | | id | String | auto | Custom id for the grid |

rowLabel falls back to the row key, then the row index. A dataSource defaults to FetchDataSource or ArrayDataSource; plugin-backed options are described in docs/plugins.md. Set column.wrap to override the grid-wide wrap policy for an individual data column. saveState persists the query, the column order, explicit visibility and user-resized widths, and requires a stable id.

Attributes

The main attributes are src, loading, page, page-size, sortable, filterable, searchable, search-placeholder, search-delay, min-search-length, filter-delay, responsive, responsive-toggle, responsive-start-open, row-details-start-open, details-display, selectable, single-select, select-visible-only, row-click, row-key, row-label, collapse-actions, save-state, no-data, error-message, page-sizes, row-actions, reorder, menu, wrap, snap-columns, autosize, enter-moves-down, resizable, autoheight, autohide-pager, pager, show-page-size, debug, dir, density. Example:

<data-grid
    src="/api/users"
    sortable
    filterable
    searchable
    selectable
    select-visible-only="false"
    row-key="UserID"
    min-search-length="3"
    page-sizes="10,25,50"
    no-data="No users"
></data-grid>

loading, page and page-size are initial-only: they seed the first load and later attribute mutations are intentionally ignored. Runtime pagination uses setQuery() and the pager methods. With pager="more" the footer counts the loaded rows and a Load more button appends the next chunk through loadMore() instead of replacing the page; the query stays on page 1, and any search, filter, sort, page-size or data-source change restarts from the first chunk. A backend may omit total and send hasMore instead (see docs/server-data.md); the footer then counts without a total. See demo/logs.html.

The Load more button stays focusable while busy (aria-disabled="true"). When it disappears at the end of the results, keyboard focus moves to the first new row, or to the scroll viewport if the final chunk is empty. A chunk appends only the new rows: mounted rows keep their elements, so an in-progress edit, focus or an open inline detail row survives the load. A search, filter, sort, page-size or data-source change still replaces the whole body.

Scrollable grid

The .dg-frame owns the outer border and radius. Its .dg-scroll viewport contains the table and anchors the sticky header (including the filter row). The footer sits after the viewport and stays visible outside its scroll. The viewport is a Tab stop only while it actually scrolls (vertically or horizontally); otherwise it stays programmatically focusable without adding an empty focus stop. This is the default behavior — as soon as the grid is given a constrained height, the viewport takes the remaining space and its chrome stays visible while rows scroll:

.results-grid {
  max-height: 70vh;
}
<data-grid class="results-grid"></data-grid>

The host is a vertical flex column: an optional topbar sits above the viewport, which expands to fill the rest of the height. On an unconstrained grid the viewport grows with its content, so there is nothing to stick against. Pin the height only when you want an internal vertical viewport.

Theming

The core stylesheet handles the grid's geometry and interaction states. Its themeable values are exposed through --dg-* custom properties, which a theme maps to an application's design system. The package ships with two demo themes: Bootstrap 5 and Actual CSS. Load one after the core stylesheet:

<link rel="stylesheet" href="dist/data-grid.css" />
<!-- pick a theme -->
<link rel="stylesheet" href="themes/bootstrap.css" />
<link rel="stylesheet" href="themes/actual.css" />

For Actual CSS controls, configure the roles after the element is defined:

customElements.get("data-grid").setDefaultControlClasses({
    button: "btn outline sm",
    action: "btn outline sm",
    input: "input sm",
    select: "select sm",
});

Existing connected controls update too. Per-grid overrides and the styling contract are documented in Styling.

See Styling for the complete token reference and custom theme guidance.

Lazy initial load

loading="lazy" defers the first data source fetch until the grid nears the viewport (using a one-shot IntersectionObserver with a ~200px margin). It is ideal for grids in hidden tabs or far below the fold:

<data-grid src="/api/users" loading="lazy"></data-grid>

The grid still builds its chrome (header, filters, footer) and fires connected immediately; only the fetch is postponed. It applies to async sources (src/dataSource) — a local declarative table or a provided initialResult renders right away. Default is "eager" (load on connect).

Query changes before activation accumulate normally: any filters, search or page state set while hidden are applied in a single request when the grid becomes visible. An explicit refresh()/load() (or a src change) always loads immediately, regardless of visibility.

Responsive stacked

responsiveStartOpen turns the responsive detail rows into a start-open "stacked" view: when columns are hidden on a narrow grid, their values are shown immediately inside the existing responsive detail row instead of behind a chevron toggle.

<data-grid responsive responsive-start-open></data-grid>

It is a presentation-only option — search, sort, filters, selection, actions, pagination and the accessibility model are unchanged. Use responsive: 0 for columns that must stay in the main row, and (optionally) responsiveToggle false for a clean record layout without a toggle column:

<data-grid responsive responsive-start-open responsive-toggle="false"></data-grid>

Columns that are actively sorted or filtered are never hidden, so an active criterion stays visible. Users can still collapse individual rows; the grid never overrides an explicit collapse, and the choice resets on the next data load.

When responsive columns are combined with application-rendered rowDetails, the default responsive toggle is replaced by the row-details disclosure. One control opens and closes both the hidden column values and application details.

To keep responsive values visible while application details remain independently collapsible, use the start-open presentation without a responsive toggle:

new DataGrid({
    responsive: true,
    responsiveStartOpen: true,
    responsiveToggle: false,
    rowDetails: ({ row }) => renderCustomerActivity(row),
});

Responsive content is then the narrow-screen representation of normal table columns, while the remaining row-details chevron reveals only the application content.

Row details dialog

detailsDisplay: "dialog" (attribute details-display="dialog") opens row details in a modal instead of an inline row. It is initial-only: configure it before connection.

new DataGrid({
    responsive: true,
    detailsDisplay: "dialog",
    rowDetails: ({ row }) => renderCustomerActivity(row),
});

rowDetails is optional in this mode. Without it, the grid builds a record sheet from its visible and responsive-hidden columns; hidden: true, virtual and attr columns are excluded. When rowDetails is set, its content is appended after the sheet. The dialog is modal, closes on Escape or its close button, and survives body rerenders. See docs/plugins.md for the full composition rules.

API

The runtime state is a QueryState (page, pageSize, sort, filters). Query methods reload through the single load() path (AbortController + stale response protection).

| Member | Description | |----------------------------------------------------------|-------------------------------------------------| | grid.query | snapshot of the current query state (getter) | | grid.page | current page (getter) | | grid.rows / grid.total / grid.meta | result of the current query | | grid.loading / grid.error | load status and last error | | setQuery(patch) | merge a query patch and reload | | resetQuery() | reset to the initial query and reload | | refresh() / load() | reload the current query | | getColumns() | normalized column list of the current cycle | | showColumn(field) / hideColumn(field) | toggle a column | | getFilterOptions(column) | options for a select filter | | getSelectionState() | { mode, ids, except } snapshot (server-first) | | isRowSelected(row) | whether a row is selected | | selectRow(row) / deselectRow(row) / toggleRow(row) | row selection | | selectAll() / clearSelection() | select/reset the selection | | selectAllMatching() | select all matching results, clearing exclusions | | getSelection(...keys) | page-local selected rows | | setSearch(value) / clearSearch() | set / clear the global search | | updateRow(rowKey, patch) / removeRow(rowKey) | mutate / remove a row (see docs/actions.md) | | getFirst() / getPrev() / getNext() / getLast() | paging | | clearFilters() | clear the current filters | | sortAsc(field) / sortDesc(field) / sortNone(field) | sort helpers | | DataGrid.registerPlugins(map) | merge plugin constructors by name | | DataGrid.getLabels() / setLabels(labels) | read / translate the UI labels | | DataGrid.loadLabels(url) | fetch a JSON label file and apply it | | grid.updateLabels() | refresh labels on one connected grid |

Column

| Name | Type | Description | |-----------------------------------------|----------------------|-------------------------------------------------------------------------| | field | String | the key in the data | | title | String | header title (defaults to field) | | id | String | stable identifier (defaults to field) | | width | Number | preferred width (the column stays flexible without one) | | class | String | class on the column (th.class / td.class) | | attr | String | set a row attribute instead of rendering (on* names are refused) | | hidden | Boolean | hide the column | | sortable | Boolean | disable sorting for this column (defaults to grid) | | filterable | Boolean | disable filtering for this column (defaults to grid) | | transform | String \| Function | "uppercase" / "lowercase" / "array", or (value, ctx) => value | | minWidth | Number | never compress below this width | | align | String | header and cell alignment: start / center / end | | format | String | formatter: "boolean" / "date" / "datetime" / "number" | | formatOptions | Object | options for Intl.DateTimeFormat / Intl.NumberFormat | | summary | "sum" \| "avg" | display source-provided aggregates in the table footer | | editable / editableType | Boolean / String | inline editing (see docs/editing.md) | | renderEditor | (ctx) => content | application-owned editor for editable columns (see docs/editing.md) | | validate | Function | (value, ctx) => true \| "error message" | | responsive | Number | responsive priority (0 disables) | | filterType | String | filter mode: text / select / boolean / number / date | | filterList | FilterOption[] | business options for a select filter | | firstFilterOption | FilterOption | empty select-filter option (blank by default) | | filterMultiple | Boolean | checkbox popover (in) when supported; otherwise single select (eq) | | renderHeaderCell / renderFilterCell | (th, ctx) => void | custom renderers (core creates the <th>) | | renderCell | (ctx) => content | custom cell renderer (primitive / Node / { html }) | | cellClass | String \| Function | body cells only, per row: string or (ctx) => class |

Select filters always include an option with value: "", so filterList can contain only business values. Use firstFilterOption to customize its label (for example, All plans); an explicit text: "" remains empty. This automatic option applies only to filters, not to editing controls.

Column sizing follows three notions: minWidth is a floor the column is never compressed below, width is a preferred width, and a column without a preferred width stays flexible and absorbs the remaining space. Formatter defaults contribute a floor — and a preferred width for predictable formats — unless the column sets its own. With autosize, widthless text columns are measured once at render and pinned to a computed width instead of staying flexible.

Formatting

A column can format its cell values with a built-in formatter. The formatter owns the representation, the column owns the sizing: format and formatOptions are rendering concerns, while align, minWidth and width stay generic column geometry (a formatter only contributes safe defaults for them: an alignment, a floor, and a preferred width for predictable formats — boolean, date, datetime and percent numbers).

Formatters also suggest a preferred filter mode (boolean → tri-state select, number → typed numeric equality, date → partial ISO prefix match); an explicit filterType always wins. See docs/filtering.md.

| Format | Output | Intl options | |------------|-------------------------------------|------------------------------| | boolean | accessible <span> mark, CSS-drawn | none | | date | <time datetime> | Intl.DateTimeFormatOptions | | datetime | <time datetime> | Intl.DateTimeFormatOptions | | number | formatted text | Intl.NumberFormatOptions |

{
    field: "created",
    format: "date",
}

{
    field: "price",
    format: "number",
    formatOptions: {
        style: "currency",
        currency: "EUR",
    },
}

{
    field: "active",
    format: "boolean",
}

format: "date" is a calendar date without a time zone: it accepts Date, timestamp or a validated YYYY-MM-DD, and rejects time / timeZone options. format: "datetime" is an instant: Date, timestamp or an ISO datetime string with a time zone (2026-08-26T08:30:00Z, 2026-08-26T10:30:00+02:00). The locale comes from the closest lang attribute (the grid itself included), falling back to the document element. Custom DOM rendering stays in renderCell, which always takes precedence over format.

formatOptions are passed to Intl after applying the formatter defaults and convenience inferences: { currency } / { unit } imply style, and a date { style } maps to dateStyle / timeStyle. When granular date options (year, month, ...) are present, no default dateStyle / timeStyle is injected. An invalid Intl configuration throws visibly, so a misconfigured column is never silently hidden.

explicit column option > formatter default > normal grid behavior

Action

| Name | Type | Description | |------------|--------------------------------------------------------|-----------------------------------------| | name | String | action name (button[data-action]) | | label | String | button label and accessible name | | intent | "default" \| "primary" \| "danger" | sets data-intent | | href | String \| Function | renders an <a> link | | class | String | class on the button | | visible | (row, ctx) => Boolean | hide the action when falsy | | disabled | Boolean \| (row, ctx) => Boolean | block the action (aria-disabled) | | render | ({ action, row, grid }) => content | replace the button content | | confirm | Boolean \| String \| (row, ctx) => Boolean \| String | ask for confirmation before dispatching | | default | Boolean | row click triggers the action |

visible/disabled/href/confirm receive (row, ctx) with ctx = { grid, action, rowKey }.

Actions can also be driven by the server: a row.$actions array lists which actions a row gets (strings are looked up by name, objects override the definition), and meta.actions provides server-side definitions. Set rowActions: true (or row-actions) to activate the column without static actions.

See docs/actions.md for the full contract.

Events

| Name | Detail | Trigger | |--------------------|----------------------------------------------------|----------------------------| | connected | - | the grid is connected | | disconnected | - | the grid is disconnected | | querychange | { query } | the query changes | | rowClick | { row, rowKey, rowIndex, originalEvent } | a data row is clicked | | loadError | error | a load fails | | selectionChange | { selectionState } | the selection changes | | columnVisibility | { col, visibility } | a column is hidden/shown | | columnResized | { id, col, width } | a column is resized | | columnReordered | { col, from, to } | a column is dragged | | headerRendered | - | the header is rendered | | bodyRendered | - | the body is rendered | | rowRendered | { rowData, tr } | a row is rendered | | action | { action, name, row, rowKey, rowIndex, trigger } | an action is performed | | bulkAction | { action, name, selection, query, trigger } | a bulk action is performed | | edit | { data, value, field, column } (cancelable) | an edit is committed | | rowDetailsToggle | { row, rowKey, expanded } | row details are toggled |

rowClick is cancelable: call preventDefault() to veto the configured row-click behavior. action is cancelable too: its detail also includes originalEvent, and preventDefault() blocks a link action's native navigation so the application can take over (see docs/actions.md).

Server

For large data sets, pagination, sorting and filtering happen on the server. See docs/server-data.md. The response contract is a PageResult:

{
    "rows": [...],
    "total": 142,
    "meta": { "filters": { "status": [{ "value": "active", "text": "Active" }] } }
}

demo/server.js (bun demo/server.js) is a working example using the same filter/sort helpers as the client.

Translations

All UI labels are plain strings, configurable through setLabels(), loadLabels() or the shipped locales/* modules. See docs/translations.md.

Browser Support

The core runtime targets modern evergreen browsers with native ES modules and these early-2022 floors: Chromium 99+, Firefox 98+ and Safari 15.4+. Newer platform features such as native Popover are used as progressive enhancements: filterMultiple falls back to a working single-select filter emitting eq, and the optional context menu plugin only requires Popover support. Floating panels are kept aligned by @lekoala/floating, the package's single runtime dependency. See docs/development.md#browser-baseline for the exact baseline and feature gating.

License

data-grid-component is licensed under the MIT license.