jacksonkasi1/tnks-data-table(新しいタブで開きます)
主要言語: TypeScript
Advanced data table component with server-side operations using Hono.js, Drizzle ORM, and PostgreSQL
- star 数
- 243
- watcher 数
- 2
- fork 数
- 22
- issue 数
- 1
- 最終更新
- 2026/03/26
主要言語: TypeScript
Advanced data table component with server-side operations using Hono.js, Drizzle ORM, and PostgreSQL
Important
Recommendation: Check out TableCraft! It is the spiritual successor to this repository, offering almost all the same functionality but with a vastly simplified, adapter-driven architecture that is easier to use and maintain.
Compatible with: Next.js, Vite, Remix, TanStack Start, and all modern React frameworks.
👉 Check out the Vite Example Repository for a quick integration guide.
📖 Complete Documentation: tnks-docs.vercel.app/docs - Comprehensive guides, API reference, and interactive examples built with Fumadocs.
Version: 0.5.0 Updated: 2026-02-08 Author: Jackson Kasi
If you find this project helpful, consider supporting its development!
Your support helps maintain and improve this component. Every contribution matters! 🚀
Install the data table component with a single command using Shadcn CLI:
# Install required Shadcn UI components first npx shadcn@latest init npx shadcn@latest add button checkbox input select popover calendar dropdown-menu separator table command # Install the data-table component npx shadcn@latest add https://tnks-data-table.vercel.app/r/data-table.json # Install the calendar-date-picker (required dependency) npx shadcn@latest add https://tnks-data-table.vercel.app/r/calendar-date-picker.json
Important: You must add the table styles to your globals.css for proper functionality. Get the styles here.
That's it! See Installation & Setup for detailed installation options.
The Advanced Data Table component is a highly configurable and feature-rich table implementation built on top of Shadcn UI components and TanStack Table (React Table v8). It is fully compatible with all modern React frameworks including Next.js, Vite, Remix, TanStack Start, and others. It's designed to handle enterprise-level requirements including complex data operations, server-side processing, and customizable UI elements.
This documentation provides comprehensive guidance on how to implement, configure, and extend the data table for your specific needs regardless of your chosen framework.
Check out the API development document to understand the default configuration for this table.
The Data Table includes the following features:
The data table implementation follows a modular structure to separate concerns and improve maintainability. Below is the recommended file structure for implementing the data table in your project:
src/ ├── api/ # API integration layer │ └── entity/ # Entity-specific API functions │ ├── add-entity.ts # Create operation │ ├── delete-entity.ts # Delete operation │ ├── fetch-entities.ts # List operation with filters │ └── fetch-entity-by-ids.ts # Fetch specific entities │ ├── components/ # Shared UI components └── 📁data-table # Core data table components └── 📁hooks # Custom React hooks for data-table └── use-table-column-resize.ts # Hook for managing column resize state and persistence └── 📁utils # Utility functions and helpers └── column-sizing.ts # Functions for calculating and managing column widths └── conditional-state.ts # Logic for conditional rendering and state transitions └── date-format.ts # Date formatting and manipulation utilities └── deep-utils.ts # Deep object comparison and manipulation └── export-utils.ts # Utilities for data export (CSV/Excel) └── index.ts # Export barrel file for utilities └── keyboard-navigation.ts # Keyboard navigation and accessibility └── search.ts # Search functionality and text matching └── table-config.ts # Table configuration types and defaults └── table-state-handlers.ts # Handlers for table state changes └── url-state.ts # URL state persistence utilities └── column-header.tsx # Sortable column header component └── data-export.tsx # Component for export functionality UI └── data-table-resizer.tsx # Column resize handler component └── data-table.tsx # Main data table component └── pagination.tsx # Pagination controls component └── toolbar.tsx # Table toolbar with search and filtering └── view-options.tsx # Column visibility and display options │ ├── app/ # Application routes and pages │ └── (section)/ # Section grouping │ └── entity-table/ # Entity-specific implementation │ ├── components/ # Entity table components │ │ ├── columns.tsx # Column definitions │ │ ├── row-actions.tsx # Row action menu │ │ ├── toolbar-options.tsx # Toolbar customizations │ │ └── actions/ # Action components │ │ ├── add-entity-popup.tsx # Add modal │ │ ├── delete-entity-popup.tsx # Delete confirmation │ │ └── bulk-delete-popup.tsx # Bulk delete confirmation │ ├── schema/ # Data schemas │ │ ├── entity-schema.ts # Entity type definitions │ │ └── index.ts # Schema exports │ ├── utils/ # Utility functions │ │ ├── config.ts # Table configuration │ │ └── data-fetching.ts # Data fetching hooks │ └── index.tsx # Table component entry
This file structure follows a clear separation of concerns:
By following this structure, you can easily maintain and extend your data tables while keeping each part focused on its specific responsibility.
You can install the data table component in two ways:
The easiest way to install the data table component is using the Shadcn CLI with our custom registry.
# Install the complete data-table component with all dependencies npx shadcn@latest add https://tnks-data-table.vercel.app/r/data-table.json # Also install the calendar-date-picker (required dependency) npx shadcn@latest add https://tnks-data-table.vercel.app/r/calendar-date-picker.json
If you're developing locally or testing the component:
# 1. Clone the repository git clone https://github.com/jacksonkasi1/tnks-data-table.git cd tnks-data-table # 2. Install dependencies and build registry npm install npm run registry:build # 3. Start the development server npm run dev # 4. In your project directory, install from localhost npx shadcn@latest add http://localhost:3000/r/data-table.json npx shadcn@latest add http://localhost:3000/r/calendar-date-picker.json
The registry installation will automatically:
Before installing the data-table component, ensure you have these Shadcn UI components installed:
# Initialize Shadcn UI in your project (if not already done) npx shadcn@latest init # Install required Shadcn UI components npx shadcn@latest add button checkbox input select popover calendar dropdown-menu separator table command
Note: The registry will handle installing the data-table specific components, but you need to have the base Shadcn UI components installed first.
Important: You must add the table styles to your globals.css for proper functionality. Get the styles here.
Install all required packages for the data table to work properly:
# Core dependencies for data table functionality npm install @tanstack/react-table @tanstack/react-query @hookform/resolvers react-hook-form zod sonner date-fns date-fns-tz xlsx class-variance-authority clsx tailwind-merge lucide-react # Radix UI components (required for Shadcn UI) npm install @radix-ui/react-avatar @radix-ui/react-checkbox @radix-ui/react-dialog @radix-ui/react-dropdown-menu @radix-ui/react-icons @radix-ui/react-label @radix-ui/react-popover @radix-ui/react-select @radix-ui/react-separator @radix-ui/react-slot # Additional UI dependencies npm install react-day-picker cmdk # TypeScript types for XLSX npm install @types/xlsx # Optional: Development dependencies for better linting npm install --save-dev @tanstack/eslint-plugin-query
Or with other package managers:
# Using Yarn yarn add @tanstack/react-table @tanstack/react-query @hookform/resolvers react-hook-form zod sonner date-fns date-fns-tz xlsx class-variance-authority clsx tailwind-merge lucide-react @radix-ui/react-avatar @radix-ui/react-checkbox @radix-ui/react-dialog @radix-ui/react-dropdown-menu @radix-ui/react-icons @radix-ui/react-label @radix-ui/react-popover @radix-ui/react-select @radix-ui/react-separator @radix-ui/react-slot react-day-picker cmdk @types/xlsx # Using pnpm pnpm add @tanstack/react-table @tanstack/react-query @hookform/resolvers react-hook-form zod sonner date-fns date-fns-tz xlsx class-variance-authority clsx tailwind-merge lucide-react @radix-ui/react-avatar @radix-ui/react-checkbox @radix-ui/react-dialog @radix-ui/react-dropdown-menu @radix-ui/react-icons @radix-ui/react-label @radix-ui/react-popover @radix-ui/react-select @radix-ui/react-separator @radix-ui/react-slot react-day-picker cmdk @types/xlsx # Using Bun bun add @tanstack/react-table @tanstack/react-query @hookform/resolvers react-hook-form zod sonner date-fns date-fns-tz xlsx class-variance-authority clsx tailwind-merge lucide-react @radix-ui/react-avatar @radix-ui/react-checkbox @radix-ui/react-dialog @radix-ui/react-dropdown-menu @radix-ui/react-icons @radix-ui/react-label @radix-ui/react-popover @radix-ui/react-select @radix-ui/react-separator @radix-ui/react-slot react-day-picker cmdk @types/xlsx
Note: The registry will handle installing the data-table-specific components, but you need to have the base Shadcn UI components installed first.
Important: You must add the table styles to your globals.css for proper functionality.
👉 Copy the styles from here
Add the following styles to your global CSS file (e.g., src/styles/globals.css or src/index.css). This is crucial for table resizing and interactive elements.
/* ------------------ TABLE STYLES ------------------ */ /* Custom cursor pointer for interactive elements in the data table */ button, .rdp-button, .rdp-day, select, input[type="checkbox"], input[type="radio"], [role="button"], [role="tab"], .react-day-picker button, [data-slot="select-trigger"], [data-slot="checkbox"], .day-range-start, .day-range-end, .day-range-middle, [data-radix-collection-item], [data-slot="button"], [data-slot="day"], [data-slot="select-item"] { cursor: pointer !important; } /* Fix specifically for calendar date picker buttons and elements */ .rdp-button, .rdp-nav_button, .rdp-month-dropdown-button, .rdp-dropdown_option, [data-testid="rdp-nav-button-previous"], [data-testid="rdp-nav-button-next"], [data-slot="day"], [data-slot="day_button"], [data-slot="day_selected"], [data-slot="day_range"], [data-slot="day_today"], [data-slot="day_outside"], [data-slot="day_disabled"], [data-slot="day_hidden"], .day-outside, .day-range-start, .day-range-end, .day-selected, div[data-testid="calendar"] button { cursor: pointer !important; } /* Fix for pagination buttons and components */ .flex-1 button, .h-8.w-8.p-0, nav button, [data-state="active"], [data-state="inactive"], [data-orientation="horizontal"], [data-orientation="vertical"] { cursor: pointer !important; } /* Fixes for Select elements and dropdowns */ [data-radix-select-trigger], [data-radix-popper-content-wrapper] *, [data-state="open"] *, [data-radix-collection-item], [data-value], [role="combobox"], [role="listbox"], [role="option"] { cursor: pointer !important; } /* Fix for checkbox in table rows and other inputs */ [data-state="checked"], [data-state="unchecked"], [data-state="indeterminate"], input[type="date"], input[type="datetime-local"], input[type="month"], input[type="time"], input[type="week"] { cursor: pointer !important; } /* Fix for calendar date picker component specifically */ [id^="calendar-date-picker"], [id^="calendar-date-picker"] *, [id="rdp-1"], [id="rdp-1"] *, [id="rdp-2"], [id="rdp-2"] * { cursor: pointer !important; } /* Table column resizer styles */ @layer components { .resizer { @apply absolute right-0 top-0 h-full w-1.5 cursor-col-resize select-none touch-none opacity-0 hover:opacity-100 hover:bg-primary/50 group-hover:block; } .resizer.isResizing { @apply opacity-100 bg-primary; } table { width: 100%; } .resizable-table { @apply table-fixed relative; } .resizable-table th { position: relative; overflow: hidden; } .resizable-table th, .resizable-table td { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } } /* Table column resizing styles - ENHANCED VERSION */ @layer components { /* Prevent layout shifts during resize */ .resizable-table { table-layout: fixed; width: 100%; will-change: contents; } /* Use hardware acceleration for smoother transitions */ .resizable-table th { position: relative; overflow: hidden; transform: translateZ(0); backface-visibility: hidden; perspective: 1000px; transition: width 0ms linear; } /* Prevent text selection when resizing */ body[data-resizing="true"] { cursor: col-resize !important; user-select: none !important; -webkit-user-select: none !important; touch-action: none !important; pointer-events: none !important; } body[data-resizing="true"] * { cursor: col-resize !important; } /* Remove transition during active resize for instant feedback */ .resizable-table th[data-column-resizing="true"] { transition: none !important; } /* Better hover interaction for resize handles */ .resizable-table th:hover [data-resizing] { opacity: 0.8; } /* Enhanced visual feedback during resize */ .resizable-table [data-resizing="true"] { opacity: 1 !important; } } /* Custom cursor for column resizing */ @layer components { /* Better cursor for column resize */ .cursor-col-resize { cursor: col-resize; } /* Apply the cursor to the resize handle and its children */ [data-resizing], [data-resizing] * { cursor: col-resize !important; } /* Ensure the resize handle is always on top */ [data-resizing] { z-index: 10; } } /* Custom hover and interaction styles for the resizer */ @layer components { /* Resize handle hover effects */ .resizable-table [data-resizing] { transition: opacity 0.15s ease, transform 0.1s ease; } .resizable-table [data-resizing]:hover { transform: scaleX(1.5); opacity: 1 !important; } /* Active state during resize */ .resizable-table [data-resizing="true"] { transform: scaleX(1.5) !important; } /* Ensure the grip icon is positioned correctly */ .resizable-table [data-resizing] svg { transition: opacity 0.15s ease, color 0.15s ease; } .resizable-table [data-resizing]:hover svg { opacity: 1 !important; color: hsl(var(--primary)) !important; } }
Create the utility function file:
// src/lib/utils.ts import { clsx, type ClassValue } from "clsx" import { twMerge } from "tailwind-merge" export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)) }
Use the Shadcn CLI to automatically install all required base components (do not copy these manually):
# Initialize Shadcn UI (if you haven't already) npx shadcn@latest init # Install all required components in one command npx shadcn@latest add alert avatar badge button calendar checkbox command dialog dropdown-menu form input label popover select separator skeleton sonner table
Create these custom components in your project:
A. Calendar Date Picker Component
// src/components/calendar-date-picker.tsx // Copy from: src/components/calendar-date-picker.tsx in this repository
B. Data Table Core Components
Create /src/components/data-table/ directory and copy these files:
src/components/data-table/ ├── column-header.tsx # Sortable column headers ├── data-export.tsx # Export functionality UI ├── data-table-resizer.tsx # Column resize handler ├── data-table.tsx # Main data table component ├── pagination.tsx # Pagination controls ├── toolbar.tsx # Table toolbar with filters ├── view-options.tsx # Column visibility options ├── hooks/ │ └── use-table-column-resize.ts # Column resize hook └── utils/ ├── case-utils.ts # Case format conversion utilities ├── column-sizing.ts # Column sizing utilities ├── conditional-state.ts # Conditional state management ├── date-format.ts # Date formatting utilities ├── deep-utils.ts # Deep object utilities ├── export-utils.ts # Export functionality ├── index.ts # Utility exports ├── keyboard-navigation.ts # Keyboard navigation ├── search.ts # Search utilities ├── table-config.ts # Table configuration ├── table-state-handlers.ts # State handlers └── url-state.ts # URL state management
C. Format Utilities
// src/utils/format.ts // Copy from: src/utils/format.ts in this repository
Wrap your app with React Query provider:
// src/app/layout.tsx or your root layout "use client"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { useState } from "react"; export default function RootLayout({ children, }: { children: React.ReactNode; }) { const [queryClient] = useState(() => new QueryClient({ defaultOptions: { queries: { staleTime: 60 * 1000, // 1 minute retry: false, }, }, })); return ( <html lang="en"> <body> <QueryClientProvider client={queryClient}> {children} </QueryClientProvider> </body> </html> ); }
Add the Sonner toaster to your layout:
// src/app/layout.tsx import { Toaster } from "@/components/ui/sonner"; export default function RootLayout({ children, }: { children: React.ReactNode; }) { return ( <html lang="en"> <body> <QueryClientProvider client={queryClient}> {children} <Toaster /> </QueryClientProvider> </body> </html> ); }
Create your API directory structure:
src/api/
└── [entity]/
├── add-entity.ts # Create operations
├── delete-entity.ts # Delete operations
├── fetch-entities.ts # List with filtering
└── fetch-entities-by-ids.ts # Bulk fetch by IDsFollow the Basic Usage section to create your first data table implementation.
Here's what each major dependency does:
| Package | Purpose |
|---|---|
@tanstack/react-table |
Core table functionality, sorting, filtering |
@tanstack/react-query |
Data fetching, caching, and synchronization |
@hookform/resolvers |
Form validation integration |
react-hook-form |
Form state management |
zod |
Runtime type validation and schemas |
sonner |
Toast notifications |
date-fns |
Date manipulation and formatting |
date-fns-tz |
Timezone support for dates |
xlsx |
Excel export functionality |
class-variance-authority |
Utility for managing CSS classes |
clsx & tailwind-merge |
CSS class utilities |
lucide-react |
Icon library |
react-day-picker |
Date picker component |
cmdk |
Command palette functionality |
@radix-ui/* |
Headless UI components (used by Shadcn) |
Common Issues:
@types/* packages are installedlucide-react is properly installedxlsx and @types/xlsx are installedVersion Compatibility:
Here's a basic example of how to implement a data table for your entity:
// src/app/(section)/entity-table/schema/entity-schema.ts import { z } from "zod"; export const entitySchema = z.object({ id: z.number(), name: z.string(), email: z.string(), created_at: z.string(), // Add other fields as needed }); export type Entity = z.infer<typeof entitySchema>; export const entitiesResponseSchema = z.object({ success: z.boolean(), data: z.array(entitySchema), pagination: z.object({ page: z.number(), limit: z.number(), total_pages: z.number(), total_items: z.number(), }), });
// src/api/entity/fetch-entities.ts import { z } from "zod"; import { entitiesResponseSchema } from "@/app/(section)/entity-table/schema"; const API_BASE_URL = "/api"; export async function fetchEntities({ search = "", from_date = "", to_date = "", sort_by = "created_at", sort_order = "desc", page = 1, limit = 10, }) { // Build query parameters const params = new URLSearchParams(); if (search) params.append("search", search); if (from_date) params.append("from_date", from_date); if (to_date) params.append("to_date", to_date); params.append("sort_by", sort_by); params.append("sort_order", sort_order); params.append("page", page.toString()); params.append("limit", limit.toString()); // Fetch data const response = await fetch(`${API_BASE_URL}/entities?${params.toString()}`); if (!response.ok) { throw new Error(`Failed to fetch entities: ${response.statusText}`); } const data = await response.json(); return entitiesResponseSchema.parse(data); }
// src/app/(section)/entity-table/utils/data-fetching.ts import { useQuery, keepPreviousData } from "@tanstack/react-query"; import { fetchEntities } from "@/api/entity/fetch-entities"; export function useEntitiesData( page: number, pageSize: number, search: string, dateRange: { from_date: string; to_date: string }, sortBy: string, sortOrder: string ) { return useQuery({ queryKey: [ "entities", page, pageSize, search, dateRange, sortBy, sortOrder, ], queryFn: () => fetchEntities({ page, limit: pageSize, search, from_date: dateRange.from_date, to_date: dateRange.to_date, sort_by: sortBy, sort_order: sortOrder, }), placeholderData: keepPreviousData, }); } // Add this property for the DataTable component useEntitiesData.isQueryHook = true;
// src/app/(section)/entity-table/components/columns.tsx "use client"; import { format } from "date-fns"; import { ColumnDef } from "@tanstack/react-table"; // Import components import { DataTableColumnHeader } from "@/components/data-table/column-header"; import { Checkbox } from "@/components/ui/checkbox"; // Import schema and actions import { Entity } from "../schema"; import { DataTableRowActions } from "./row-actions"; export const getColumns = ( handleRowDeselection: ((rowId: string) => void) | null | undefined ): ColumnDef<Entity>[] => { const baseColumns: ColumnDef<Entity>[] = [ { accessorKey: "name", header: ({ column }) => ( <DataTableColumnHeader column={column} title="Name" /> ), cell: ({ row }) => ( <div className="font-medium">{row.getValue("name")}</div> ), size: 200, }, { accessorKey: "email", header: ({ column }) => ( <DataTableColumnHeader column={column} title="Email" /> ), cell: ({ row }) => <div>{row.getValue("email")}</div>, size: 250, }, { accessorKey: "created_at", header: ({ column }) => ( <DataTableColumnHeader column={column} title="Created" /> ), cell: ({ row }) => { const date = new Date(row.getValue("created_at")); const formatted = format(date, "MMM d, yyyy"); return <div>{formatted}</div>; }, size: 120, }, { id: "actions", header: ({ column }) => ( <DataTableColumnHeader column={column} title="Actions" /> ), cell: ({ row, table }) => <DataTableRowActions row={row} table={table} />, size: 100, }, ]; // Only include selection column if row selection is enabled if (handleRowDeselection !== null) { return [ { id: "select", header: ({ table }) => ( <Checkbox checked={table.getIsAllPageRowsSelected()} onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value) } aria-label="Select all" className="translate-y-0.5 cursor-pointer" /> ), cell: ({ row }) => ( <Checkbox checked={row.getIsSelected()} onCheckedChange={(value) => { row.toggleSelected(!!value); if (!value && handleRowDeselection) { handleRowDeselection(row.id); } }} aria-label="Select row" className="translate-y-0.5 cursor-pointer" /> ), enableSorting: false, enableHiding: false, size: 50, }, ...baseColumns, ]; } return baseColumns; };
// src/app/(section)/entity-table/index.tsx "use client"; import { useRouter } from "next/navigation"; import { DataTable } from "@/components/data-table/data-table"; import { getColumns } from "./components/columns"; import { useExportConfig } from "./utils/config"; import { fetchEntitiesByIds } from "@/api/entity/fetch-entities-by-ids"; import { useEntitiesData } from "./utils/data-fetching"; import { ToolbarOptions } from "./components/toolbar-options"; import { Entity } from "./schema"; export default function EntityTable() { const router = useRouter(); // Handle row clicks for navigation const handleRowClick = (entity: Entity, rowIndex: number) => { router.push(`/entities/${entity.id}`); }; return ( <DataTable<Enti
| | | |
| | |
| | |||
| | |||
| | |||
| | |||
| | | ||
| | |||
| | |||
| | |||
| | |||
| | |||
| |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | ||
| | ||
| | | |
| | ||
| | | |
| | |
| | |
| | |
| | |
| |
| | |
| | |
| | |
| |