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

@zuilib/data-grid

v0.5.0

Published

ZUI — an enterprise, server-driven data grid on TanStack Table, styled through ZUI tokens

Downloads

58,297

Readme

@zuilib/data-grid

A server-driven enterprise data grid on TanStack Table, built from the @zuilib/primitives primitives and styled only through ZUI tokens.

pnpm add @zuilib/data-grid

React and React DOM are the only required peers. TanStack Table, the ZUI primitives, Base UI and tokens install transitively.

For a Tailwind v4 host, load the component and grid source entries once:

@import "tailwindcss";
@import "@zuilib/primitives/tailwind.css";
@import "@zuilib/data-grid/tailwind.css";
import DataGrid, { defaultDataGridState, toQuery, toQueryKey } from '@zuilib/data-grid'

const [state, setState] = useState(defaultDataGridState)
const key = toQueryKey(state) // changes only when the request would
useEffect(() => { fetch(toQuery(state)).then(setPage) }, [key])

<DataGrid columns={columns} rows={page} rowCount={total} state={state} onStateChange={setState} loading={loading}>
  <DataGrid.Toolbar />
</DataGrid>

Leave state out and the grid keeps it itself, starting from defaultState; onStateChange still reports every change. Controlled, every emission is the state prop plus one change: apply it, or ignore it to reject it.

Exports

| Subpath | Exports | |---------|---------| | @zuilib/data-grid | Everything below in one entry; DataGrid is the default export | | @zuilib/data-grid/data-grid | DataGrid (default) with DataGrid.Toolbar, plus the column, label, filter and selection-action types | | @zuilib/data-grid/use-data-grid | useDataGrid — the TanStack table wiring without the layout, for composing the parts yourself | | @zuilib/data-grid/server-adapter | toQuery, fromQuery, toQueryKey, toServerState, toColumnState, fromColumnState | | @zuilib/data-grid/cells | dateCell, dateTimeCell, textColumn, twoValuesColumn |

Component API

DataGrid — @zuilib/data-grid

| Prop | Type | Default | |------|------|---------| | columns | DataGridColumnDef<TData>[] | required — TanStack column defs plus the ZUI extras below | | rows | TData[] | required — the current page of rows, rendered as given | | rowCount | number | required — total rows on the server (all pages); drives the page count and the range text. Ignored with manualPagination={false}: the rows that pass the filters are counted instead | | state / defaultState / onStateChange | Partial<DataGridState> | controlled or uncontrolled — onStateChange receives the whole next state on every interaction | | manualPagination / manualSorting / manualFiltering | boolean | true — the server answers the state; false pages, sorts or filters rows client-side | | getRowId | (row, index, parent?) => string | row index | | enableRowSelection | boolean \| (row) => boolean | true — adds the leading checkbox column | | enableColumnResizing | boolean | true | | loading | boolean | false — renders skeleton rows (keeping the layout) and marks the table busy | | skeletonWhileRefreshing | boolean | false — show skeleton rows while loading even when rows are already displayed | | error | ReactNode | — a string gets the danger Alert; with no rows it replaces the body, with rows it sits above them as a banner | | emptyState | ReactNode | — replaces the default "No results" empty state | | density | 'compact' \| 'comfortable' | 'comfortable' | | stickyHeader | boolean | false — pins the header row to the top of the scroll container (bound it with scrollAreaClassName) | | onRowClick | (row, event) => void | — | | onCellCommit | (commit: DataGridCellCommit) => void | — commit of an inline edit (enableInlineEdit on a column) | | commitOnBlur | boolean | true — an inline edit that loses focus is committed; false drops it instead | | renderExpanded | (row) => ReactNode | — content of the full-width row under an expanded row; setting it adds the chevron column | | getRowCanExpand | (row) => boolean | every row when renderExpanded is set | | selectionActions | DataGridSelectionAction[] | [] — buttons in the bar that appears while rows are selected | | selectAllMatching | boolean | true — offer "Select all N" once every row on the page is selected and more rows match | | showPagination | boolean | true — render the pagination footer | | pageSizeOptions | number[] | [10, 25, 50, 100] | | labels | Partial<DataGridLabels> | English — every string the grid renders (defaultDataGridLabels lists them) | | locale | string | user's locale — BCP 47 tag for the numbers the grid formats | | aria-label | string | 'Data grid' | | tableOptions | Partial<TableOptions> | — anything else TanStack accepts (defaultColumn, meta, …) | | children | ReactNode | — <DataGrid.Toolbar> and anything else to render above the table | | className / tableClassName / scrollAreaClassName | string | root / <table> / scrolling wrapper |

Layout

The root is a CSS container (@container, inline-size containment) and the table scrolls horizontally inside its own wrapper, so the grid fits any column it is placed in and never widens the page; pinned columns and the sticky header work inside that scroller. The parts respond to the grid's width, not the viewport: below 40rem the toolbar search is full width, the column manager button shows only its icon, and the footer is one row (page size, range, previous / next). On touch screens the row controls, head triggers and page buttons reach 44px and the resize handles are visible; desktop keeps the density's geometry. When composing the parts yourself with useDataGrid, put them inside an element with @container for the same behaviour.

DataGridSelectionAction is { id, label, variant?, tone?, onSelect } — variant and tone are the @zuilib/primitives Button axes (ButtonVariant / ButtonTone); onSelect(selectedIds, { allMatching, rowCount }) receives the selected row ids.

DataGrid.Toolbar

| Prop | Type | Default | |------|------|---------| | showSearch | boolean | true — the global search field, debounced into the search state | | searchPlaceholder | string | labels.searchPlaceholder | | searchDebounceMs | number | 300 — ms between the last keystroke and the search change | | showFilterChips | boolean | true — a removable chip per active column filter | | showColumnManager | boolean | true — visibility, order and pinning in one popover | | addableColumns | DataGridAddableField[] | — fields offered under "Add column" | | onAddColumn | (id: string) => void | — the consumer appends the column def | | children | ReactNode | — rendered between the chips and the column manager |

Columns

DataGridColumnDef is a TanStack ColumnDef plus the ZUI extras — on the def itself or on meta:

| Extra | Type | Effect | |-------|------|--------| | align | 'start' \| 'center' \| 'end' | Head and cell alignment; 'end' for numbers | | width | number | Initial width in px; the user resizes from there | | filter | { control: 'text' } \| { control: 'select', options } \| { control: 'date-range' } | Declares the column's filter popover | | enableInlineEdit | boolean | Double-click or Enter turns the cell into an input; see onCellCommit | | meta.label | string | The column's name in the column manager, filter chips and pin labels when header is not a string |

State

DataGridState holds pagination, sorting, columnFilters, search (the free-text search), columnVisibility, columnOrder, columnPinning ({ start, end } — logical directions), rowSelection, expanded, allMatching and optionally columnSizing.

Server adapter — @zuilib/data-grid/server-adapter

Pure functions between DataGridState and your API: toQuery(state) builds the request ({ page, pageSize, sort, filters, search, allMatching }, 1-based page, filters keyed in column-id order) and fromQuery(query) restores the state from one; toQueryKey(state) is a stable string that changes only when the server request would (drop it in a useEffect dependency or a react-query key); toServerState(state) strips the UI-only keys; toColumnState / fromColumnState pack the saved-view keys (visibility, order, pinning) for persistence.

Cells — @zuilib/data-grid/cells

Column helpers for the common enterprise shapes: dateCell / dateTimeCell (locale-formatted presets of createDateCell(options)), textColumn (accessor + header + sorting in one call, on one row field), twoValuesColumn (primary line + muted secondary line, each a { field, render? }).

Headless — @zuilib/data-grid/use-data-grid

useDataGrid(options) is the state and TanStack wiring without the layout: it returns { table, state, setState, replaceState } so the toolbar, selection bar, body and pagination can be composed differently. <DataGrid> is this hook plus the default layout (DataGrid.Toolbar, DataGrid.SelectionBar, DataGrid.Body, DataGrid.Pagination, DataGrid.ColumnManager).

Keyboard

The grid is one tab stop (the row, cell or head last focused). ArrowUp / ArrowDown move between rows, ArrowRight enters the cells and the arrows move between them (ArrowUp from the first row reaches the header); Enter / Space on a cell act on its checkbox or chevron, start an inline edit, or click the row. Shift+ArrowRight / Shift+ArrowLeft open / close an expandable row.

Localisation

labels overrides any string (defaultDataGridLabels lists them); locale formats the numbers.

Bulk selection

Once every row on the page is selected, "Select all N" sets allMatching in the state; toQuery passes it on and selection actions receive {allMatching, rowCount}.

Docs: the "Data grid" section of the ZUI documentation site.