A powerful, feature-rich data grid component for Svelte 5. This is a port of tablecn.com - the original React implementation.
# Using bun
bunx shadcn-svelte@latest add https://svelte-tablecn.vercel.app/r/data-grid.json
# Using pnpm
pnpm dlx shadcn-svelte@latest add https://svelte-tablecn.vercel.app/r/data-grid.json
# Using npm
npx shadcn-svelte@latest add https://svelte-tablecn.vercel.app/r/data-grid.json
Make sure you have shadcn-svelte configured in your project with Tailwind CSS v4:
bunx shadcn-svelte@latest init
<script lang="ts">
import { DataGrid } from '$lib/components/data-grid';
import { useDataGrid } from '$lib/hooks/use-data-grid.svelte';
import type { ColumnDef } from '@tanstack/table-core';
type Employee = {
id: string;
name: string;
email: string;
age: number;
department: string;
startDate: string;
isActive: boolean;
};
const departments = ['Engineering', 'Marketing', 'Sales', 'HR', 'Finance'];
let data = $state<Employee[]>([
{ id: '1', name: 'John Doe', email: '[email protected]', age: 32, department: 'Engineering', startDate: '2022-03-15', isActive: true },
{ id: '2', name: 'Jane Smith', email: '[email protected]', age: 28, department: 'Marketing', startDate: '2021-07-22', isActive: true },
{ id: '3', name: 'Bob Johnson', email: '[email protected]', age: 45, department: 'Sales', startDate: '2019-11-08', isActive: false },
{ id: '4', name: 'Alice Williams', email: '[email protected]', age: 35, department: 'HR', startDate: '2020-05-30', isActive: true },
{ id: '5', name: 'Charlie Brown', email: '[email protected]', age: 29, department: 'Finance', startDate: '2023-01-10', isActive: true },
{ id: '6', name: 'Diana Ross', email: '[email protected]', age: 41, department: 'Engineering', startDate: '2018-09-14', isActive: true },
{ id: '7', name: 'Edward Chen', email: '[email protected]', age: 33, department: 'Marketing', startDate: '2021-02-28', isActive: false },
{ id: '8', name: 'Fiona Garcia', email: '[email protected]', age: 27, department: 'Sales', startDate: '2022-08-05', isActive: true },
{ id: '9', name: 'George Wilson', email: '[email protected]', age: 52, department: 'HR', startDate: '2017-04-18', isActive: true },
{ id: '10', name: 'Hannah Lee', email: '[email protected]', age: 31, department: 'Finance', startDate: '2020-12-01', isActive: true },
]);
const columns: ColumnDef<Employee, unknown>[] = [
{
accessorKey: 'name',
header: 'Name',
meta: { cell: { variant: 'short-text' } }
},
{
accessorKey: 'email',
header: 'Email',
meta: { cell: { variant: 'short-text' } }
},
{
accessorKey: 'age',
header: 'Age',
meta: { cell: { variant: 'number', min: 18, max: 100 } }
},
{
accessorKey: 'department',
header: 'Department',
meta: {
cell: {
variant: 'select',
options: departments.map(d => ({ label: d, value: d }))
}
}
},
{
accessorKey: 'startDate',
header: 'Start Date',
meta: { cell: { variant: 'date' } }
},
{
accessorKey: 'isActive',
header: 'Active',
meta: { cell: { variant: 'checkbox' } }
}
];
const { table, ...dataGridProps } = useDataGrid({
data: () => data,
columns,
onDataChange: (newData) => {
data = newData;
},
getRowId: (row) => row.id,
enableSearch: true
});
</script>
<DataGrid {...dataGridProps} {table} height={600} />
The data grid supports multiple cell types:
| Variant | Description |
|---|---|
short-text |
Single-line text input |
long-text |
Multi-line text with expandable editor |
number |
Numeric input with optional min/max/step |
date |
Date picker |
select |
Single select dropdown |
multi-select |
Multiple select with tags |
checkbox |
Boolean checkbox |
url |
URL input with link preview |
file |
File upload cell |
row-select |
Row selection checkbox |
| Shortcut | Action |
|---|---|
| Arrow keys | Navigate cells |
| Tab / Shift+Tab | Move right/left |
| Enter | Start editing / Move down |
| Escape | Cancel editing / Clear selection |
| Ctrl/Cmd + C | Copy selected cells |
| Ctrl/Cmd + V | Paste |
| Ctrl/Cmd + X | Cut |
| Ctrl/Cmd + Z | Undo |
| Ctrl/Cmd + Shift + Z | Redo |
| Ctrl/Cmd + F | Open search |
| Delete/Backspace | Clear cell content |
| Option | Type | Description |
|---|---|---|
data |
TData[] | (() => TData[]) |
Data array or getter function for reactivity |
columns |
ColumnDef<TData>[] |
Column definitions |
getRowId |
(row) => string |
Function to get unique row ID |
enableSearch |
boolean |
Enable search functionality |
enablePaste |
boolean |
Enable paste functionality |
readOnly |
boolean |
Make grid read-only |
rowHeight |
'short' | 'medium' | 'tall' | 'extra-tall' |
Row height preset |
initialState |
object |
Initial table state (sorting, filters, etc.) |
onDataChange |
(data: TData[]) => void |
Called when data changes |
onRowAdd |
() => void |
Called when a new row is added |
onRowsAdd |
(count: number) => void |
Called when multiple rows are added |
onRowsDelete |
(rows, indices) => void |
Called when rows are deleted |
onFilesUpload |
(params) => Promise<FileCellData[]> |
Handle file uploads |
onFilesDelete |
(params) => void |
Handle file deletions |
meta: {
label: string; // Column label
cell: {
variant: CellVariant; // Cell type
// Variant-specific options:
min?: number; // For 'number'
max?: number; // For 'number'
step?: number; // For 'number'
options?: Option[]; // For 'select' and 'multi-select'
maxFiles?: number; // For 'file'
maxFileSize?: number; // For 'file'
accept?: string; // For 'file'
}
}
This is a Svelte 5 port of tablecn.com. All credit for the original design and implementation goes to the tablecn team.
Built with:
MIT