shadcn-svelte Custom Component in Datatable

January 16, 2026

With the original shadcn for react, using an existing component to be displayed in the datatable cell is as easy as the following:

something.tsx
cell: ({ row }) => (
<Checkbox
checked={row.getIsSelected()}
onCheckedChange={(value) => row.toggleSelected(!!value)}
aria-label="Select row"
/>
),

But with svelte, it’s different.

The following code adds the <Checkbox /> component from shadcn-svelte to the table cell.

+page.svelte
<script lang="ts">
import Checkbox from '$lib/components/ui/checkbox/checkbox.svelte';
import type { ColumnDef } from '@tanstack/table-core';
import { createRawSnippet, mount } from 'svelte';
const columns: ColumnDef<DataType>[] = [
{
accessorKey: 'key',
cell: ({ row }) => {
const snippet = createRawSnippet(() => ({
render: () => `<div></div>`,
setup: (target) => {
mount(Checkbox, {
target,
props: {
checked: isChecked,
onCheckedChange: (value) => {}
}
});
}
}));
return renderSnippet(snippet);
}
}
];
const data = [];
</script>
<DataTable {columns} {data} />

For reference this is the code of my <Datatable />.

data-table.svelte
129 collapsed lines
<script lang="ts" generics="TData, TValue">
import {
type ColumnDef,
type PaginationState,
type SortingState,
type ColumnFiltersState,
getCoreRowModel,
getPaginationRowModel,
getSortedRowModel,
getFilteredRowModel
} from '@tanstack/table-core';
import { Input } from '$lib/components/ui/input/index.js';
import { createSvelteTable } from '$lib/components/ui/data-table';
import * as Table from '$lib/components/ui/table';
import FlexRender from '$lib/components/ui/data-table/flex-render.svelte';
import type { Snippet } from 'svelte';
import { cn } from '$lib/utils';
type DataTableProps<TData, TValue> = {
columns: ColumnDef<TData, TValue>[];
data: TData[];
};
let {
columns,
data,
filterInput,
containerClass
}: DataTableProps<TData, TValue> & { filterInput?: Snippet; containerClass?: string } = $props();
let pagination = $state<PaginationState>({ pageIndex: 0, pageSize: 100 });
let sorting = $state<SortingState>([]);
let columnFilters = $state<ColumnFiltersState>([]);
const table = createSvelteTable({
get data() {
return data;
},
columns,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getSortedRowModel: getSortedRowModel(),
getFilteredRowModel: getFilteredRowModel(),
onPaginationChange: (updater) => {
if (typeof updater === 'function') {
pagination = updater(pagination);
} else {
pagination = updater;
}
},
onSortingChange: (updater) => {
if (typeof updater === 'function') {
sorting = updater(sorting);
} else {
sorting = updater;
}
},
onColumnFiltersChange: (updater) => {
if (typeof updater === 'function') {
columnFilters = updater(columnFilters);
} else {
columnFilters = updater;
}
},
state: {
get pagination() {
return pagination;
},
get sorting() {
return sorting;
},
get columnFilters() {
return columnFilters;
}
}
});
</script>
<div class={cn(containerClass)}>
<div class="flex items-center py-4">
<!-- TODO: Make reusable -->
<Input
placeholder="Filter IGNs..."
value={(table.getColumn('ign')?.getFilterValue() as string) ?? ''}
onchange={(e) => {
table.getColumn('ign')?.setFilterValue(e.currentTarget.value);
}}
oninput={(e) => {
table.getColumn('ign')?.setFilterValue(e.currentTarget.value);
}}
class="max-w-sm"
/>
</div>
<div class="rounded-md border w-full overflow-x-auto">
<Table.Root>
<Table.Header>
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
<Table.Row>
{#each headerGroup.headers as header (header.id)}
<Table.Head colspan={header.colSpan}>
{#if !header.isPlaceholder}
<FlexRender
content={header.column.columnDef.header}
context={header.getContext()}
/>
{/if}
</Table.Head>
{/each}
</Table.Row>
{/each}
</Table.Header>
<Table.Body>
{#each table.getRowModel().rows as row (row.id)}
<Table.Row data-state={row.getIsSelected() && 'selected'}>
{#each row.getVisibleCells() as cell (cell.id)}
<Table.Cell>
<FlexRender content={cell.column.columnDef.cell} context={cell.getContext()} />
</Table.Cell>
{/each}
</Table.Row>
{:else}
<Table.Row>
<Table.Cell colspan={columns.length} class="h-24 text-center">No results.</Table.Cell>
</Table.Row>
{/each}
</Table.Body>
</Table.Root>
</div>
</div>

For more information regarding shadcn-svelte’s <Datatable /> ↗️.