# Table with Actions

> A table with actions block.

A shadcn/ui block from the [@blocks-so](https://blocks.so) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/blocks-so/table-02).

- Page, with a live preview: https://shadcn.exchange/r/blocks-so/table-02
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/blocks-so/table-02-light-0866ab7d84eafb92.png
- Registry item: https://blocks.so/r/table-02.json
- Categories: tables
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://blocks.so/r/table-02.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- npm packages: `lucide-react`
- Registry items: `badge`, `button`, `table`, `tooltip`

## Files

### `content/components/tables/table-02.tsx`

```tsx
'use client';

import {
  CheckCircle,
  FileTextIcon,
  Loader2,
  PauseIcon,
  PlayIcon,
  Trash2Icon,
} from 'lucide-react';
import { useState } from 'react';

import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import {
  Table,
  TableBody,
  TableCell,
  TableHead,
  TableHeader,
  TableRow,
} from '@/components/ui/table';
import {
  Tooltip,
  TooltipContent,
  TooltipProvider,
  TooltipTrigger,
} from '@/components/ui/tooltip';

interface Task {
  id: string;
  title: string;
  assignee: string;
  status: 'pending' | 'in-progress' | 'completed' | 'blocked';
  priority: 'low' | 'medium' | 'high' | 'urgent';
  dueDate: string;
  notes: string;
}

type TaskActionType = 'start' | 'pause' | 'complete' | 'delete' | 'view';

const tasks: Task[] = [
  {
    id: 'TASK-001',
    title: 'Implement User Authentication',
    assignee: 'Sarah Chen',
    status: 'in-progress',
    priority: 'high',
    dueDate: '2024-03-25',
    notes: 'OAuth 2.0 integration with Google and GitHub providers',
  },
  {
    id: 'TASK-002',
    title: 'Design Dashboard UI',
    assignee: 'Michael Torres',
    status: 'completed',
    priority: 'medium',
    dueDate: '2024-03-20',
    notes: 'Finalize dashboard layout with responsive grid system',
  },
  {
    id: 'TASK-003',
    title: 'API Performance Optimization',
    assignee: 'Emma Rodriguez',
    status: 'pending',
    priority: 'urgent',
    dueDate: '2024-03-22',
    notes: 'Reduce API response time by implementing caching strategy',
  },
  {
    id: 'TASK-004',
    title: 'Write Unit Tests',
    assignee: 'James Wilson',
    status: 'in-progress',
    priority: 'medium',
    dueDate: '2024-03-28',
    notes: 'Achieve 80% code coverage for authentication module',
  },
  {
    id: 'TASK-005',
    title: 'Database Migration',
    assignee: 'Olivia Martinez',
    status: 'blocked',
    priority: 'high',
    dueDate: '2024-03-24',
    notes: 'Waiting for infrastructure team approval before proceeding',
  },
  {
    id: 'TASK-006',
    title: 'Update Documentation',
    assignee: 'Lucas Anderson',
    status: 'pending',
    priority: 'low',
    dueDate: '2024-03-30',
    notes: 'Document new API endpoints and authentication flow',
  },
  {
    id: 'TASK-007',
    title: 'Security Audit',
    assignee: 'Sophia Taylor',
    status: 'completed',
    priority: 'urgent',
    dueDate: '2024-03-19',
    notes:
      'Conducted comprehensive security review and vulnerability assessment',
  },
];

function getStatusBadge(status: Task['status']) {
  switch (status) {
    case 'pending':
      return (
        <Badge
          className="border-0 bg-amber-500/15 text-amber-700 hover:bg-amber-500/25 dark:bg-amber-500/10 dark:text-amber-300 dark:hover:bg-amber-500/20"
          variant="outline"
        >
          Pending
        </Badge>
      );
    case 'in-progress':
      return (
        <Badge
          className="border-0 bg-blue-500/15 text-blue-700 hover:bg-blue-500/25 dark:bg-blue-500/10 dark:text-blue-400 dark:hover:bg-blue-500/20"
          variant="outline"
        >
          In Progress
        </Badge>
      );
    case 'completed':
      return (
        <Badge
          className="border-0 bg-green-500/15 text-green-700 hover:bg-green-500/25 dark:bg-green-500/10 dark:text-green-400 dark:hover:bg-green-500/20"
          variant="outline"
        >
          Completed
        </Badge>
      );
    case 'blocked':
      return (
        <Badge
          className="border-0 bg-rose-500/15 text-rose-700 hover:bg-rose-500/25 dark:bg-rose-500/10 dark:text-rose-400 dark:hover:bg-rose-500/20"
          variant="outline"
        >
          Blocked
        </Badge>
      );
    default:
      return <Badge variant="secondary">{status}</Badge>;
  }
}

export default function Table02() {
  const [pendingAction, setPendingAction] = useState<{
    id: string;
    type: TaskActionType;
  } | null>(null);

  const isTaskActionPending = (action: TaskActionType, taskId: string) =>
    pendingAction?.id === taskId && pendingAction.type === action;

  const isTaskBusy = (taskId: string) => pendingAction?.id === taskId;

  const handleAction = (task: Task, actionType: TaskActionType) => {
    setPendingAction({ id: task.id, type: actionType });
    setTimeout(() => {
      setPendingAction(null);
      console.log(`Action "${actionType}" completed for task:`, task.title);
    }, 1000);
  };

  const renderTaskRow = (task: Task) => {
    const busy = isTaskBusy(task.id);
    const startPending = isTaskActionPending('start', task.id);
    const pausePending = isTaskActionPending('pause', task.id);
    const completePending = isTaskActionPending('complete', task.id);
    const deletePending = isTaskActionPending('delete', task.id);

    return (
      <TableRow className="hover:bg-muted/50" key={task.id}>
        <TableCell className="h-16 px-4 font-medium">{task.title}</TableCell>
        <TableCell className="h-16 px-4 text-muted-foreground text-sm">
          {task.assignee}
        </TableCell>
        <TableCell className="h-16 px-4">
          {getStatusBadge(task.status)}
        </TableCell>

        <TableCell className="h-16 px-4 text-muted-foreground text-sm">
          {task.dueDate}
        </TableCell>
        <TableCell className="h-16 max-w-[300px] px-4 text-muted-foreground text-sm">
          <TooltipProvider>
            <Tooltip>
              <TooltipTrigger
                render={<span className="block cursor-help truncate" />}
              >
                {task.notes}
              </TooltipTrigger>
              <TooltipContent className="max-w-md">{task.notes}</TooltipContent>
            </Tooltip>
          </TooltipProvider>
        </TableCell>
        <TableCell className="h-16 px-4">
          <TooltipProvider>
            <div className="flex items-center gap-1">
              {(task.status === 'pending' || task.status === 'blocked') && (
                <Tooltip>
                  <TooltipTrigger
                    render={
                      <Button
                        aria-label="Start"
                        className="h-8 w-8"
                        disabled={busy}
                        onClick={() => handleAction(task, 'start')}
                        size="icon"
                        variant="outline"
                      />
                    }
                  >
                    {startPending ? (
                      <Loader2 className="size-4 animate-spin" />
                    ) : (
                      <PlayIcon className="size-4" />
                    )}
                  </TooltipTrigger>
                  <TooltipContent>Start</TooltipContent>
                </Tooltip>
              )}
              {task.status === 'in-progress' && (
                <>
                  <Tooltip>
                    <TooltipTrigger
                      render={
                        <Button
                          aria-label="Pause"
                          className="h-8 w-8"
                          disabled={busy}
                          onClick={() => handleAction(task, 'pause')}
                          size="icon"
                          variant="outline"
                        />
                      }
                    >
                      {pausePending ? (
                        <Loader2 className="size-4 animate-spin" />
                      ) : (
                        <PauseIcon className="size-4" />
                      )}
                    </TooltipTrigger>
                    <TooltipContent>Pause</TooltipContent>
                  </Tooltip>
                  <Tooltip>
                    <TooltipTrigger
                      render={
                        <Button
                          aria-label="Complete"
                          className="h-8 w-8"
                          disabled={busy}
                          onClick={() => handleAction(task, 'complete')}
                          size="icon"
                          variant="outline"
                        />
                      }
                    >
                      {completePending ? (
                        <Loader2 className="size-4 animate-spin" />
                      ) : (
                        <CheckCircle className="size-4" />
                      )}
                    </TooltipTrigger>
                    <TooltipContent>Complete</TooltipContent>
                  </Tooltip>
                </>
              )}
              <Tooltip>
                <TooltipTrigger
                  render={
                    <Button
                      aria-label="Delete"
                      className="h-8 w-8 text-destructive hover:bg-destructive hover:text-white"
                      disabled={busy}
                      onClick={() => handleAction(task, 'delete')}
                      size="icon"
                      variant="outline"
                    />
                  }
                >
                  {deletePending ? (
                    <Loader2 className="size-4 animate-spin" />
                  ) : (
                    <Trash2Icon className="size-4" />
                  )}
                </TooltipTrigger>
                <TooltipContent>Delete</TooltipContent>
              </Tooltip>
              <Tooltip>
                <TooltipTrigger
                  render={
                    <Button
                      aria-label="View details"
                      className="h-8 w-8"
                      disabled={busy}
                      onClick={() => handleAction(task, 'view')}
                      size="icon"
                      variant="outline"
                    />
                  }
                >
                  <FileTextIcon className="size-4" />
                </TooltipTrigger>
                <TooltipContent>View Details</TooltipContent>
              </Tooltip>
            </div>
          </TooltipProvider>
        </TableCell>
      </TableRow>
    );
  };

  return (
    <div className="w-[95%] rounded-lg border bg-card">
      <Table>
        <TableHeader>
          <TableRow className="border-b hover:bg-transparent">
            <TableHead className="h-12 px-4 font-medium">Title</TableHead>
            <TableHead className="h-12 px-4 font-medium">Assignee</TableHead>
            <TableHead className="h-12 w-[120px] px-4 font-medium">
              Status
            </TableHead>

            <TableHead className="h-12 px-4 font-medium">Due Date</TableHead>
            <TableHead className="h-12 px-4 font-medium">Notes</TableHead>
            <TableHead className="h-12 w-[180px] px-4 font-medium">
              Actions
            </TableHead>
          </TableRow>
        </TableHeader>
        <TableBody>{tasks.map(renderTaskRow)}</TableBody>
      </Table>
    </div>
  );
}
```
