lister

Data lists — list, grid, compact; states and keys for free

Minimal list — attribute config, only the render callback in JS

Name
Email
Role

Reorder & atypical data — map + reorder

Minimal grid — init options

The rules

  • ANY tabular data — any list of records — is a lister with card rows; this is THE house way to show data (real .tables are the rare small-summary exception, tables). Never a hand-rolled fetch+render loop. One element (.lister + the slots) and one call: g.lister(el, { endpoint, render }).
  • Config lives in lister-* ATTRIBUTES on the root (the minimal list above: endpoint, per-page, keys off) or in init options — attributes win; the render callback is the one thing that stays JS.
  • Pages use THE STANDARD SHELL (lister.md "Page markup" — the card below, whole on the data list): the toolbar inside .lister-main (the Filters toggle, icon + word, + the bulk Actions left; the single actions right — no toolbar Select all), the panel as two cards (Filters: title + the lister-reset Reset link + the close button, the search, the filters; Display: order + page size), the list card (.lister-list-card: the header, its select-all checkbox first, + the rows), the count + the pager on one .lister-footer line, the details as .lister-more-inset. The minimal instances here keep only the parts they use.
  • Rows use THE STANDARD ROW (lister.md "The standard row"; whole on the data list): the card is the details toggle (rowClick: 'expand' + renderMore + lister-more-inset), "Details" marked lister-expand rides first in the row's dropdown — or stands as the bare .btn-sm .btn-actions-contrast button when it is the row's only action (the shell card below). No caret at the row end; renderMore lays its fields out as the row g-3 small grid, label .text-body-secondary over value.
  • Views: list (rows — the default; the full page: data list) and grid (cards; the full page: data grid). The view is a PAGE decision, never a user toggle.
  • List rows follow the column standard with FIT columns (lister.md "Fit columns"; the full rules: data list): each cell in .lister-cols declares its share and readable minimum (lister-col-w="40" lister-col-min="9rem"), each one that may hide a lister-col-priority + lister-col="Label" — the lib hides them by the LIST's width (open the filter panel below) and shows them at the top of the open details; the identity (the name) never hides. No w-* / d-none d-*-block classes on the cells. Row buttons live in the fixed-width .lister-actions column, present on every row.
  • The header row .lister-header is the first row of the list card: ONE .lister-cols with plain column labels over the cells, in the rows' order (no width classes — the lib sizes the labels like the rows' FIT cells and hides a label with its column), hidden below the stacking breakpoint (d-none d-sm-flex); with selection: 'multiple' the select-all checkbox (.lister-check) comes first. Column-like rows get one; free-form rows (the checklist above) go without. When the list has an actions column, the header reserves its width AUTOMATICALLY from --lister-actions-w — never author a .lister-actions inside the header (it would reserve twice; without the reservation the last label would right-align over the buttons while its column stops before them).
  • The render callback returns a template literal — escape EVERY interpolation with h.esc.
  • Skeletons, empty, error+retry, paging, selection, URL sync and keyboard come FROM the lib — configure, never rebuild (states).
  • An ATYPICAL payload (rows under a key, side data riding along) is no reason to skip the lister: map: (data) => rows extracts the rows, and lister:loaded carries the raw data for the extras. The lister standardizes the LOOK — it never constrains your data. Side info shown to the user (pending invitations, side totals) has a standardized home: the NOTICE STRIP, [lister-notice] above the rows (tabbed data shows it live).
  • Orderable lists declare reorder ({ handle, group }): the lib drags rows within their group and emits lister:reorder with the new id order — SAVING is the page's job (endpoint, then reload()). Mouse-only; pair with paging: 'none'.
  • Docs: templates/app/docs/libs/lister.md — the attribute/option/event reference.

The standard shell — what every list PAGE uses (lister.md "Page markup")

Name
Email
Role