A domain-agnostic<gmad-grid> element. It knows nothing about contacts, recipes, or sport — it renders any rows-and-columns data driven entirely by three external primitives (schema · prefs · data) over a swappable DataProvider. Switch the dataset below: same engine, zero code change.
Try it
Switch dataset — Contacts / Recipe ingredients / Sports table. Same element, different schema+data (and the sports one arrives as SPARQL-results JSON).
Sort — click a header; shift-click a second for multi-sort.
Filter — per-column boxes, or the search bar (all columns).
Select — row checkboxes; shift-click for a range; header box selects all → bulk bar.
Edit — double-click an editable cell (rule-validated where declared).
Columns — Columns in the toolbar opens a schema-driven menu to show/hide, reorder (▲▼), or freeze (❄) any column (the selector column can't be hidden).
Layout — the Auto / Table / Cards toggle forces a layout on any device (Auto follows width); state survives the switch.
Reorder / resize / freeze (table mode) — drag a header to reorder, drag its right edge to resize (double-click = reset), and freeze columns stick to the left during horizontal scroll.
Add / Delete — + Row, or select rows then Delete.
CSV import — Contacts only: Import CSV in the grid toolbar, or the sample button below.
The engine carries no domain knowledge: column aliases, icon glyphs, formatters and rules all live in each schema.*.json. Contacts also proves suppression (gm:deadname is in the data but has no column, so it never renders) and external-id sync (re-importing the sample updates Ben Engel, doesn't duplicate).