Skip to content
bahtuiv0.6.0

<baht-table>

Server-side paginated table with a ServiceNow-style column picker. It never slices or fetches; you feed the current page.

Ledger

Toolbar count, selection, sortable headers, numeric column, tinted pills and a row-actions button. Every control emits; the host fetches and sorts. Search and filters live outside the table (e.g. <baht-filter> above it).

<baht-table row-key="id" total="143" selectable="true" row-actions="true"></baht-table>

<script>
  const t = document.querySelector('baht-table');
  const tone = { food: 'success', transport: 'warning', fun: 'danger', salary: 'info', other: 'neutral' };
  t.renderers = {
  category: (v) => Object.assign(document.createElement('baht-badge'), { tone: tone[v] ?? 'neutral', label: String(v), dot: false }),
  account: (v) => Object.assign(document.createElement('baht-badge'), { tone: 'outline', label: String(v) }),
  amount: (v) => {
    const el = document.createElement('span');
    el.className = v < 0 ? 'baht-text--danger' : 'baht-text--success';
    el.textContent = (v < 0 ? '-' : '') + '฿' + Math.abs(v).toLocaleString();
    return el;
  },
  };
  t.addEventListener('baht-selection-change', (e) => (t.selected = e.detail.selected));
  t.addEventListener('baht-sort-change', (e) => (t.sort = e.detail));
</script>

Basic

Listen to baht-page-change and fetch that page yourself.

<baht-table row-key="id"></baht-table>

<script>
  const el = document.querySelector('baht-table');
  el.columns = {
    "name": "Name",
    "role": "Role",
    "status": "Status"
  };
  el.items = [
    {
      "id": 1,
      "name": "Somchai",
      "role": "Admin",
      "status": "active"
    },
    {
      "id": 2,
      "name": "Malee",
      "role": "Editor",
      "status": "active"
    },
    {
      "id": 3,
      "name": "Prasert",
      "role": "Viewer",
      "status": "invited"
    }
  ];
  el.page = 1;
  el.totalPages = 4;
</script>

Dot-walking into reference columns

Open Columns, expand Requested by, and add Manager, Name. The header reads "Requested by › Manager › Name" and the cell resolves the path. OK emits baht-columns-change; feed detail.visibleColumns back to remember the layout.

<baht-table row-key="number"></baht-table>

<script>
  const el = document.querySelector('baht-table');
  el.columns = {
    "number": "Number",
    "state": "State",
    "requester": {
      "label": "Requested by",
      "fields": {
        "name": "Name",
        "department": "Department",
        "manager": {
          "label": "Manager",
          "fields": {
            "name": "Name",
            "title": "Title"
          }
        }
      }
    }
  };
  el.visibleColumns = [
    "number",
    "requester.name",
    "requester.department",
    "state"
  ];
  el.items = [
    {
      "number": "REQ-1041",
      "state": "approval",
      "requester": {
        "name": "Alice Tanaka",
        "department": "IT Operations",
        "manager": {
          "name": "Nok Srisuk",
          "title": "Head of IT"
        }
      }
    },
    {
      "number": "REQ-1042",
      "state": "open",
      "requester": {
        "name": "Chai Boonmee",
        "department": "Finance",
        "manager": {
          "name": "Prasert Wong",
          "title": "CFO"
        }
      }
    }
  ];
  el.showIndex = false;
</script>

Cursor pagination

No total count: the footer is Prev/Next and baht-cursor-change tells you which way to fetch.

<baht-table cursor-mode="true" has-next="true" row-key="id"></baht-table>

<script>
  const el = document.querySelector('baht-table');
  el.columns = {
    "id": "Id",
    "name": "Name"
  };
  el.items = [
    {
      "id": "c_01",
      "name": "Somchai"
    },
    {
      "id": "c_02",
      "name": "Malee"
    }
  ];
</script>

Loading skeleton

<baht-table loading="true" skeleton-rows="3"></baht-table>

<script>
  const el = document.querySelector('baht-table');
  el.columns = {
    "name": "Name",
    "role": "Role"
  };
</script>

Empty + error states

<baht-table empty-message="No users yet. Invite one."></baht-table>

<script>
  const el = document.querySelector('baht-table');
  el.columns = {
    "name": "Name"
  };
  el.items = [];
</script>
NameTypeDefaultDescription
itemsJSRecord<string, unknown>[][]Rows of the CURRENT page
columnsJSColumnMap{}key → header, or { label, fields } for a reference column the picker can dot-walk into (fields nest). Empty = derive from the first row; nested objects become reference columns.
loadingbooleanfalseSkeleton rows
errorbooleanfalseError state
error-messagestring"Couldn't load data. Try again."Error text
empty-messagestring'No records yet.'Empty text
show-indexbooleantrueRow-number column
index-offsetnumber0Add to row numbers (page offset)
skeleton-rowsnumber10Skeletons while loading
row-keystring''Row property used as id in baht-row-click
clickablebooleantrueRows clickable
renderersJSRecord<string, CellRenderer>{}Custom cell renderers by key or dotted path (JS only)
visibleColumnsJSstring[][]Subset + order of columns to render; dotted keys allowed (requester.department). Empty = all top-level columns
column-pickerbooleantrueBuilt-in "Columns" button opening the Available/Selected picker. Set the JS property to false to hide it
totalnumber | undefinedundefinedTotal matching rows across pages; shows "{n} results" in the toolbar
selectablebooleanfalseCheckbox column + select-all header; controlled by `selected`
selectedJSunknown[][]Selected row keys (row-key values, or row indexes without one)
sortablebooleanfalseEvery header becomes a sort button (or set sortable per column)
sortJSSortState | nullnullCurrent sort { key, direction }; mirrored as aria-sort
row-actionsbooleanfalseTrailing "…" button per row; emits baht-row-actions
pagenumber1Current page
total-pagesnumber1
cursor-modebooleanfalsePrev/Next footer instead of page numbers (cursor-based APIs)
has-nextbooleanfalsecursor-mode only
has-prevbooleanfalsecursor-mode only
totalPagesnumber1Total pages

Column specs accept numeric (right-aligned tabular figures), align (start | center | end) and sortable alongside label and fields.

EventdetailDescription
nameCustomEvent
baht-row-click{ row, index, id? }Row activated
baht-page-change{ page }User wants another page. Fetch it
baht-cursor-change{ direction: 'next'|'prev' }cursor-mode: fetch that direction
baht-columns-change{ visibleColumns: string[], columns: { key, label }[] }Picker confirmed with OK. Persist visibleColumns and feed it back
baht-selection-change{ selected: unknown[], rows }Checkbox toggled or select-all. Feed `selected` back
baht-sort-change{ key, direction: 'asc'|'desc' }Header clicked. Refetch sorted and feed `sort` back
baht-row-actions{ row, index, id? }Row "…" button pressed. Open your menu