Advanced table sorting and filtering in Markdown enables dynamic data presentations that transform static content into interactive experiences, allowing users to organize, search, and analyze information efficiently. While standard Markdown tables provide basic data structure, implementing sorting and filtering capabilities through JavaScript integration, CSS enhancements, and component frameworks creates professional data management systems that rival dedicated database interfaces.

Why Implement Interactive Table Features in Markdown?

Interactive table functionality provides essential benefits for data-driven content:

  • Enhanced User Experience: Enable readers to customize data views and find specific information quickly
  • Professional Data Presentation: Create sophisticated interfaces that handle complex datasets effectively
  • Dynamic Content Management: Allow real-time data manipulation without page refreshes or external tools
  • Improved Accessibility: Provide multiple ways to navigate and understand tabular information
  • Scalable Information Architecture: Handle large datasets efficiently with client-side processing

Foundation Table Structure for Interactive Enhancement

Basic Markdown Table with Enhanced Markup

Creating semantic table structures that support JavaScript enhancement:

# Enhanced Table Structure Examples

## Customer Data Management Table

| Customer ID | Name | Email | Registration Date | Status | Total Orders | Revenue |
|-------------|------|-------|------------------|---------|--------------|---------|
| CU001 | Alice Johnson | [email protected] | 2024-01-15 | Active | 12 | $1,250.50 |
| CU002 | Bob Smith | [email protected] | 2024-02-03 | Inactive | 3 | $345.75 |
| CU003 | Carol Davis | [email protected] | 2024-01-28 | Active | 8 | $890.25 |
| CU004 | David Wilson | [email protected] | 2024-03-10 | Active | 15 | $2,150.00 |
| CU005 | Eva Martinez | [email protected] | 2024-02-14 | Active | 6 | $672.30 |
| CU006 | Frank Thompson | [email protected] | 2024-01-05 | Inactive | 2 | $198.60 |

## Product Inventory Table  

| Product Code | Product Name | Category | Stock Quantity | Unit Price | Supplier | Last Restocked |
|--------------|--------------|----------|----------------|------------|----------|----------------|
| PR001 | Wireless Headphones | Electronics | 45 | $79.99 | TechSupplier | 2024-03-15 |
| PR002 | Ergonomic Keyboard | Electronics | 23 | $129.50 | OfficeGear | 2024-03-20 |
| PR003 | Standing Desk | Furniture | 8 | $399.00 | WorkSpace | 2024-03-01 |
| PR004 | Monitor Stand | Accessories | 67 | $34.95 | DeskTools | 2024-03-18 |
| PR005 | USB-C Hub | Electronics | 31 | $49.99 | TechSupplier | 2024-03-22 |
| PR006 | Office Chair | Furniture | 12 | $245.00 | WorkSpace | 2024-02-28 |

## Project Management Dashboard

| Project | Status | Team Lead | Start Date | Due Date | Completion % | Budget | Spent |
|---------|---------|----------|------------|----------|--------------|---------|--------|
| Website Redesign | In Progress | Sarah Chen | 2024-01-10 | 2024-04-15 | 65% | $25,000 | $16,250 |
| Mobile App | Planning | Mike Rodriguez | 2024-03-01 | 2024-08-30 | 15% | $45,000 | $6,750 |
| Database Migration | Completed | Anna Kumar | 2023-11-15 | 2024-02-28 | 100% | $15,000 | $14,800 |
| API Integration | On Hold | Tom Jackson | 2024-02-01 | 2024-05-15 | 25% | $20,000 | $5,000 |
| Security Audit | In Progress | Lisa Wang | 2024-03-10 | 2024-04-30 | 40% | $12,000 | $4,800 |

HTML Table Structure with Enhanced Attributes

Adding semantic markup and data attributes for JavaScript targeting:

<!-- Enhanced HTML table with sorting and filtering support -->
<div class="interactive-table-container" id="customer-data-table">
  <div class="table-controls">
    <div class="search-controls">
      <label for="table-search">Search:</label>
      <input 
        type="text" 
        id="table-search" 
        placeholder="Search customers..." 
        class="table-search-input"
        data-table-target="customer-table"
      >
    </div>
    
    <div class="filter-controls">
      <label for="status-filter">Status:</label>
      <select id="status-filter" class="table-filter" data-column="status">
        <option value="">All Statuses</option>
        <option value="Active">Active</option>
        <option value="Inactive">Inactive</option>
      </select>
      
      <label for="date-filter">Registration Date:</label>
      <input 
        type="date" 
        id="date-from" 
        class="table-date-filter" 
        data-column="registration_date"
        data-filter-type="from"
      >
      <input 
        type="date" 
        id="date-to" 
        class="table-date-filter" 
        data-column="registration_date"
        data-filter-type="to"
      >
    </div>
    
    <div class="display-controls">
      <label for="rows-per-page">Rows per page:</label>
      <select id="rows-per-page" class="pagination-control">
        <option value="10">10</option>
        <option value="25" selected>25</option>
        <option value="50">50</option>
        <option value="100">100</option>
      </select>
    </div>
  </div>

  <table 
    id="customer-table" 
    class="sortable-table filterable-table"
    data-table-type="customer-data"
    aria-label="Customer data management table"
  >
    <thead>
      <tr>
        <th 
          data-column="customer_id" 
          data-type="text" 
          class="sortable-header"
          tabindex="0"
          role="button"
          aria-label="Sort by Customer ID"
        >
          Customer ID
          <span class="sort-indicator" aria-hidden="true"></span>
        </th>
        <th 
          data-column="name" 
          data-type="text" 
          class="sortable-header"
          tabindex="0"
          role="button"
          aria-label="Sort by Name"
        >
          Name
          <span class="sort-indicator" aria-hidden="true"></span>
        </th>
        <th 
          data-column="email" 
          data-type="text" 
          class="sortable-header"
          tabindex="0"
          role="button"
          aria-label="Sort by Email"
        >
          Email
          <span class="sort-indicator" aria-hidden="true"></span>
        </th>
        <th 
          data-column="registration_date" 
          data-type="date" 
          class="sortable-header"
          tabindex="0"
          role="button"
          aria-label="Sort by Registration Date"
        >
          Registration Date
          <span class="sort-indicator" aria-hidden="true"></span>
        </th>
        <th 
          data-column="status" 
          data-type="text" 
          class="sortable-header"
          tabindex="0"
          role="button"
          aria-label="Sort by Status"
        >
          Status
          <span class="sort-indicator" aria-hidden="true"></span>
        </th>
        <th 
          data-column="total_orders" 
          data-type="number" 
          class="sortable-header"
          tabindex="0"
          role="button"
          aria-label="Sort by Total Orders"
        >
          Total Orders
          <span class="sort-indicator" aria-hidden="true"></span>
        </th>
        <th 
          data-column="revenue" 
          data-type="currency" 
          class="sortable-header"
          tabindex="0"
          role="button"
          aria-label="Sort by Revenue"
        >
          Revenue
          <span class="sort-indicator" aria-hidden="true"></span>
        </th>
      </tr>
    </thead>
    <tbody>
      <tr data-row-id="CU001">
        <td data-column="customer_id">CU001</td>
        <td data-column="name">Alice Johnson</td>
        <td data-column="email">[email protected]</td>
        <td data-column="registration_date" data-sort-value="2024-01-15">2024-01-15</td>
        <td data-column="status">Active</td>
        <td data-column="total_orders" data-sort-value="12">12</td>
        <td data-column="revenue" data-sort-value="1250.50">$1,250.50</td>
      </tr>
      <tr data-row-id="CU002">
        <td data-column="customer_id">CU002</td>
        <td data-column="name">Bob Smith</td>
        <td data-column="email">[email protected]</td>
        <td data-column="registration_date" data-sort-value="2024-02-03">2024-02-03</td>
        <td data-column="status">Inactive</td>
        <td data-column="total_orders" data-sort-value="3">3</td>
        <td data-column="revenue" data-sort-value="345.75">$345.75</td>
      </tr>
      <tr data-row-id="CU003">
        <td data-column="customer_id">CU003</td>
        <td data-column="name">Carol Davis</td>
        <td data-column="email">[email protected]</td>
        <td data-column="registration_date" data-sort-value="2024-01-28">2024-01-28</td>
        <td data-column="status">Active</td>
        <td data-column="total_orders" data-sort-value="8">8</td>
        <td data-column="revenue" data-sort-value="890.25">$890.25</td>
      </tr>
      <!-- Additional rows would continue here -->
    </tbody>
  </table>
  
  <div class="table-pagination" id="customer-pagination">
    <div class="pagination-info">
      Showing <span id="pagination-start">1</span> to <span id="pagination-end">25</span> 
      of <span id="pagination-total">100</span> entries
    </div>
    
    <div class="pagination-controls">
      <button 
        id="pagination-prev" 
        class="pagination-btn"
        aria-label="Previous page"
        disabled
      >
        Previous
      </button>
      
      <div class="pagination-numbers" id="pagination-numbers">
        <button class="pagination-number active" data-page="1">1</button>
        <button class="pagination-number" data-page="2">2</button>
        <button class="pagination-number" data-page="3">3</button>
        <span class="pagination-ellipsis">...</span>
        <button class="pagination-number" data-page="4">4</button>
      </div>
      
      <button 
        id="pagination-next" 
        class="pagination-btn"
        aria-label="Next page"
      >
        Next
      </button>
    </div>
  </div>
</div>

JavaScript Implementation for Table Interactivity

Comprehensive Table Sorting System

Professional sorting implementation with multiple data type support:

// advanced-table-sorting.js - Complete table sorting system
class MarkdownTableSorter {
  constructor(tableSelector, options = {}) {
    this.table = document.querySelector(tableSelector);
    this.tbody = this.table.querySelector('tbody');
    this.headers = this.table.querySelectorAll('th.sortable-header');
    this.rows = Array.from(this.tbody.querySelectorAll('tr'));
    
    this.options = {
      multiSort: options.multiSort || false,
      defaultSort: options.defaultSort || null,
      sortIndicators: options.sortIndicators !== false,
      animationDuration: options.animationDuration || 300,
      ...options
    };
    
    this.sortState = new Map();
    this.currentSort = [];
    
    this.init();
  }
  
  init() {
    this.bindHeaderEvents();
    this.setupSortIndicators();
    
    if (this.options.defaultSort) {
      this.applySorting(this.options.defaultSort);
    }
    
    // Keyboard accessibility
    this.headers.forEach(header => {
      header.addEventListener('keydown', (e) => {
        if (e.key === 'Enter' || e.key === ' ') {
          e.preventDefault();
          this.handleHeaderClick(e);
        }
      });
    });
  }
  
  bindHeaderEvents() {
    this.headers.forEach(header => {
      header.addEventListener('click', (e) => this.handleHeaderClick(e));
      header.style.cursor = 'pointer';
    });
  }
  
  handleHeaderClick(e) {
    const header = e.currentTarget;
    const column = header.dataset.column;
    const dataType = header.dataset.type || 'text';
    
    let sortDirection = 'asc';
    
    if (!this.options.multiSort || !e.ctrlKey && !e.metaKey) {
      // Single column sort
      if (this.sortState.get(column) === 'asc') {
        sortDirection = 'desc';
      }
      this.sortState.clear();
      this.currentSort = [];
    } else {
      // Multi-column sort
      const currentDirection = this.sortState.get(column);
      if (currentDirection === 'asc') {
        sortDirection = 'desc';
      } else if (currentDirection === 'desc') {
        this.sortState.delete(column);
        this.currentSort = this.currentSort.filter(sort => sort.column !== column);
        this.applySorting();
        this.updateSortIndicators();
        return;
      }
    }
    
    this.sortState.set(column, sortDirection);
    
    // Update current sort array for multi-sort
    const existingIndex = this.currentSort.findIndex(sort => sort.column === column);
    const sortConfig = { column, direction: sortDirection, type: dataType };
    
    if (existingIndex >= 0) {
      this.currentSort[existingIndex] = sortConfig;
    } else {
      if (!this.options.multiSort) {
        this.currentSort = [sortConfig];
      } else {
        this.currentSort.push(sortConfig);
      }
    }
    
    this.applySorting();
    this.updateSortIndicators();
  }
  
  applySorting(sortConfig = null) {
    const sortConfigs = sortConfig ? [sortConfig] : this.currentSort;
    
    if (sortConfigs.length === 0) {
      this.restoreOriginalOrder();
      return;
    }
    
    const sortedRows = [...this.rows].sort((a, b) => {
      for (const config of sortConfigs) {
        const result = this.compareRows(a, b, config);
        if (result !== 0) return result;
      }
      return 0;
    });
    
    this.animateRowChanges(sortedRows);
  }
  
  compareRows(rowA, rowB, { column, direction, type }) {
    const cellA = rowA.querySelector(`[data-column="${column}"]`);
    const cellB = rowB.querySelector(`[data-column="${column}"]`);
    
    let valueA = this.extractSortValue(cellA, type);
    let valueB = this.extractSortValue(cellB, type);
    
    let comparison = this.compareValues(valueA, valueB, type);
    
    return direction === 'desc' ? -comparison : comparison;
  }
  
  extractSortValue(cell, type) {
    // Check for explicit sort value
    const sortValue = cell.dataset.sortValue;
    if (sortValue !== undefined) {
      return this.convertValue(sortValue, type);
    }
    
    const text = cell.textContent.trim();
    return this.convertValue(text, type);
  }
  
  convertValue(value, type) {
    switch (type) {
      case 'number':
        const numMatch = value.match(/([\d,]+\.?\d*)/);
        return numMatch ? parseFloat(numMatch[1].replace(/,/g, '')) : 0;
        
      case 'currency':
        const currencyMatch = value.match(/[\d,]+\.?\d*/);
        return currencyMatch ? parseFloat(currencyMatch[0].replace(/,/g, '')) : 0;
        
      case 'date':
        return new Date(value);
        
      case 'percentage':
        const percentMatch = value.match(/(\d+\.?\d*)%/);
        return percentMatch ? parseFloat(percentMatch[1]) : 0;
        
      case 'boolean':
        return value.toLowerCase() === 'true' || value.toLowerCase() === 'yes' || value === '1';
        
      case 'text':
      default:
        return value.toLowerCase();
    }
  }
  
  compareValues(a, b, type) {
    if (type === 'date') {
      return a.getTime() - b.getTime();
    }
    
    if (typeof a === 'number' && typeof b === 'number') {
      return a - b;
    }
    
    if (typeof a === 'boolean' && typeof b === 'boolean') {
      return a === b ? 0 : a ? 1 : -1;
    }
    
    return a.localeCompare(b);
  }
  
  animateRowChanges(newOrder) {
    if (this.options.animationDuration === 0) {
      this.replaceRows(newOrder);
      return;
    }
    
    // Add animation classes
    this.rows.forEach(row => {
      row.style.transition = `transform ${this.options.animationDuration}ms ease`;
    });
    
    // Calculate position changes
    const positions = new Map();
    this.rows.forEach((row, index) => {
      positions.set(row, index);
    });
    
    newOrder.forEach((row, newIndex) => {
      const oldIndex = positions.get(row);
      const offset = (newIndex - oldIndex) * row.offsetHeight;
      row.style.transform = `translateY(${offset}px)`;
    });
    
    // Complete animation and reorder DOM
    setTimeout(() => {
      this.replaceRows(newOrder);
      this.rows.forEach(row => {
        row.style.transition = '';
        row.style.transform = '';
      });
    }, this.options.animationDuration);
  }
  
  replaceRows(newOrder) {
    const fragment = document.createDocumentFragment();
    newOrder.forEach(row => fragment.appendChild(row));
    this.tbody.appendChild(fragment);
    this.rows = newOrder;
  }
  
  restoreOriginalOrder() {
    const originalOrder = [...this.rows].sort((a, b) => {
      const aIndex = parseInt(a.dataset.originalIndex || '0');
      const bIndex = parseInt(b.dataset.originalIndex || '0');
      return aIndex - bIndex;
    });
    
    this.replaceRows(originalOrder);
  }
  
  setupSortIndicators() {
    if (!this.options.sortIndicators) return;
    
    this.headers.forEach(header => {
      const indicator = header.querySelector('.sort-indicator');
      if (indicator) {
        indicator.innerHTML = '↕️';
        indicator.setAttribute('aria-label', 'Not sorted');
      }
    });
  }
  
  updateSortIndicators() {
    if (!this.options.sortIndicators) return;
    
    // Reset all indicators
    this.headers.forEach(header => {
      const indicator = header.querySelector('.sort-indicator');
      const column = header.dataset.column;
      
      if (indicator) {
        const sortDirection = this.sortState.get(column);
        const sortIndex = this.currentSort.findIndex(sort => sort.column === column);
        
        if (sortDirection) {
          const arrow = sortDirection === 'asc' ? '' : '';
          const number = this.options.multiSort && this.currentSort.length > 1 
            ? ` ${sortIndex + 1}` : '';
          
          indicator.innerHTML = arrow + number;
          indicator.setAttribute('aria-label', 
            `Sorted ${sortDirection === 'asc' ? 'ascending' : 'descending'}${number ? `, priority ${sortIndex + 1}` : ''}`
          );
          
          header.classList.add('sorted', `sorted-${sortDirection}`);
        } else {
          indicator.innerHTML = '↕️';
          indicator.setAttribute('aria-label', 'Not sorted');
          header.classList.remove('sorted', 'sorted-asc', 'sorted-desc');
        }
      }
    });
  }
  
  // Public API methods
  sortByColumn(column, direction = 'asc', type = 'text') {
    this.sortState.clear();
    this.sortState.set(column, direction);
    this.currentSort = [{ column, direction, type }];
    this.applySorting();
    this.updateSortIndicators();
  }
  
  addSort(column, direction = 'asc', type = 'text') {
    if (!this.options.multiSort) {
      this.sortByColumn(column, direction, type);
      return;
    }
    
    this.sortState.set(column, direction);
    
    const existingIndex = this.currentSort.findIndex(sort => sort.column === column);
    const sortConfig = { column, direction, type };
    
    if (existingIndex >= 0) {
      this.currentSort[existingIndex] = sortConfig;
    } else {
      this.currentSort.push(sortConfig);
    }
    
    this.applySorting();
    this.updateSortIndicators();
  }
  
  clearSort() {
    this.sortState.clear();
    this.currentSort = [];
    this.restoreOriginalOrder();
    this.updateSortIndicators();
  }
  
  getSortState() {
    return {
      sorts: [...this.currentSort],
      sortMap: new Map(this.sortState)
    };
  }
}

// Initialize table sorting
document.addEventListener('DOMContentLoaded', function() {
  // Basic sorting initialization
  const basicTable = new MarkdownTableSorter('#customer-table', {
    defaultSort: { column: 'name', direction: 'asc', type: 'text' },
    animationDuration: 250
  });
  
  // Multi-sort enabled table
  const advancedTable = new MarkdownTableSorter('#product-table', {
    multiSort: true,
    sortIndicators: true,
    animationDuration: 300
  });
  
  // Project management table with custom sorting
  const projectTable = new MarkdownTableSorter('#project-table', {
    defaultSort: { column: 'due_date', direction: 'asc', type: 'date' },
    multiSort: true
  });
});

Advanced Filtering Implementation

Comprehensive filtering system with multiple filter types:

// advanced-table-filtering.js - Complete table filtering system
class MarkdownTableFilter {
  constructor(tableSelector, options = {}) {
    this.table = document.querySelector(tableSelector);
    this.tbody = this.table.querySelector('tbody');
    this.allRows = Array.from(this.tbody.querySelectorAll('tr'));
    this.visibleRows = [...this.allRows];
    
    this.options = {
      searchDelay: options.searchDelay || 300,
      caseSensitive: options.caseSensitive || false,
      searchHighlight: options.searchHighlight !== false,
      liveFilter: options.liveFilter !== false,
      ...options
    };
    
    this.filters = new Map();
    this.searchTerm = '';
    this.searchTimeout = null;
    
    this.init();
  }
  
  init() {
    this.setupSearchFilter();
    this.setupColumnFilters();
    this.setupDateRangeFilters();
    this.bindFilterEvents();
    
    // Store original row indices for restoration
    this.allRows.forEach((row, index) => {
      row.dataset.originalIndex = index;
    });
  }
  
  setupSearchFilter() {
    const searchInput = document.querySelector(`[data-table-target="${this.table.id}"]`);
    if (!searchInput) return;
    
    searchInput.addEventListener('input', (e) => {
      clearTimeout(this.searchTimeout);
      
      if (this.options.liveFilter) {
        this.searchTimeout = setTimeout(() => {
          this.setSearchFilter(e.target.value);
        }, this.options.searchDelay);
      }
    });
    
    searchInput.addEventListener('keydown', (e) => {
      if (e.key === 'Enter') {
        clearTimeout(this.searchTimeout);
        this.setSearchFilter(e.target.value);
      }
    });
  }
  
  setupColumnFilters() {
    const columnFilters = document.querySelectorAll('.table-filter');
    
    columnFilters.forEach(filter => {
      filter.addEventListener('change', (e) => {
        const column = e.target.dataset.column;
        const value = e.target.value;
        
        if (value === '') {
          this.removeFilter(column);
        } else {
          this.setColumnFilter(column, value, 'exact');
        }
      });
    });
  }
  
  setupDateRangeFilters() {
    const dateFilters = document.querySelectorAll('.table-date-filter');
    
    dateFilters.forEach(filter => {
      filter.addEventListener('change', (e) => {
        const column = e.target.dataset.column;
        const filterType = e.target.dataset.filterType;
        const value = e.target.value;
        
        if (value === '') {
          this.removeDateRangeFilter(column, filterType);
        } else {
          this.setDateRangeFilter(column, filterType, value);
        }
      });
    });
  }
  
  bindFilterEvents() {
    // Custom filter events
    this.table.addEventListener('filterChange', (e) => {
      this.applyAllFilters();
    });
    
    // Reset filter button
    const resetButton = document.querySelector(`[data-reset-table="${this.table.id}"]`);
    if (resetButton) {
      resetButton.addEventListener('click', () => {
        this.clearAllFilters();
      });
    }
  }
  
  setSearchFilter(searchTerm) {
    this.searchTerm = searchTerm;
    this.applyAllFilters();
    
    // Dispatch custom event
    this.table.dispatchEvent(new CustomEvent('searchFilter', {
      detail: { searchTerm, resultCount: this.visibleRows.length }
    }));
  }
  
  setColumnFilter(column, value, operator = 'exact') {
    const filterKey = `column_${column}`;
    this.filters.set(filterKey, {
      type: 'column',
      column,
      value,
      operator
    });
    
    this.applyAllFilters();
  }
  
  setDateRangeFilter(column, rangeType, value) {
    const filterKey = `date_${column}_${rangeType}`;
    this.filters.set(filterKey, {
      type: 'dateRange',
      column,
      rangeType,
      value: new Date(value)
    });
    
    this.applyAllFilters();
  }
  
  setNumericRangeFilter(column, min, max) {
    const filterKey = `numeric_${column}`;
    this.filters.set(filterKey, {
      type: 'numericRange',
      column,
      min: min !== undefined ? parseFloat(min) : null,
      max: max !== undefined ? parseFloat(max) : null
    });
    
    this.applyAllFilters();
  }
  
  setCustomFilter(key, filterFunction) {
    this.filters.set(key, {
      type: 'custom',
      filterFunction
    });
    
    this.applyAllFilters();
  }
  
  removeFilter(key) {
    // Handle column filter shorthand
    if (!key.includes('_')) {
      key = `column_${key}`;
    }
    
    this.filters.delete(key);
    this.applyAllFilters();
  }
  
  removeDateRangeFilter(column, rangeType) {
    const filterKey = `date_${column}_${rangeType}`;
    this.filters.delete(filterKey);
    this.applyAllFilters();
  }
  
  clearAllFilters() {
    this.filters.clear();
    this.searchTerm = '';
    
    // Clear UI elements
    const searchInput = document.querySelector(`[data-table-target="${this.table.id}"]`);
    if (searchInput) searchInput.value = '';
    
    document.querySelectorAll('.table-filter').forEach(filter => {
      filter.value = '';
    });
    
    document.querySelectorAll('.table-date-filter').forEach(filter => {
      filter.value = '';
    });
    
    this.applyAllFilters();
  }
  
  applyAllFilters() {
    this.visibleRows = this.allRows.filter(row => {
      // Apply search filter
      if (this.searchTerm && !this.matchesSearch(row, this.searchTerm)) {
        return false;
      }
      
      // Apply all other filters
      for (const filter of this.filters.values()) {
        if (!this.matchesFilter(row, filter)) {
          return false;
        }
      }
      
      return true;
    });
    
    this.updateTableDisplay();
    this.updateFilterStats();
    
    // Highlight search terms if enabled
    if (this.options.searchHighlight && this.searchTerm) {
      this.highlightSearchTerms();
    } else {
      this.removeHighlights();
    }
  }
  
  matchesSearch(row, searchTerm) {
    const searchText = this.options.caseSensitive ? searchTerm : searchTerm.toLowerCase();
    const cells = row.querySelectorAll('td');
    
    return Array.from(cells).some(cell => {
      const cellText = this.options.caseSensitive 
        ? cell.textContent.trim() 
        : cell.textContent.trim().toLowerCase();
      
      return cellText.includes(searchText);
    });
  }
  
  matchesFilter(row, filter) {
    switch (filter.type) {
      case 'column':
        return this.matchesColumnFilter(row, filter);
      case 'dateRange':
        return this.matchesDateRangeFilter(row, filter);
      case 'numericRange':
        return this.matchesNumericRangeFilter(row, filter);
      case 'custom':
        return filter.filterFunction(row);
      default:
        return true;
    }
  }
  
  matchesColumnFilter(row, filter) {
    const cell = row.querySelector(`[data-column="${filter.column}"]`);
    if (!cell) return false;
    
    const cellValue = cell.textContent.trim();
    const filterValue = filter.value;
    
    switch (filter.operator) {
      case 'exact':
        return cellValue === filterValue;
      case 'contains':
        return cellValue.toLowerCase().includes(filterValue.toLowerCase());
      case 'startsWith':
        return cellValue.toLowerCase().startsWith(filterValue.toLowerCase());
      case 'endsWith':
        return cellValue.toLowerCase().endsWith(filterValue.toLowerCase());
      case 'regex':
        try {
          const regex = new RegExp(filterValue, 'i');
          return regex.test(cellValue);
        } catch {
          return false;
        }
      default:
        return true;
    }
  }
  
  matchesDateRangeFilter(row, filter) {
    const cell = row.querySelector(`[data-column="${filter.column}"]`);
    if (!cell) return false;
    
    const cellValue = cell.dataset.sortValue || cell.textContent.trim();
    const cellDate = new Date(cellValue);
    
    if (isNaN(cellDate.getTime())) return false;
    
    if (filter.rangeType === 'from') {
      const fromFilters = Array.from(this.filters.values())
        .filter(f => f.type === 'dateRange' && f.column === filter.column && f.rangeType === 'to');
      
      const toFilter = fromFilters.length > 0 ? fromFilters[0] : null;
      
      if (toFilter) {
        return cellDate >= filter.value && cellDate <= toFilter.value;
      } else {
        return cellDate >= filter.value;
      }
    } else if (filter.rangeType === 'to') {
      const fromFilters = Array.from(this.filters.values())
        .filter(f => f.type === 'dateRange' && f.column === filter.column && f.rangeType === 'from');
      
      const fromFilter = fromFilters.length > 0 ? fromFilters[0] : null;
      
      if (fromFilter) {
        return cellDate >= fromFilter.value && cellDate <= filter.value;
      } else {
        return cellDate <= filter.value;
      }
    }
    
    return true;
  }
  
  matchesNumericRangeFilter(row, filter) {
    const cell = row.querySelector(`[data-column="${filter.column}"]`);
    if (!cell) return false;
    
    const cellValue = cell.dataset.sortValue || cell.textContent.trim();
    const numValue = parseFloat(cellValue.replace(/[$,]/g, ''));
    
    if (isNaN(numValue)) return false;
    
    if (filter.min !== null && numValue < filter.min) return false;
    if (filter.max !== null && numValue > filter.max) return false;
    
    return true;
  }
  
  updateTableDisplay() {
    // Hide all rows first
    this.allRows.forEach(row => {
      row.style.display = 'none';
      row.setAttribute('aria-hidden', 'true');
    });
    
    // Show visible rows
    this.visibleRows.forEach(row => {
      row.style.display = '';
      row.setAttribute('aria-hidden', 'false');
    });
    
    // Update empty state
    this.updateEmptyState();
  }
  
  updateEmptyState() {
    const existingEmptyRow = this.tbody.querySelector('.empty-state-row');
    if (existingEmptyRow) {
      existingEmptyRow.remove();
    }
    
    if (this.visibleRows.length === 0) {
      const colCount = this.table.querySelectorAll('th').length;
      const emptyRow = document.createElement('tr');
      emptyRow.className = 'empty-state-row';
      emptyRow.innerHTML = `
        <td colspan="${colCount}" class="empty-state-message">
          No results found. Try adjusting your search or filter criteria.
        </td>
      `;
      this.tbody.appendChild(emptyRow);
    }
  }
  
  updateFilterStats() {
    const statsElement = document.querySelector(`[data-filter-stats="${this.table.id}"]`);
    if (!statsElement) return;
    
    const total = this.allRows.length;
    const visible = this.visibleRows.length;
    const filtered = total - visible;
    
    statsElement.textContent = filtered > 0 
      ? `Showing ${visible} of ${total} entries (${filtered} filtered out)`
      : `Showing ${visible} entries`;
  }
  
  highlightSearchTerms() {
    if (!this.searchTerm) return;
    
    const searchRegex = new RegExp(`(${this.searchTerm})`, 'gi');
    
    this.visibleRows.forEach(row => {
      const cells = row.querySelectorAll('td');
      cells.forEach(cell => {
        const originalText = cell.textContent;
        const highlightedText = originalText.replace(searchRegex, '<mark class="search-highlight">$1</mark>');
        
        if (highlightedText !== originalText) {
          cell.innerHTML = highlightedText;
          cell.dataset.originalText = originalText;
        }
      });
    });
  }
  
  removeHighlights() {
    this.tbody.querySelectorAll('[data-original-text]').forEach(cell => {
      cell.textContent = cell.dataset.originalText;
      delete cell.dataset.originalText;
    });
  }
  
  // Public API methods
  getVisibleRows() {
    return [...this.visibleRows];
  }
  
  getFilterStats() {
    return {
      total: this.allRows.length,
      visible: this.visibleRows.length,
      filtered: this.allRows.length - this.visibleRows.length,
      activeFilters: this.filters.size,
      hasSearch: this.searchTerm.length > 0
    };
  }
  
  exportFilteredData(format = 'csv') {
    const headers = Array.from(this.table.querySelectorAll('th')).map(th => 
      th.textContent.trim()
    );
    
    const data = this.visibleRows.map(row => {
      return Array.from(row.querySelectorAll('td')).map(td => 
        td.textContent.trim()
      );
    });
    
    if (format === 'csv') {
      return this.exportToCSV(headers, data);
    } else if (format === 'json') {
      return this.exportToJSON(headers, data);
    }
  }
  
  exportToCSV(headers, data) {
    const csvContent = [
      headers.join(','),
      ...data.map(row => 
        row.map(cell => `"${cell.replace(/"/g, '""')}"`).join(',')
      )
    ].join('\n');
    
    return csvContent;
  }
  
  exportToJSON(headers, data) {
    const jsonData = data.map(row => {
      const obj = {};
      headers.forEach((header, index) => {
        obj[header] = row[index];
      });
      return obj;
    });
    
    return JSON.stringify(jsonData, null, 2);
  }
}

// Initialize table filtering
document.addEventListener('DOMContentLoaded', function() {
  // Basic filtering
  const customerFilter = new MarkdownTableFilter('#customer-table', {
    searchDelay: 250,
    searchHighlight: true,
    liveFilter: true
  });
  
  // Advanced filtering with custom filters
  const productFilter = new MarkdownTableFilter('#product-table', {
    caseSensitive: false,
    searchHighlight: true
  });
  
  // Add custom low stock filter
  productFilter.setCustomFilter('low_stock', (row) => {
    const stockCell = row.querySelector('[data-column="stock_quantity"]');
    const stock = parseInt(stockCell.textContent);
    return stock < 10;
  });
});

CSS Styling for Interactive Tables

Professional Table Design with Interactive Elements

Comprehensive styling for sortable and filterable tables:

/* interactive-table-styles.css - Professional table styling */

/* Container and Layout */
.interactive-table-container {
  background: #ffffff;
  border-radius: 8px;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
  margin: 2rem 0;
  overflow: hidden;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
}

/* Table Controls */
.table-controls {
  background: #f8f9fa;
  padding: 1.5rem;
  border-bottom: 1px solid #e9ecef;
  display: flex;
  flex-wrap: wrap;
  gap: 1.5rem;
  align-items: center;
}

.search-controls,
.filter-controls,
.display-controls {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.search-controls {
  flex: 1;
  min-width: 250px;
}

.table-search-input {
  flex: 1;
  padding: 0.5rem 0.75rem;
  border: 1px solid #ced4da;
  border-radius: 4px;
  font-size: 0.9rem;
  transition: border-color 0.2s ease, box-shadow 0.2s ease;
}

.table-search-input:focus {
  outline: none;
  border-color: #80bdff;
  box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
}

.table-filter,
.pagination-control {
  padding: 0.5rem;
  border: 1px solid #ced4da;
  border-radius: 4px;
  background: white;
  font-size: 0.9rem;
}

.table-date-filter {
  padding: 0.5rem;
  border: 1px solid #ced4da;
  border-radius: 4px;
  font-size: 0.9rem;
}

/* Table Styling */
.sortable-table {
  width: 100%;
  border-collapse: collapse;
  background: white;
}

.sortable-table th {
  background: #f8f9fa;
  padding: 1rem 0.75rem;
  text-align: left;
  font-weight: 600;
  color: #495057;
  border-bottom: 1px solid #dee2e6;
  position: relative;
  user-select: none;
}

.sortable-header {
  transition: background-color 0.2s ease;
}

.sortable-header:hover {
  background-color: #e9ecef;
}

.sortable-header:focus {
  outline: 2px solid #80bdff;
  outline-offset: -2px;
}

.sortable-header.sorted {
  background-color: #e3f2fd;
  color: #1565c0;
}

.sortable-header.sorted-asc {
  background-color: #e8f5e8;
}

.sortable-header.sorted-desc {
  background-color: #ffeaa7;
}

.sort-indicator {
  margin-left: 0.5rem;
  font-size: 0.8em;
  opacity: 0.6;
  transition: opacity 0.2s ease;
}

.sortable-header:hover .sort-indicator,
.sortable-header.sorted .sort-indicator {
  opacity: 1;
}

.sortable-table td {
  padding: 0.75rem;
  border-bottom: 1px solid #dee2e6;
  transition: background-color 0.2s ease;
}

.sortable-table tbody tr:hover {
  background-color: #f8f9fa;
}

.sortable-table tbody tr[aria-hidden="true"] {
  display: none;
}

/* Search Highlighting */
.search-highlight {
  background-color: #ffeb3b;
  padding: 0.1em 0.2em;
  border-radius: 2px;
  font-weight: bold;
}

/* Empty State */
.empty-state-row td {
  text-align: center;
  padding: 3rem;
  color: #6c757d;
  font-style: italic;
}

.empty-state-message {
  background-color: #f8f9fa;
  border: 2px dashed #dee2e6;
  border-radius: 4px;
}

/* Pagination */
.table-pagination {
  background: #f8f9fa;
  padding: 1rem 1.5rem;
  border-top: 1px solid #e9ecef;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.pagination-info {
  color: #6c757d;
  font-size: 0.9rem;
}

.pagination-controls {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.pagination-btn {
  padding: 0.5rem 0.75rem;
  border: 1px solid #dee2e6;
  background: white;
  color: #495057;
  border-radius: 4px;
  cursor: pointer;
  font-size: 0.9rem;
  transition: all 0.2s ease;
}

.pagination-btn:hover:not(:disabled) {
  background-color: #e9ecef;
  border-color: #adb5bd;
}

.pagination-btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.pagination-numbers {
  display: flex;
  gap: 0.25rem;
}

.pagination-number {
  padding: 0.5rem 0.75rem;
  border: 1px solid #dee2e6;
  background: white;
  color: #495057;
  border-radius: 4px;
  cursor: pointer;
  font-size: 0.9rem;
  min-width: 40px;
  text-align: center;
  transition: all 0.2s ease;
}

.pagination-number:hover {
  background-color: #e9ecef;
  border-color: #adb5bd;
}

.pagination-number.active {
  background-color: #007bff;
  color: white;
  border-color: #007bff;
}

.pagination-ellipsis {
  padding: 0.5rem 0.25rem;
  color: #6c757d;
  align-self: flex-end;
}

/* Filter Statistics */
.filter-stats {
  padding: 0.75rem 1.5rem;
  background: #e3f2fd;
  border-bottom: 1px solid #bbdefb;
  font-size: 0.9rem;
  color: #1565c0;
}

/* Status Indicators */
.status-active {
  color: #28a745;
  font-weight: 600;
}

.status-inactive {
  color: #dc3545;
  font-weight: 600;
}

.status-pending {
  color: #ffc107;
  font-weight: 600;
}

/* Data Type Specific Styling */
.currency-cell {
  text-align: right;
  font-family: 'SF Mono', monospace;
  font-weight: 500;
}

.number-cell {
  text-align: right;
  font-family: 'SF Mono', monospace;
}

.date-cell {
  font-family: 'SF Mono', monospace;
  font-size: 0.9em;
}

.percentage-cell {
  text-align: right;
  font-weight: 500;
}

/* Loading States */
.table-loading {
  position: relative;
  overflow: hidden;
}

.table-loading::after {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: rgba(255, 255, 255, 0.8);
  display: flex;
  align-items: center;
  justify-content: center;
}

.loading-spinner {
  width: 40px;
  height: 40px;
  border: 4px solid #f3f3f3;
  border-top: 4px solid #007bff;
  border-radius: 50%;
  animation: spin 1s linear infinite;
}

@keyframes spin {
  0% { transform: rotate(0deg); }
  100% { transform: rotate(360deg); }
}

/* Responsive Design */
@media (max-width: 768px) {
  .table-controls {
    flex-direction: column;
    align-items: stretch;
    gap: 1rem;
  }
  
  .search-controls,
  .filter-controls,
  .display-controls {
    flex-direction: column;
    align-items: stretch;
    gap: 0.5rem;
  }
  
  .interactive-table-container {
    margin: 1rem -1rem;
    border-radius: 0;
  }
  
  .sortable-table {
    font-size: 0.9rem;
  }
  
  .sortable-table th,
  .sortable-table td {
    padding: 0.5rem 0.25rem;
  }
  
  .table-pagination {
    flex-direction: column;
    gap: 1rem;
    text-align: center;
  }
  
  .pagination-controls {
    flex-wrap: wrap;
    justify-content: center;
  }
}

/* High Contrast Mode */
@media (prefers-contrast: high) {
  .sortable-table {
    border: 2px solid #000;
  }
  
  .sortable-table th {
    border-bottom: 2px solid #000;
    background: #f0f0f0;
  }
  
  .sortable-table td {
    border-bottom: 1px solid #666;
  }
  
  .search-highlight {
    background-color: #ffff00;
    color: #000;
    border: 1px solid #000;
  }
}

/* Reduced Motion */
@media (prefers-reduced-motion: reduce) {
  .sortable-table td,
  .sortable-header,
  .pagination-btn,
  .pagination-number {
    transition: none;
  }
  
  .loading-spinner {
    animation: none;
  }
}

/* Print Styles */
@media print {
  .table-controls,
  .table-pagination {
    display: none;
  }
  
  .interactive-table-container {
    box-shadow: none;
    border: 1px solid #000;
  }
  
  .sortable-table {
    font-size: 12px;
  }
  
  .sortable-table th {
    background: #f0f0f0 !important;
    -webkit-print-color-adjust: exact;
  }
  
  .sort-indicator {
    display: none;
  }
}

Platform-Specific Integration

GitHub Pages and Jekyll Enhancement

<!-- _includes/interactive-table.html -->
{% assign table_id = include.id | default: "interactive-table" %}
{% assign data_source = include.data | default: page.table_data %}
{% assign sortable = include.sortable | default: true %}
{% assign filterable = include.filterable | default: true %}
{% assign searchable = include.searchable | default: true %}

<div class="interactive-table-container" id="{{ table_id }}-container">
  {% if filterable or searchable %}
  <div class="table-controls">
    {% if searchable %}
    <div class="search-controls">
      <label for="{{ table_id }}-search">Search:</label>
      <input 
        type="text" 
        id="{{ table_id }}-search" 
        placeholder="Search table..." 
        class="table-search-input"
        data-table-target="{{ table_id }}"
      >
    </div>
    {% endif %}
    
    {% if filterable and include.filters %}
    <div class="filter-controls">
      {% for filter in include.filters %}
      <label for="{{ table_id }}-{{ filter.column }}">{{ filter.label }}:</label>
      {% if filter.type == "select" %}
      <select id="{{ table_id }}-{{ filter.column }}" class="table-filter" data-column="{{ filter.column }}">
        <option value="">All {{ filter.label }}</option>
        {% for option in filter.options %}
        <option value="{{ option.value }}">{{ option.label }}</option>
        {% endfor %}
      </select>
      {% elsif filter.type == "date" %}
      <input 
        type="date" 
        id="{{ table_id }}-{{ filter.column }}-from" 
        class="table-date-filter" 
        data-column="{{ filter.column }}"
        data-filter-type="from"
        placeholder="From date"
      >
      <input 
        type="date" 
        id="{{ table_id }}-{{ filter.column }}-to" 
        class="table-date-filter" 
        data-column="{{ filter.column }}"
        data-filter-type="to"
        placeholder="To date"
      >
      {% endif %}
      {% endfor %}
    </div>
    {% endif %}
  </div>
  {% endif %}

  <table 
    id="{{ table_id }}" 
    class="{% if sortable %}sortable-table{% endif %} {% if filterable %}filterable-table{% endif %}"
    data-table-type="{{ include.type | default: 'data' }}"
  >
    <thead>
      <tr>
        {% for column in data_source.columns %}
        <th 
          {% if sortable %}
          data-column="{{ column.key }}" 
          data-type="{{ column.type | default: 'text' }}" 
          class="sortable-header"
          tabindex="0"
          role="button"
          aria-label="Sort by {{ column.label }}"
          {% endif %}
        >
          {{ column.label }}
          {% if sortable %}<span class="sort-indicator" aria-hidden="true"></span>{% endif %}
        </th>
        {% endfor %}
      </tr>
    </thead>
    <tbody>
      {% for row in data_source.rows %}
      <tr data-row-id="{{ row.id | default: forloop.index }}">
        {% for column in data_source.columns %}
        {% assign cell_value = row[column.key] %}
        <td 
          data-column="{{ column.key }}"
          {% if column.type == "number" or column.type == "currency" %}
          data-sort-value="{{ cell_value | remove: '$' | remove: ',' }}"
          class="{{ column.type }}-cell"
          {% elsif column.type == "date" %}
          data-sort-value="{{ cell_value }}"
          class="date-cell"
          {% endif %}
        >
          {% if column.format == "currency" %}
            ${{ cell_value | number_with_precision: precision: 2 }}
          {% elsif column.format == "percentage" %}
            {{ cell_value }}%
          {% elsif column.format == "date" %}
            {{ cell_value | date: "%Y-%m-%d" }}
          {% else %}
            {{ cell_value }}
          {% endif %}
        </td>
        {% endfor %}
      </tr>
      {% endfor %}
    </tbody>
  </table>
</div>

<script>
document.addEventListener('DOMContentLoaded', function() {
  {% if sortable %}
  new MarkdownTableSorter('#{{ table_id }}', {
    {% if include.multi_sort %}multiSort: true,{% endif %}
    {% if include.default_sort %}
    defaultSort: {
      column: '{{ include.default_sort.column }}',
      direction: '{{ include.default_sort.direction | default: "asc" }}',
      type: '{{ include.default_sort.type | default: "text" }}'
    },
    {% endif %}
    animationDuration: {{ include.animation_duration | default: 250 }}
  });
  {% endif %}
  
  {% if filterable %}
  new MarkdownTableFilter('#{{ table_id }}', {
    searchDelay: {{ include.search_delay | default: 300 }},
    searchHighlight: {{ include.search_highlight | default: true }},
    liveFilter: {{ include.live_filter | default: true }}
  });
  {% endif %}
});
</script>

Jekyll Usage Example:

---
title: "Customer Data Management"
table_data:
  columns:
    - key: "customer_id"
      label: "Customer ID"
      type: "text"
    - key: "name"
      label: "Name"
      type: "text"
    - key: "email"
      label: "Email"
      type: "text"
    - key: "registration_date"
      label: "Registration Date"
      type: "date"
    - key: "status"
      label: "Status"
      type: "text"
    - key: "revenue"
      label: "Revenue"
      type: "currency"
      format: "currency"
  rows:
    - id: "CU001"
      customer_id: "CU001"
      name: "Alice Johnson"
      email: "[email protected]"
      registration_date: "2024-01-15"
      status: "Active"
      revenue: 1250.50
    - id: "CU002"
      customer_id: "CU002"
      name: "Bob Smith"
      email: "[email protected]"
      registration_date: "2024-02-03"
      status: "Inactive"
      revenue: 345.75
---
{% include interactive-table.html
   id="customer-management"
   sortable=true
   filterable=true
   searchable=true
   multi_sort=true
   search_highlight=true
   default_sort.column="name"
   default_sort.direction="asc"
   filters=site.data.customer_filters
%}

React Component Integration

Modern React component for Markdown table enhancement:

// InteractiveMarkdownTable.jsx - React component for enhanced tables
import React, { useState, useEffect, useMemo, useCallback } from 'react';
import { ChevronUpIcon, ChevronDownIcon, MagnifyingGlassIcon } from '@heroicons/react/24/outline';

const InteractiveMarkdownTable = ({ 
  data, 
  columns,
  sortable = true,
  filterable = true,
  searchable = true,
  pagination = false,
  pageSize = 10,
  className = "",
  onRowClick,
  onSelectionChange
}) => {
  const [sortConfig, setSortConfig] = useState({ key: null, direction: 'asc' });
  const [searchTerm, setSearchTerm] = useState('');
  const [filters, setFilters] = useState({});
  const [currentPage, setCurrentPage] = useState(1);
  const [selectedRows, setSelectedRows] = useState(new Set());

  // Memoized filtered and sorted data
  const processedData = useMemo(() => {
    let filteredData = [...data];

    // Apply search filter
    if (searchTerm) {
      filteredData = filteredData.filter(row =>
        columns.some(column =>
          String(row[column.key] || '').toLowerCase().includes(searchTerm.toLowerCase())
        )
      );
    }

    // Apply column filters
    Object.entries(filters).forEach(([key, value]) => {
      if (value) {
        filteredData = filteredData.filter(row => {
          const cellValue = String(row[key] || '').toLowerCase();
          const filterValue = String(value).toLowerCase();
          return cellValue.includes(filterValue);
        });
      }
    });

    // Apply sorting
    if (sortConfig.key) {
      filteredData.sort((a, b) => {
        const aValue = a[sortConfig.key];
        const bValue = b[sortConfig.key];
        
        let comparison = 0;
        
        if (typeof aValue === 'number' && typeof bValue === 'number') {
          comparison = aValue - bValue;
        } else if (aValue instanceof Date && bValue instanceof Date) {
          comparison = aValue.getTime() - bValue.getTime();
        } else {
          comparison = String(aValue || '').localeCompare(String(bValue || ''));
        }
        
        return sortConfig.direction === 'desc' ? -comparison : comparison;
      });
    }

    return filteredData;
  }, [data, searchTerm, filters, sortConfig, columns]);

  // Pagination logic
  const paginatedData = useMemo(() => {
    if (!pagination) return processedData;
    
    const startIndex = (currentPage - 1) * pageSize;
    return processedData.slice(startIndex, startIndex + pageSize);
  }, [processedData, currentPage, pageSize, pagination]);

  const totalPages = Math.ceil(processedData.length / pageSize);

  // Event handlers
  const handleSort = useCallback((key) => {
    if (!sortable) return;
    
    setSortConfig(current => ({
      key,
      direction: current.key === key && current.direction === 'asc' ? 'desc' : 'asc'
    }));
  }, [sortable]);

  const handleFilterChange = useCallback((key, value) => {
    setFilters(current => ({ ...current, [key]: value }));
    setCurrentPage(1);
  }, []);

  const handleSearch = useCallback((term) => {
    setSearchTerm(term);
    setCurrentPage(1);
  }, []);

  const handleRowSelect = useCallback((rowId, isSelected) => {
    const newSelection = new Set(selectedRows);
    if (isSelected) {
      newSelection.add(rowId);
    } else {
      newSelection.delete(rowId);
    }
    setSelectedRows(newSelection);
    onSelectionChange?.(Array.from(newSelection));
  }, [selectedRows, onSelectionChange]);

  const handleSelectAll = useCallback((isSelected) => {
    if (isSelected) {
      const allIds = paginatedData.map(row => row.id);
      setSelectedRows(new Set(allIds));
      onSelectionChange?.(allIds);
    } else {
      setSelectedRows(new Set());
      onSelectionChange?.([]);
    }
  }, [paginatedData, onSelectionChange]);

  // Render sort indicator
  const renderSortIndicator = (columnKey) => {
    if (sortConfig.key !== columnKey) {
      return <span className="sort-indicator neutral"></span>;
    }
    return (
      <span className="sort-indicator active">
        {sortConfig.direction === 'asc' ? 
          <ChevronUpIcon className="w-4 h-4" /> : 
          <ChevronDownIcon className="w-4 h-4" />
        }
      </span>
    );
  };

  // Render pagination controls
  const renderPagination = () => {
    if (!pagination || totalPages <= 1) return null;

    const pageNumbers = [];
    const maxVisiblePages = 5;
    let startPage = Math.max(1, currentPage - Math.floor(maxVisiblePages / 2));
    let endPage = Math.min(totalPages, startPage + maxVisiblePages - 1);

    if (endPage - startPage + 1 < maxVisiblePages) {
      startPage = Math.max(1, endPage - maxVisiblePages + 1);
    }

    for (let i = startPage; i <= endPage; i++) {
      pageNumbers.push(i);
    }

    return (
      <div className="pagination-container">
        <div className="pagination-info">
          Showing {((currentPage - 1) * pageSize) + 1} to{' '}
          {Math.min(currentPage * pageSize, processedData.length)} of{' '}
          {processedData.length} entries
        </div>
        
        <div className="pagination-controls">
          <button
            onClick={() => setCurrentPage(p => Math.max(1, p - 1))}
            disabled={currentPage === 1}
            className="pagination-btn"
          >
            Previous
          </button>
          
          {startPage > 1 && (
            <>
              <button onClick={() => setCurrentPage(1)} className="pagination-number">1</button>
              {startPage > 2 && <span className="pagination-ellipsis">...</span>}
            </>
          )}
          
          {pageNumbers.map(number => (
            <button
              key={number}
              onClick={() => setCurrentPage(number)}
              className={`pagination-number ${number === currentPage ? 'active' : ''}`}
            >
              {number}
            </button>
          ))}
          
          {endPage < totalPages && (
            <>
              {endPage < totalPages - 1 && <span className="pagination-ellipsis">...</span>}
              <button onClick={() => setCurrentPage(totalPages)} className="pagination-number">
                {totalPages}
              </button>
            </>
          )}
          
          <button
            onClick={() => setCurrentPage(p => Math.min(totalPages, p + 1))}
            disabled={currentPage === totalPages}
            className="pagination-btn"
          >
            Next
          </button>
        </div>
      </div>
    );
  };

  return (
    <div className={`interactive-markdown-table ${className}`}>
      {/* Controls */}
      {(searchable || filterable) && (
        <div className="table-controls">
          {searchable && (
            <div className="search-controls">
              <div className="search-input-wrapper">
                <MagnifyingGlassIcon className="search-icon" />
                <input
                  type="text"
                  placeholder="Search table..."
                  value={searchTerm}
                  onChange={(e) => handleSearch(e.target.value)}
                  className="search-input"
                />
              </div>
            </div>
          )}
          
          {filterable && (
            <div className="filter-controls">
              {columns
                .filter(column => column.filterable)
                .map(column => (
                  <div key={column.key} className="filter-group">
                    <label htmlFor={`filter-${column.key}`}>{column.label}:</label>
                    <select
                      id={`filter-${column.key}`}
                      value={filters[column.key] || ''}
                      onChange={(e) => handleFilterChange(column.key, e.target.value)}
                      className="filter-select"
                    >
                      <option value="">All</option>
                      {column.filterOptions?.map(option => (
                        <option key={option.value} value={option.value}>
                          {option.label}
                        </option>
                      ))}
                    </select>
                  </div>
                ))}
            </div>
          )}
        </div>
      )}

      {/* Table */}
      <div className="table-wrapper">
        <table className="data-table">
          <thead>
            <tr>
              {onSelectionChange && (
                <th className="select-column">
                  <input
                    type="checkbox"
                    checked={selectedRows.size === paginatedData.length && paginatedData.length > 0}
                    onChange={(e) => handleSelectAll(e.target.checked)}
                    aria-label="Select all rows"
                  />
                </th>
              )}
              {columns.map(column => (
                <th
                  key={column.key}
                  className={`${sortable && column.sortable !== false ? 'sortable' : ''} ${column.className || ''}`}
                  onClick={() => column.sortable !== false && handleSort(column.key)}
                  role={sortable && column.sortable !== false ? 'button' : undefined}
                  tabIndex={sortable && column.sortable !== false ? 0 : undefined}
                  onKeyDown={(e) => {
                    if (sortable && column.sortable !== false && (e.key === 'Enter' || e.key === ' ')) {
                      e.preventDefault();
                      handleSort(column.key);
                    }
                  }}
                >
                  <div className="header-content">
                    <span>{column.label}</span>
                    {sortable && column.sortable !== false && renderSortIndicator(column.key)}
                  </div>
                </th>
              ))}
            </tr>
          </thead>
          <tbody>
            {paginatedData.length === 0 ? (
              <tr>
                <td colSpan={columns.length + (onSelectionChange ? 1 : 0)} className="empty-state">
                  No data found. Try adjusting your search or filter criteria.
                </td>
              </tr>
            ) : (
              paginatedData.map(row => (
                <tr
                  key={row.id}
                  className={`${selectedRows.has(row.id) ? 'selected' : ''} ${onRowClick ? 'clickable' : ''}`}
                  onClick={() => onRowClick?.(row)}
                >
                  {onSelectionChange && (
                    <td className="select-column">
                      <input
                        type="checkbox"
                        checked={selectedRows.has(row.id)}
                        onChange={(e) => handleRowSelect(row.id, e.target.checked)}
                        onClick={(e) => e.stopPropagation()}
                        aria-label={`Select row ${row.id}`}
                      />
                    </td>
                  )}
                  {columns.map(column => (
                    <td key={column.key} className={column.className || ''}>
                      {column.render ? column.render(row[column.key], row) : row[column.key]}
                    </td>
                  ))}
                </tr>
              ))
            )}
          </tbody>
        </table>
      </div>

      {/* Pagination */}
      {renderPagination()}
    </div>
  );
};

export default InteractiveMarkdownTable;

// Usage Example Component
const TableDemo = () => {
  const sampleData = [
    {
      id: 'CU001',
      customerID: 'CU001',
      name: 'Alice Johnson',
      email: '[email protected]',
      registrationDate: new Date('2024-01-15'),
      status: 'Active',
      totalOrders: 12,
      revenue: 1250.50
    },
    // ... more data
  ];

  const columns = [
    {
      key: 'customerID',
      label: 'Customer ID',
      sortable: true
    },
    {
      key: 'name',
      label: 'Name',
      sortable: true
    },
    {
      key: 'email',
      label: 'Email',
      sortable: true
    },
    {
      key: 'registrationDate',
      label: 'Registration Date',
      sortable: true,
      render: (value) => value.toLocaleDateString()
    },
    {
      key: 'status',
      label: 'Status',
      sortable: true,
      filterable: true,
      filterOptions: [
        { value: 'Active', label: 'Active' },
        { value: 'Inactive', label: 'Inactive' }
      ],
      render: (value) => (
        <span className={`status status-${value.toLowerCase()}`}>
          {value}
        </span>
      )
    },
    {
      key: 'totalOrders',
      label: 'Total Orders',
      sortable: true,
      className: 'number-column'
    },
    {
      key: 'revenue',
      label: 'Revenue',
      sortable: true,
      className: 'currency-column',
      render: (value) => `$${value.toFixed(2)}`
    }
  ];

  const handleRowClick = (row) => {
    console.log('Row clicked:', row);
  };

  const handleSelectionChange = (selectedIds) => {
    console.log('Selection changed:', selectedIds);
  };

  return (
    <InteractiveMarkdownTable
      data={sampleData}
      columns={columns}
      sortable={true}
      filterable={true}
      searchable={true}
      pagination={true}
      pageSize={10}
      onRowClick={handleRowClick}
      onSelectionChange={handleSelectionChange}
    />
  );
};

Integration with Modern Workflows

Interactive table sorting and filtering integrates seamlessly with comprehensive Markdown documentation systems. When combined with advanced table formatting techniques, dynamic table functionality creates sophisticated data management interfaces that maintain professional presentation standards while enabling powerful user interactions.

For comprehensive content management systems, table interactivity works effectively with custom CSS styling frameworks to create branded data interfaces that scale across different platforms while preserving design consistency and user experience quality throughout complex documentation workflows.

When managing large-scale data presentations, interactive table features complement responsive design techniques to ensure optimal functionality across desktop and mobile devices while maintaining accessibility standards and performance optimization for efficient data processing.

Performance Optimization and Best Practices

Efficient Data Processing

Optimizing table performance for large datasets:

// performance-optimized-table.js - High-performance table implementation
class OptimizedInteractiveTable {
  constructor(selector, options = {}) {
    this.container = document.querySelector(selector);
    this.options = {
      virtualScroll: options.virtualScroll || false,
      itemHeight: options.itemHeight || 50,
      bufferSize: options.bufferSize || 5,
      debounceDelay: options.debounceDelay || 150,
      chunkSize: options.chunkSize || 100,
      ...options
    };
    
    this.data = [];
    this.filteredData = [];
    this.visibleRange = { start: 0, end: 0 };
    this.scrollContainer = null;
    this.viewportHeight = 0;
    
    // Performance tracking
    this.performanceMetrics = {
      filterTime: 0,
      sortTime: 0,
      renderTime: 0
    };
    
    this.init();
  }
  
  init() {
    this.setupVirtualScroll();
    this.setupPerformanceMonitoring();
    this.bindEvents();
  }
  
  setupVirtualScroll() {
    if (!this.options.virtualScroll) return;
    
    this.scrollContainer = document.createElement('div');
    this.scrollContainer.className = 'virtual-scroll-container';
    this.scrollContainer.style.height = `${this.options.viewportHeight || 400}px`;
    this.scrollContainer.style.overflow = 'auto';
    
    this.viewport = document.createElement('div');
    this.viewport.className = 'virtual-viewport';
    this.scrollContainer.appendChild(this.viewport);
    
    this.container.appendChild(this.scrollContainer);
    
    this.scrollContainer.addEventListener('scroll', 
      this.debounce(() => this.handleVirtualScroll(), this.options.debounceDelay)
    );
  }
  
  setupPerformanceMonitoring() {
    // Web Vitals monitoring
    if (typeof PerformanceObserver !== 'undefined') {
      const observer = new PerformanceObserver((list) => {
        for (const entry of list.getEntries()) {
          if (entry.name.includes('table-')) {
            console.log(`Table performance: ${entry.name} took ${entry.duration}ms`);
          }
        }
      });
      
      observer.observe({ entryTypes: ['measure'] });
    }
  }
  
  setData(data) {
    performance.mark('table-data-set-start');
    
    this.data = data;
    this.filteredData = [...data];
    
    if (this.options.virtualScroll) {
      this.updateVirtualScrollHeight();
      this.renderVirtualRows();
    } else {
      this.renderAllRows();
    }
    
    performance.mark('table-data-set-end');
    performance.measure('table-data-set', 'table-data-set-start', 'table-data-set-end');
  }
  
  filterData(filterFunction) {
    performance.mark('table-filter-start');
    
    // Use Web Workers for large datasets
    if (this.data.length > 10000 && typeof Worker !== 'undefined') {
      this.filterDataWithWorker(filterFunction);
    } else {
      this.filteredData = this.data.filter(filterFunction);
      this.updateDisplay();
    }
    
    performance.mark('table-filter-end');
    performance.measure('table-filter', 'table-filter-start', 'table-filter-end');
  }
  
  filterDataWithWorker(filterFunction) {
    const worker = new Worker(URL.createObjectURL(new Blob([`
      self.onmessage = function(e) {
        const { data, filterFunctionString } = e.data;
        const filterFunction = new Function('return ' + filterFunctionString)();
        const filteredData = data.filter(filterFunction);
        self.postMessage(filteredData);
      };
    `], { type: 'application/javascript' })));
    
    worker.postMessage({
      data: this.data,
      filterFunctionString: filterFunction.toString()
    });
    
    worker.onmessage = (e) => {
      this.filteredData = e.data;
      this.updateDisplay();
      worker.terminate();
    };
  }
  
  sortData(compareFn) {
    performance.mark('table-sort-start');
    
    // Use TimSort implementation for better performance
    this.filteredData = this.timsort(this.filteredData, compareFn);
    this.updateDisplay();
    
    performance.mark('table-sort-end');
    performance.measure('table-sort', 'table-sort-start', 'table-sort-end');
  }
  
  // TimSort implementation for efficient sorting
  timsort(arr, compareFn) {
    // Simplified TimSort - in production, use a full implementation
    return arr.sort(compareFn);
  }
  
  handleVirtualScroll() {
    if (!this.options.virtualScroll) return;
    
    const scrollTop = this.scrollContainer.scrollTop;
    const viewportHeight = this.scrollContainer.clientHeight;
    
    const startIndex = Math.floor(scrollTop / this.options.itemHeight);
    const endIndex = Math.min(
      this.filteredData.length - 1,
      Math.floor((scrollTop + viewportHeight) / this.options.itemHeight) + this.options.bufferSize
    );
    
    if (startIndex !== this.visibleRange.start || endIndex !== this.visibleRange.end) {
      this.visibleRange = { start: startIndex, end: endIndex };
      this.renderVirtualRows();
    }
  }
  
  renderVirtualRows() {
    performance.mark('table-render-start');
    
    const fragment = document.createDocumentFragment();
    const visibleData = this.filteredData.slice(this.visibleRange.start, this.visibleRange.end + 1);
    
    // Batch DOM updates
    requestAnimationFrame(() => {
      this.viewport.innerHTML = '';
      
      visibleData.forEach((row, index) => {
        const rowElement = this.createRowElement(row, this.visibleRange.start + index);
        rowElement.style.position = 'absolute';
        rowElement.style.top = `${(this.visibleRange.start + index) * this.options.itemHeight}px`;
        rowElement.style.height = `${this.options.itemHeight}px`;
        
        fragment.appendChild(rowElement);
      });
      
      this.viewport.appendChild(fragment);
      
      performance.mark('table-render-end');
      performance.measure('table-render', 'table-render-start', 'table-render-end');
    });
  }
  
  renderAllRows() {
    performance.mark('table-render-all-start');
    
    const tbody = this.container.querySelector('tbody');
    const fragment = document.createDocumentFragment();
    
    // Process in chunks to avoid blocking the UI
    const processChunk = (startIndex) => {
      const endIndex = Math.min(startIndex + this.options.chunkSize, this.filteredData.length);
      
      for (let i = startIndex; i < endIndex; i++) {
        const row = this.createRowElement(this.filteredData[i], i);
        fragment.appendChild(row);
      }
      
      if (endIndex < this.filteredData.length) {
        // Process next chunk
        setTimeout(() => processChunk(endIndex), 0);
      } else {
        // Finished processing all rows
        tbody.innerHTML = '';
        tbody.appendChild(fragment);
        
        performance.mark('table-render-all-end');
        performance.measure('table-render-all', 'table-render-all-start', 'table-render-all-end');
      }
    };
    
    processChunk(0);
  }
  
  createRowElement(rowData, index) {
    const tr = document.createElement('tr');
    tr.dataset.index = index;
    
    // Create cells based on column configuration
    this.options.columns.forEach(column => {
      const td = document.createElement('td');
      td.textContent = rowData[column.key];
      if (column.className) {
        td.className = column.className;
      }
      tr.appendChild(td);
    });
    
    return tr;
  }
  
  updateVirtualScrollHeight() {
    if (this.options.virtualScroll) {
      const totalHeight = this.filteredData.length * this.options.itemHeight;
      this.viewport.style.height = `${totalHeight}px`;
    }
  }
  
  updateDisplay() {
    if (this.options.virtualScroll) {
      this.updateVirtualScrollHeight();
      this.renderVirtualRows();
    } else {
      this.renderAllRows();
    }
    
    this.updateStats();
  }
  
  updateStats() {
    const statsElement = this.container.querySelector('.table-stats');
    if (statsElement) {
      statsElement.textContent = 
        `Showing ${this.filteredData.length} of ${this.data.length} entries`;
    }
  }
  
  // Utility functions
  debounce(func, wait) {
    let timeout;
    return function executedFunction(...args) {
      const later = () => {
        clearTimeout(timeout);
        func(...args);
      };
      clearTimeout(timeout);
      timeout = setTimeout(later, wait);
    };
  }
  
  throttle(func, limit) {
    let inThrottle;
    return function() {
      const args = arguments;
      const context = this;
      if (!inThrottle) {
        func.apply(context, args);
        inThrottle = true;
        setTimeout(() => inThrottle = false, limit);
      }
    };
  }
  
  // Public API
  getPerformanceMetrics() {
    const measures = performance.getEntriesByType('measure');
    return measures.filter(measure => measure.name.includes('table-'));
  }
  
  clearPerformanceMetrics() {
    performance.clearMeasures();
    performance.clearMarks();
  }
}

// Usage with performance monitoring
const optimizedTable = new OptimizedInteractiveTable('#large-data-table', {
  virtualScroll: true,
  itemHeight: 50,
  bufferSize: 10,
  chunkSize: 50,
  viewportHeight: 400,
  columns: [
    { key: 'id', className: 'id-column' },
    { key: 'name', className: 'name-column' },
    { key: 'email', className: 'email-column' },
    { key: 'status', className: 'status-column' }
  ]
});

// Load large dataset
fetch('/api/large-dataset')
  .then(response => response.json())
  .then(data => {
    optimizedTable.setData(data);
    
    // Monitor performance
    setTimeout(() => {
      const metrics = optimizedTable.getPerformanceMetrics();
      console.table(metrics.map(m => ({ name: m.name, duration: `${m.duration.toFixed(2)}ms` })));
    }, 1000);
  });

Memory Management

Efficient memory usage for interactive tables:

// memory-efficient-table.js - Memory-optimized table implementation
class MemoryEfficientTable {
  constructor(selector, options = {}) {
    this.container = document.querySelector(selector);
    this.options = {
      maxCachedRows: options.maxCachedRows || 1000,
      recycleThreshold: options.recycleThreshold || 0.8,
      cleanupInterval: options.cleanupInterval || 30000, // 30 seconds
      ...options
    };
    
    // Memory management
    this.rowElementCache = new Map();
    this.unusedElements = [];
    this.memoryUsage = { cached: 0, total: 0 };
    
    // Cleanup timer
    this.cleanupTimer = setInterval(() => this.performMemoryCleanup(), this.options.cleanupInterval);
    
    // Observer for memory pressure
    this.setupMemoryPressureObserver();
    
    this.init();
  }
  
  init() {
    // Set up intersection observer for visible rows
    this.intersectionObserver = new IntersectionObserver(
      (entries) => this.handleIntersection(entries),
      { root: this.container, threshold: 0 }
    );
    
    // Monitor memory usage
    this.monitorMemoryUsage();
  }
  
  setupMemoryPressureObserver() {
    if ('memory' in performance) {
      // Monitor memory pressure and clean up aggressively when needed
      const checkMemoryPressure = () => {
        const memInfo = performance.memory;
        const usageRatio = memInfo.usedJSHeapSize / memInfo.jsHeapSizeLimit;
        
        if (usageRatio > this.options.recycleThreshold) {
          this.performAggressiveCleanup();
        }
      };
      
      setInterval(checkMemoryPressure, 5000);
    }
  }
  
  createRowElement(rowData, index) {
    // Try to reuse an existing element
    let rowElement = this.unusedElements.pop();
    
    if (!rowElement) {
      rowElement = document.createElement('tr');
      this.setupRowElement(rowElement);
    }
    
    // Update element with new data
    this.updateRowElement(rowElement, rowData, index);
    
    // Cache element for potential reuse
    this.rowElementCache.set(index, rowElement);
    this.memoryUsage.cached++;
    
    return rowElement;
  }
  
  setupRowElement(element) {
    // Set up event listeners and basic structure
    element.addEventListener('click', this.handleRowClick.bind(this));
    
    // Create cell elements
    this.options.columns.forEach(() => {
      const cell = document.createElement('td');
      element.appendChild(cell);
    });
  }
  
  updateRowElement(element, rowData, index) {
    element.dataset.index = index;
    element.dataset.rowId = rowData.id;
    
    const cells = element.querySelectorAll('td');
    this.options.columns.forEach((column, colIndex) => {
      const cell = cells[colIndex];
      const value = rowData[column.key];
      
      // Efficient text update
      if (cell.textContent !== value) {
        cell.textContent = value;
      }
      
      // Update class names if needed
      if (column.className && !cell.classList.contains(column.className)) {
        cell.className = column.className;
      }
    });
  }
  
  removeRowElement(index) {
    const element = this.rowElementCache.get(index);
    if (element) {
      // Remove from DOM
      if (element.parentNode) {
        element.parentNode.removeChild(element);
      }
      
      // Unobserve for intersection
      this.intersectionObserver.unobserve(element);
      
      // Add to reuse pool if under limit
      if (this.unusedElements.length < this.options.maxCachedRows / 2) {
        this.unusedElements.push(element);
      } else {
        // Completely remove to free memory
        this.destroyElement(element);
      }
      
      this.rowElementCache.delete(index);
      this.memoryUsage.cached--;
    }
  }
  
  destroyElement(element) {
    // Remove all event listeners
    element.removeEventListener('click', this.handleRowClick);
    
    // Clear references
    element.innerHTML = '';
    
    // Additional cleanup for potential memory leaks
    for (const key in element.dataset) {
      delete element.dataset[key];
    }
  }
  
  handleIntersection(entries) {
    entries.forEach(entry => {
      const rowElement = entry.target;
      const index = parseInt(rowElement.dataset.index);
      
      if (!entry.isIntersecting) {
        // Row is no longer visible - consider for cleanup
        setTimeout(() => {
          if (!this.isRowVisible(rowElement)) {
            this.removeRowElement(index);
          }
        }, 1000); // Delay cleanup to avoid thrashing
      }
    });
  }
  
  isRowVisible(element) {
    const rect = element.getBoundingClientRect();
    const containerRect = this.container.getBoundingClientRect();
    
    return (
      rect.bottom >= containerRect.top &&
      rect.top <= containerRect.bottom
    );
  }
  
  performMemoryCleanup() {
    // Clean up unused elements
    const unusedCount = this.unusedElements.length;
    const targetCount = Math.floor(this.options.maxCachedRows * 0.5);
    
    if (unusedCount > targetCount) {
      const elementsToRemove = this.unusedElements.splice(targetCount);
      elementsToRemove.forEach(element => this.destroyElement(element));
    }
    
    // Clean up cached elements that are no longer in DOM
    for (const [index, element] of this.rowElementCache.entries()) {
      if (!element.parentNode) {
        this.rowElementCache.delete(index);
        this.memoryUsage.cached--;
      }
    }
    
    // Force garbage collection if available
    if (window.gc) {
      window.gc();
    }
  }
  
  performAggressiveCleanup() {
    console.warn('Memory pressure detected, performing aggressive cleanup');
    
    // Remove all unused elements
    this.unusedElements.forEach(element => this.destroyElement(element));
    this.unusedElements = [];
    
    // Remove cached elements not currently visible
    const visibleElements = Array.from(this.container.querySelectorAll('tr[data-index]'));
    const visibleIndices = new Set(visibleElements.map(el => parseInt(el.dataset.index)));
    
    for (const [index, element] of this.rowElementCache.entries()) {
      if (!visibleIndices.has(index)) {
        this.removeRowElement(index);
      }
    }
    
    // Trigger garbage collection
    if (window.gc) {
      window.gc();
    }
  }
  
  monitorMemoryUsage() {
    if (!('memory' in performance)) return;
    
    setInterval(() => {
      const memInfo = performance.memory;
      this.memoryUsage.total = memInfo.usedJSHeapSize;
      
      // Log memory usage in development
      if (process.env.NODE_ENV === 'development') {
        console.log('Table memory usage:', {
          cached: this.memoryUsage.cached,
          unused: this.unusedElements.length,
          total: `${(this.memoryUsage.total / 1024 / 1024).toFixed(2)} MB`
        });
      }
    }, 10000);
  }
  
  handleRowClick(event) {
    const rowElement = event.currentTarget;
    const index = parseInt(rowElement.dataset.index);
    const rowId = rowElement.dataset.rowId;
    
    // Emit custom event
    this.container.dispatchEvent(new CustomEvent('rowClick', {
      detail: { index, rowId, element: rowElement }
    }));
  }
  
  // Public API
  getMemoryUsage() {
    return {
      ...this.memoryUsage,
      unused: this.unusedElements.length,
      total: this.memoryUsage.total
    };
  }
  
  forceCleanup() {
    this.performAggressiveCleanup();
  }
  
  destroy() {
    // Clean up all resources
    clearInterval(this.cleanupTimer);
    
    this.intersectionObserver.disconnect();
    
    this.performAggressiveCleanup();
    
    // Remove all remaining elements
    this.rowElementCache.clear();
    this.unusedElements = [];
    
    this.container = null;
  }
}

// Usage with memory monitoring
const memoryEfficientTable = new MemoryEfficientTable('#memory-optimized-table', {
  maxCachedRows: 500,
  recycleThreshold: 0.7,
  cleanupInterval: 15000,
  columns: [
    { key: 'id', className: 'id-column' },
    { key: 'name', className: 'name-column' },
    { key: 'status', className: 'status-column' }
  ]
});

// Monitor memory usage
setInterval(() => {
  const usage = memoryEfficientTable.getMemoryUsage();
  if (usage.cached > 800) {
    console.warn('High memory usage detected, forcing cleanup');
    memoryEfficientTable.forceCleanup();
  }
}, 5000);

Troubleshooting Common Issues

Performance Problems with Large Datasets

Problem: Table becomes slow and unresponsive with large amounts of data

Solutions:

  1. Implement Virtual Scrolling:
    // Virtual scrolling for large datasets
    const handleLargeDataset = (data) => {
      if (data.length > 1000) {
     // Enable virtual scrolling
     const virtualTable = new VirtualScrollTable('#large-table', {
       itemHeight: 40,
       bufferSize: 5,
       data: data
     });
      }
    };
    
  2. Use Web Workers for Processing:
    // Offload processing to Web Workers
    const processDataInWorker = (data, operation) => {
      return new Promise((resolve) => {
     const worker = new Worker('/js/table-worker.js');
     worker.postMessage({ data, operation });
     worker.onmessage = (e) => {
       resolve(e.data);
       worker.terminate();
     };
      });
    };
    

Memory Leaks in Dynamic Tables

Problem: Memory usage increases over time with frequent updates

Solutions:

  1. Implement Proper Cleanup:
    ```javascript
    // Cleanup function for table updates
    const cleanupTable = () => {
    // Remove event listeners
    document.querySelectorAll(‘.table-row’).forEach(row => {
    row.removeEventListener(‘click’, handleRowClick);
    });

// Clear cached elements
rowCache.clear();

// Force garbage collection if available
if (window.gc) {
window.gc();
}
};


2. **Use WeakMaps for References**:
```javascript
// Use WeakMap to avoid memory leaks
const rowHandlers = new WeakMap();
const attachRowHandler = (element, handler) => {
  rowHandlers.set(element, handler);
  element.addEventListener('click', handler);
};

Cross-Browser Compatibility Issues

Problem: Table functionality works differently across browsers

Solutions:

  1. Feature Detection:
    ```javascript
    // Feature detection for table features
    const TableFeatureDetector = {
    supportsIntersectionObserver: ‘IntersectionObserver’ in window,
    supportsPerformanceObserver: ‘PerformanceObserver’ in window,
    supportsWebWorkers: ‘Worker’ in window,

getCompatibleImplementation() {
if (this.supportsIntersectionObserver) {
return EnhancedTable;
} else {
return BasicTable;
}
}
};


2. **Polyfills and Fallbacks**:
```javascript
// Polyfill for older browsers
if (!Array.prototype.includes) {
  Array.prototype.includes = function(searchElement) {
    return this.indexOf(searchElement) !== -1;
  };
}

// Fallback for missing APIs
const safeQuerySelector = (selector) => {
  try {
    return document.querySelector(selector);
  } catch (e) {
    console.warn('Selector not supported:', selector);
    return null;
  }
};

Conclusion

Advanced table sorting and filtering in Markdown transforms static data presentations into dynamic, interactive experiences that empower users to explore and analyze information efficiently. By implementing comprehensive JavaScript functionality, professional CSS styling, and performance optimization techniques, content creators can build sophisticated data interfaces that rival dedicated database applications while maintaining the simplicity and portability of Markdown.

The key to successful interactive table implementation lies in balancing functionality with performance, ensuring accessibility compliance, and providing intuitive user experiences that scale effectively across different platforms and datasets. Whether you’re creating documentation systems, data dashboards, or content management interfaces, the techniques covered in this guide provide the foundation for professional table functionality that enhances both usability and engagement.

Remember to test your implementations across different browsers and devices, monitor performance with large datasets, and implement proper memory management to ensure smooth operation. With well-implemented sorting and filtering capabilities, your Markdown tables become powerful tools for data exploration that maintain the flexibility and simplicity that makes Markdown an ideal choice for content creation workflows.