Lightweight Vanilla JS Data Grid for Modern Web Apps – BWDataTable

Lightweight Vanilla JS Data Grid for Modern Web Apps – BWDataTable
Lightweight Vanilla JS Data Grid for Modern Web Apps – BWDataTable
BWDataTable is a vanilla JavaScript library that renders interactive, feature-rich data tables from JSON arrays.

The library comes with a plugin architecture that enables you to extend functionality with official plugins for undo/redo operations, CSV export, clipboard integration, and URL state management.

Key
Features:

  • Zero Dependencies: Built entirely with vanilla JavaScript.
  • Lightweight Footprint: The minified bundle is approximately 32KB.
  • Inline Editing: You can edit cell content via double-click interactions.
  • Multi-Column Sorting: Supports sorting by multiple fields simultaneously.
  • Custom Theming: Highly customizable via CSS variables.
  • Pagination: Renders pagination controls automatically in the footer.
  • Row Selection: Supports single or multi-row selection with checkboxes.
  • Accessibility: Keyboard navigation and ARIA attributes.

See It In Action:

How To Use It:

1. Install BWDataTable with NPM and import it into your project.

# Core
$ npm install @bw-ui/datatable

# Optional Plugins
$ npm install @bw-ui/datatable-history
$ npm install @bw-ui/datatable-export
$ npm install @bw-ui/datatable-clipboard
$ npm install @bw-ui/datatable-url-state
// Core
import { BWDataTable } from '@bw-ui/datatable';
import '@bw-ui/datatable/dist/bw-datatable.min.css';

// Optional Plugins
import { HistoryPlugin } from '@bw-ui/datatable-history';
import { ExportPlugin } from '@bw-ui/datatable-export';
import { ClipboardPlugin } from '@bw-ui/datatable-clipboard';
import { UrlStatePlugin } from '@bw-ui/datatable-url-state';

2. You can also download the distribution files and reference them in your HTML document.

<!-- Link the CSS file in the head section -->
<link rel="stylesheet" href="/dist/bw-datatable.min.css" />

<!-- Load the core library -->
<script src="/dist/bw-datatable.min.js"></script>

<!-- Load optional plugins after core library -->
<script src="/packages/plugins/history/dist/history.min.js"></script>
<script src="/packages/plugins/export/dist/export.min.js"></script>
<script src="/packages/plugins/clipboard/dist/clipboard.min.js"></script>
<script src="/packages/plugins/url-state/dist/url-state.min.js"></script>

3. Create a container element for the data table.

<div id="my-table"></div>

4. Initialize with the BWDataTable constructor.

const table = new BWDataTable('#my-table', {
  data: [
    { id: 1, name: 'John Doe', email: 'john@example.com' },
    { id: 2, name: 'Jane Smith', email: 'jane@example.com' },
    // ...
  ],
});

5. Register plugins with the use method after initializing the table. Plugins extend the table instance with additional methods.

const table = new BWDataTable('#my-table', {
  data: [
    { id: 1, name: 'John Doe', email: 'john@example.com' },
    { id: 2, name: 'Jane Smith', email: 'jane@example.com' },
    // ...
  ],
})
  .use(HistoryPlugin) // Adds undo() and redo() methods
  .use(ExportPlugin) // Adds exportCSV() and exportJSON() methods
  .use(ClipboardPlugin) // Adds copyToClipboard() method
  .use(UrlStatePlugin); // Syncs state with URL parameters

// Use plugin methods
table.undo(); 
table.exportCSV(); 
table.copyToClipboard();

6. All available configuration options to customize the data table.

  • data (Array): Array of row objects. Each object represents one table row. The keys become column IDs unless you provide explicit column definitions.
  • columns (Array|null): Column definition array. Set to null for automatic column detection from data keys. Each column object can specify id, header, field, type, width, sortable, editable, hidden, and render properties.
  • rowId (String): The field name to use as the unique row identifier. This defaults to ‘id’ but can be any unique field in your data objects.
  • editable (Boolean): Master switch for inline editing. Set to true to enable editing functionality. Defaults to false.
  • editableColumns (Array): Array of column IDs that allow editing. Only columns in this array can be edited when editable is true. Empty array means no columns are editable.
  • selectable (Boolean): Enables row selection with checkboxes. Set to true to display selection controls. Defaults to false.
  • selectionMode (String): Controls selection behavior. Options are ‘single’ for one row at a time, ‘multi’ for multiple rows, or ‘none’ to disable selection. Defaults to ‘multi’.
  • sortable (Boolean): Enables column sorting. When true, clicking column headers toggles sort direction. Defaults to true.
  • paginated (Boolean): Enables pagination controls in the footer. Set to false to display all rows without pagination. Defaults to true.
  • pageSize (Number): Number of rows to display per page. This setting only applies when paginated is true. Defaults to 20.
  • searchable (Boolean): Displays a global search input in the header. The search filters across all visible columns. Defaults to true.
  • showHeader (Boolean): Controls header row visibility. Set to false to hide column headers. Defaults to true.
  • showFooter (Boolean): Controls footer visibility where pagination controls render. Set to false to hide the footer. Defaults to true.
  • loadingText (String): Text to display in the loading overlay. This appears when setLoading(true) is called. Defaults to ‘Loading…’.
  • emptyText (String): Text to display when the data array is empty or all rows are filtered out. Defaults to ‘No data’.
  • onEditEnd (Function|null): Callback function that fires when cell editing completes. Receives parameters: rowId, columnId, value, oldValue. Use this to save changes to your backend.
  • onSelect (Function|null): Callback function that fires when row selection changes. Receives an array of selected row IDs as the only parameter.
  • onSort (Function|null): Callback function that fires after sorting completes. Receives two parameters: column (the column ID) and direction (‘asc’ or ‘desc’).
  • onFilter (Function|null): Callback function that fires when filters are applied or cleared. Receives a filters object containing active filter criteria.
  • onPageChange (Function|null): Callback function that fires when the user navigates to a different page. Receives the new page number as a parameter (zero-indexed).
const table = new BWDataTable('#my-table', {
  
  // Data
  data: [],
  dataPath: null, // Path to array in data object (e.g., 'results.items')
  totalPath: null, // Path to total count (e.g., 'results.total')
  rowId: 'id', // Field to use as row ID, or function

  // Columns
  columns: null, // null = auto-detect from data

  // Features (can disable)
  sortable: true,
  filterable: true,
  selectable: true,
  editable: false,
  paginated: true,

  // Pagination
  page: 0,
  pageSize: 20,
  pageSizes: [10, 20, 50, 100],

  // Selection
  selectionMode: 'multi', // 'single' | 'multi' | 'none'

  // Styling
  theme: 'light', // 'light' | 'dark' | 'auto'
  striped: true,
  bordered: true,
  hoverable: true,

  // Classes
  containerClass: '',
  tableClass: '',

  // Editing
  editOnClick: false, // Single click to edit (default: double-click)
  editableColumns: null, // Array of column IDs, null = all (if editable: true)

  // Column Resize
  resizable: true, // Enable column resize
  minColumnWidth: 50, // Minimum column width (px)
  maxColumnWidth: null, // Maximum column width (px), null = no limit

  // Keyboard
  keyboardNavigation: true, // Enable keyboard navigation

  // States
  loading: false, // Show loading overlay
  loadingText: 'Loading...', // Loading message
  emptyText: 'No data available', // Empty state message
  errorText: null, // Error message (null = no error)

  // Callbacks
  onReady: null,
  onSort: null,
  onFilter: null,
  onSelect: null,
  onEdit: null,
  onPageChange: null,
  onError: null,
  onEditStart: null, 
  onEditEnd: null,
  onEditCancel: null, 

})

7. API methods.

  • table.getData(): Returns the current dataset (respecting filters and sort).
  • table.getOriginalData(): Returns the initial dataset.
  • table.setData(newData): Replaces the entire dataset with a new array.
  • table.addRow(row): Appends a single row object to the table.
  • table.removeRow(rowId): Deletes a row based on its unique ID.
  • table.updateRow(rowId, data): Updates specific fields of a row.
  • table.getSelected(): Returns an array of selected row objects.
  • table.getSelectedIds(): Returns an array of selected row IDs.
  • table.selectAll(): Selects all currently visible rows.
  • table.clearSelection(): Deselects all rows.
  • table.sort(column, direction): Sorts the table by a specific column (‘asc’ or ‘desc’).
  • table.filter(column, value): Applies a filter to a specific column or ‘global’.
  • table.clearFilters(): Removes all active filters.
  • table.goToPage(pageIndex): Navigates to a specific page (0-indexed).
  • table.setPageSize(size): Changes the number of rows displayed per page.
  • table.startEdit(rowId, colId): Programmatically triggers edit mode for a cell.
  • table.setCellValue(rowId, colId, value): Updates a cell’s value directly.
  • table.getVisibleColumns(): Returns the list of currently visible columns.
  • table.hideColumn(colId): Hides a specific column.
  • table.showColumn(colId): Shows a previously hidden column.
  • table.render(): Forces a full re-render of the table DOM.
  • table.setLoading(state): Toggles the loading overlay.
  • table.destroy(): Removes the table from the DOM and cleans up listeners.

8. BWDataTable uses CSS custom properties for all visual styling. You can override these variables in your own stylesheet to match your design system.

:root {
  /* Colors - Light Theme */
  --bw-dt-bg: #ffffff;
  --bw-dt-bg-secondary: #f9fafb;
  --bw-dt-border: #e5e7eb;
  --bw-dt-text: #111827;
  --bw-dt-text-secondary: #6b7280;
  --bw-dt-primary: #3b82f6;
  --bw-dt-primary-hover: #2563eb;

  /* Header */
  --bw-dt-header-bg: #f9fafb;
  --bw-dt-header-text: #374151;
  --bw-dt-header-border: #e5e7eb;

  /* Rows */
  --bw-dt-row-bg: #ffffff;
  --bw-dt-row-hover: #f3f4f6;
  --bw-dt-row-selected: #eff6ff;
  --bw-dt-row-stripe: #f9fafb;

  /* Cells */
  --bw-dt-cell-padding: 12px 16px;
  --bw-dt-cell-border: #e5e7eb;

  /* Sorting */
  --bw-dt-sort-icon: #9ca3af;
  --bw-dt-sort-active: #3b82f6;

  /* Pagination */
  --bw-dt-pagination-bg: #ffffff;
  --bw-dt-pagination-border: #e5e7eb;
  --bw-dt-pagination-btn-bg: #ffffff;
  --bw-dt-pagination-btn-hover: #f3f4f6;
  --bw-dt-pagination-btn-disabled: #e5e7eb;

  /* Overlay */
  --bw-dt-overlay-bg: rgba(255, 255, 255, 0.8);

  /* Spacing */
  --bw-dt-border-radius: 8px;
  --bw-dt-spacing-sm: 8px;
  --bw-dt-spacing-md: 16px;
  --bw-dt-spacing-lg: 24px;

  /* Typography */
  --bw-dt-font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
    'Helvetica Neue', Arial, sans-serif;
  --bw-dt-font-size: 14px;
  --bw-dt-font-size-sm: 12px;
  --bw-dt-line-height: 1.5;

  /* Transitions */
  --bw-dt-transition: 150ms ease;
}

/* Dark Theme */
.bw-datatable--dark {
  --bw-dt-bg: #111827;
  --bw-dt-bg-secondary: #1f2937;
  --bw-dt-border: #374151;
  --bw-dt-text: #f9fafb;
  --bw-dt-text-secondary: #9ca3af;
  --bw-dt-primary: #60a5fa;
  --bw-dt-primary-hover: #3b82f6;

  /* Header */
  --bw-dt-header-bg: #1f2937;
  --bw-dt-header-text: #e5e7eb;
  --bw-dt-header-border: #374151;

  /* Rows */
  --bw-dt-row-bg: #111827;
  --bw-dt-row-hover: #1f2937;
  --bw-dt-row-selected: #1e3a5f;
  --bw-dt-row-stripe: #1f2937;

  /* Cells */
  --bw-dt-cell-border: #374151;

  /* Sorting */
  --bw-dt-sort-icon: #6b7280;
  --bw-dt-sort-active: #60a5fa;

  /* Pagination */
  --bw-dt-pagination-bg: #1f2937;
  --bw-dt-pagination-border: #374151;
  --bw-dt-pagination-btn-bg: #1f2937;
  --bw-dt-pagination-btn-hover: #374151;
  --bw-dt-pagination-btn-disabled: #374151;

  /* Overlay */
  --bw-dt-overlay-bg: rgba(17, 24, 39, 0.8);
}

/* Auto Theme (follows system preference) */
@media (prefers-color-scheme: dark) {
  .bw-datatable--auto {
    --bw-dt-bg: #111827;
    --bw-dt-bg-secondary: #1f2937;
    --bw-dt-border: #374151;
    --bw-dt-text: #f9fafb;
    --bw-dt-text-secondary: #9ca3af;
    --bw-dt-primary: #60a5fa;
    --bw-dt-primary-hover: #3b82f6;
    --bw-dt-header-bg: #1f2937;
    --bw-dt-header-text: #e5e7eb;
    --bw-dt-header-border: #374151;
    --bw-dt-row-bg: #111827;
    --bw-dt-row-hover: #1f2937;
    --bw-dt-row-selected: #1e3a5f;
    --bw-dt-row-stripe: #1f2937;
    --bw-dt-cell-border: #374151;
    --bw-dt-sort-icon: #6b7280;
    --bw-dt-sort-active: #60a5fa;
    --bw-dt-pagination-bg: #1f2937;
    --bw-dt-pagination-border: #374151;
    --bw-dt-pagination-btn-bg: #1f2937;
    --bw-dt-pagination-btn-hover: #374151;
    --bw-dt-pagination-btn-disabled: #374151;
    --bw-dt-overlay-bg: rgba(17, 24, 39, 0.8);
  }
}

Alternatives

FAQs

Q: How do I handle validation during inline editing?
A: The onEditEnd callback receives both the new value and old value parameters. You can perform validation logic inside this callback and call setCellValue() with the old value to revert invalid changes.

Q: Can I load data asynchronously after initialization?
A: Yes. Initialize the table with an empty data array, then call setData() once your async request completes. You can show the loading overlay with setLoading(true) before the request and setLoading(false) after calling setData().

Q: How do I integrate with a backend API for server-side pagination?
A: Set paginated to false to disable client-side pagination. Use the onFilter and onSort callbacks to detect when users change filters or sorting. In these callbacks, make API requests with the current filter and sort parameters. Call setData() with the results and implement your own pagination controls outside the table.

Q: How do I style specific rows or cells conditionally?
A: Use the render function in column definitions to add custom classes or inline styles based on the cell value or row data. For row-level styling, add a class to all cells in that row by checking a condition against the row object parameter. You can also add data attributes and target them with CSS selectors.

The post Lightweight Vanilla JS Data Grid for Modern Web Apps – BWDataTable appeared first on CSS Script.


Discover more from RSS Feeds Cloud

Subscribe to get the latest posts sent to your email.

Discover more from RSS Feeds Cloud

Subscribe now to keep reading and get access to the full archive.

Continue reading