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

@umriss-ui/table

v0.13.6

Published

React data table for data-dense applications - sorting, filtering, grouping, aggregates, virtualisation. Declared the way it reads: columns as JSX, typed against their rows.

Downloads

4,788

Readme

@umriss-ui/table

A React data table for data-dense applications, declared the way it reads: columns are JSX elements, typed against the rows they came from. The hook binds the row kind once and hands back the Table and the Column that belong to it, so a column can only name a field the row actually has.

Filtering, sorting, grouping, tree rows, aggregates, paging, selection, row detail, row actions, column widths and virtualisation are in the model, not in the markup — and the model is a pure module with tests of its own.

Install

pnpm add @umriss-ui/table @umriss-ui/core

@umriss-ui/core is a peer dependency (ADR-0016): the table reads its provider, its formats and its wording, and enters it by the public entry only. React 18 or 19 as a peer as well.

The smallest table that runs

import { useTable } from "@umriss-ui/table";

interface Order {
  number: string;
  customer: string;
  quantity: number;
}

const ORDERS: Order[] = [
  { number: "A-2041", customer: "Brandt Metalworks", quantity: 120 },
  { number: "A-2042", customer: "Keller & Sons", quantity: 48 },
  { number: "A-2043", customer: "Northworks", quantity: 1250 },
];

export function Orders() {
  const { Table, Column } = useTable(ORDERS, { rowKey: (o) => o.number });

  return (
    <Table ariaLabel="Orders">
      <Column value="number" label="Order" rowHeader />
      <Column value="customer" label="Customer" />
      <Column value="quantity" label="Quantity" />
    </Table>
  );
}

What the columns do not say is decided by the value: text on the left, a number right-aligned in the provider's notation and sortable. rowHeader makes the order number the name of the row — for a screen reader, and for the sticky column.

A table wider than its place scrolls in its own frame, never the page; in a narrow place - a phone, beside a sidebar - the toolbar and the paging bar wrap instead of running out of it.

Grouped, in one option

const { Table, Column } = useTable(orders, {
  rowKey: (o) => o.number,
  defaultGrouping: ["line", "customer"],
});

<Table ariaLabel="Orders by line and customer">
  <Column value="customer" label="Customer" />
  <Column value="number" label="Order" rowHeader />
  <Column value="line" label="Line" />
  <Column value="quantity" label="Quantity" aggregate="sum" />
</Table>;

Each line gets a group header with its count and its sum under the Quantity column; the customers stand beside their orders as a span (ADR-0029). Without defaultGrouping the user groups from the column menu. aggregate takes sum, avg, min, max, range, count, distinct or a function of one's own, and is the footer over the filtered set as well — it used to be called footer, and the old name still works for this version.

Styles

Nothing to import. dist/table.js loads its own stylesheet, and @umriss-ui/core — which the table imports — loads core's, with the tokens the table's styles read. Both touch nothing but their own elements and lie in the layers umriss.tokens, umriss.base and umriss.components, so an application's CSS wins and every token can be overridden; light and dark follow the application's color-scheme; fonts are the application's (Geist recommended). The details stand in the README of @umriss-ui/core, under Styles. @umriss-ui/table/styles.css stays exported for setups that link stylesheets by hand.

What it can do

  • Columns by composition (ADR-0017): a value from a field or computed, its presentation, defaults per value type, absent values, aggregates, a row header.
  • Grouping (ADR-0029): by a column or by a value that is none, up to three levels, switched on with one option or chosen by the user in the column menu. The outer level is a group header carrying every aggregate under its column; the innermost of several is a span beside its rows. Groups fold into a summary, select as a whole, page and virtualise.
  • Tree rows: childRows turns the rows into the roots of a hierarchy of any number of levels, uneven as the application holds it - an organisation, regions and places, cost centres. Every level sorts on its own, a search shows a match with the rows above it, the footer sums the top level, the export writes the level, and a leaf's detail opens from its own fold.
  • Restricting a column: the values that occur, two bounds, or a filter the application writes itself — plus conditions set from outside and read back.
  • Restricting by the whole row: a row filter asks any question of a row ("overdue" = past its window and not delivered) through a control of the application's own, and counts, resets and travels in the view like any other condition.
  • Rows that hold still (ADR-0042): every line - head, row, group header, placeholder, footer - is one row pitch tall in every browser, a value stays on its line and shows whole in a tip, and a page keeps its height over a short last page, a search, a filter, a grouping and a reload.
  • Around the table: toolbar, search, column menu, export, paging — each usable inside the table toolbar or anywhere else on the page with of, and one size for the whole bar that a control of one's own can match.
  • On a row: a detail row that stays open across a change of filter, row actions that stay quiet until the row is meant, and a bulk action that always receives a list.
  • For monitoring: VerdictColumn reads a measured value against a limit set, and AlarmList shows alarms with a lifecycle — active or resolved, acknowledged or not. The library generates no alarms (ADR-0009).
  • A million rows on a server: in server mode (server: true, rowCount) the rows are one page a server answered; the table reports the request - search, conditions, sort, page, page size - through onRequest once per change and keeps the previous page, dimmed, while the next is on its way.
  • Twenty thousand rows where it has to be: virtualisation, sticky parts, columns pinned to either side and column widths that survive a view being restored.
  • A grid, on request (ADR-0034, ADR-0036): <Table grid> is one tab stop whose Active cell the arrows walk, and a column with edit is edited in place from the first click - with the core field for its value, a validate that keeps a wrong draft open, and onCellEdit reporting what the application applies. editMode="row" edits a whole row as one draft, saved on purpose (onRowSave); onRowAdd and onRowDelete add and delete rows by the table's own buttons. Without grid the table stays a native table.

More

  • The demo: https://romanhaendler.github.io/umriss-ui/table/, or locally pnpm dev:table (port 4175). It is the documentation — one page per feature, each with running examples and their source, and the props tables generated from src/ where a page documents a type, each prop linked to the examples that show it. An API index lists every export, one search finds across all five packages, and the header switches the examples to German.
  • CHANGELOG.md — what changes for a caller.
  • For a coding agent: docs/llms-full.md inside the installed package — the demo as one Markdown file, pinned to the installed version: every page with its examples' source, its props tables and why it is built as it is, and an index of every export with its comment and its declaration. Each page also has a Markdown twin online; for the latest version: https://romanhaendler.github.io/umriss-ui/table/llms.txt.
  • ../../docs/design-language.md — the design language all five packages share.
  • ../core/README.md — the component library underneath.

Licence

MIT — see LICENSE.