Skip to main content

Style

Size

Appearance

Features

Selection

Page Size

5 rows

Colors

Themes

Preview

NameEmailRoleStatusJoined
John Doejohn@example.comAdminActive2023-01-15
Jane Smithjane@example.comEditorActive2023-02-20
Bob Johnsonbob@example.comViewerInactive2023-03-10
Alice Brownalice@example.comEditorActive2023-04-05
Charlie Wilsoncharlie@example.comAdminActive2023-05-18
Page 1 of 2
HTML
<div class="data-table-container">
  <div class="table-toolbar">
    <input type="text" class="table-filter" placeholder="Search..." />
  </div>
  <div class="table-wrapper">
    <table class="data-table striped hoverable bordered">
      <thead>
        <tr>
          
          
          <th class="sortable">Name</th>
          <th class="sortable">Email</th>
          <th class="sortable">Role</th>
          <th class="sortable">Status</th>
          <th class="sortable">Joined</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          
          
          <td>John Doe</td>
          <td>john@example.com</td>
          <td>Admin</td>
          <td><span class="status-badge active">Active</span></td>
          <td>2023-01-15</td>
        </tr>
        <!-- More rows... -->
      </tbody>
    </table>
  </div>
  <div class="table-pagination">
    <button class="page-btn" disabled>&laquo; Prev</button>
    <span class="page-info">Page 1 of 2</span>
    <button class="page-btn">Next &raquo;</button>
  </div>
</div>
CSS
.data-table-container {
  background: #ffffff;
  border-radius: 8px;
  overflow: hidden;
  
}

.table-toolbar {
  padding: 16px;
  border-bottom: 1px solid #dee2e6;
}

.table-filter {
  padding: 10px 16px;
  border: 1px solid #dee2e6;
  border-radius: 6px;
  font-size: 14px;
  width: 300px;
  max-width: 100%;
  background: #ffffff;
  color: #333333;
}

.table-filter:focus {
  outline: none;
  border-color: #007bff;
}

.table-wrapper {
  overflow-x: auto;
}

.table-wrapper.sticky-header {
  max-height: 400px;
  overflow-y: auto;
}

.table-wrapper.sticky-header thead {
  position: sticky;
  top: 0;
  z-index: 1;
}

.data-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 14px;
  color: #333333;
}

.data-table th,
.data-table td {
  padding: 12px 16px;
  text-align: left;
}

.data-table thead {
  background: #f8f9fa;
}

.data-table th {
  font-weight: 600;
  white-space: nowrap;
}

.data-table th.sortable {
  cursor: pointer;
  user-select: none;
}

.data-table th.sortable:hover {
  background: #e9ecef;
}

.data-table th .sort-icon {
  margin-left: 8px;
  opacity: 0.5;
}

.data-table th.sorted .sort-icon {
  opacity: 1;
  color: #007bff;
}

.data-table.bordered th,
.data-table.bordered td {
  border: 1px solid #dee2e6;
}

.data-table:not(.bordered) tbody tr {
  border-bottom: 1px solid #dee2e6;
}

.data-table.striped tbody tr:nth-child(even) {
  background: #f8f9fa;
}

.data-table.hoverable tbody tr:hover {
  background: #e9ecef;
}

.data-table .select-cell {
  width: 40px;
  text-align: center;
}

.data-table .row-number {
  width: 50px;
  text-align: center;
  color: #999;
}

.data-table input[type="checkbox"],
.data-table input[type="radio"] {
  width: 16px;
  height: 16px;
  cursor: pointer;
  accent-color: #007bff;
}

.status-badge {
  display: inline-block;
  padding: 4px 10px;
  border-radius: 12px;
  font-size: 12px;
  font-weight: 500;
}

.status-badge.active {
  background: #dcfce7;
  color: #166534;
}

.status-badge.inactive {
  background: #fee2e2;
  color: #991b1b;
}

.status-badge.pending {
  background: #fef3c7;
  color: #92400e;
}

.table-pagination {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 16px;
  padding: 16px;
  border-top: 1px solid #dee2e6;
}

.page-btn {
  padding: 8px 16px;
  border: 1px solid #dee2e6;
  border-radius: 6px;
  background: #ffffff;
  color: #333333;
  cursor: pointer;
  font-size: 14px;
  transition: all 0.2s;
}

.page-btn:hover:not(:disabled) {
  background: #e9ecef;
  border-color: #007bff;
}

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

.page-info {
  font-size: 14px;
  color: #333333;
}
JavaScript
class DataTable {
  constructor(container, options = {}) {
    this.container = container;
    this.options = {
      data: [],
      columns: [],
      sortable: true,
      filterable: true,
      pagination: true,
      pageSize: 5,
      selectable: false,
      selectType: 'checkbox',
      ...options
    };
    this.currentPage = 1;
    this.sortColumn = null;
    this.sortDirection = 'asc';
    this.selectedRows = new Set();
    this.filterText = '';
    this.init();
  }

  init() {
    this.render();
    this.bindEvents();
  }

  bindEvents() {
    // Filter input
    const filterInput = this.container.querySelector('.table-filter');
    if (filterInput) {
      filterInput.addEventListener('input', (e) => {
        this.filterText = e.target.value;
        this.currentPage = 1;
        this.render();
      });
    }

    // Sortable headers
    this.container.querySelectorAll('th.sortable').forEach((th, index) => {
      th.addEventListener('click', () => {
        const column = this.options.columns[index].key;
        if (this.sortColumn === column) {
          this.sortDirection = this.sortDirection === 'asc' ? 'desc' : 'asc';
        } else {
          this.sortColumn = column;
          this.sortDirection = 'asc';
        }
        this.render();
      });
    });

    // Pagination
    this.container.addEventListener('click', (e) => {
      if (e.target.classList.contains('page-prev')) {
        this.currentPage = Math.max(1, this.currentPage - 1);
        this.render();
      } else if (e.target.classList.contains('page-next')) {
        this.currentPage = Math.min(this.getTotalPages(), this.currentPage + 1);
        this.render();
      }
    });

    // Row selection
    this.container.addEventListener('change', (e) => {
      if (e.target.classList.contains('select-all')) {
        this.toggleAllRows(e.target.checked);
      } else if (e.target.classList.contains('row-select')) {
        const rowId = e.target.closest('tr').dataset.id;
        this.toggleRow(rowId);
      }
    });
  }

  getFilteredData() {
    if (!this.filterText) return this.options.data;
    return this.options.data.filter(row =>
      Object.values(row).some(value =>
        String(value).toLowerCase().includes(this.filterText.toLowerCase())
      )
    );
  }

  getSortedData() {
    const filtered = this.getFilteredData();
    if (!this.sortColumn) return filtered;
    return [...filtered].sort((a, b) => {
      const aVal = a[this.sortColumn];
      const bVal = b[this.sortColumn];
      if (aVal < bVal) return this.sortDirection === 'asc' ? -1 : 1;
      if (aVal > bVal) return this.sortDirection === 'asc' ? 1 : -1;
      return 0;
    });
  }

  getPaginatedData() {
    const sorted = this.getSortedData();
    if (!this.options.pagination) return sorted;
    const start = (this.currentPage - 1) * this.options.pageSize;
    return sorted.slice(start, start + this.options.pageSize);
  }

  getTotalPages() {
    return Math.ceil(this.getSortedData().length / this.options.pageSize);
  }

  toggleRow(id) {
    if (this.options.selectType === 'radio') {
      this.selectedRows.clear();
      this.selectedRows.add(id);
    } else {
      if (this.selectedRows.has(id)) {
        this.selectedRows.delete(id);
      } else {
        this.selectedRows.add(id);
      }
    }
    this.render();
  }

  toggleAllRows(selected) {
    if (selected) {
      this.getPaginatedData().forEach(row => this.selectedRows.add(row.id));
    } else {
      this.selectedRows.clear();
    }
    this.render();
  }

  getSelectedRows() {
    return Array.from(this.selectedRows);
  }

  render() {
    // Re-render the table with current state
    // Implementation depends on your template system
  }
}

// Usage
const table = new DataTable(document.querySelector('.data-table-container'), {
  data: yourData,
  columns: [
    { key: 'name', label: 'Name' },
    { key: 'email', label: 'Email' },
    // ...
  ]
});
React Component
import React, { useState, useMemo } from 'react';
import './DataTable.css';

const DataTable = ({
  data = [],
  columns = [],
  sortable = true,
  filterable = true,
  pagination = true,
  pageSize = 5,
  selectable = false,
  selectType = 'checkbox',
  striped = true,
  hoverable = true,
  showRowNumbers = false
}) => {
  const [sortColumn, setSortColumn] = useState(null);
  const [sortDirection, setSortDirection] = useState('asc');
  const [filterText, setFilterText] = useState('');
  const [currentPage, setCurrentPage] = useState(1);
  const [selectedRows, setSelectedRows] = useState([]);

  const handleSort = (column) => {
    if (!sortable) return;
    if (sortColumn === column) {
      setSortDirection(prev => prev === 'asc' ? 'desc' : 'asc');
    } else {
      setSortColumn(column);
      setSortDirection('asc');
    }
  };

  const filteredData = useMemo(() => {
    if (!filterText) return data;
    return data.filter(row =>
      Object.values(row).some(value =>
        String(value).toLowerCase().includes(filterText.toLowerCase())
      )
    );
  }, [data, filterText]);

  const sortedData = useMemo(() => {
    if (!sortColumn) return filteredData;
    return [...filteredData].sort((a, b) => {
      const aVal = a[sortColumn];
      const bVal = b[sortColumn];
      if (aVal < bVal) return sortDirection === 'asc' ? -1 : 1;
      if (aVal > bVal) return sortDirection === 'asc' ? 1 : -1;
      return 0;
    });
  }, [filteredData, sortColumn, sortDirection]);

  const paginatedData = useMemo(() => {
    if (!pagination) return sortedData;
    const start = (currentPage - 1) * pageSize;
    return sortedData.slice(start, start + pageSize);
  }, [sortedData, currentPage, pageSize, pagination]);

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

  const toggleRowSelection = (id) => {
    if (selectType === 'radio') {
      setSelectedRows([id]);
    } else {
      setSelectedRows(prev =>
        prev.includes(id)
          ? prev.filter(r => r !== id)
          : [...prev, id]
      );
    }
  };

  const toggleAllRows = () => {
    if (selectedRows.length === paginatedData.length) {
      setSelectedRows([]);
    } else {
      setSelectedRows(paginatedData.map(r => r.id));
    }
  };

  return (
    <div className="data-table-container">
      {filterable && (
        <div className="table-toolbar">
          <input
            type="text"
            className="table-filter"
            placeholder="Search..."
            value={filterText}
            onChange={(e) => {
              setFilterText(e.target.value);
              setCurrentPage(1);
            }}
          />
        </div>
      )}
      <div className="table-wrapper">
        <table className={`data-table ${striped ? 'striped' : ''} ${hoverable ? 'hoverable' : ''}`}>
          <thead>
            <tr>
              {selectable && (
                <th className="select-cell">
                  {selectType === 'checkbox' && (
                    <input
                      type="checkbox"
                      checked={selectedRows.length === paginatedData.length}
                      onChange={toggleAllRows}
                    />
                  )}
                </th>
              )}
              {showRowNumbers && <th className="row-number">#</th>}
              {columns.map(col => (
                <th
                  key={col.key}
                  className={sortable ? 'sortable' : ''}
                  onClick={() => handleSort(col.key)}
                >
                  {col.label}
                  {sortColumn === col.key && (
                    <span className="sort-icon">
                      {sortDirection === 'asc' ? 'โ–ฒ' : 'โ–ผ'}
                    </span>
                  )}
                </th>
              ))}
            </tr>
          </thead>
          <tbody>
            {paginatedData.map((row, index) => (
              <tr key={row.id}>
                {selectable && (
                  <td className="select-cell">
                    <input
                      type={selectType}
                      name="row-select"
                      checked={selectedRows.includes(row.id)}
                      onChange={() => toggleRowSelection(row.id)}
                    />
                  </td>
                )}
                {showRowNumbers && (
                  <td className="row-number">
                    {(currentPage - 1) * pageSize + index + 1}
                  </td>
                )}
                {columns.map(col => (
                  <td key={col.key}>{row[col.key]}</td>
                ))}
              </tr>
            ))}
          </tbody>
        </table>
      </div>
      {pagination && totalPages > 1 && (
        <div className="table-pagination">
          <button
            className="page-btn"
            disabled={currentPage === 1}
            onClick={() => setCurrentPage(p => p - 1)}
          >
            &laquo; Prev
          </button>
          <span className="page-info">
            Page {currentPage} of {totalPages}
          </span>
          <button
            className="page-btn"
            disabled={currentPage === totalPages}
            onClick={() => setCurrentPage(p => p + 1)}
          >
            Next &raquo;
          </button>
        </div>
      )}
    </div>
  );
};

export default DataTable;
Export Data

Rate this tool