A powerful, feature-rich data table with sorting, filtering, pagination, selection, and export capabilities
Get started with Smart Table in under 2 minutes. Simply add the required files and a single data attribute.
<!-- CSS -->
<link rel="stylesheet" href="styles/smart-table.css">
<!-- JavaScript (at end of body) -->
<script src="javascript/smart-table.js"></script>
<table data-rsl-smart-table>
<thead>
<tr>
<th>Name</th>
<th>Email</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<tr>
<td>John Smith</td>
<td>john@example.com</td>
<td>Active</td>
</tr>
<!-- More rows... -->
</tbody>
</table>
With just data-rsl-smart-table, you get a fully functional table with search, export options, column visibility controls, and pagination. Smart Table uses progressive enhancement - add more attributes to unlock more features.
Click headers to sort. Hold Shift for multi-column sorting with visual indicators.
Text search or dropdown filters per column. Global search with match highlighting.
Export to CSV, JSON, or Excel. Export filtered results or all data.
Checkbox selection with shift-click range selection and bulk actions.
Toggle column visibility with a dropdown. Respects responsive breakpoints.
Automatically converts to card layout on mobile devices.
Keep headers visible while scrolling through large datasets.
ARIA labels, keyboard navigation, screen reader announcements.
Smart Table is configured entirely through data attributes. Here's the complete reference:
Apply these to the <table> element:
| Attribute | Description | Default |
|---|---|---|
data-rsl-smart-table |
Initializes Smart Table. Required. | - |
data-sortable |
Enable column sorting (click headers to sort) | true |
data-filterable |
Enable column filters below headers | false |
data-searchable |
Enable global search in toolbar | true |
data-selectable |
Enable row selection with checkboxes | false |
data-paginated |
Enable pagination controls | true |
data-page-size |
Number of rows per page | 10 |
data-page-sizes |
Page size options (comma-separated) | 10,25,50,100 |
data-exportable |
Enable export button in toolbar | true |
data-export-formats |
Available export formats (comma-separated) | csv,json,excel |
data-export-filename |
Base filename for exports | table-export |
data-column-toggle |
Enable column visibility toggle | true |
data-sticky-header |
Keep header fixed while scrolling | false |
data-sticky-offset |
Offset from top when sticky (e.g., "60px") | 0 |
data-responsive |
Enable responsive card layout on mobile | true |
data-responsive-breakpoint |
Breakpoint for card mode (pixels) | 768 |
data-empty-message |
Message when no results found | No results found |
data-loading-message |
Message during loading state | Loading data... |
data-server-mode |
Enable server-side processing mode | false |
Enable powerful dashboard and editing features:
| Attribute | Description | Default |
|---|---|---|
data-row-actions |
Enable per-row action buttons (View/Edit/Delete) | false |
data-row-action-items |
Which actions to show (comma-separated) | view,edit,delete |
data-copyable |
Enable copy to clipboard button | false |
data-inline-editing |
Enable double-click to edit cells | false |
data-editable-columns |
Which columns can be edited (comma-separated, empty = all) | all |
data-summary-row |
Enable summary/totals row at bottom | false |
data-expandable-rows |
Enable row expansion with detail panel | false |
data-expandable-columns |
Which columns to show in expanded view (comma-separated) | all |
data-column-resizing |
Enable drag-to-resize column borders | false |
data-min-column-width |
Minimum column width in pixels when resizing | 100 |
data-column-reordering |
Enable drag-and-drop column reordering | false |
data-frozen-columns |
JSON object specifying columns to freeze left/right | {"left":[],"right":[]} |
data-bulk-actions |
Enable bulk actions bar when rows selected (requires data-selectable) | false |
data-bulk-action-items |
Which bulk actions to show (comma-separated) | delete,export |
data-bulk-status-options |
Status options for bulk status change (comma-separated) | - |
data-saved-views |
Enable save/load view configurations | false |
data-view-storage-key |
localStorage key for saved views (auto-generated if not set) | auto |
data-full-page-mode |
Enable full-page toggle button | false |
data-full-page-padding |
Padding around table in full-page mode (pixels) | 20 |
data-filter-subscribe |
Comma-separated FilterBus keys to subscribe to for external filtering | status,region,search |
data-filter-map |
Map FilterBus keys to column keys (format: busKey:columnKey) | status:statusCol,region:regionCol |
data-filter-publish |
Publish internal filter changes to FilterBus (future) | false |
Apply these to <th> elements:
| Attribute | Description | Values |
|---|---|---|
data-type |
Data type for smart sorting | string, number, date, currency, percent |
data-sortable |
Override sorting for this column | true, false |
data-filter |
Filter type for this column | text, select, none |
data-filter-options |
Options for select filter (comma-separated) | Active,Inactive,Pending |
data-hidden |
Initially hide this column | true, false |
data-priority |
Column priority (lower = show first) | 1, 2, 3... |
data-export |
Include column in exports | true, false |
data-searchable |
Include in global search | true, false |
Apply these to <td> elements:
| Attribute | Description | Example |
|---|---|---|
data-sort-value |
Custom value for sorting (useful for formatted data) | data-sort-value="1699900" |
data-filter-value |
Custom value for filtering | data-filter-value="active" |
data-export-value |
Custom value for export | data-export-value="John Smith" |
For programmatic control, Smart Table provides a comprehensive JavaScript API:
| Method | Description | Returns |
|---|---|---|
RSL.SmartTable.init(element, options) |
Initialize a Smart Table (or call without args to init all) | SmartTable instance |
RSL.SmartTable.create(element, options) |
Create a new instance (alias for init) | SmartTable instance |
RSL.SmartTable.getInstance(element) |
Get existing instance for element | SmartTable instance or null |
RSL.SmartTable.destroy(element) |
Remove Smart Table and restore original table | void |
| Method | Description |
|---|---|
sort(column, direction) |
Sort by column index. Direction: 'asc' or 'desc' |
filter(column, value) |
Filter column by value |
search(query) |
Perform global search |
goToPage(page) |
Navigate to page number |
setPageSize(size) |
Change rows per page |
selectRow(index) |
Select row by index |
selectAll() |
Select all visible rows |
deselectAll() |
Clear all selections |
getSelectedRows() |
Get array of selected row data |
export(format, allData) |
Export data. Format: 'csv', 'json', 'excel' |
showColumn(index) |
Show hidden column |
hideColumn(index) |
Hide visible column |
refresh() |
Re-render the table |
getData() |
Get all table data as array of objects |
setData(data) |
Replace table data programmatically |
getTableData() |
Get complete table state as JSON (headers, rows, config) - ideal for page builder integration |
loadData(json) |
Load table from JSON object (recreates headers and rows from saved state) |
copyToClipboard() |
Copy current table data to clipboard as tab-separated text |
getColumnWidths() |
Get object with current column widths |
setColumnWidths(widths) |
Set column widths from object {colKey: width} |
getColumnOrder() |
Get array of column keys in current order |
setColumnOrder(order) |
Set column order from array of column keys |
pinColumn(colKey, side) |
Pin a column to 'left' or 'right' side |
unpinColumn(colKey) |
Unpin a column from frozen position |
saveView(name) |
Save current view configuration with name |
loadView(name) |
Load a saved view by name |
deleteView(name) |
Delete a saved view |
resetView() |
Reset to default view (clear all customizations) |
getSavedViews() |
Get object of all saved views |
toggleFullPage() |
Toggle full-page mode on/off |
isFullPage() |
Check if currently in full-page mode |
destroy() |
Remove Smart Table and restore original |
Pass an options object to init() or create():
const table = RSL.SmartTable.create(document.querySelector('table'), {
// Core Features
sortable: true,
filterable: true,
searchable: true,
selectable: true,
paginated: true,
pageSize: 25,
pageSizes: [10, 25, 50, 100],
exportable: true,
exportFormats: ['csv', 'json', 'excel'],
exportFilename: 'my-data',
columnToggle: true,
stickyHeader: true,
stickyOffset: '60px',
responsive: true,
responsiveBreakpoint: 768,
emptyMessage: 'No matching records',
loadingMessage: 'Please wait...',
serverMode: false,
// Advanced Features
rowActions: true,
rowActionItems: ['view', 'edit', 'delete'],
copyable: true,
inlineEditing: true,
editableColumns: ['Name', 'Email', 'Status'], // empty array = all columns
summaryRow: true,
summaryColumns: [
{ column: 'Amount', type: 'sum', label: 'Total' },
{ column: 'Quantity', type: 'avg', label: 'Average' },
{ column: 'Status', type: 'count', label: 'Count' }
],
expandableRows: true,
expandableColumns: ['Notes', 'Details', 'History'],
expandTemplate: function(rowData) {
return '<div class="custom-detail">' + JSON.stringify(rowData) + '</div>';
},
// Quick Filters and Conditional Formatting
quickFilters: [
{ label: 'Active', column: 'Status', value: 'Active' },
{ label: 'Pending', column: 'Status', value: 'Pending' },
{ label: 'High Value', column: 'Amount', operator: '>', value: 1000 }
],
conditionalFormatting: [
{ column: 'Status', value: 'Active', className: 'status-active' },
{ column: 'Status', value: 'Inactive', className: 'status-inactive' },
{ column: 'Amount', condition: 'gt', value: 5000, className: 'highlight-high' }
]
});
Smart Table emits custom events you can listen for:
| Event | Description | Detail Properties |
|---|---|---|
smart-table:sort |
Fired when sorting changes | column, direction, sortState |
smart-table:filter |
Fired when filters change | filters, filteredCount |
smart-table:search |
Fired when global search changes | query, matchCount |
smart-table:page |
Fired when page changes | page, pageSize, totalPages |
smart-table:select |
Fired when selection changes | selectedRows, selectedCount |
smart-table:export |
Fired when data is exported | format, rowCount |
smart-table:column-toggle |
Fired when column visibility changes | column, visible |
rsl-smart-table:cell-edit |
Fired when a cell is edited (inline editing) | row, column, oldValue, newValue, rowData |
rsl-smart-table:row-expand |
Fired when a row is expanded | row, rowData |
rsl-smart-table:row-collapse |
Fired when a row is collapsed | row, rowData |
rsl-smart-table:row-action |
Fired when a row action button is clicked | action, row, rowData |
rsl-smart-table:column-resize |
Fired when a column is resized | column, width |
rsl-smart-table:column-reorder |
Fired when columns are reordered | fromIndex, toIndex, columnOrder |
rsl-smart-table:bulk-delete |
Fired when bulk delete is executed | rows, count |
rsl-smart-table:bulk-export |
Fired when bulk export is executed | rows, format |
rsl-smart-table:bulk-status-change |
Fired when bulk status change is triggered | rows, newStatus |
rsl-smart-table:view-saved |
Fired when a view is saved | name, config |
rsl-smart-table:view-loaded |
Fired when a view is loaded | name, config |
rsl-smart-table:view-deleted |
Fired when a view is deleted | name |
rsl-smart-table:view-reset |
Fired when view is reset to default | - |
rsl-smart-table:fullpage-toggle |
Fired when full-page mode is toggled | isFullPage |
rsl-smart-table:filterbus-update |
Fired when table is filtered via FilterBus | state, meta, instance |
const table = document.querySelector('[data-rsl-smart-table]');
table.addEventListener('smart-table:sort', (e) => {
console.log('Sorted by column', e.detail.column, e.detail.direction);
});
table.addEventListener('smart-table:select', (e) => {
console.log('Selected', e.detail.selectedCount, 'rows');
// Access selected row data
console.log(e.detail.selectedRows);
});
table.addEventListener('smart-table:export', (e) => {
console.log('Exported', e.detail.rowCount, 'rows as', e.detail.format);
});
Listen for inline editing, row expansion, and row action events:
// Inline editing - capture cell changes for saving
table.addEventListener('rsl-smart-table:cell-edit', (e) => {
console.log('Cell edited:', e.detail);
// e.detail contains: row, column, oldValue, newValue, rowData
// Save to server
fetch('/api/update', {
method: 'POST',
body: JSON.stringify({
row: e.detail.row,
column: e.detail.column,
value: e.detail.newValue
})
});
});
// Row expansion events
table.addEventListener('rsl-smart-table:row-expand', (e) => {
console.log('Row expanded:', e.detail.row, e.detail.rowData);
});
table.addEventListener('rsl-smart-table:row-collapse', (e) => {
console.log('Row collapsed:', e.detail.row);
});
// Row action events (View/Edit/Delete buttons)
table.addEventListener('rsl-smart-table:row-action', (e) => {
const { action, row, rowData } = e.detail;
switch(action) {
case 'view':
showDetailModal(rowData);
break;
case 'edit':
openEditForm(rowData);
break;
case 'delete':
if (confirm('Delete this row?')) {
deleteRow(row);
}
break;
}
});
Smart Table supports full JSON serialization for page builder integration:
const instance = RSL.SmartTable.getInstance(table);
// Get complete table state as JSON
const tableJSON = instance.getTableData();
console.log(tableJSON);
// Returns:
// {
// headers: ['Name', 'Email', 'Status'],
// rows: [
// ['John Doe', 'john@example.com', 'Active'],
// ['Jane Smith', 'jane@example.com', 'Pending']
// ],
// config: {
// sortable: true,
// filterable: true,
// // ... all current config options
// }
// }
// Save to localStorage or server
localStorage.setItem('savedTable', JSON.stringify(tableJSON));
// Load table from saved JSON
const savedData = JSON.parse(localStorage.getItem('savedTable'));
instance.loadData(savedData);
// Use in page builder to recreate table
function buildTableFromConfig(config) {
const table = document.createElement('table');
table.setAttribute('data-rsl-smart-table', '');
// Create headers
const thead = document.createElement('thead');
const headerRow = document.createElement('tr');
config.headers.forEach(header => {
const th = document.createElement('th');
th.textContent = header;
headerRow.appendChild(th);
});
thead.appendChild(headerRow);
table.appendChild(thead);
// Create body
const tbody = document.createElement('tbody');
config.rows.forEach(row => {
const tr = document.createElement('tr');
row.forEach(cell => {
const td = document.createElement('td');
td.textContent = cell;
tr.appendChild(td);
});
tbody.appendChild(tr);
});
table.appendChild(tbody);
// Initialize with saved config
return RSL.SmartTable.create(table, config.config);
}
For server-side processing, use callback functions:
const instance = RSL.SmartTable.getInstance(table);
// Server-side sorting
instance.onSortChange = async (sortState) => {
const data = await fetch(`/api/data?sort=${sortState.column}&dir=${sortState.direction}`);
instance.setData(await data.json());
};
// Server-side filtering
instance.onFilterChange = async (filters) => {
const params = new URLSearchParams(filters);
const data = await fetch(`/api/data?${params}`);
instance.setData(await data.json());
};
// Server-side pagination
instance.onPageChange = async (page, pageSize) => {
const data = await fetch(`/api/data?page=${page}&limit=${pageSize}`);
instance.setData(await data.json());
};
Smart Table uses BEM-style class naming. Here are the main classes for customization:
| Class | Description |
|---|---|
.rsl-smart-table-wrapper |
Outer container wrapping everything |
.rsl-smart-table-toolbar |
Top toolbar with search and controls |
.rsl-smart-table-search |
Search input wrapper |
.rsl-smart-table-actions |
Toolbar action buttons container |
.rsl-smart-table-container |
Scrollable table container |
.rsl-smart-table |
The table element itself |
.rsl-smart-table-header |
Column header cell (th) |
.rsl-smart-table-sort-icon |
Sort indicator icon |
.rsl-smart-table-filter |
Column filter input |
.rsl-smart-table-row |
Table row (tr) |
.rsl-smart-table-row--selected |
Selected row state |
.rsl-smart-table-checkbox |
Selection checkbox |
.rsl-smart-table-pagination |
Pagination controls container |
.rsl-smart-table-selection-bar |
Selection count indicator |
.rsl-smart-table-empty |
Empty state message row |
.rsl-smart-table-loading |
Loading overlay |
| Class | Description |
|---|---|
.rsl-editing |
Applied to cell currently being edited (inline editing) |
.rsl-inline-edit-input |
The text input field shown during inline editing |
.rsl-summary-row |
The summary/totals row at the bottom of the table |
.rsl-summary-cell |
Individual cell in the summary row |
.rsl-summary-label |
Label text in summary cell (e.g., "Total:") |
.rsl-summary-value |
Calculated value in summary cell |
.rsl-expand-col |
The expand button column header/cell |
.rsl-expand-btn |
The expand/collapse toggle button |
.rsl-expanded |
Applied to expanded row |
.rsl-expanded-row |
The row containing expanded content |
.rsl-expanded-content |
The expanded detail panel container |
.rsl-row-actions |
Container for row action buttons |
.rsl-action-btn |
Individual row action button (View/Edit/Delete) |
.rsl-quick-filters |
Container for quick filter chips |
.rsl-quick-filter |
Individual quick filter chip button |
.rsl-quick-filter.active |
Active state for selected quick filter |
.rsl-column-resize-handle |
Draggable resize handle on column borders |
.rsl-resizing |
Applied to table during column resize |
.rsl-reorderable |
Applied to columns that can be reordered |
.rsl-dragging |
Applied to column being dragged |
.rsl-drag-over |
Applied to column being dragged over |
.rsl-frozen-column |
Applied to frozen/pinned columns |
.rsl-frozen-left |
Applied to left-pinned columns |
.rsl-frozen-right |
Applied to right-pinned columns |
.rsl-bulk-actions-bar |
Bulk actions toolbar when rows selected |
.rsl-bulk-action-btn |
Individual bulk action button |
.rsl-bulk-action-danger |
Danger/delete style for bulk action button |
.rsl-saved-views-container |
Container for saved views buttons |
.rsl-saved-view-item |
Individual saved view item in dropdown |
.rsl-full-page-mode |
Applied to container in full-page mode |
.rsl-full-page-btn |
Full-page toggle button |
Override these variables for theming:
:root {
--rsl-smart-table-bg: #ffffff;
--rsl-smart-table-border: #e2e8f0;
--rsl-smart-table-header-bg: #f8fafc;
--rsl-smart-table-header-text: #1e293b;
--rsl-smart-table-row-hover: #f1f5f9;
--rsl-smart-table-row-selected: #eff6ff;
--rsl-smart-table-primary: #6E7BFF;
--rsl-smart-table-text: #334155;
--rsl-smart-table-text-muted: #64748b;
--rsl-smart-table-radius: 8px;
--rsl-smart-table-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
Smart Table is built with accessibility in mind:
Full keyboard support for all interactive elements. Arrow keys for navigation, Enter/Space for activation.
Proper ARIA roles, labels, and live regions for dynamic content announcements.
Visible focus indicators and logical focus order. Focus trapped in dropdowns.
Uses proper <table>, <thead>, <tbody> structure.
| Attribute | Applied To | Purpose |
|---|---|---|
aria-sort |
Column headers | Indicates current sort direction |
aria-label |
Buttons, inputs | Descriptive labels for screen readers |
aria-expanded |
Dropdown buttons | Indicates dropdown state |
aria-selected |
Selectable rows | Indicates selection state |
aria-live |
Status messages | Announces dynamic updates |
role="status" |
Selection bar | Status announcement region |
For the best accessibility, provide meaningful column headers and use data-type attributes to help screen reader users understand data types.
Smart Table can subscribe to the RSL FilterBus for external, shared filtering across multiple components. This is ideal for dashboard scenarios where a single filter control affects multiple data displays.
Smart Table already has powerful built-in filtering. Use FilterBus when you need:
Connect a Smart Table to the FilterBus using data attributes:
<!-- Filter Component publishes to FilterBus -->
<div data-rsl-filter
data-filter-key="status"
data-filter-options="All,Active,Pending,Inactive">
</div>
<div data-rsl-filter
data-filter-key="region"
data-filter-options="All,North,South,East,West">
</div>
<!-- Smart Table subscribes to FilterBus -->
<table data-rsl-smart-table
data-filter-subscribe="status,region"
data-filter-map="status:Status,region:Region">
<thead>
<tr>
<th>Name</th>
<th>Email</th>
<th>Status</th>
<th>Region</th>
</tr>
</thead>
<tbody>
<tr>
<td>John Smith</td>
<td>john@example.com</td>
<td>Active</td>
<td>North</td>
</tr>
<!-- More rows... -->
</tbody>
</table>
| Attribute | Purpose | Example |
|---|---|---|
data-filter-subscribe |
Comma-separated list of FilterBus keys to listen for | "status,region,search" |
data-filter-map |
Maps FilterBus keys to column names (busKey:columnName) | "status:Status,region:Region" |
data-filter-publish |
Publish internal filter changes back to FilterBus (future feature) | true |
The following FilterBus keys have special behavior:
| Key | Behavior |
|---|---|
search |
Applies to global search (same as toolbar search box) |
globalSearch |
Alias for search |
| Any other key | Mapped to column filter via data-filter-map |
When the table is filtered via FilterBus, it emits an event:
const table = document.querySelector('[data-rsl-smart-table]');
table.addEventListener('rsl-smart-table:filterbus-update', (e) => {
console.log('FilterBus state:', e.detail.state);
console.log('Source:', e.detail.meta.source);
console.log('Table instance:', e.detail.instance);
});
Combine Filter components with Smart Tables, KPI Cards, and Charts all subscribed to the same FilterBus keys for a fully synchronized dashboard experience.
Smart Table can filter rows by date range when connected to a Date Picker via FilterBus. Use data-filterbus-date-field to specify which column contains the date to filter by.
<!-- Date Picker publishes date range -->
<input type="text"
data-rsl-date-picker
data-mode="range"
data-show-presets="true"
data-filterbus-publish="dateFilter">
<!-- Smart Table filters rows by date column -->
<table data-rsl-smart-table
data-filter-subscribe="dateFilter"
data-filterbus-date-field="orderDate">
<thead>
<tr>
<th data-col="orderDate" data-type="date">Order Date</th>
<th data-col="customer">Customer</th>
<th data-col="amount" data-type="currency">Amount</th>
</tr>
</thead>
<tbody>
<!-- Rows will be filtered to show only those within selected date range -->
</tbody>
</table>
| Attribute | Description |
|---|---|
data-filterbus-date-field |
Column key (from data-col) containing dates to filter. Supports ISO (YYYY-MM-DD), US (MM/DD/YYYY), and European (DD/MM/YYYY) date formats. |
The date column can contain dates in various formats - the Smart Table will automatically parse ISO format (2025-12-03), US format (12/03/2025), and European format (03/12/2025). For best results, use ISO format or mark the column with data-type="date".