@marmooo/table is a dependency-free HTML <table> library.

Sorting, per-column search, pagination, column visibility, resizing, and cell editing β€” each an opt-in plugin or component. Every table on this page is a live, working instance.

Built-in styling assumes Bootstrap is loaded (as it is on this page).

1. Basic table

No plugins, no components β€” just data and columns. A column can supply render(row, td) to control how a cell is drawn.

import { Editable, Resizable, Sortable, Table } from "@marmooo/table";
// (the examples below reuse this same import)

new Table({
  data: tracks,
  columns: [
    { id: "title", name: "Title" },
    { id: "artist", name: "Artist" },
    { id: "duration", name: "Duration", render: (row, td) => {
      td.textContent = formatDuration(row.duration);
    } },
  ],
}).render(document.querySelector("#table-basic"));

2. Sortable

Click a header to sort, click again to reverse. Headers are also keyboard-focusable β€” try Tab then Enter or Space. The current column/direction is exposed via aria-sort and the arrow icon.

const table = new Table({ data: tracks, columns });
    table.options.plugins = [new Sortable(table)];
    table.render(container);

3. Non-sortable column + custom compare

sortable: false keeps a column (like an action button) out of sorting entirely. compare lets a column define its own ascending-order comparator β€” useful for dates, which sort incorrectly as plain strings.

{
  id: "releasedAt",
  name: "Released",
  compare: (a, b) => new Date(a.releasedAt) - new Date(b.releasedAt),
},
{
  id: "actions",
  name: "",
  sortable: false,
  render: (row, td) => { td.appendChild(removeButton(row)); },
}


5. Pagination

Set a pageSize and a container for the page controls. table.pagination.goToPage(n) and getTotalPages() are available for programmatic control.

new Table({
  data: manyTracks,
  columns,
  components: {
    pagination: { pageSize: 5, container: paginationEl, maxPageButtons: 5 },
  },
}).render(container);

6. Column selector

Lets people show/hide columns from a dropdown checklist. Works together with Resizable, which auto-resets widths when visibility changes.

new Table({
  data: tracks,
  columns,
  components: { columnSelector: { container: selectorEl } },
}).render(container);

7. Resizable columns

Drag a column border to resize it.

const table = new Table({ data: tracks, columns });
table.options.plugins = [new Resizable(table)];
table.render(container);

8. Editable cells

Click any body cell to edit it in place. Editable doesn't write the value back into data β€” read it from the DOM (e.g. on blur) if you need to persist it.

const table = new Table({ data: tracks, columns });
table.options.plugins = [new Editable(table)];
table.render(container);

9. Custom sort indicator

components.sortIndicator.render replaces the default arrow. It's called with the current direction and its return value replaces the old indicator β€” return null to hide it for a given state.

components: {
  sortIndicator: {
    render: (direction) => {
      if (direction === "none") return null;
      const span = document.createElement("span");
      span.textContent = direction === "ascending" ? " β–²" : " β–Ό";
      return span;
    },
  },
}

10. setData()

Swap the underlying rows at any time β€” existing filters/sort are re-applied and pagination resets to page 1.

const table = new Table({ data: tracks, columns });
table.render(container);
// later, e.g. after an async fetch:
table.setData(freshRows);

11. Everything together

Sortable + resizable + column search + pagination + column selector, all on one table.

See README.md for the full API reference.