TL;DR (Too Long; Didn't Read) ๐
After months of building the same data table features over and over, I created the ultimate data table component that handles everything from server-side operations to Excel exports. Today, it's available with one-click installation via Shadcn registry! Plus, sub-row support is coming soon with 3 powerful modes.
npx shadcn@latest add https://tnks-data-table.vercel.app/r/data-table.json
The Problem Every Developer Faces ๐ค
Picture this: Your PM walks in and says, "We need a data table for our admin panel. Should be simple, right?"
Two weeks later:
- "Can we add sorting?" โ
- "We need server-side pagination" โ
- "Users want to export to Excel" โ
- "Can we add date range filters?" โ
- "We need bulk operations" โ
- "Oh, and make it work with our snake_case API" โ
- "Row selection across pages would be nice" โ
- "URL state persistence for sharing filters?" โ
Sound familiar? I've built this same "simple table" at least 20 times. So I decided to build it once more - but this time, build it right.
Introducing TNKS Data Table ๐
A production-ready data table that actually handles real-world requirements. Built on TanStack Table v8, fully typed with TypeScript, and now installable in seconds via Shadcn registry.
๐ฏ What Makes It Different?
This isn't just another table component. It's a complete data management solution:
<DataTable<User, any>
getColumns={getColumns}
exportConfig={useExportConfig()}
fetchDataFn={useUsersData}
fetchByIdsFn={fetchUsersByIds}
idField="id"
onRowClick={(user, rowIndex) => router.push(`/users/${user.id}`)}
renderToolbarContent={({ selectedRows, totalSelectedCount }) => (
<ToolbarActions
selectedCount={totalSelectedCount}
onBulkDelete={() => handleBulkDelete(selectedRows)}
/>
)}
config={{
enableRowSelection: true,
enableSearch: true,
enableDateFilter: true,
enableColumnVisibility: true,
enableUrlState: true,
defaultSortBy: "created_at",
searchPlaceholder: "Search by name, email, or ID...",
}}
/>
That's it. This single component handles EVERYTHING.
๐ฅ Features That Will Save Your Sanity
1. One-Command Installation
Forget copying 20 files and installing 15 dependencies:
# Install Shadcn UI components (if you haven't)
npx shadcn@latest init
npx shadcn@latest add button checkbox input select table # ...etc
# Install the complete data-table with ALL features
npx shadcn@latest add https://tnks-data-table.vercel.app/r/data-table.json
# Also grab the date picker
npx shadcn@latest add https://tnks-data-table.vercel.app/r/calendar-date-picker.json
Done. You now have a production-ready data table.
2. Server-Side Everything
No more client-side filtering of 10,000 rows:
// Your API gets clean, typed parameters
{
search: "john",
from_date: "2024-01-01",
to_date: "2024-12-31",
sort_by: "created_at", // or sortBy for camelCase APIs
sort_order: "desc",
page: 1,
limit: 20
}
3. Smart Row Selection Across Pages
Users can select rows across multiple pages. The table tracks both visible selections AND all selected IDs:
renderToolbarContent={({
selectedRows, // Currently visible selected rows
allSelectedIds, // ALL selected IDs across pages
totalSelectedCount, // Total count
resetSelection // Clear function
}) => (
<Button onClick={() => deleteUsers(allSelectedIds)}>
Delete {totalSelectedCount} users
</Button>
)}
4. Export with Data Transformation
Export to CSV/Excel with calculated columns that don't even exist in your table:
const transformFunction = (user) => ({
...user,
// Format existing data
created_at: formatDate(user.created_at),
total_spent: formatCurrency(user.total_spent),
// ADD COMPLETELY NEW COLUMNS for export only!
account_age_days: daysSince(user.created_at),
customer_tier: user.total_spent > 1000 ? "Premium" : "Standard",
risk_score: calculateRiskScore(user),
});
5. Works with ANY API Case Format
Your API uses snake_case? camelCase? PascalCase? Doesn't matter.
// Snake case API? Just use it directly!
columns = [
{ accessorKey: "user_name", header: "Name" },
{ accessorKey: "created_at", header: "Created" },
]
// Camel case API? Same thing!
columns = [
{ accessorKey: "userName", header: "Name" },
{ accessorKey: "createdAt", header: "Created" },
]
No conversion layers. No transformers. It just works.
6. URL State Persistence
Share filtered views with a simple URL:
/users?search=john&sort_by=created_at&sort_order=desc&page=2
Your users can bookmark filtered views, share them in Slack, whatever they want.
7. Built-in Row Click Navigation
Make entire rows clickable without conflicting with buttons or checkboxes:
onRowClick={(user, rowIndex) => {
router.push(`/users/${user.id}`);
}}
The table automatically prevents clicks on interactive elements from triggering row navigation. Smart!
๐ญ Coming Soon: Sub-Row Support (This is HUGE!)
We're adding three powerful sub-row modes that will handle complex hierarchical data:
Mode 1: Same Columns (Orders โ Products)
โผ ORD-001 โ John Doe โ Laptop โ 1 qty โ $999.99
โโ ORD-001 โ John Doe โ Mouse โ 2 qty โ $59.98
โโ ORD-001 โ John Doe โ Keyboard โ 1 qty โ $79.99
Mode 2: Custom Columns (Logistics โ Stops)
โผ Booking B-001 โ NYC โ Boston โ 4 stops โ $450
โโ ๐ Pickup โ 123 Main St, NYC โ 9:00 AM
โโ ๐ Pickup โ 456 Oak Ave, NYC โ 10:30 AM
โโ ๐ฆ Delivery โ 789 Elm St, Boston โ 4:00 PM
โโ ๐ฆ Delivery โ 321 Pine Rd, Boston โ 5:30 PM
Mode 3: Custom Component (Support Tickets โ Timeline)
โผ TKT-001 โ Cannot login โ High Priority โ Open
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ ๐ค John: Can't login, getting errors โ
โ ๐จโ๐ผ Sarah: Looking into this now... โ
โ ๐ค Status changed to "In Progress" โ
โ ๐จโ๐ผ Sarah: Fixed! Try again. โ
โ [Reply box] [Attach] [Close ticket] โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โผ TKT-002 โ Cannot login โ High Priority โ Open
etc...
This will make complex data relationships actually manageable!
๐ป Real-World Example
Here's a complete working example you can copy-paste:
// app/users/page.tsx
"use client";
import { DataTable } from "@/components/data-table/data-table";
import { useRouter } from "next/navigation";
export default function UsersPage() {
const router = useRouter();
// Column definitions
const columns = [
{
id: "select",
header: ({ table }) => (
<Checkbox
checked={table.getIsAllPageRowsSelected()}
onCheckedChange={(value) =>
table.toggleAllPageRowsSelected(!!value)
}
/>
),
cell: ({ row }) => (
<Checkbox
checked={row.getIsSelected()}
onCheckedChange={(value) => row.toggleSelected(!!value)}
/>
),
},
{
accessorKey: "name",
header: ({ column }) => (
<DataTableColumnHeader column={column} title="Name" />
),
},
{
accessorKey: "email",
header: "Email",
},
{
accessorKey: "created_at",
header: "Joined",
cell: ({ row }) => formatDate(row.getValue("created_at")),
},
{
id: "actions",
cell: ({ row }) => <RowActions user={row.original} />,
},
];
// Data fetching hook
const useUsersData = (page, limit, search, dateRange, sortBy, sortOrder) => {
return useQuery({
queryKey: ["users", page, limit, search, dateRange, sortBy, sortOrder],
queryFn: () => fetchUsers({ page, limit, search, ...dateRange, sortBy, sortOrder }),
});
};
return (
<DataTable
getColumns={() => columns}
fetchDataFn={useUsersData}
idField="id"
onRowClick={(user) => router.push(`/users/${user.id}`)}
config={{
enableRowSelection: true,
enableSearch: true,
enableDateFilter: true,
enableUrlState: true,
}}
/>
);
}
๐๏ธ Built with Modern Stack
- Next.js 15 with App Router
- TanStack Table v8 for table logic
- TanStack Query for data fetching
- Zod for runtime validation
- TypeScript for type safety
- Tailwind CSS for styling
- Shadcn UI for base components
๐ Performance Metrics
In production, this table handles:
- 50,000+ rows with server-side pagination
- 100+ concurrent users with optimistic updates
- Sub-50ms response times for sort/filter operations
- 2MB+ Excel exports without freezing the UI
๐ Why I'm Sharing This
I've spent countless hours building and rebuilding data tables. Every project needed the same features, but slightly different. This component is my gift to developers who are tired of reinventing the wheel.
It's MIT licensed, production-tested, and actively maintained.
๐ Get Started in 2 Minutes
# 1. Install it
npx shadcn@latest add https://tnks-data-table.vercel.app/r/data-table.json
# 2. Create your table
import { DataTable } from "@/components/data-table/data-table";
# 3. Ship to production
npm run build
๐ Resources
- ๐ GitHub Repo
- ๐ Full Documentation
- ๐ฎ Live Demo
- ๐ Report Issues
๐ค Contributing
Found a bug? Have a feature request? PRs are welcome! This is a community project, and I'd love your input.
Special shoutout to the Shadcn UI team for creating an amazing component system that makes sharing like this possible.
๐ญ Final Thoughts
Building this table taught me that the "simple" features are often the hardest to get right. URL state persistence? Took 3 refactors. Row selection across pages? 5 iterations. Snake_case/camelCase support? Don't ask. ๐
But now it's done, tested, and ready for you to use. No more building the same table features over and over.
Your turn: What's the most complex data table requirement you've had to implement? Drop a comment below!๐
If this saved you some time, give it a โญ on GitHub or share it with your team. Happy coding! ๐
Top comments (2)
Soon ๐คฉ

Some comments may only be visible to logged-in visitors. Sign in to view all comments.