# Datatable 01 - Exportable Datatable

> A full-featured datatable/tanstack table with built-in one-click CSV export, letting users instantly download the current view as a spreadsheet — ideal for reporting dashboards and data-heavy admin panels.

A shadcn/ui block from the [@shadcn-space](https://shadcnspace.com) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/shadcn-space/datatable-01).

- Page, with a live preview: https://shadcn.exchange/r/shadcn-space/datatable-01
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/shadcn-space/datatable-01-light-90340473b030265e.png
- Registry item: https://shadcnspace.com/r/datatable-01.json
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://shadcnspace.com/r/datatable-01.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: `@tanstack/react-table`, `lucide-react`
- Registry items: `button`, `badge`, `table`

## Files

### `src/components/shadcn-space/blocks/datatable-01/page.tsx`

```tsx
import Datatable from "@/components/shadcn-space/blocks/datatable-01/datatable"

export default function Page() {
    return (
        <Datatable />
    )
}
```

### `src/components/shadcn-space/blocks/datatable-01/datatable.tsx`

```tsx
"use client"
import { useState } from "react";
import {
    createColumnHelper,
    useTable,
    tableFeatures,
    columnVisibilityFeature,
    flexRender,
} from "@tanstack/react-table";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { Download } from "lucide-react";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";

export interface TableType2 {
    name: string;
    email: string;
    role: string;
    plan: string;
    billing: string;
    status: string;
    avatar: string;
}

const basicTableData: TableType2[] = [
    {
        name: "Olivia Rhye",
        email: "olivia@ui.com",
        role: "Maintainer",
        plan: "Enterprise",
        billing: "Auto debit",
        status: "Active",
        avatar: "https://images.shadcnspace.com/assets/profiles/user-11.jpg",
    },
    {
        name: "Barbara Steele",
        email: "steele@ui.com",
        role: "Admin",
        plan: "Enterprise",
        billing: "Auto debit",
        status: "Inactive",
        avatar: "https://images.shadcnspace.com/assets/profiles/user-8.jpg",
    },
    {
        name: "Leonard Gordon",
        email: "leonard@ui.com",
        role: "Editor",
        plan: "Team",
        billing: "Manual - PayPal",
        status: "Active",
        avatar: "https://images.shadcnspace.com/assets/profiles/user-3.jpg",
    },
    {
        name: "Evelyn Pope",
        email: "evelyn@ui.com",
        role: "Author",
        plan: "Basic",
        billing: "Manual - cash",
        status: "Pending",
        avatar: "https://images.shadcnspace.com/assets/profiles/user-4.jpg",
    },
    {
        name: "Tommy Garza",
        email: "tommy@ui.com",
        role: "Subscriber",
        plan: "Company",
        billing: "Auto debit",
        status: "Inactive",
        avatar: "https://images.shadcnspace.com/assets/profiles/user-5.jpg",
    },
    {
        name: "Isabel Vasquez",
        email: "isabel@ui.com",
        role: "Editor",
        plan: "Team",
        billing: "Auto debit",
        status: "Active",
        avatar: "https://images.shadcnspace.com/assets/profiles/user-12.jpg",
    }
];

// Colors for status badges
const statusColors: Record<string, string> = {
    Active: "bg-teal-400/10 text-teal-400 hover:bg-teal-400/10",
    Pending: "bg-orange-400/10 text-orange-400 hover:bg-orange-400/10",
    Inactive: "bg-red-500/10 text-red-500 hover:bg-red-500/10",
};

const features = tableFeatures({ columnVisibilityFeature });

const columnHelper = createColumnHelper<typeof features, TableType2>();

const columns = columnHelper.columns([
    columnHelper.accessor("name", {
        header: () => <span>User</span>,
        cell: (info) => (
            <div className="flex gap-3 items-center">
                <img
                    src={info.row.original.avatar}
                    alt="avatar"
                    height={40}
                    width={40}
                    className="h-10 w-10 rounded-full"
                />
                <div>
                    <h6 className="text-sm font-medium">{info.getValue()}</h6>
                    <p className="text-xs text-muted-foreground">
                        {info.row.original.email}
                    </p>
                </div>
            </div>
        ),
    }),
    columnHelper.accessor("role", {
        header: () => <span>Role</span>,
        cell: (info) => (
            <span className="text-sm">{info.getValue()}</span>
        ),
    }),
    columnHelper.accessor("plan", {
        header: () => <span>Plan</span>,
        cell: (info) => (
            <span className="text-sm">{info.getValue()}</span>
        ),
    }),
    columnHelper.accessor("billing", {
        header: () => <span>Billing</span>,
        cell: (info) => (
            <span className="text-sm">{info.getValue()}</span>
        ),
    }),
    columnHelper.accessor("status", {
        header: () => <span>Status</span>,
        cell: (info) => (
            <Badge className={`${statusColors[info.getValue()]} border-0 capitalize shadow-none`}>
                {info.getValue()}
            </Badge>
        ),
    }),
]);

const Datatable = () => {
    const [data] = useState(basicTableData);

    const table = useTable({
        features,
        data,
        columns,
    });

    const handleDownload = () => {
        const headers = ["Name", "Email", "Role", "Plan", "Billing", "Status"];

        const csvRows = data.map((item) =>
            [
                `"${item.name}"`,
                `"${item.email}"`,
                `"${item.role}"`,
                `"${item.plan}"`,
                `"${item.billing}"`,
                `"${item.status}"`,
            ].join(",")
        );

        const csvContent = [headers.join(","), ...csvRows].join("\n");

        const blob = new Blob([csvContent], { type: "text/csv;charset=utf-8;" });
        const url = URL.createObjectURL(blob);
        const link = document.createElement("a");
        link.href = url;
        link.setAttribute("download", "table-data.csv");
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);
        URL.revokeObjectURL(url);
    };

    return (
        <section className="lg:py-20 sm:py-16 py-8 px-4 max-w-4xl mx-auto">
            {/* Toolbar */}
            <div className="flex items-center justify-end mb-3">
                <Button
                    onClick={handleDownload}
                    className="inline-flex items-center gap-2 cursor-pointer hover:bg-primary/80"
                >
                    <Download size={15} />
                    Export CSV
                </Button>
            </div>

            <div className="border rounded-md border-border overflow-hidden">
                <div className="overflow-x-auto">
                    <Table className="min-w-full">
                        <TableHeader>
                            {table.getHeaderGroups().map((headerGroup) => (
                                <TableRow key={headerGroup.id}>
                                    {headerGroup.headers.map((header) => (
                                        <TableHead
                                            key={header.id}
                                            className="text-base font-medium text-left border-b border-border px-4 py-3 h-auto"
                                        >
                                            {header.isPlaceholder
                                                ? null
                                                : flexRender(
                                                    header.column.columnDef.header,
                                                    header.getContext(),
                                                )}
                                        </TableHead>
                                    ))}
                                </TableRow>
                            ))}
                        </TableHeader>
                        <TableBody className="divide-y divide-border">
                            {table.getRowModel().rows.map((row) => (
                                <TableRow key={row.id}>
                                    {row.getVisibleCells().map((cell) => (
                                        <TableCell
                                            key={cell.id}
                                            className="whitespace-nowrap py-3 px-4"
                                        >
                                            {flexRender(
                                                cell.column.columnDef.cell,
                                                cell.getContext(),
                                            )}
                                        </TableCell>
                                    ))}
                                </TableRow>
                            ))}
                        </TableBody>
                    </Table>
                </div>
            </div>
        </section>
    );
};

export default Datatable;
```
