Documentation menu

ResponsiveDataExplorer

A complete local-data workflow that stays a semantic table on desktop, becomes usable cards on mobile, and makes filtering, recovery, selection, and navigation state explicit.

Loading preview…

Installation

The CLI installs editable source, AsyncState, and the compatible TanStack Table dependency. Elsecase itself is not a runtime package.

pnpm dlx shadcn@latest add https://elsecase.vercel.app/r/data-explorer.json

Basic usage

import {
  ResponsiveDataExplorer,
  createDataExplorerColumnHelper,
} from "@/components/data-explorer"

const helper = createDataExplorerColumnHelper<User>()
const columns = helper.columns([
  helper.accessor("name", { header: "Name" }),
  helper.accessor("role", { header: "Role" }),
])

<ResponsiveDataExplorer
  data={users}
  columns={columns}
  getRowId={(user) => user.id}
  search={{ getSearchText: (user) => user.name }}
  mobileCard={(user) => <UserCard user={user} />}
  syncStateToUrl
/>

API reference

PropPurposeDefault
data / columns / getRowIdRows, v9 column definitions, and stable identity.Required
mobileCardAccessible narrow-screen representation of one row.Required
status / error / onRetryAsyncState integration for request conditions.Derived success/empty
searchLabel, placeholder, and searchable-text accessor.None
filtersNamed select filters with options and value accessors.[]
pagination / sorting / selectionOptional controlled state slices.Internally owned
on*ChangeResolved controlled-state callbacks.None
bulkActionsSurface mounted only while at least one row is selected.None
emptyState / noResultsStateDistinct zero-data and zero-match views.Built-in views
syncStateToUrlRestores and writes supported explorer query state.false

States and edge cases

The example covers initial loading, stale-content refreshing, empty source data, no matching results, request failure, offline recovery, long emails, multiple pages, and selected rows. Search and filter changes always return to the first page.

Accessibility behavior

Desktop output uses native table, header, and cell semantics. Sorting is operated by real buttons and exposed through aria-sort. Selection uses named native checkboxes, bulk-action changes are announced, and all controls meet a 44px minimum target. Async conditions inherit AsyncState’s live-region behavior.

Responsive behavior

At the medium breakpoint, the desktop table is removed from layout and the same paginated rows are rendered as consumer-defined cards. The mobile renderer receives the original typed row, so it can prioritize information instead of squeezing columns into a narrow table.

URL state

Search, filters, sorting, page, and page size use readable query keys. Unrelated parameters are preserved, defaults are omitted, and the component listens for browser back and forward navigation. This logic uses the browser History API, so the registry component is not coupled to Next.js.

Testing example

fireEvent.change(screen.getByRole("searchbox"), {
  target: { value: "ada" },
})
expect(screen.getByText("Ada Lovelace")).toBeInTheDocument()
expect(screen.queryByText("Grace Hopper")).not.toBeInTheDocument()

Customization

<ResponsiveDataExplorer
  {...props}
  emptyState={<InviteFirstMember />}
  noResultsState={<SavedSearchEmpty />}
  bulkActions={<MemberBulkActions />}
  mobileCard={(user) => <CompactUserCard user={user} />}
/>

Dependencies

React, AsyncState, and @tanstack/react-table@^9.1.2. The component intentionally targets TanStack Table v9 rather than its deprecated v8 compatibility adapter.

Known limitations

Version 0.1 processes local rows. Controlled sorting and pagination are designed to feed server requests, but fetching and cache ownership stay with the application. URL keys are intentionally human-readable, so use one synchronized explorer per route unless you scope it in a wrapper.

Source

Review the implementation and behavior tests on GitHub:
github.com/heysinghaaa/Elsecase/tree/main/registry/data-explorer