Table Components

Beautiful, responsive table components built with TailwindCSS. Perfect for displaying data with sorting, filtering, and modern designs.

1. Simple Table

A clean and simple table with avatars, names, and actions.

Avatar Name Email Role Actions
A
1. Alice Johnson alice@example.com Admin
B
2. Bob Smith bob@example.com User
C
3. Charlie Brown charlie@example.com Editor
D
4. Diana Prince diana@example.com Moderator
E
5. Ethan Hunt ethan@example.com User
F
6. Fiona Green fiona@example.com Admin
G
7. George Lucas george@example.com Editor
H
8. Hannah Montana hannah@example.com User
I
9. Isaac Newton isaac@example.com Moderator
J
10. Julia Roberts julia@example.com Admin
<!-- Simple Table -->
<div class="overflow-x-auto">
  <table class="min-w-full divide-y divide-gray-200">
    <thead class="bg-gray-50">
      <tr>
        <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Avatar</th>
        <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Name</th>
        <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Email</th>
        <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Role</th>
        <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th>
      </tr>
    </thead>
    <tbody class="bg-white divide-y divide-gray-200">
      <tr>
        <td class="px-6 py-4 whitespace-nowrap">
          <div class="flex items-center">
            <div class="w-10 h-10 bg-blue-500 rounded-full flex items-center justify-center text-white font-bold">A</div>
          </div>
        </td>
        <td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">1. Alice Johnson</td>
        <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">alice@example.com</td>
        <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">Admin</td>
        <td class="px-6 py-4 whitespace-nowrap text-sm font-medium">
          <button class="text-indigo-600 hover:text-indigo-900">Edit</button>
        </td>
      </tr>
      <!-- Add more rows for 2-10 users similarly -->
    </tbody>
  </table>
</div>
<template>
  <!-- Simple Table -->
  <div class="overflow-x-auto">
    <table class="min-w-full divide-y divide-gray-200">
      <thead class="bg-gray-50">
        <tr>
          <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Avatar</th>
          <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Name</th>
          <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Email</th>
          <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Role</th>
          <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th>
        </tr>
      </thead>
      <tbody class="bg-white divide-y divide-gray-200">
        <tr v-for="user in users" :key="user.id">
          <td class="px-6 py-4 whitespace-nowrap">
            <div class="flex items-center">
              <div :class="['w-10 h-10 rounded-full flex items-center justify-center text-white font-bold', user.avatarBg]">{{ user.initial }}</div>
            </div>
          </td>
          <td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">{{ user.id }}. {{ user.name }}</td>
          <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{{ user.email }}</td>
          <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{{ user.role }}</td>
          <td class="px-6 py-4 whitespace-nowrap text-sm font-medium">
            <button class="text-indigo-600 hover:text-indigo-900">Edit</button>
          </td>
        </tr>
      </tbody>
    </table>
  </div>
</template>

<script setup>
const users = [
  { id: 1, initial: 'A', avatarBg: 'bg-blue-500', name: 'Alice Johnson', email: 'alice@example.com', role: 'Admin' },
  { id: 2, initial: 'B', avatarBg: 'bg-green-500', name: 'Bob Smith', email: 'bob@example.com', role: 'User' },
  // Add more users up to 10
]
</script>
import React from 'react'

export default function SimpleTable() {
  const users = [
    { id: 1, initial: 'A', avatarBg: 'bg-blue-500', name: 'Alice Johnson', email: 'alice@example.com', role: 'Admin' },
    { id: 2, initial: 'B', avatarBg: 'bg-green-500', name: 'Bob Smith', email: 'bob@example.com', role: 'User' },
    // Add more users up to 10
  ]

  return (
    <div className="overflow-x-auto">
      <table className="min-w-full divide-y divide-gray-200">
        <thead className="bg-gray-50">
          <tr>
            <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Avatar</th>
            <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Name</th>
            <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Email</th>
            <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Role</th>
            <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th>
          </tr>
        </thead>
        <tbody className="bg-white divide-y divide-gray-200">
          {users.map(user => (
            <tr key={user.id}>
              <td className="px-6 py-4 whitespace-nowrap">
                <div className="flex items-center">
                  <div className={`w-10 h-10 rounded-full flex items-center justify-center text-white font-bold ${user.avatarBg}`}>{user.initial}</div>
                </div>
              </td>
              <td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">{user.id}. {user.name}</td>
              <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{user.email}</td>
              <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{user.role}</td>
              <td className="px-6 py-4 whitespace-nowrap text-sm font-medium">
                <button className="text-indigo-600 hover:text-indigo-900">Edit</button>
              </td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}

2. Striped Rows Table

A table with alternating striped rows using white and gray colors for better readability.

Avatar Name Email Role Actions
Avatar
1. Alice Johnson alice@example.com Admin
Avatar
2. Bob Smith bob@example.com User
Avatar
3. Charlie Brown charlie@example.com Editor
Avatar
4. Diana Prince diana@example.com Moderator
Avatar
5. Ethan Hunt ethan@example.com User
Avatar
6. Fiona Green fiona@example.com Admin
Avatar
7. George Lucas george@example.com Editor
Avatar
8. Hannah Montana hannah@example.com User
Avatar
9. Isaac Newton isaac@example.com Moderator
Avatar
10. Julia Roberts julia@example.com Admin
<!-- Striped Rows Table -->
<div class="overflow-x-auto">
  <table class="min-w-full divide-y divide-gray-200">
    <thead class="bg-gray-50">
      <tr>
        <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Avatar</th>
        <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Name</th>
        <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Email</th>
        <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Role</th>
        <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th>
      </tr>
    </thead>
    <tbody class="bg-white divide-y divide-gray-200">
      <tr class="bg-white">
        <td class="px-6 py-4 whitespace-nowrap">
          <div class="flex items-center">
            <img src="https://images.unsplash.com/photo-1494790108755-2616b612b786?auto=format&fit=crop&w=100&q=80" alt="Avatar" class="w-10 h-10 rounded-full">
          </div>
        </td>
        <td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">1. Alice Johnson</td>
        <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">alice@example.com</td>
        <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">Admin</td>
        <td class="px-6 py-4 whitespace-nowrap text-sm font-medium">
          <button class="text-indigo-600 hover:text-indigo-900">Edit</button>
        </td>
      </tr>
      <tr class="bg-gray-50">
        <!-- Similar structure for other rows -->
      </tr>
      <!-- Add more rows for 3-10 users -->
    </tbody>
  </table>
</div>
<template>
  <!-- Striped Rows Table -->
  <div class="overflow-x-auto">
    <table class="min-w-full divide-y divide-gray-200">
      <thead class="bg-gray-50">
        <tr>
          <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Avatar</th>
          <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Name</th>
          <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Email</th>
          <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Role</th>
          <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th>
        </tr>
      </thead>
      <tbody class="bg-white divide-y divide-gray-200">
        <tr v-for="(user, index) in users" :key="user.id" :class="index % 2 === 0 ? 'bg-white' : 'bg-gray-50'">
          <td class="px-6 py-4 whitespace-nowrap">
            <div class="flex items-center">
              <img :src="user.image" alt="Avatar" class="w-10 h-10 rounded-full">
            </div>
          </td>
          <td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">{{ user.id }}. {{ user.name }}</td>
          <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{{ user.email }}</td>
          <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{{ user.role }}</td>
          <td class="px-6 py-4 whitespace-nowrap text-sm font-medium">
            <button class="text-indigo-600 hover:text-indigo-900">Edit</button>
          </td>
        </tr>
      </tbody>
    </table>
  </div>
</template>

<script setup>
const users = [
  { id: 1, name: 'Alice Johnson', email: 'alice@example.com', role: 'Admin', image: 'https://images.unsplash.com/photo-1494790108755-2616b612b786?auto=format&fit=crop&w=100&q=80' },
  { id: 2, name: 'Bob Smith', email: 'bob@example.com', role: 'User', image: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?auto=format&fit=crop&w=100&q=80' },
  // Add more users up to 10
]
</script>
import React from 'react'

export default function StripedRowsTable() {
  const users = [
    { id: 1, name: 'Alice Johnson', email: 'alice@example.com', role: 'Admin', image: 'https://images.unsplash.com/photo-1494790108755-2616b612b786?auto=format&fit=crop&w=100&q=80' },
    { id: 2, name: 'Bob Smith', email: 'bob@example.com', role: 'User', image: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?auto=format&fit=crop&w=100&q=80' },
    // Add more users up to 10
  ]

  return (
    <div className="overflow-x-auto">
      <table className="min-w-full divide-y divide-gray-200">
        <thead className="bg-gray-50">
          <tr>
            <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Avatar</th>
            <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Name</th>
            <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Email</th>
            <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Role</th>
            <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th>
          </tr>
        </thead>
        <tbody className="bg-white divide-y divide-gray-200">
          {users.map((user, index) => (
            <tr key={user.id} className={index % 2 === 0 ? 'bg-white' : 'bg-gray-50'}>
              <td className="px-6 py-4 whitespace-nowrap">
                <div className="flex items-center">
                  <img src={user.image} alt="Avatar" className="w-10 h-10 rounded-full" />
                </div>
              </td>
              <td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">{user.id}. {user.name}</td>
              <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{user.email}</td>
              <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{user.role}</td>
              <td className="px-6 py-4 whitespace-nowrap text-sm font-medium">
                <button className="text-indigo-600 hover:text-indigo-900">Edit</button>
              </td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}

3. Table with Pagination

A table with pagination controls to navigate through large datasets efficiently.

Avatar Name Email Role Actions
Showing to of results
<!-- Table with Pagination -->
<div x-data="{ currentPage: 1, itemsPerPage: 5, users: [
  { id: 1, initial: 'A', avatarBg: 'bg-blue-500', name: 'Alice Johnson', email: 'alice@example.com', role: 'Admin' },
  { id: 2, initial: 'B', avatarBg: 'bg-green-500', name: 'Bob Smith', email: 'bob@example.com', role: 'User' },
  // Add more users up to 10
]}">
  <div class="overflow-x-auto">
    <table class="min-w-full divide-y divide-gray-200">
      <thead class="bg-gray-50">
        <tr>
          <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Avatar</th>
          <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Name</th>
          <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Email</th>
          <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Role</th>
          <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th>
        </tr>
      </thead>
      <tbody class="bg-white divide-y divide-gray-200">
        <template x-for="user in paginatedUsers" :key="user.id">
          <tr>
            <td class="px-6 py-4 whitespace-nowrap">
              <div class="flex items-center">
                <div :class="['w-10 h-10 rounded-full flex items-center justify-center text-white font-bold', user.avatarBg]" x-text="user.initial"></div>
              </div>
            </td>
            <td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900" x-text="user.id + '. ' + user.name"></td>
            <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500" x-text="user.email"></td>
            <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500" x-text="user.role"></td>
            <td class="px-6 py-4 whitespace-nowrap text-sm font-medium">
              <button class="text-indigo-600 hover:text-indigo-900">Edit</button>
            </td>
          </tr>
        </template>
      </tbody>
    </table>
  </div>

  <!-- Pagination Controls -->
  <div class="flex items-center justify-between mt-6">
    <div class="text-sm text-gray-700">
      Showing <span class="font-medium" x-text="(currentPage - 1) * itemsPerPage + 1"></span> to <span class="font-medium" x-text="Math.min(currentPage * itemsPerPage, users.length)"></span> of <span class="font-medium" x-text="users.length"></span> results
    </div>
    <div class="flex items-center space-x-2">
      <button @click="currentPage = Math.max(1, currentPage - 1)" :disabled="currentPage === 1" class="px-3 py-2 text-sm font-medium text-gray-500 bg-white border border-gray-300 rounded-md hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed">Previous</button>
      <template x-for="page in totalPages" :key="page">
        <button @click="currentPage = page" :class="page === currentPage ? 'bg-purple-600 text-white' : 'text-gray-500 bg-white hover:bg-gray-50'" class="px-3 py-2 text-sm font-medium border border-gray-300 rounded-md" x-text="page"></button>
      </template>
      <button @click="currentPage = Math.min(totalPages, currentPage + 1)" :disabled="currentPage === totalPages" class="px-3 py-2 text-sm font-medium text-gray-500 bg-white border border-gray-300 rounded-md hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed">Next</button>
    </div>
  </div>
</div>
<template>
  <!-- Table with Pagination -->
  <div>
    <div class="overflow-x-auto">
      <table class="min-w-full divide-y divide-gray-200">
        <thead class="bg-gray-50">
          <tr>
            <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Avatar</th>
            <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Name</th>
            <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Email</th>
            <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Role</th>
            <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th>
          </tr>
        </thead>
        <tbody class="bg-white divide-y divide-gray-200">
          <tr v-for="user in paginatedUsers" :key="user.id">
            <td class="px-6 py-4 whitespace-nowrap">
              <div class="flex items-center">
                <div :class="['w-10 h-10 rounded-full flex items-center justify-center text-white font-bold', user.avatarBg]">{{ user.initial }}</div>
              </div>
            </td>
            <td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">{{ user.id }}. {{ user.name }}</td>
            <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{{ user.email }}</td>
            <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{{ user.role }}</td>
            <td class="px-6 py-4 whitespace-nowrap text-sm font-medium">
              <button class="text-indigo-600 hover:text-indigo-900">Edit</button>
            </td>
          </tr>
        </tbody>
      </table>
    </div>

    <!-- Pagination Controls -->
    <div class="flex items-center justify-between mt-6">
      <div class="text-sm text-gray-700">
        Showing <span class="font-medium">{{ (currentPage - 1) * itemsPerPage + 1 }}</span> to <span class="font-medium">{{ Math.min(currentPage * itemsPerPage, users.length) }}</span> of <span class="font-medium">{{ users.length }}</span> results
      </div>
      <div class="flex items-center space-x-2">
        <button @click="currentPage = Math.max(1, currentPage - 1)" :disabled="currentPage === 1" class="px-3 py-2 text-sm font-medium text-gray-500 bg-white border border-gray-300 rounded-md hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed">Previous</button>
        <button v-for="page in totalPages" :key="page" @click="currentPage = page" :class="page === currentPage ? 'bg-purple-600 text-white' : 'text-gray-500 bg-white hover:bg-gray-50'" class="px-3 py-2 text-sm font-medium border border-gray-300 rounded-md">{{ page }}</button>
        <button @click="currentPage = Math.min(totalPages, currentPage + 1)" :disabled="currentPage === totalPages" class="px-3 py-2 text-sm font-medium text-gray-500 bg-white border border-gray-300 rounded-md hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed">Next</button>
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref, computed } from 'vue'

const currentPage = ref(1)
const itemsPerPage = ref(5)
const users = ref([
  { id: 1, initial: 'A', avatarBg: 'bg-blue-500', name: 'Alice Johnson', email: 'alice@example.com', role: 'Admin' },
  { id: 2, initial: 'B', avatarBg: 'bg-green-500', name: 'Bob Smith', email: 'bob@example.com', role: 'User' },
  // Add more users up to 10
])

const totalPages = computed(() => Math.ceil(users.value.length / itemsPerPage.value))
const paginatedUsers = computed(() => {
  const start = (currentPage.value - 1) * itemsPerPage.value
  const end = start + itemsPerPage.value
  return users.value.slice(start, end)
})
</script>
import React, { useState, useMemo } from 'react'

export default function TableWithPagination() {
  const [currentPage, setCurrentPage] = useState(1)
  const itemsPerPage = 5
  const users = [
    { id: 1, initial: 'A', avatarBg: 'bg-blue-500', name: 'Alice Johnson', email: 'alice@example.com', role: 'Admin' },
    { id: 2, initial: 'B', avatarBg: 'bg-green-500', name: 'Bob Smith', email: 'bob@example.com', role: 'User' },
    // Add more users up to 10
  ]

  const totalPages = Math.ceil(users.length / itemsPerPage)
  const paginatedUsers = useMemo(() => {
    const start = (currentPage - 1) * itemsPerPage
    const end = start + itemsPerPage
    return users.slice(start, end)
  }, [currentPage, users])

  return (
    <div>
      <div className="overflow-x-auto">
        <table className="min-w-full divide-y divide-gray-200">
          <thead className="bg-gray-50">
            <tr>
              <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Avatar</th>
              <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Name</th>
              <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Email</th>
              <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Role</th>
              <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th>
            </tr>
          </thead>
          <tbody className="bg-white divide-y divide-gray-200">
            {paginatedUsers.map(user => (
              <tr key={user.id}>
                <td className="px-6 py-4 whitespace-nowrap">
                  <div className="flex items-center">
                    <div className={`w-10 h-10 rounded-full flex items-center justify-center text-white font-bold ${user.avatarBg}`}>{user.initial}</div>
                  </div>
                </td>
                <td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">{user.id}. {user.name}</td>
                <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{user.email}</td>
                <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{user.role}</td>
                <td className="px-6 py-4 whitespace-nowrap text-sm font-medium">
                  <button className="text-indigo-600 hover:text-indigo-900">Edit</button>
                </td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>

      <!-- Pagination Controls -->
      <div className="flex items-center justify-between mt-6">
        <div className="text-sm text-gray-700">
          Showing <span className="font-medium">{(currentPage - 1) * itemsPerPage + 1}</span> to <span className="font-medium">{Math.min(currentPage * itemsPerPage, users.length)}</span> of <span className="font-medium">{users.length}</span> results
        </div>
        <div className="flex items-center space-x-2">
          <button 
            onClick={() => setCurrentPage(Math.max(1, currentPage - 1))} 
            disabled={currentPage === 1} 
            className="px-3 py-2 text-sm font-medium text-gray-500 bg-white border border-gray-300 rounded-md hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed"
          >
            Previous
          </button>
          {Array.from({ length: totalPages }, (_, i) => i + 1).map(page => (
            <button 
              key={page} 
              onClick={() => setCurrentPage(page)} 
              className={`px-3 py-2 text-sm font-medium border border-gray-300 rounded-md ${page === currentPage ? 'bg-purple-600 text-white' : 'text-gray-500 bg-white hover:bg-gray-50'}`}
            >
              {page}
            </button>
          ))}
          <button 
            onClick={() => setCurrentPage(Math.min(totalPages, currentPage + 1))} 
            disabled={currentPage === totalPages} 
            className="px-3 py-2 text-sm font-medium text-gray-500 bg-white border border-gray-300 rounded-md hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed"
          >
            Next
          </button>
        </div>
      </div>
    </div>
  );
}

4. Advanced Table with Search, Filter, and Pagination

A comprehensive table with search, category filter dropdown, actions, and pagination for user management.

Choose category
User name Category Role Email Actions
<!-- Advanced Table with Search, Filter, and Pagination -->
<div x-data="{ searchTerm: '', selectedCategory: 'All', currentPage: 1, itemsPerPage: 5, dropdownOpen: false, users: [
  { id: 1, name: 'Alice Johnson', category: 'Admin', role: 'Manager', email: 'alice@example.com' },
  // Add more users up to 10
], get filteredUsers() { return this.users.filter(user => (this.selectedCategory === 'All' || user.category === this.selectedCategory) && user.name.toLowerCase().includes(this.searchTerm.toLowerCase())); }, get totalPages() { return Math.ceil(this.filteredUsers.length / this.itemsPerPage); }, get paginatedUsers() { const start = (this.currentPage - 1) * this.itemsPerPage; const end = start + this.itemsPerPage; return this.filteredUsers.slice(start, end); }, getRoleBadgeClass(role) { const roleColors = { 'Manager': 'bg-purple-100 text-purple-800', 'Developer': 'bg-blue-100 text-blue-800', 'Designer': 'bg-pink-100 text-pink-800', 'Analyst': 'bg-green-100 text-green-800', 'Tester': 'bg-yellow-100 text-yellow-800', 'Supervisor': 'bg-red-100 text-red-800', 'Writer': 'bg-indigo-100 text-indigo-800', 'Support': 'bg-gray-100 text-gray-800', 'Researcher': 'bg-cyan-100 text-cyan-800', 'Director': 'bg-orange-100 text-orange-800' }; return roleColors[role] || 'bg-gray-100 text-gray-800'; }, selectCategory(category) { this.selectedCategory = category; this.currentPage = 1; this.dropdownOpen = false; } }" x-init="$watch('searchTerm', () => { currentPage = 1; })" @click.away="dropdownOpen = false">
  <!-- Search and Filter -->
  <div class="flex flex-col md:flex-row items-center justify-between space-y-3 md:space-y-0 md:space-x-4 p-4 bg-white rounded-lg shadow-sm border border-gray-200 mb-4">
    <div class="w-full md:w-1/2">
      <form class="flex items-center">
        <label for="simple-search" class="sr-only">Search</label>
        <div class="relative w-full">
          <div class="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
            <svg aria-hidden="true" class="w-5 h-5 text-gray-500" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
              <path fill-rule="evenodd" d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z" clip-rule="evenodd" />
            </svg>
          </div>
          <input x-model="searchTerm" type="text" id="simple-search" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full pl-10 p-2" placeholder="Search users..." required="">
        </div>
      </form>
    </div>
    <div class="w-full md:w-auto flex flex-col md:flex-row space-y-2 md:space-y-0 items-stretch md:items-center justify-end md:space-x-3 flex-shrink-0">
      <button type="button" class="flex items-center justify-center text-white bg-blue-600 hover:bg-blue-700 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-4 py-2 focus:outline-none">
        <svg class="h-3.5 w-3.5 mr-2" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
          <path clip-rule="evenodd" fill-rule="evenodd" d="M10 3a1 1 0 011 1v5h5a1 1 0 110 2h-5v5a1 1 0 11-2 0v-5H4a1 1 0 110-2h5V4a1 1 0 011-1z" />
        </svg>
        Add user
      </button>
      <div class="relative">
        <button @click="dropdownOpen = !dropdownOpen" class="w-full md:w-auto flex items-center justify-center py-2 px-4 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded-lg border border-gray-200 hover:bg-gray-100 hover:text-blue-700 focus:z-10 focus:ring-4 focus:ring-gray-200" type="button">
          <svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" class="h-4 w-4 mr-2 text-gray-400" viewBox="0 0 20 20" fill="currentColor">
            <path fill-rule="evenodd" d="M3 3a1 1 0 011-1h12a1 1 0 011 1v3a1 1 0 01-.293.707L12 11.414V15a1 1 0 01-.293.707l-2 2A1 1 0 018 17v-5.586L3.293 6.707A1 1 0 013 6V3z" clip-rule="evenodd" />
          </svg>
          <span x-text="selectedCategory === 'All' ? 'Filter by Category' : selectedCategory"></span>
          <svg class="-mr-1 ml-1.5 w-5 h-5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
            <path clip-rule="evenodd" fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" />
          </svg>
        </button>
        <div x-show="dropdownOpen" x-transition class="absolute right-0 z-10 w-48 mt-2 bg-white rounded-lg shadow-lg border border-gray-200">
          <div class="p-3">
            <h6 class="mb-3 text-sm font-medium text-gray-900">Choose category</h6>
            <ul class="space-y-2 text-sm">
              <li class="flex items-center">
                <input @click="selectCategory('All')" type="radio" id="all" name="category" value="All" :checked="selectedCategory === 'All'" class="w-4 h-4 bg-gray-100 border-gray-300 text-blue-600 focus:ring-blue-500">
                <label for="all" class="ml-2 text-sm font-medium text-gray-900 cursor-pointer">All</label>
              </li>
              <!-- Add more radio buttons for other categories -->
            </ul>
          </div>
        </div>
      </div>
    </div>
  </div>

  <!-- Table -->
  <div class="bg-white rounded-lg shadow-sm border border-gray-200 overflow-hidden">
    <div class="overflow-x-auto">
      <table class="w-full text-sm text-left text-gray-500">
        <thead class="text-xs text-gray-700 uppercase bg-gray-50">
          <tr>
            <th scope="col" class="px-4 py-3">User name</th>
            <th scope="col" class="px-4 py-3">Category</th>
            <th scope="col" class="px-4 py-3">Role</th>
            <th scope="col" class="px-4 py-3">Email</th>
            <th scope="col" class="px-4 py-3">
              <span class="sr-only">Actions</span>
            </th>
          </tr>
        </thead>
        <tbody>
          <template x-for="user in paginatedUsers" :key="user.id">
            <tr class="border-b hover:bg-gray-50">
              <th scope="row" class="px-4 py-3 font-medium text-gray-900 whitespace-nowrap" x-text="user.name"></th>
              <td class="px-4 py-3">
                <span :class="user.category === 'Admin' ? 'bg-red-100 text-red-800' : user.category === 'User' ? 'bg-gray-100 text-gray-800' : user.category === 'Editor' ? 'bg-blue-100 text-blue-800' : 'bg-green-100 text-green-800'" class="px-2 py-1 text-xs font-medium rounded-full" x-text="user.category"></span>
              </td>
              <td class="px-4 py-3">
                <span :class="getRoleBadgeClass(user.role)" class="px-2 py-1 text-xs font-medium rounded-full" x-text="user.role"></span>
              </td>
              <td class="px-4 py-3" x-text="user.email"></td>
              <td class="px-4 py-3 flex items-center justify-end">
                <div class="relative" x-data="{ actionOpen: false }">
                  <button @click="actionOpen = !actionOpen" class="inline-flex items-center p-0.5 text-sm font-medium text-center text-gray-500 hover:text-gray-800 rounded-lg focus:outline-none" type="button">
                    <svg class="w-5 h-5" aria-hidden="true" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
                      <path d="M6 10a2 2 0 11-4 0 2 2 0 014 0zM12 10a2 2 0 11-4 0 2 2 0 014 0zM16 12a2 2 0 100-4 2 2 0 000 4z" />
                    </svg>
                  </button>
                  <div x-show="actionOpen" @click.away="actionOpen = false" x-transition class="absolute right-0 z-10 w-32 mt-2 bg-white rounded-lg shadow-lg border border-gray-200">
                    <ul class="py-1 text-sm text-gray-700">
                      <li><a href="#" class="block px-4 py-2 hover:bg-gray-100">Edit</a></li>
                      <li><a href="#" class="block px-4 py-2 hover:bg-gray-100">View</a></li>
                      <li><a href="#" class="block px-4 py-2 hover:bg-gray-100 text-red-600">Delete</a></li>
                    </ul>
                  </div>
                </div>
              </td>
            </tr>
          </template>
        </tbody>
      </table>
    </div>

    <!-- Pagination -->
    <nav class="flex flex-col md:flex-row justify-between items-start md:items-center space-y-3 md:space-y-0 p-4 bg-gray-50" aria-label="Table navigation">
      <span class="text-sm font-normal text-gray-500">
        Showing
        <span class="font-semibold text-gray-900" x-text="filteredUsers.length === 0 ? 0 : (currentPage - 1) * itemsPerPage + 1"></span>
        to
        <span class="font-semibold text-gray-900" x-text="Math.min(currentPage * itemsPerPage, filteredUsers.length)"></span>
        of
        <span class="font-semibold text-gray-900" x-text="filteredUsers.length"></span>
      </span>
      <ul class="inline-flex items-stretch -space-x-px" x-show="totalPages > 1">
        <li>
          <button @click="currentPage = Math.max(1, currentPage - 1)" :disabled="currentPage === 1" class="flex items-center justify-center h-full py-1.5 px-3 ml-0 text-gray-500 bg-white rounded-l-lg border border-gray-300 hover:bg-gray-100 hover:text-gray-700 disabled:opacity-50 disabled:cursor-not-allowed">
            <span class="sr-only">Previous</span>
            <svg class="w-5 h-5" aria-hidden="true" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
              <path fill-rule="evenodd" d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z" clip-rule="evenodd" />
            </svg>
          </button>
        </li>
        <template x-for="page in totalPages" :key="page">
          <li>
            <button @click="currentPage = page" :class="page === currentPage ? 'text-blue-600 bg-blue-50 border border-blue-300 hover:bg-blue-100 hover:text-blue-700' : 'text-gray-500 bg-white border border-gray-300 hover:bg-gray-100 hover:text-gray-700'" class="flex items-center justify-center text-sm py-2 px-3 leading-tight">
              <span x-text="page"></span>
            </button>
          </li>
        </template>
        <li>
          <button @click="currentPage = Math.min(totalPages, currentPage + 1)" :disabled="currentPage === totalPages" class="flex items-center justify-center h-full py-1.5 px-3 leading-tight text-gray-500 bg-white rounded-r-lg border border-gray-300 hover:bg-gray-100 hover:text-gray-700 disabled:opacity-50 disabled:cursor-not-allowed">
            <span class="sr-only">Next</span>
            <svg class="w-5 h-5" aria-hidden="true" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
              <path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd" />
            </svg>
          </button>
        </li>
      </ul>
    </nav>
  </div>
</div>

5. Sortable Table

A table with sortable columns for better data organization.

Name Email Role Status
<!-- Sortable Table -->
<div x-data="{ sortBy: 'name', sortOrder: 'asc', users: [/* users array */], get sortedUsers() { return this.users.sort((a, b) => { let aVal = a[this.sortBy]; let bVal = b[this.sortBy]; if (this.sortOrder === 'asc') { return aVal > bVal ? 1 : -1; } else { return aVal < bVal ? 1 : -1; } }); }, toggleSort(column) { if (this.sortBy === column) { this.sortOrder = this.sortOrder === 'asc' ? 'desc' : 'asc'; } else { this.sortBy = column; this.sortOrder = 'asc'; } } }">
  <table class="min-w-full divide-y divide-gray-200">
    <thead class="bg-gray-50">
      <tr>
        <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer" @click="toggleSort('name')">Name <span x-show="sortBy === 'name'" :class="sortOrder === 'asc' ? 'rotate-0' : 'rotate-180'" class="ml-1 inline-block transform transition-transform"><svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd"></path></svg></span></th>
        <!-- Similar for other columns -->
      </tr>
    </thead>
    <tbody class="bg-white divide-y divide-gray-200">
      <template x-for="user in sortedUsers" :key="user.id">
        <tr>
          <td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900" x-text="user.name"></td>
          <!-- Other cells -->
        </tr>
      </template>
    </tbody>
  </table>
</div>
<template>
  <div>
    <table class="min-w-full divide-y divide-gray-200">
      <thead class="bg-gray-50">
        <tr>
          <th @click="toggleSort('name')" class="cursor-pointer">Name</th>
          <!-- Other headers -->
        </tr>
      </thead>
      <tbody>
        <tr v-for="user in sortedUsers" :key="user.id">
          <td>{{ user.name }}</td>
          <!-- Other cells -->
        </tr>
      </tbody>
    </table>
  </div>
</template>

<script setup>
import { ref, computed } from 'vue'

const sortBy = ref('name')
const sortOrder = ref('asc')
const users = ref([/* users array */])

const sortedUsers = computed(() => {
  return users.value.sort((a, b) => {
    let aVal = a[sortBy.value]
    let bVal = b[sortBy.value]
    if (sortOrder.value === 'asc') {
      return aVal > bVal ? 1 : -1
    } else {
      return aVal < bVal ? 1 : -1
    }
  })
})

const toggleSort = (column) => {
  if (sortBy.value === column) {
    sortOrder.value = sortOrder.value === 'asc' ? 'desc' : 'asc'
  } else {
    sortBy.value = column
    sortOrder.value = 'asc'
  }
}
</script>
import React, { useState, useMemo } from 'react'

export default function SortableTable() {
  const [sortBy, setSortBy] = useState('name')
  const [sortOrder, setSortOrder] = useState('asc')
  const users = [/* users array */]

  const sortedUsers = useMemo(() => {
    return users.sort((a, b) => {
      let aVal = a[sortBy]
      let bVal = b[sortBy]
      if (sortOrder === 'asc') {
        return aVal > bVal ? 1 : -1
      } else {
        return aVal < bVal ? 1 : -1
      }
    })
  }, [users, sortBy, sortOrder])

  const toggleSort = (column) => {
    if (sortBy === column) {
      setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc')
    } else {
      setSortBy(column)
      setSortOrder('asc')
    }
  }

  return (
    <div className="overflow-x-auto">
      <table className="min-w-full divide-y divide-gray-200">
        <thead className="bg-gray-50">
          <tr>
            <th onClick={() => toggleSort('name')} className="cursor-pointer">Name</th>
            <!-- Other headers -->
          </tr>
        </thead>
        <tbody className="bg-white divide-y divide-gray-200">
          {sortedUsers.map(user => (
            <tr key={user.id}>
              <td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">{user.name}</td>
              <!-- Other cells -->
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  )
}

We use cookies to improve your experience and analytics. You can accept all cookies or reject non-essential ones.

Learn More