Skip to content

Configuration Reference

Everything you pass to DataTable.create(config). This is the single source of truth.

Quick Reference

js
import { DataTable } from 'mundane-ui'

const table = DataTable.create({
  // Required
  el: '#my-table',           // where to mount
  mode: 'frontend',          // or 'backend'
  data: [...],               // your rows
  columns: [...],            // column definitions

  // Optional (these are the defaults)
  pageSize: 10,
  pageSizeOptions: [10, 25, 50, 100],
  search: { enabled: true, mode: 'global', debounce: 300 },
  showInfo: true,
  showPageSize: true,
  emptyText: 'No data available',
  loadingText: 'Loading...',
  classes: {},
})

Base Options

Shared by both frontend and backend modes.

OptionTypeDefaultDescription
elstring | HTMLElementrequiredCSS selector or DOM element to mount into
columnsColumn[]requiredColumn definitions (see below)
pageSizenumber10Rows per page
pageSizeOptionsnumber[][10, 25, 50, 100]Options in the page size dropdown
searchSearchConfigsee belowSearch/filter configuration
classesClassOverrides{}CSS class overrides for any element
showInfobooleantrueShow "Showing X–Y of Z" info text
showPageSizebooleantrueShow the page size dropdown
emptyTextstring'No data available'Text shown when there are zero rows
loadingTextstring'Loading...'Text in the loading overlay

Frontend Mode

Client-side sorting, filtering, and pagination. Give it all your data.

js
const table = DataTable.create({
  el: '#my-table',
  mode: 'frontend',
  data: [
    { name: 'Alice', age: 30 },
    { name: 'Bob', age: 25 },
  ],
  columns: [
    { key: 'name', label: 'Name', type: 'string', sortable: true },
    { key: 'age', label: 'Age', type: 'number', sortable: true },
  ],
})
OptionTypeRequiredDescription
mode'frontend'yesEnables client-side data processing
dataRecord<string, any>[]yesThe complete dataset — all rows

Backend Mode

The table doesn't touch your data. It emits query strings, you fetch.

js
const table = DataTable.create({
  el: '#my-table',
  mode: 'backend',
  data: firstPageData,
  totalRows: 5000,
  columns: [
    { key: 'name', label: 'Name', type: 'string', sortable: true },
  ],
  onStateChange: async (queryString, state) => {
    const res = await fetch('/api/users' + queryString)
    const { data, total } = await res.json()
    table.setData(data, total)
  },
})
OptionTypeRequiredDescription
mode'backend'yesEnables server-side data processing
dataRecord<string, any>[]yesCurrent page of data
totalRowsnumberyesTotal count across all pages (for pagination math)
onStateChange(queryString: string, state: DataTableState) => voidyesFires on every user interaction (sort, search, page change)

The queryString looks like: ?page=2&pageSize=10&sortBy=name&sortOrder=asc&search=john

Column filters use bracket notation: ?filter[name]=john&filter[status]=active

SearchConfig

Controls the search bar(s) above and within the table.

js
search: {
  enabled: true,                    // show search UI (default: true)
  mode: 'global',                   // 'global' | 'column' | 'both'
  debounce: 300,                    // ms delay before filtering fires
  placeholder: 'Search...',         // placeholder text
  searchableColumns: ['name'],      // limit which columns are searched
}
OptionTypeDefaultDescription
enabledbooleantrueWhether to show search UI at all
mode'global' | 'column' | 'both''global'Search mode (see Frontend Mode)
debouncenumber300Milliseconds to wait before applying the filter
placeholderstring'Search...'Placeholder text for the global search input
searchableColumnsstring[]all searchable columnsLimit global search to specific column keys

Column Definitions

Every column needs key, label, and type. Everything else is optional.

Common Options (all types)

OptionTypeDefaultDescription
keystringrequiredThe property name in your data objects
labelstringrequiredWhat shows in the column header
type'string' | 'number' | 'date' | 'custom'requiredDetermines sorting, filtering, and rendering behavior
sortablebooleanfalseCan the user sort by this column?
searchablebooleantrue (false for custom)Is this column included in search/filter?
widthstringautoCSS width value (e.g. '200px', '20%')
align'left' | 'center' | 'right''left' ('right' for number)Text alignment for header and cells

String Column

The simplest type. No extra options.

js
{ key: 'name', label: 'Name', type: 'string', sortable: true }

Number Column

js
{ key: 'salary', label: 'Salary', type: 'number', sortable: true, thousandSeparator: true }
OptionTypeDefaultDescription
thousandSeparatorbooleanfalseDisplay with commas (e.g. 10001,000)

Date Column

js
{ key: 'created', label: 'Created', type: 'date', dateFormat: 'YYYY-MM-DD', displayFormat: 'DD/MM/YYYY' }
OptionTypeDefaultDescription
dateFormatstringrequiredHow to parse the raw value. Tokens: YYYY, MM, DD, HH, mm, ss
displayFormatstringraw valueHow to display the parsed date. Same tokens.

Custom Column

Full control. You return the HTML.

js
{
  key: 'actions',
  label: 'Actions',
  type: 'custom',
  render: (row, index) => `<button onclick="edit(${index})">Edit</button>`,
  comparator: (a, b) => a.priority - b.priority,
  filterFn: (value, filter, row) => row.status.includes(filter),
}
OptionTypeDefaultDescription
render(row: Record<string, any>, rowIndex: number) => stringrequiredReturns an HTML string for the cell
comparator(a: any, b: any) => numbernoneCustom sort function (return negative, zero, or positive)
filterFn(cellValue: any, filterValue: string, row: Record<string, any>) => booleannoneCustom filter function

XSS

The render function outputs raw HTML. If you're rendering user-generated content, sanitize it yourself. The table won't escape custom column output — that's your responsibility.

ClassOverrides

Pass CSS classes (including Tailwind utilities) to any element in the table. This is how you style without fighting the library.

js
classes: {
  table: 'w-full',
  headerCell: 'bg-gray-100 font-bold uppercase text-xs',
  row: 'border-b border-gray-200',
  rowEven: 'bg-gray-50',
  cell: 'px-4 py-2',
  pageButton: 'px-3 py-1 rounded',
  pageButtonActive: 'bg-blue-500 text-white',
}

Every key maps to a BEM class in the DOM:

KeyBEM ClassElement
root.mu-datatableOutermost wrapper
toolbar.mu-datatable__toolbarSearch + page size bar
searchInput.mu-datatable__search-inputGlobal search input
pageSizeSelect.mu-datatable__page-size-selectPage size dropdown
table.mu-datatable__tableThe <table> element
header.mu-datatable__header<thead>
headerRow.mu-datatable__header-row<tr> in thead
headerCell.mu-datatable__header-cell<th>
headerCellActiveActive sort column header
body.mu-datatable__body<tbody>
row.mu-datatable__row<tr> in tbody
rowEven.mu-datatable__row--evenEven-indexed rows
rowHoverHovered row
cell.mu-datatable__cell<td>
pagination.mu-datatable__paginationPagination container
pageButton.mu-datatable__page-btnEach page button
pageButtonActive.mu-datatable__page-btn--activeCurrent page button
loading.mu-datatable__loadingLoading overlay
empty.mu-datatable__emptyEmpty state message
columnFilter.mu-datatable__column-filterPer-column filter inputs
info.mu-datatable__info"Showing X–Y of Z" text

MIT Licensed. Built with frustration and love.