Category · Feature Sections Difficulty Level · Advanced Published on · January 15, 2024

Advanced Data List with Filtering & Sorting

A comprehensive data list component with advanced filtering, sorting, pagination, and search functionality

#list #data #table #filter #sort #pagination #search #responsive

Responsive Design

Yes

Dark Mode Support

No

lines

1092

Browser Compatibility

No

Live Preview

Interact with the component without leaving the page.

300px

Advanced Data List with Filtering & Sorting

A powerful and flexible data list component that provides comprehensive functionality for displaying, filtering, sorting, and managing large datasets. Perfect for admin panels, product catalogs, user directories, and any application requiring advanced data presentation.

Features

  • Advanced Filtering: Multiple filter types including text, select, date range, and custom filters
  • Multi-column Sorting: Sort by multiple columns with visual indicators
  • Smart Search: Real-time search across all visible columns with highlighting
  • Responsive Design: Adaptive layout that works on all screen sizes
  • Pagination: Efficient pagination with customizable page sizes
  • Row Selection: Single and multi-row selection with bulk actions
  • Export Functionality: Export filtered data to CSV, JSON, or print
  • Column Management: Show/hide columns and reorder them
  • Loading States: Smooth loading animations and skeleton screens
  • Accessibility: Full keyboard navigation and screen reader support
  • Performance Optimized: Virtual scrolling for large datasets

HTML

<div class="data-list-container">
  <!-- Header Section -->
  <div class="data-list-header">
    <div class="header-content">
      <h2 class="list-title">Employee Directory</h2>
      <p class="list-description">Manage and view employee information with advanced filtering and sorting</p>
    </div>
    
    <!-- Action Buttons -->
    <div class="header-actions">
      <button class="btn btn-secondary" id="columnToggle">
        <svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor">
          <rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
          <line x1="9" y1="9" x2="15" y2="9"></line>
          <line x1="9" y1="15" x2="15" y2="15"></line>
        </svg>
        Columns
      </button>
      <button class="btn btn-secondary" id="exportData">
        <svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor">
          <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
          <polyline points="7,10 12,15 17,10"></polyline>
          <line x1="12" y1="15" x2="12" y2="3"></line>
        </svg>
        Export
      </button>
      <button class="btn btn-primary" id="addNew">
        <svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor">
          <line x1="12" y1="5" x2="12" y2="19"></line>
          <line x1="5" y1="12" x2="19" y2="12"></line>
        </svg>
        Add Employee
      </button>
    </div>
  </div>

  <!-- Filters Section -->
  <div class="filters-section">
    <!-- Search Bar -->
    <div class="search-container">
      <div class="search-input-wrapper">
        <svg class="search-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor">
          <circle cx="11" cy="11" r="8"></circle>
          <path d="m21 21-4.35-4.35"></path>
        </svg>
        <input 
          type="text" 
          id="globalSearch" 
          class="search-input" 
          placeholder="Search employees..."
          aria-label="Search employees"
        >
        <button class="search-clear" id="searchClear" aria-label="Clear search">
          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor">
            <line x1="18" y1="6" x2="6" y2="18"></line>
            <line x1="6" y1="6" x2="18" y2="18"></line>
          </svg>
        </button>
      </div>
    </div>

    <!-- Advanced Filters -->
    <div class="advanced-filters">
      <div class="filter-group">
        <label for="departmentFilter">Department</label>
        <select id="departmentFilter" class="filter-select">
          <option value="">All Departments</option>
          <option value="engineering">Engineering</option>
          <option value="marketing">Marketing</option>
          <option value="sales">Sales</option>
          <option value="hr">Human Resources</option>
          <option value="finance">Finance</option>
        </select>
      </div>

      <div class="filter-group">
        <label for="statusFilter">Status</label>
        <select id="statusFilter" class="filter-select">
          <option value="">All Status</option>
          <option value="active">Active</option>
          <option value="inactive">Inactive</option>
          <option value="pending">Pending</option>
        </select>
      </div>

      <div class="filter-group">
        <label for="locationFilter">Location</label>
        <select id="locationFilter" class="filter-select">
          <option value="">All Locations</option>
          <option value="new-york">New York</option>
          <option value="san-francisco">San Francisco</option>
          <option value="london">London</option>
          <option value="remote">Remote</option>
        </select>
      </div>

      <div class="filter-group">
        <label for="hireDateFrom">Hire Date From</label>
        <input type="date" id="hireDateFrom" class="filter-input">
      </div>

      <div class="filter-group">
        <label for="hireDateTo">Hire Date To</label>
        <input type="date" id="hireDateTo" class="filter-input">
      </div>

      <button class="btn btn-secondary" id="clearFilters">
        Clear Filters
      </button>
    </div>
  </div>

  <!-- Results Info -->
  <div class="results-info">
    <div class="results-count">
      <span id="resultsText">Showing 1-10 of 150 employees</span>
    </div>
    
    <div class="results-actions">
      <div class="page-size-selector">
        <label for="pageSize">Show:</label>
        <select id="pageSize" class="page-size-select">
          <option value="10">10</option>
          <option value="25" selected>25</option>
          <option value="50">50</option>
          <option value="100">100</option>
        </select>
        <span>per page</span>
      </div>
      
      <div class="bulk-actions" id="bulkActions" style="display: none;">
        <span class="selected-count" id="selectedCount">2 selected</span>
        <button class="btn btn-sm btn-secondary" id="bulkEdit">Edit</button>
        <button class="btn btn-sm btn-danger" id="bulkDelete">Delete</button>
      </div>
    </div>
  </div>

  <!-- Data Table -->
  <div class="table-container">
    <div class="table-wrapper">
      <table class="data-table" id="dataTable">
        <thead>
          <tr>
            <th class="select-column">
              <input type="checkbox" id="selectAll" aria-label="Select all rows">
            </th>
            <th class="sortable" data-column="name" data-type="string">
              <div class="th-content">
                <span>Name</span>
                <div class="sort-indicators">
                  <svg class="sort-icon sort-asc" viewBox="0 0 24 24" fill="none" stroke="currentColor">
                    <polyline points="18,15 12,9 6,15"></polyline>
                  </svg>
                  <svg class="sort-icon sort-desc" viewBox="0 0 24 24" fill="none" stroke="currentColor">
                    <polyline points="6,9 12,15 18,9"></polyline>
                  </svg>
                </div>
              </div>
            </th>
            <th class="sortable" data-column="email" data-type="string">
              <div class="th-content">
                <span>Email</span>
                <div class="sort-indicators">
                  <svg class="sort-icon sort-asc" viewBox="0 0 24 24" fill="none" stroke="currentColor">
                    <polyline points="18,15 12,9 6,15"></polyline>
                  </svg>
                  <svg class="sort-icon sort-desc" viewBox="0 0 24 24" fill="none" stroke="currentColor">
                    <polyline points="6,9 12,15 18,9"></polyline>
                  </svg>
                </div>
              </div>
            </th>
            <th class="sortable" data-column="department" data-type="string">
              <div class="th-content">
                <span>Department</span>
                <div class="sort-indicators">
                  <svg class="sort-icon sort-asc" viewBox="0 0 24 24" fill="none" stroke="currentColor">
                    <polyline points="18,15 12,9 6,15"></polyline>
                  </svg>
                  <svg class="sort-icon sort-desc" viewBox="0 0 24 24" fill="none" stroke="currentColor">
                    <polyline points="6,9 12,15 18,9"></polyline>
                  </svg>
                </div>
              </div>
            </th>
            <th class="sortable" data-column="position" data-type="string">
              <div class="th-content">
                <span>Position</span>
                <div class="sort-indicators">
                  <svg class="sort-icon sort-asc" viewBox="0 0 24 24" fill="none" stroke="currentColor">
                    <polyline points="18,15 12,9 6,15"></polyline>
                  </svg>
                  <svg class="sort-icon sort-desc" viewBox="0 0 24 24" fill="none" stroke="currentColor">
                    <polyline points="6,9 12,15 18,9"></polyline>
                  </svg>
                </div>
              </div>
            </th>
            <th class="sortable" data-column="location" data-type="string">
              <div class="th-content">
                <span>Location</span>
                <div class="sort-indicators">
                  <svg class="sort-icon sort-asc" viewBox="0 0 24 24" fill="none" stroke="currentColor">
                    <polyline points="18,15 12,9 6,15"></polyline>
                  </svg>
                  <svg class="sort-icon sort-desc" viewBox="0 0 24 24" fill="none" stroke="currentColor">
                    <polyline points="6,9 12,15 18,9"></polyline>
                  </svg>
                </div>
              </div>
            </th>
            <th class="sortable" data-column="hireDate" data-type="date">
              <div class="th-content">
                <span>Hire Date</span>
                <div class="sort-indicators">
                  <svg class="sort-icon sort-asc" viewBox="0 0 24 24" fill="none" stroke="currentColor">
                    <polyline points="18,15 12,9 6,15"></polyline>
                  </svg>
                  <svg class="sort-icon sort-desc" viewBox="0 0 24 24" fill="none" stroke="currentColor">
                    <polyline points="6,9 12,15 18,9"></polyline>
                  </svg>
                </div>
              </div>
            </th>
            <th class="sortable" data-column="status" data-type="string">
              <div class="th-content">
                <span>Status</span>
                <div class="sort-indicators">
                  <svg class="sort-icon sort-asc" viewBox="0 0 24 24" fill="none" stroke="currentColor">
                    <polyline points="18,15 12,9 6,15"></polyline>
                  </svg>
                  <svg class="sort-icon sort-desc" viewBox="0 0 24 24" fill="none" stroke="currentColor">
                    <polyline points="6,9 12,15 18,9"></polyline>
                  </svg>
                </div>
              </div>
            </th>
            <th class="actions-column">Actions</th>
          </tr>
        </thead>
        <tbody id="tableBody">
          <!-- Data rows will be populated by JavaScript -->
        </tbody>
      </table>
    </div>
    
    <!-- Loading State -->
    <div class="loading-state" id="loadingState" style="display: none;">
      <div class="loading-spinner"></div>
      <p>Loading data...</p>
    </div>
    
    <!-- Empty State -->
    <div class="empty-state" id="emptyState" style="display: none;">
      <svg class="empty-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor">
        <circle cx="11" cy="11" r="8"></circle>
        <path d="m21 21-4.35-4.35"></path>
      </svg>
      <h3>No employees found</h3>
      <p>Try adjusting your search criteria or filters</p>
      <button class="btn btn-primary" onclick="clearAllFilters()">Clear All Filters</button>
    </div>
  </div>

  <!-- Pagination -->
  <div class="pagination-container">
    <div class="pagination-info">
      <span id="paginationInfo">Showing 1 to 25 of 150 entries</span>
    </div>
    
    <nav class="pagination" aria-label="Data table pagination">
      <button class="pagination-btn" id="firstPage" aria-label="First page">
        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor">
          <polyline points="11,17 6,12 11,7"></polyline>
          <polyline points="18,17 13,12 18,7"></polyline>
        </svg>
      </button>
      <button class="pagination-btn" id="prevPage" aria-label="Previous page">
        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor">
          <polyline points="15,18 9,12 15,6"></polyline>
        </svg>
      </button>
      
      <div class="pagination-numbers" id="paginationNumbers">
        <!-- Page numbers will be populated by JavaScript -->
      </div>
      
      <button class="pagination-btn" id="nextPage" aria-label="Next page">
        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor">
          <polyline points="9,18 15,12 9,6"></polyline>
        </svg>
      </button>
      <button class="pagination-btn" id="lastPage" aria-label="Last page">
        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor">
          <polyline points="13,17 18,12 13,7"></polyline>
          <polyline points="6,17 11,12 6,7"></polyline>
        </svg>
      </button>
    </nav>
  </div>

  <!-- Column Toggle Modal -->
  <div class="modal" id="columnModal">
    <div class="modal-content">
      <div class="modal-header">
        <h3>Manage Columns</h3>
        <button class="modal-close" id="closeColumnModal">
          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor">
            <line x1="18" y1="6" x2="6" y2="18"></line>
            <line x1="6" y1="6" x2="18" y2="18"></line>
          </svg>
        </button>
      </div>
      <div class="modal-body">
        <p>Select which columns to display in the table:</p>
        <div class="column-toggles" id="columnToggles">
          <!-- Column toggles will be populated by JavaScript -->
        </div>
      </div>
      <div class="modal-footer">
        <button class="btn btn-secondary" id="resetColumns">Reset to Default</button>
        <button class="btn btn-primary" id="applyColumns">Apply Changes</button>
      </div>
    </div>
  </div>

  <!-- Export Modal -->
  <div class="modal" id="exportModal">
    <div class="modal-content">
      <div class="modal-header">
        <h3>Export Data</h3>
        <button class="modal-close" id="closeExportModal">
          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor">
            <line x1="18" y1="6" x2="6" y2="18"></line>
            <line x1="6" y1="6" x2="18" y2="18"></line>
          </svg>
        </button>
      </div>
      <div class="modal-body">
        <p>Choose export format and options:</p>
        <div class="export-options">
          <div class="export-format">
            <label>Export Format:</label>
            <div class="radio-group">
              <label class="radio-option">
                <input type="radio" name="exportFormat" value="csv" checked>
                <span>CSV</span>
              </label>
              <label class="radio-option">
                <input type="radio" name="exportFormat" value="json">
                <span>JSON</span>
              </label>
              <label class="radio-option">
                <input type="radio" name="exportFormat" value="print">
                <span>Print</span>
              </label>
            </div>
          </div>
          
          <div class="export-scope">
            <label>Export Scope:</label>
            <div class="radio-group">
              <label class="radio-option">
                <input type="radio" name="exportScope" value="current" checked>
                <span>Current Page</span>
              </label>
              <label class="radio-option">
                <input type="radio" name="exportScope" value="filtered">
                <span>All Filtered Results</span>
              </label>
              <label class="radio-option">
                <input type="radio" name="exportScope" value="all">
                <span>All Data</span>
              </label>
            </div>
          </div>
        </div>
      </div>
      <div class="modal-footer">
        <button class="btn btn-secondary" id="cancelExport">Cancel</button>
        <button class="btn btn-primary" id="confirmExport">Export</button>
      </div>
    </div>
  </div>
</div>

CSS

/* Base Styles */
.data-list-container {
  max-width: 1400px;
  margin: 0 auto;
  padding: 20px;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  background: #f8fafc;
  min-height: 100vh;
}

/* Header */
.data-list-header {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  margin-bottom: 30px;
  gap: 20px;
}

.header-content {
  flex: 1;
}

.list-title {
  font-size: 2rem;
  font-weight: 700;
  color: #1a202c;
  margin: 0 0 8px 0;
}

.list-description {
  color: #718096;
  margin: 0;
  font-size: 1rem;
}

.header-actions {
  display: flex;
  gap: 12px;
  flex-wrap: wrap;
}

/* Buttons */
.btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 10px 16px;
  border: none;
  border-radius: 8px;
  font-size: 0.875rem;
  font-weight: 500;
  cursor: pointer;
  transition: all 0.2s ease;
  text-decoration: none;
  white-space: nowrap;
}

.btn-primary {
  background: #4f46e5;
  color: white;
}

.btn-primary:hover {
  background: #4338ca;
  transform: translateY(-1px);
}

.btn-secondary {
  background: white;
  color: #374151;
  border: 1px solid #d1d5db;
}

.btn-secondary:hover {
  background: #f9fafb;
  border-color: #9ca3af;
}

.btn-danger {
  background: #ef4444;
  color: white;
}

.btn-danger:hover {
  background: #dc2626;
}

.btn-sm {
  padding: 6px 12px;
  font-size: 0.75rem;
}

.icon {
  width: 16px;
  height: 16px;
  stroke-width: 2;
}

/* Filters Section */
.filters-section {
  background: white;
  border-radius: 12px;
  padding: 24px;
  margin-bottom: 24px;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}

.search-container {
  margin-bottom: 20px;
}

.search-input-wrapper {
  position: relative;
  max-width: 400px;
}

.search-icon {
  position: absolute;
  left: 12px;
  top: 50%;
  transform: translateY(-50%);
  width: 20px;
  height: 20px;
  color: #9ca3af;
  pointer-events: none;
}

.search-input {
  width: 100%;
  padding: 12px 40px 12px 40px;
  border: 2px solid #e5e7eb;
  border-radius: 8px;
  font-size: 0.875rem;
  transition: border-color 0.2s ease;
}

.search-input:focus {
  outline: none;
  border-color: #4f46e5;
}

.search-clear {
  position: absolute;
  right: 12px;
  top: 50%;
  transform: translateY(-50%);
  background: none;
  border: none;
  width: 20px;
  height: 20px;
  color: #9ca3af;
  cursor: pointer;
  opacity: 0;
  transition: opacity 0.2s ease;
}

.search-clear.visible {
  opacity: 1;
}

.advanced-filters {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 16px;
  align-items: end;
}

.filter-group {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.filter-group label {
  font-size: 0.875rem;
  font-weight: 500;
  color: #374151;
}

.filter-select,
.filter-input {
  padding: 8px 12px;
  border: 1px solid #d1d5db;
  border-radius: 6px;
  font-size: 0.875rem;
  background: white;
}

.filter-select:focus,
.filter-input:focus {
  outline: none;
  border-color: #4f46e5;
}

/* Results Info */
.results-info {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 16px;
  gap: 20px;
}

.results-count {
  color: #6b7280;
  font-size: 0.875rem;
}

.results-actions {
  display: flex;
  align-items: center;
  gap: 20px;
}

.page-size-selector {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 0.875rem;
  color: #6b7280;
}

.page-size-select {
  padding: 4px 8px;
  border: 1px solid #d1d5db;
  border-radius: 4px;
  font-size: 0.875rem;
}

.bulk-actions {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 8px 16px;
  background: #eff6ff;
  border: 1px solid #bfdbfe;
  border-radius: 8px;
}

.selected-count {
  font-size: 0.875rem;
  color: #1e40af;
  font-weight: 500;
}

/* Table */
.table-container {
  background: white;
  border-radius: 12px;
  overflow: hidden;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
  margin-bottom: 24px;
}

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

.data-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.875rem;
}

.data-table th {
  background: #f8fafc;
  padding: 16px 12px;
  text-align: left;
  font-weight: 600;
  color: #374151;
  border-bottom: 1px solid #e5e7eb;
  white-space: nowrap;
}

.data-table td {
  padding: 16px 12px;
  border-bottom: 1px solid #f3f4f6;
  vertical-align: middle;
}

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

.data-table tbody tr.selected {
  background: #eff6ff;
}

/* Sortable Headers */
.sortable {
  cursor: pointer;
  user-select: none;
  position: relative;
}

.sortable:hover {
  background: #f1f5f9;
}

.th-content {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}

.sort-indicators {
  display: flex;
  flex-direction: column;
  gap: 1px;
}

.sort-icon {
  width: 12px;
  height: 12px;
  color: #d1d5db;
  transition: color 0.2s ease;
}

.sortable.sort-asc .sort-asc,
.sortable.sort-desc .sort-desc {
  color: #4f46e5;
}

/* Column Types */
.select-column {
  width: 40px;
  text-align: center;
}

.actions-column {
  width: 120px;
  text-align: center;
}

/* Status Badges */
.status-badge {
  display: inline-flex;
  align-items: center;
  padding: 4px 8px;
  border-radius: 12px;
  font-size: 0.75rem;
  font-weight: 500;
  text-transform: capitalize;
}

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

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

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

/* Action Buttons */
.action-btn {
  padding: 4px 8px;
  border: none;
  border-radius: 4px;
  font-size: 0.75rem;
  cursor: pointer;
  margin: 0 2px;
  transition: all 0.2s ease;
}

.action-btn.edit {
  background: #dbeafe;
  color: #1e40af;
}

.action-btn.edit:hover {
  background: #bfdbfe;
}

.action-btn.delete {
  background: #fee2e2;
  color: #991b1b;
}

.action-btn.delete:hover {
  background: #fecaca;
}

/* Loading State */
.loading-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 60px 20px;
  color: #6b7280;
}

.loading-spinner {
  width: 40px;
  height: 40px;
  border: 3px solid #f3f4f6;
  border-top: 3px solid #4f46e5;
  border-radius: 50%;
  animation: spin 1s linear infinite;
  margin-bottom: 16px;
}

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

/* Empty State */
.empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 60px 20px;
  text-align: center;
}

.empty-icon {
  width: 64px;
  height: 64px;
  color: #d1d5db;
  margin-bottom: 16px;
}

.empty-state h3 {
  font-size: 1.25rem;
  font-weight: 600;
  color: #374151;
  margin: 0 0 8px 0;
}

.empty-state p {
  color: #6b7280;
  margin: 0 0 24px 0;
}

/* Pagination */
.pagination-container {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 20px;
}

.pagination-info {
  color: #6b7280;
  font-size: 0.875rem;
}

.pagination {
  display: flex;
  align-items: center;
  gap: 4px;
}

.pagination-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  border: 1px solid #d1d5db;
  background: white;
  border-radius: 6px;
  cursor: pointer;
  transition: all 0.2s ease;
}

.pagination-btn:hover:not(:disabled) {
  background: #f9fafb;
  border-color: #9ca3af;
}

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

.pagination-btn svg {
  width: 16px;
  height: 16px;
}

.pagination-numbers {
  display: flex;
  gap: 4px;
  margin: 0 8px;
}

.page-number {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  border: 1px solid #d1d5db;
  background: white;
  border-radius: 6px;
  cursor: pointer;
  font-size: 0.875rem;
  font-weight: 500;
  transition: all 0.2s ease;
}

.page-number:hover {
  background: #f9fafb;
  border-color: #9ca3af;
}

.page-number.active {
  background: #4f46e5;
  color: white;
  border-color: #4f46e5;
}

.page-ellipsis {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  color: #9ca3af;
  font-weight: 500;
}

/* Modals */
.modal {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: rgba(0, 0, 0, 0.5);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1000;
  opacity: 0;
  visibility: hidden;
  transition: all 0.2s ease;
}

.modal.active {
  opacity: 1;
  visibility: visible;
}

.modal-content {
  background: white;
  border-radius: 12px;
  width: 90%;
  max-width: 500px;
  max-height: 90vh;
  overflow: hidden;
  transform: scale(0.9);
  transition: transform 0.2s ease;
}

.modal.active .modal-content {
  transform: scale(1);
}

.modal-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 20px 24px;
  border-bottom: 1px solid #e5e7eb;
}

.modal-header h3 {
  margin: 0;
  font-size: 1.25rem;
  font-weight: 600;
  color: #1a202c;
}

.modal-close {
  background: none;
  border: none;
  width: 24px;
  height: 24px;
  cursor: pointer;
  color: #6b7280;
}

.modal-close:hover {
  color: #374151;
}

.modal-body {
  padding: 24px;
  max-height: 60vh;
  overflow-y: auto;
}

.modal-footer {
  display: flex;
  justify-content: flex-end;
  gap: 12px;
  padding: 20px 24px;
  border-top: 1px solid #e5e7eb;
  background: #f8fafc;
}

/* Column Toggles */
.column-toggles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 12px;
  margin-top: 16px;
}

.column-toggle {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px;
  border: 1px solid #e5e7eb;
  border-radius: 6px;
  cursor: pointer;
  transition: all 0.2s ease;
}

.column-toggle:hover {
  background: #f8fafc;
}

.column-toggle input[type="checkbox"] {
  margin: 0;
}

/* Export Options */
.export-options {
  margin-top: 16px;
}

.export-format,
.export-scope {
  margin-bottom: 20px;
}

.export-format label,
.export-scope label {
  display: block;
  font-weight: 500;
  color: #374151;
  margin-bottom: 8px;
}

.radio-group {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.radio-option {
  display: flex;
  align-items: center;
  gap: 8px;
  cursor: pointer;
  font-weight: normal !important;
}

.radio-option input[type="radio"] {
  margin: 0;
}

/* Search Highlighting */
.highlight {
  background: #fef3c7;
  padding: 1px 2px;
  border-radius: 2px;
  font-weight: 600;
}

/* Responsive Design */
@media (max-width: 1024px) {
  .data-list-header {
    flex-direction: column;
    align-items: stretch;
  }
  
  .header-actions {
    justify-content: flex-start;
  }
  
  .advanced-filters {
    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  }
}

@media (max-width: 768px) {
  .data-list-container {
    padding: 16px;
  }
  
  .list-title {
    font-size: 1.5rem;
  }
  
  .filters-section {
    padding: 16px;
  }
  
  .advanced-filters {
    grid-template-columns: 1fr;
  }
  
  .results-info {
    flex-direction: column;
    align-items: stretch;
    gap: 12px;
  }
  
  .results-actions {
    justify-content: space-between;
  }
  
  .pagination-container {
    flex-direction: column;
    gap: 12px;
  }
  
  .pagination {
    justify-content: center;
  }
  
  /* Hide less important columns on mobile */
  .data-table th:nth-child(n+6),
  .data-table td:nth-child(n+6) {
    display: none;
  }
}

@media (max-width: 480px) {
  .header-actions {
    flex-direction: column;
  }
  
  .btn {
    justify-content: center;
  }
  
  .data-table th:nth-child(n+5),
  .data-table td:nth-child(n+5) {
    display: none;
  }
  
  .pagination-numbers {
    display: none;
  }
}

JavaScript

// Advanced Data List Controller
class AdvancedDataList {
  constructor(options = {}) {
    this.options = {
      pageSize: 25,
      sortColumn: 'name',
      sortDirection: 'asc',
      searchDebounce: 300,
      ...options
    };
    
    this.data = [];
    this.filteredData = [];
    this.currentPage = 1;
    this.totalPages = 1;
    this.selectedRows = new Set();
    this.visibleColumns = new Set(['name', 'email', 'department', 'position', 'location', 'hireDate', 'status']);
    
    this.filters = {
      search: '',
      department: '',
      status: '',
      location: '',
      hireDateFrom: '',
      hireDateTo: ''
    };
    
    this.sortState = {
      column: this.options.sortColumn,
      direction: this.options.sortDirection
    };
    
    this.init();
  }

  async init() {
    this.setupEventListeners();
    this.setupKeyboardNavigation();
    await this.loadData();
    this.render();
  }

  setupEventListeners() {
    // Search
    const searchInput = document.getElementById('globalSearch');
    const searchClear = document.getElementById('searchClear');
    
    let searchTimeout;
    searchInput.addEventListener('input', (e) => {
      clearTimeout(searchTimeout);
      searchTimeout = setTimeout(() => {
        this.filters.search = e.target.value;
        this.updateSearchClear();
        this.applyFilters();
      }, this.options.searchDebounce);
    });
    
    searchClear.addEventListener('click', () => {
      searchInput.value = '';
      this.filters.search = '';
      this.updateSearchClear();
      this.applyFilters();
    });

    // Filters
    ['departmentFilter', 'statusFilter', 'locationFilter', 'hireDateFrom', 'hireDateTo'].forEach(id => {
      const element = document.getElementById(id);
      element.addEventListener('change', (e) => {
        const filterKey = id.replace('Filter', '').replace('hireDate', 'hireDate');
        this.filters[filterKey] = e.target.value;
        this.applyFilters();
      });
    });

    // Clear filters
    document.getElementById('clearFilters').addEventListener('click', () => {
      this.clearAllFilters();
    });

    // Page size
    document.getElementById('pageSize').addEventListener('change', (e) => {
      this.options.pageSize = parseInt(e.target.value);
      this.currentPage = 1;
      this.render();
    });

    // Pagination
    document.getElementById('firstPage').addEventListener('click', () => this.goToPage(1));
    document.getElementById('prevPage').addEventListener('click', () => this.goToPage(this.currentPage - 1));
    document.getElementById('nextPage').addEventListener('click', () => this.goToPage(this.currentPage + 1));
    document.getElementById('lastPage').addEventListener('click', () => this.goToPage(this.totalPages));

    // Select all
    document.getElementById('selectAll').addEventListener('change', (e) => {
      this.toggleSelectAll(e.target.checked);
    });

    // Bulk actions
    document.getElementById('bulkEdit').addEventListener('click', () => this.bulkEdit());
    document.getElementById('bulkDelete').addEventListener('click', () => this.bulkDelete());

    // Header actions
    document.getElementById('columnToggle').addEventListener('click', () => this.showColumnModal());
    document.getElementById('exportData').addEventListener('click', () => this.showExportModal());
    document.getElementById('addNew').addEventListener('click', () => this.addNewEmployee());

    // Modal events
    this.setupModalEvents();
  }

  setupModalEvents() {
    // Column modal
    document.getElementById('closeColumnModal').addEventListener('click', () => this.hideColumnModal());
    document.getElementById('resetColumns').addEventListener('click', () => this.resetColumns());
    document.getElementById('applyColumns').addEventListener('click', () => this.applyColumnChanges());

    // Export modal
    document.getElementById('closeExportModal').addEventListener('click', () => this.hideExportModal());
    document.getElementById('cancelExport').addEventListener('click', () => this.hideExportModal());
    document.getElementById('confirmExport').addEventListener('click', () => this.performExport());

    // Close modals on backdrop click
    document.querySelectorAll('.modal').forEach(modal => {
      modal.addEventListener('click', (e) => {
        if (e.target === modal) {
          modal.classList.remove('active');
        }
      });
    });
  }

  setupKeyboardNavigation() {
    document.addEventListener('keydown', (e) => {
      // Escape to close modals
      if (e.key === 'Escape') {
        document.querySelectorAll('.modal.active').forEach(modal => {
          modal.classList.remove('active');
        });
      }
      
      // Ctrl+F to focus search
      if (e.ctrlKey && e.key === 'f') {
        e.preventDefault();
        document.getElementById('globalSearch').focus();
      }
    });
  }

  async loadData() {
    this.showLoading(true);
    
    try {
      // Simulate API call - replace with actual data loading
      await new Promise(resolve => setTimeout(resolve, 1000));
      
      this.data = this.generateSampleData();
      this.applyFilters();
    } catch (error) {
      console.error('Error loading data:', error);
      this.showError('Failed to load data');
    } finally {
      this.showLoading(false);
    }
  }

  generateSampleData() {
    const departments = ['engineering', 'marketing', 'sales', 'hr', 'finance'];
    const positions = {
      engineering: ['Software Engineer', 'Senior Developer', 'Tech Lead', 'DevOps Engineer'],
      marketing: ['Marketing Manager', 'Content Creator', 'SEO Specialist', 'Brand Manager'],
      sales: ['Sales Representative', 'Account Manager', 'Sales Director', 'Business Development'],
      hr: ['HR Manager', 'Recruiter', 'HR Business Partner', 'Compensation Analyst'],
      finance: ['Financial Analyst', 'Accountant', 'Finance Manager', 'Controller']
    };
    const locations = ['new-york', 'san-francisco', 'london', 'remote'];
    const statuses = ['active', 'inactive', 'pending'];
    const firstNames = ['John', 'Jane', 'Michael', 'Sarah', 'David', 'Emily', 'Robert', 'Lisa', 'James', 'Maria'];
    const lastNames = ['Smith', 'Johnson', 'Williams', 'Brown', 'Jones', 'Garcia', 'Miller', 'Davis', 'Rodriguez', 'Martinez'];
    
    const data = [];
    
    for (let i = 1; i <= 150; i++) {
      const firstName = firstNames[Math.floor(Math.random() * firstNames.length)];
      const lastName = lastNames[Math.floor(Math.random() * lastNames.length)];
      const department = departments[Math.floor(Math.random() * departments.length)];
      const position = positions[department][Math.floor(Math.random() * positions[department].length)];
      
      data.push({
        id: i,
        name: `${firstName} ${lastName}`,
        email: `${firstName.toLowerCase()}.${lastName.toLowerCase()}@company.com`,
        department,
        position,
        location: locations[Math.floor(Math.random() * locations.length)],
        hireDate: new Date(2020 + Math.floor(Math.random() * 4), Math.floor(Math.random() * 12), Math.floor(Math.random() * 28) + 1),
        status: statuses[Math.floor(Math.random() * statuses.length)]
      });
    }
    
    return data;
  }

  applyFilters() {
    this.filteredData = this.data.filter(item => {
      // Search filter
      if (this.filters.search) {
        const searchTerm = this.filters.search.toLowerCase();
        const searchableText = `${item.name} ${item.email} ${item.department} ${item.position} ${item.location}`.toLowerCase();
        if (!searchableText.includes(searchTerm)) {
          return false;
        }
      }
      
      // Department filter
      if (this.filters.department && item.department !== this.filters.department) {
        return false;
      }
      
      // Status filter
      if (this.filters.status && item.status !== this.filters.status) {
        return false;
      }
      
      // Location filter
      if (this.filters.location && item.location !== this.filters.location) {
        return false;
      }
      
      // Date range filter
      if (this.filters.hireDateFrom) {
        const fromDate = new Date(this.filters.hireDateFrom);
        if (item.hireDate < fromDate) {
          return false;
        }
      }
      
      if (this.filters.hireDateTo) {
        const toDate = new Date(this.filters.hireDateTo);
        if (item.hireDate > toDate) {
          return false;
        }
      }
      
      return true;
    });
    
    this.applySorting();
    this.currentPage = 1;
    this.selectedRows.clear();
    this.render();
  }

  applySorting() {
    this.filteredData.sort((a, b) => {
      const aVal = a[this.sortState.column];
      const bVal = b[this.sortState.column];
      
      let comparison = 0;
      
      if (aVal instanceof Date && bVal instanceof Date) {
        comparison = aVal.getTime() - bVal.getTime();
      } else if (typeof aVal === 'string' && typeof bVal === 'string') {
        comparison = aVal.localeCompare(bVal);
      } else {
        comparison = aVal < bVal ? -1 : aVal > bVal ? 1 : 0;
      }
      
      return this.sortState.direction === 'asc' ? comparison : -comparison;
    });
  }

  render() {
    this.calculatePagination();
    this.renderTable();
    this.renderPagination();
    this.updateResultsInfo();
    this.updateBulkActions();
  }

  calculatePagination() {
    this.totalPages = Math.ceil(this.filteredData.length / this.options.pageSize);
    if (this.currentPage > this.totalPages) {
      this.currentPage = Math.max(1, this.totalPages);
    }
  }

  renderTable() {
    const tbody = document.getElementById('tableBody');
    const startIndex = (this.currentPage - 1) * this.options.pageSize;
    const endIndex = startIndex + this.options.pageSize;
    const pageData = this.filteredData.slice(startIndex, endIndex);
    
    if (pageData.length === 0) {
      this.showEmptyState(true);
      return;
    }
    
    this.showEmptyState(false);
    
    tbody.innerHTML = pageData.map(item => {
      const isSelected = this.selectedRows.has(item.id);
      return `
        <tr class="${isSelected ? 'selected' : ''}" data-id="${item.id}">
          <td class="select-column">
            <input type="checkbox" ${isSelected ? 'checked' : ''} 
                   onchange="window.dataList.toggleRowSelection(${item.id}, this.checked)">
          </td>
          <td>${this.highlightSearchTerm(item.name)}</td>
          <td>${this.highlightSearchTerm(item.email)}</td>
          <td>${this.formatDepartment(item.department)}</td>
          <td>${this.highlightSearchTerm(item.position)}</td>
          <td>${this.formatLocation(item.location)}</td>
          <td>${this.formatDate(item.hireDate)}</td>
          <td><span class="status-badge status-${item.status}">${item.status}</span></td>
          <td class="actions-column">
            <button class="action-btn edit" onclick="window.dataList.editEmployee(${item.id})" title="Edit">
              Edit
            </button>
            <button class="action-btn delete" onclick="window.dataList.deleteEmployee(${item.id})" title="Delete">
              Delete
            </button>
          </td>
        </tr>
      `;
    }).join('');
    
    // Update sort indicators
    this.updateSortIndicators();
  }

  renderPagination() {
    const numbersContainer = document.getElementById('paginationNumbers');
    const maxVisible = 5;
    const pages = [];
    
    let startPage = Math.max(1, this.currentPage - Math.floor(maxVisible / 2));
    let endPage = Math.min(this.totalPages, startPage + maxVisible - 1);
    
    if (endPage - startPage + 1 < maxVisible) {
      startPage = Math.max(1, endPage - maxVisible + 1);
    }
    
    if (startPage > 1) {
      pages.push(1);
      if (startPage > 2) {
        pages.push('...');
      }
    }
    
    for (let i = startPage; i <= endPage; i++) {
      pages.push(i);
    }
    
    if (endPage < this.totalPages) {
      if (endPage < this.totalPages - 1) {
        pages.push('...');
      }
      pages.push(this.totalPages);
    }
    
    numbersContainer.innerHTML = pages.map(page => {
      if (page === '...') {
        return '<span class="page-ellipsis">...</span>';
      }
      
      const isActive = page === this.currentPage;
      return `
        <button class="page-number ${isActive ? 'active' : ''}" 
                onclick="window.dataList.goToPage(${page})">
          ${page}
        </button>
      `;
    }).join('');
    
    // Update pagination buttons
    document.getElementById('firstPage').disabled = this.currentPage === 1;
    document.getElementById('prevPage').disabled = this.currentPage === 1;
    document.getElementById('nextPage').disabled = this.currentPage === this.totalPages;
    document.getElementById('lastPage').disabled = this.currentPage === this.totalPages;
  }

  updateResultsInfo() {
    const startIndex = (this.currentPage - 1) * this.options.pageSize + 1;
    const endIndex = Math.min(this.currentPage * this.options.pageSize, this.filteredData.length);
    
    document.getElementById('resultsText').textContent = 
      `Showing ${startIndex}-${endIndex} of ${this.filteredData.length} employees`;
    
    document.getElementById('paginationInfo').textContent = 
      `Showing ${startIndex} to ${endIndex} of ${this.filteredData.length} entries`;
  }

  updateBulkActions() {
    const bulkActions = document.getElementById('bulkActions');
    const selectedCount = document.getElementById('selectedCount');
    
    if (this.selectedRows.size > 0) {
      bulkActions.style.display = 'flex';
      selectedCount.textContent = `${this.selectedRows.size} selected`;
    } else {
      bulkActions.style.display = 'none';
    }
  }

  updateSortIndicators() {
    // Remove all sort classes
    document.querySelectorAll('.sortable').forEach(th => {
      th.classList.remove('sort-asc', 'sort-desc');
    });
    
    // Add sort class to current column
    const currentSortHeader = document.querySelector(`[data-column="${this.sortState.column}"]`);
    if (currentSortHeader) {
      currentSortHeader.classList.add(`sort-${this.sortState.direction}`);
    }
  }

  updateSearchClear() {
    const searchClear = document.getElementById('searchClear');
    const searchInput = document.getElementById('globalSearch');
    searchClear.classList.toggle('visible', searchInput.value.length > 0);
  }

  // Event handlers
  goToPage(page) {
    if (page >= 1 && page <= this.totalPages) {
      this.currentPage = page;
      this.render();
    }
  }

  sortBy(column, type) {
    if (this.sortState.column === column) {
      this.sortState.direction = this.sortState.direction === 'asc' ? 'desc' : 'asc';
    } else {
      this.sortState.column = column;
      this.sortState.direction = 'asc';
    }
    
    this.applySorting();
    this.render();
  }

  toggleRowSelection(id, checked) {
    if (checked) {
      this.selectedRows.add(id);
    } else {
      this.selectedRows.delete(id);
    }
    
    this.updateBulkActions();
    this.updateSelectAllState();
  }

  toggleSelectAll(checked) {
    const startIndex = (this.currentPage - 1) * this.options.pageSize;
    const endIndex = startIndex + this.options.pageSize;
    const pageData = this.filteredData.slice(startIndex, endIndex);
    
    pageData.forEach(item => {
      if (checked) {
        this.selectedRows.add(item.id);
      } else {
        this.selectedRows.delete(item.id);
      }
    });
    
    this.render();
  }

  updateSelectAllState() {
    const selectAll = document.getElementById('selectAll');
    const startIndex = (this.currentPage - 1) * this.options.pageSize;
    const endIndex = startIndex + this.options.pageSize;
    const pageData = this.filteredData.slice(startIndex, endIndex);
    
    const selectedOnPage = pageData.filter(item => this.selectedRows.has(item.id)).length;
    
    if (selectedOnPage === 0) {
      selectAll.checked = false;
      selectAll.indeterminate = false;
    } else if (selectedOnPage === pageData.length) {
      selectAll.checked = true;
      selectAll.indeterminate = false;
    } else {
      selectAll.checked = false;
      selectAll.indeterminate = true;
    }
  }

  clearAllFilters() {
    this.filters = {
      search: '',
      department: '',
      status: '',
      location: '',
      hireDateFrom: '',
      hireDateTo: ''
    };
    
    // Reset form inputs
    document.getElementById('globalSearch').value = '';
    document.getElementById('departmentFilter').value = '';
    document.getElementById('statusFilter').value = '';
    document.getElementById('locationFilter').value = '';
    document.getElementById('hireDateFrom').value = '';
    document.getElementById('hireDateTo').value = '';
    
    this.updateSearchClear();
    this.applyFilters();
  }

  // Utility methods
  highlightSearchTerm(text) {
    if (!this.filters.search) return text;
    
    const regex = new RegExp(`(${this.escapeRegex(this.filters.search)})`, 'gi');
    return text.replace(regex, '<span class="highlight">$1</span>');
  }

  escapeRegex(string) {
    return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
  }

  formatDepartment(dept) {
    return dept.charAt(0).toUpperCase() + dept.slice(1);
  }

  formatLocation(location) {
    return location.split('-').map(word => 
      word.charAt(0).toUpperCase() + word.slice(1)
    ).join(' ');
  }

  formatDate(date) {
    return date.toLocaleDateString('en-US', {
      year: 'numeric',
      month: 'short',
      day: 'numeric'
    });
  }

  showLoading(show) {
    document.getElementById('loadingState').style.display = show ? 'flex' : 'none';
    document.querySelector('.table-wrapper').style.display = show ? 'none' : 'block';
  }

  showEmptyState(show) {
    document.getElementById('emptyState').style.display = show ? 'flex' : 'none';
    document.querySelector('.table-wrapper').style.display = show ? 'none' : 'block';
  }

  showError(message) {
    // Implement error display logic
    console.error(message);
  }

  // Modal methods
  showColumnModal() {
    this.renderColumnToggles();
    document.getElementById('columnModal').classList.add('active');
  }

  hideColumnModal() {
    document.getElementById('columnModal').classList.remove('active');
  }

  renderColumnToggles() {
    const container = document.getElementById('columnToggles');
    const columns = [
      { key: 'name', label: 'Name' },
      { key: 'email', label: 'Email' },
      { key: 'department', label: 'Department' },
      { key: 'position', label: 'Position' },
      { key: 'location', label: 'Location' },
      { key: 'hireDate', label: 'Hire Date' },
      { key: 'status', label: 'Status' }
    ];
    
    container.innerHTML = columns.map(col => `
      <label class="column-toggle">
        <input type="checkbox" ${this.visibleColumns.has(col.key) ? 'checked' : ''} 
               data-column="${col.key}">
        <span>${col.label}</span>
      </label>
    `).join('');
  }

  resetColumns() {
    this.visibleColumns = new Set(['name', 'email', 'department', 'position', 'location', 'hireDate', 'status']);
    this.renderColumnToggles();
  }

  applyColumnChanges() {
    const checkboxes = document.querySelectorAll('#columnToggles input[type="checkbox"]');
    this.visibleColumns.clear();
    
    checkboxes.forEach(cb => {
      if (cb.checked) {
        this.visibleColumns.add(cb.dataset.column);
      }
    });
    
    this.updateTableColumns();
    this.hideColumnModal();
  }

  updateTableColumns() {
    // Update table header visibility
    const headers = document.querySelectorAll('.data-table th[data-column]');
    headers.forEach(th => {
      const column = th.dataset.column;
      th.style.display = this.visibleColumns.has(column) ? '' : 'none';
    });
    
    // Update table body visibility
    const rows = document.querySelectorAll('.data-table tbody tr');
    rows.forEach(row => {
      const cells = row.querySelectorAll('td');
      cells.forEach((cell, index) => {
        if (index === 0 || index === cells.length - 1) return; // Skip select and actions columns
        
        const headers = Array.from(document.querySelectorAll('.data-table th'));
        const header = headers[index];
        if (header && header.dataset.column) {
          cell.style.display = this.visibleColumns.has(header.dataset.column) ? '' : 'none';
        }
      });
    });
  }

  showExportModal() {
    document.getElementById('exportModal').classList.add('active');
  }

  hideExportModal() {
    document.getElementById('exportModal').classList.remove('active');
  }

  performExport() {
    const format = document.querySelector('input[name="exportFormat"]:checked').value;
    const scope = document.querySelector('input[name="exportScope"]:checked').value;
    
    let dataToExport;
    
    switch (scope) {
      case 'current':
        const startIndex = (this.currentPage - 1) * this.options.pageSize;
        const endIndex = startIndex + this.options.pageSize;
        dataToExport = this.filteredData.slice(startIndex, endIndex);
        break;
      case 'filtered':
        dataToExport = this.filteredData;
        break;
      case 'all':
        dataToExport = this.data;
        break;
    }
    
    switch (format) {
      case 'csv':
        this.exportToCSV(dataToExport);
        break;
      case 'json':
        this.exportToJSON(dataToExport);
        break;
      case 'print':
        this.printData(dataToExport);
        break;
    }
    
    this.hideExportModal();
  }

  exportToCSV(data) {
    const headers = ['Name', 'Email', 'Department', 'Position', 'Location', 'Hire Date', 'Status'];
    const csvContent = [
      headers.join(','),
      ...data.map(row => [
        `"${row.name}"`,
        `"${row.email}"`,
        `"${this.formatDepartment(row.department)}"`,
        `"${row.position}"`,
        `"${this.formatLocation(row.location)}"`,
        `"${this.formatDate(row.hireDate)}"`,
        `"${row.status}"`
      ].join(','))
    ].join('\n');
    
    const blob = new Blob([csvContent], { type: 'text/csv' });
    const url = window.URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = 'employees.csv';
    a.click();
    window.URL.revokeObjectURL(url);
  }

  exportToJSON(data) {
    const jsonContent = JSON.stringify(data, null, 2);
    const blob = new Blob([jsonContent], { type: 'application/json' });
    const url = window.URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = 'employees.json';
    a.click();
    window.URL.revokeObjectURL(url);
  }

  printData(data) {
    const printWindow = window.open('', '_blank');
    const printContent = `
      <html>
        <head>
          <title>Employee Directory</title>
          <style>
            body { font-family: Arial, sans-serif; margin: 20px; }
            table { width: 100%; border-collapse: collapse; }
            th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
            th { background-color: #f2f2f2; }
          </style>
        </head>
        <body>
          <h1>Employee Directory</h1>
          <table>
            <thead>
              <tr>
                <th>Name</th>
                <th>Email</th>
                <th>Department</th>
                <th>Position</th>
                <th>Location</th>
                <th>Hire Date</th>
                <th>Status</th>
              </tr>
            </thead>
            <tbody>
              ${data.map(row => `
                <tr>
                  <td>${row.name}</td>
                  <td>${row.email}</td>
                  <td>${this.formatDepartment(row.department)}</td>
                  <td>${row.position}</td>
                  <td>${this.formatLocation(row.location)}</td>
                  <td>${this.formatDate(row.hireDate)}</td>
                  <td>${row.status}</td>
                </tr>
              `).join('')}
            </tbody>
          </table>
        </body>
      </html>
    `;
    
    printWindow.document.write(printContent);
    printWindow.document.close();
    printWindow.print();
  }

  // Action methods
  editEmployee(id) {
    const employee = this.data.find(emp => emp.id === id);
    if (employee) {
      console.log('Edit employee:', employee);
      // Implement edit functionality
      alert(`Edit employee: ${employee.name}`);
    }
  }

  deleteEmployee(id) {
    const employee = this.data.find(emp => emp.id === id);
    if (employee && confirm(`Are you sure you want to delete ${employee.name}?`)) {
      this.data = this.data.filter(emp => emp.id !== id);
      this.selectedRows.delete(id);
      this.applyFilters();
    }
  }

  addNewEmployee() {
    console.log('Add new employee');
    // Implement add functionality
    alert('Add new employee functionality would be implemented here');
  }

  bulkEdit() {
    const selectedIds = Array.from(this.selectedRows);
    console.log('Bulk edit:', selectedIds);
    alert(`Bulk edit ${selectedIds.length} employees`);
  }

  bulkDelete() {
    const selectedIds = Array.from(this.selectedRows);
    if (confirm(`Are you sure you want to delete ${selectedIds.length} employees?`)) {
      this.data = this.data.filter(emp => !selectedIds.includes(emp.id));
      this.selectedRows.clear();
      this.applyFilters();
    }
  }
}

// Setup sortable headers
document.addEventListener('DOMContentLoaded', () => {
  // Initialize data list
  window.dataList = new AdvancedDataList();
  
  // Setup sortable headers
  document.querySelectorAll('.sortable').forEach(header => {
    header.addEventListener('click', () => {
      const column = header.dataset.column;
      const type = header.dataset.type;
      window.dataList.sortBy(column, type);
    });
  });
});

// Global functions for external use
function clearAllFilters() {
  if (window.dataList) {
    window.dataList.clearAllFilters();
  }
}

function exportData(format = 'csv') {
  if (window.dataList) {
    window.dataList.showExportModal();
  }
}

function addEmployee() {
  if (window.dataList) {
    window.dataList.addNewEmployee();
  }
}

Customization Options

Data Source

  • API Integration: Replace generateSampleData() with actual API calls
  • Real-time Updates: Add WebSocket support for live data updates
  • Caching: Implement client-side caching for better performance
  • Offline Support: Add service worker for offline functionality
  • Advanced Search: Add field-specific search operators
  • Saved Filters: Allow users to save and load filter presets
  • Quick Filters: Add one-click filter buttons for common searches
  • Search History: Implement search term suggestions and history

Display Options

  • Themes: Add dark mode and custom color schemes
  • Density: Implement compact, normal, and comfortable row heights
  • Card View: Add alternative card-based layout for mobile
  • Infinite Scroll: Replace pagination with infinite scrolling

Export & Actions

  • Additional Formats: Add Excel, PDF export options
  • Bulk Operations: Implement custom bulk actions
  • Audit Trail: Track all data changes and user actions
  • Permissions: Add role-based access control

Accessibility Features

  • Screen Reader Support: Full ARIA labeling and announcements
  • Keyboard Navigation: Complete keyboard accessibility
  • High Contrast: Support for high contrast mode
  • Focus Management: Logical tab order and focus indicators
  • Responsive Text: Support for browser zoom up to 200%

Performance Optimizations

  • Virtual Scrolling: Handle large datasets efficiently
  • Debounced Search: Prevent excessive API calls
  • Lazy Loading: Load data on demand
  • Memoization: Cache expensive calculations
  • Web Workers: Offload heavy processing to background threads

Browser Compatibility

  • ✅ Chrome 60+
  • ✅ Firefox 55+
  • ✅ Safari 12+
  • ✅ Edge 79+
  • ⚠️ IE 11 (with polyfills for modern JavaScript features)

Integration Examples

With React

import { useEffect, useRef } from 'react';

function DataListComponent({ data, onEdit, onDelete }) {
  const containerRef = useRef();
  const dataListRef = useRef();
  
  useEffect(() => {
    dataListRef.current = new AdvancedDataList({
      container: containerRef.current,
      data,
      onEdit,
      onDelete
    });
    
    return () => dataListRef.current?.destroy();
  }, []);
  
  return <div ref={containerRef} />;
}

With Vue.js

export default {
  mounted() {
    this.dataList = new AdvancedDataList({
      container: this.$refs.container,
      data: this.employees,
      onEdit: this.handleEdit,
      onDelete: this.handleDelete
    });
  },
  beforeDestroy() {
    this.dataList?.destroy();
  }
};

HTML

386

lines

CSS

706

lines


                <div class="data-list-container">
  <!-- Header Section -->
  <div class="data-list-header">
    <div class="header-content">
      <h2 class="list-title">Employee Directory</h2>
      <p class="list-description">Manage and view employee information with advanced filtering and sorting</p>
    </div>
    
    <!-- Action Buttons -->
    <div class="header-actions">
      <button class="btn btn-secondary" id="columnToggle">
        <svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor">
          <rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
          <line x1="9" y1="9" x2="15" y2="9"></line>
          <line x1="9" y1="15" x2="15" y2="15"></line>
        </svg>
        Columns
      </button>
      <button class="btn btn-secondary" id="exportData">
        <svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor">
          <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
          <polyline points="7,10 12,15 17,10"></polyline>
          <line x1="12" y1="15" x2="12" y2="3"></line>
        </svg>
        Export
      </button>
      <button class="btn btn-primary" id="addNew">
        <svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor">
          <line x1="12" y1="5" x2="12" y2="19"></line>
          <line x1="5" y1="12" x2="19" y2="12"></line>
        </svg>
        Add Employee
      </button>
    </div>
  </div>

  <!-- Filters Section -->
  <div class="filters-section">
    <!-- Search Bar -->
    <div class="search-container">
      <div class="search-input-wrapper">
        <svg class="search-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor">
          <circle cx="11" cy="11" r="8"></circle>
          <path d="m21 21-4.35-4.35"></path>
        </svg>
        <input 
          type="text" 
          id="globalSearch" 
          class="search-input" 
          placeholder="Search employees..."
          aria-label="Search employees"
        >
        <button class="search-clear" id="searchClear" aria-label="Clear search">
          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor">
            <line x1="18" y1="6" x2="6" y2="18"></line>
            <line x1="6" y1="6" x2="18" y2="18"></line>
          </svg>
        </button>
      </div>
    </div>

    <!-- Advanced Filters -->
    <div class="advanced-filters">
      <div class="filter-group">
        <label for="departmentFilter">Department</label>
        <select id="departmentFilter" class="filter-select">
          <option value="">All Departments</option>
          <option value="engineering">Engineering</option>
          <option value="marketing">Marketing</option>
          <option value="sales">Sales</option>
          <option value="hr">Human Resources</option>
          <option value="finance">Finance</option>
        </select>
      </div>

      <div class="filter-group">
        <label for="statusFilter">Status</label>
        <select id="statusFilter" class="filter-select">
          <option value="">All Status</option>
          <option value="active">Active</option>
          <option value="inactive">Inactive</option>
          <option value="pending">Pending</option>
        </select>
      </div>

      <div class="filter-group">
        <label for="locationFilter">Location</label>
        <select id="locationFilter" class="filter-select">
          <option value="">All Locations</option>
          <option value="new-york">New York</option>
          <option value="san-francisco">San Francisco</option>
          <option value="london">London</option>
          <option value="remote">Remote</option>
        </select>
      </div>

      <div class="filter-group">
        <label for="hireDateFrom">Hire Date From</label>
        <input type="date" id="hireDateFrom" class="filter-input">
      </div>

      <div class="filter-group">
        <label for="hireDateTo">Hire Date To</label>
        <input type="date" id="hireDateTo" class="filter-input">
      </div>

      <button class="btn btn-secondary" id="clearFilters">
        Clear Filters
      </button>
    </div>
  </div>

  <!-- Results Info -->
  <div class="results-info">
    <div class="results-count">
      <span id="resultsText">Showing 1-10 of 150 employees</span>
    </div>
    
    <div class="results-actions">
      <div class="page-size-selector">
        <label for="pageSize">Show:</label>
        <select id="pageSize" class="page-size-select">
          <option value="10">10</option>
          <option value="25" selected>25</option>
          <option value="50">50</option>
          <option value="100">100</option>
        </select>
        <span>per page</span>
      </div>
      
      <div class="bulk-actions" id="bulkActions" style="display: none;">
        <span class="selected-count" id="selectedCount">2 selected</span>
        <button class="btn btn-sm btn-secondary" id="bulkEdit">Edit</button>
        <button class="btn btn-sm btn-danger" id="bulkDelete">Delete</button>
      </div>
    </div>
  </div>

  <!-- Data Table -->
  <div class="table-container">
    <div class="table-wrapper">
      <table class="data-table" id="dataTable">
        <thead>
          <tr>
            <th class="select-column">
              <input type="checkbox" id="selectAll" aria-label="Select all rows">
            </th>
            <th class="sortable" data-column="name" data-type="string">
              <div class="th-content">
                <span>Name</span>
                <div class="sort-indicators">
                  <svg class="sort-icon sort-asc" viewBox="0 0 24 24" fill="none" stroke="currentColor">
                    <polyline points="18,15 12,9 6,15"></polyline>
                  </svg>
                  <svg class="sort-icon sort-desc" viewBox="0 0 24 24" fill="none" stroke="currentColor">
                    <polyline points="6,9 12,15 18,9"></polyline>
                  </svg>
                </div>
              </div>
            </th>
            <th class="sortable" data-column="email" data-type="string">
              <div class="th-content">
                <span>Email</span>
                <div class="sort-indicators">
                  <svg class="sort-icon sort-asc" viewBox="0 0 24 24" fill="none" stroke="currentColor">
                    <polyline points="18,15 12,9 6,15"></polyline>
                  </svg>
                  <svg class="sort-icon sort-desc" viewBox="0 0 24 24" fill="none" stroke="currentColor">
                    <polyline points="6,9 12,15 18,9"></polyline>
                  </svg>
                </div>
              </div>
            </th>
            <th class="sortable" data-column="department" data-type="string">
              <div class="th-content">
                <span>Department</span>
                <div class="sort-indicators">
                  <svg class="sort-icon sort-asc" viewBox="0 0 24 24" fill="none" stroke="currentColor">
                    <polyline points="18,15 12,9 6,15"></polyline>
                  </svg>
                  <svg class="sort-icon sort-desc" viewBox="0 0 24 24" fill="none" stroke="currentColor">
                    <polyline points="6,9 12,15 18,9"></polyline>
                  </svg>
                </div>
              </div>
            </th>
            <th class="sortable" data-column="position" data-type="string">
              <div class="th-content">
                <span>Position</span>
                <div class="sort-indicators">
                  <svg class="sort-icon sort-asc" viewBox="0 0 24 24" fill="none" stroke="currentColor">
                    <polyline points="18,15 12,9 6,15"></polyline>
                  </svg>
                  <svg class="sort-icon sort-desc" viewBox="0 0 24 24" fill="none" stroke="currentColor">
                    <polyline points="6,9 12,15 18,9"></polyline>
                  </svg>
                </div>
              </div>
            </th>
            <th class="sortable" data-column="location" data-type="string">
              <div class="th-content">
                <span>Location</span>
                <div class="sort-indicators">
                  <svg class="sort-icon sort-asc" viewBox="0 0 24 24" fill="none" stroke="currentColor">
                    <polyline points="18,15 12,9 6,15"></polyline>
                  </svg>
                  <svg class="sort-icon sort-desc" viewBox="0 0 24 24" fill="none" stroke="currentColor">
                    <polyline points="6,9 12,15 18,9"></polyline>
                  </svg>
                </div>
              </div>
            </th>
            <th class="sortable" data-column="hireDate" data-type="date">
              <div class="th-content">
                <span>Hire Date</span>
                <div class="sort-indicators">
                  <svg class="sort-icon sort-asc" viewBox="0 0 24 24" fill="none" stroke="currentColor">
                    <polyline points="18,15 12,9 6,15"></polyline>
                  </svg>
                  <svg class="sort-icon sort-desc" viewBox="0 0 24 24" fill="none" stroke="currentColor">
                    <polyline points="6,9 12,15 18,9"></polyline>
                  </svg>
                </div>
              </div>
            </th>
            <th class="sortable" data-column="status" data-type="string">
              <div class="th-content">
                <span>Status</span>
                <div class="sort-indicators">
                  <svg class="sort-icon sort-asc" viewBox="0 0 24 24" fill="none" stroke="currentColor">
                    <polyline points="18,15 12,9 6,15"></polyline>
                  </svg>
                  <svg class="sort-icon sort-desc" viewBox="0 0 24 24" fill="none" stroke="currentColor">
                    <polyline points="6,9 12,15 18,9"></polyline>
                  </svg>
                </div>
              </div>
            </th>
            <th class="actions-column">Actions</th>
          </tr>
        </thead>
        <tbody id="tableBody">
          <!-- Data rows will be populated by JavaScript -->
        </tbody>
      </table>
    </div>
    
    <!-- Loading State -->
    <div class="loading-state" id="loadingState" style="display: none;">
      <div class="loading-spinner"></div>
      <p>Loading data...</p>
    </div>
    
    <!-- Empty State -->
    <div class="empty-state" id="emptyState" style="display: none;">
      <svg class="empty-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor">
        <circle cx="11" cy="11" r="8"></circle>
        <path d="m21 21-4.35-4.35"></path>
      </svg>
      <h3>No employees found</h3>
      <p>Try adjusting your search criteria or filters</p>
      <button class="btn btn-primary" onclick="clearAllFilters()">Clear All Filters</button>
    </div>
  </div>

  <!-- Pagination -->
  <div class="pagination-container">
    <div class="pagination-info">
      <span id="paginationInfo">Showing 1 to 25 of 150 entries</span>
    </div>
    
    <nav class="pagination" aria-label="Data table pagination">
      <button class="pagination-btn" id="firstPage" aria-label="First page">
        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor">
          <polyline points="11,17 6,12 11,7"></polyline>
          <polyline points="18,17 13,12 18,7"></polyline>
        </svg>
      </button>
      <button class="pagination-btn" id="prevPage" aria-label="Previous page">
        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor">
          <polyline points="15,18 9,12 15,6"></polyline>
        </svg>
      </button>
      
      <div class="pagination-numbers" id="paginationNumbers">
        <!-- Page numbers will be populated by JavaScript -->
      </div>
      
      <button class="pagination-btn" id="nextPage" aria-label="Next page">
        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor">
          <polyline points="9,18 15,12 9,6"></polyline>
        </svg>
      </button>
      <button class="pagination-btn" id="lastPage" aria-label="Last page">
        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor">
          <polyline points="13,17 18,12 13,7"></polyline>
          <polyline points="6,17 11,12 6,7"></polyline>
        </svg>
      </button>
    </nav>
  </div>

  <!-- Column Toggle Modal -->
  <div class="modal" id="columnModal">
    <div class="modal-content">
      <div class="modal-header">
        <h3>Manage Columns</h3>
        <button class="modal-close" id="closeColumnModal">
          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor">
            <line x1="18" y1="6" x2="6" y2="18"></line>
            <line x1="6" y1="6" x2="18" y2="18"></line>
          </svg>
        </button>
      </div>
      <div class="modal-body">
        <p>Select which columns to display in the table:</p>
        <div class="column-toggles" id="columnToggles">
          <!-- Column toggles will be populated by JavaScript -->
        </div>
      </div>
      <div class="modal-footer">
        <button class="btn btn-secondary" id="resetColumns">Reset to Default</button>
        <button class="btn btn-primary" id="applyColumns">Apply Changes</button>
      </div>
    </div>
  </div>

  <!-- Export Modal -->
  <div class="modal" id="exportModal">
    <div class="modal-content">
      <div class="modal-header">
        <h3>Export Data</h3>
        <button class="modal-close" id="closeExportModal">
          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor">
            <line x1="18" y1="6" x2="6" y2="18"></line>
            <line x1="6" y1="6" x2="18" y2="18"></line>
          </svg>
        </button>
      </div>
      <div class="modal-body">
        <p>Choose export format and options:</p>
        <div class="export-options">
          <div class="export-format">
            <label>Export Format:</label>
            <div class="radio-group">
              <label class="radio-option">
                <input type="radio" name="exportFormat" value="csv" checked>
                <span>CSV</span>
              </label>
              <label class="radio-option">
                <input type="radio" name="exportFormat" value="json">
                <span>JSON</span>
              </label>
              <label class="radio-option">
                <input type="radio" name="exportFormat" value="print">
                <span>Print</span>
              </label>
            </div>
          </div>
          
          <div class="export-scope">
            <label>Export Scope:</label>
            <div class="radio-group">
              <label class="radio-option">
                <input type="radio" name="exportScope" value="current" checked>
                <span>Current Page</span>
              </label>
              <label class="radio-option">
                <input type="radio" name="exportScope" value="filtered">
                <span>All Filtered Results</span>
              </label>
              <label class="radio-option">
                <input type="radio" name="exportScope" value="all">
                <span>All Data</span>
              </label>
            </div>
          </div>
        </div>
      </div>
      <div class="modal-footer">
        <button class="btn btn-secondary" id="cancelExport">Cancel</button>
        <button class="btn btn-primary" id="confirmExport">Export</button>
      </div>
    </div>
  </div>
</div>

              
386lines
15408characters
HTMLLanguage

Related Code Snippets

Explore template packs

Need larger building blocks? Browse responsive landing pages and component bundles.

Open HTML Template Library →