shadcn-svelte Data Table Integration
Introduction
Build powerful data tables by combining svelte-headless-table with shadcn-svelte UI components. Unlike pre-built component libraries, this approach gives you full ownership of your table code—you can customize every aspect to fit your needs.
This guide walks you through building a fully-featured data table with:
- Server-side data fetching
- Pagination
- Sorting
- Filtering
- Column visibility controls
- Row selection
- Row actions with dropdown menus
Installation
First, add the Table component from shadcn-svelte:
npx shadcn-svelte@latest add tablenpx shadcn-svelte@latest add tableThen install svelte-headless-table:
npm install @humanspeak/svelte-headless-tablenpm install @humanspeak/svelte-headless-tablePrerequisites
For this guide, we’ll use a sample Payment type. In a real application, this would come from your database schema or API types.
// src/routes/payments/schema.ts
export type Payment = {
id: string;
amount: number;
status: "pending" | "processing" | "success" | "failed";
email: string;
};// src/routes/payments/schema.ts
export type Payment = {
id: string;
amount: number;
status: "pending" | "processing" | "success" | "failed";
email: string;
};Project Structure
We recommend organizing your data table files like this:
src/routes/payments/
├── +page.svelte # Page component
├── data-table.svelte # Table component
├── columns.ts # Column definitions
├── schema.ts # TypeScript types
└── data-table-actions.svelte # Row actions dropdownsrc/routes/payments/
├── +page.svelte # Page component
├── data-table.svelte # Table component
├── columns.ts # Column definitions
├── schema.ts # TypeScript types
└── data-table-actions.svelte # Row actions dropdownThis separation keeps your code organized and makes it easy to reuse column definitions.
Basic Table
Let’s start with a basic table that displays payment data.
Column Definitions
Create your column definitions in a separate file:
// src/routes/payments/columns.ts
import { createTable, createRender } from "@humanspeak/svelte-headless-table";
import type { Payment } from "./schema";
import type { Readable } from "svelte/store";
export function createPaymentTable(data: Readable<Payment[]>) {
const table = createTable(data);
const columns = table.createColumns([
table.column({
header: "Status",
accessor: "status",
}),
table.column({
header: "Email",
accessor: "email",
}),
table.column({
header: "Amount",
accessor: "amount",
}),
]);
return { table, columns };
}// src/routes/payments/columns.ts
import { createTable, createRender } from "@humanspeak/svelte-headless-table";
import type { Payment } from "./schema";
import type { Readable } from "svelte/store";
export function createPaymentTable(data: Readable<Payment[]>) {
const table = createTable(data);
const columns = table.createColumns([
table.column({
header: "Status",
accessor: "status",
}),
table.column({
header: "Email",
accessor: "email",
}),
table.column({
header: "Amount",
accessor: "amount",
}),
]);
return { table, columns };
}Table Component
Now create the table component:
<!-- src/routes/payments/data-table.svelte -->
<script lang="ts">
import { readable } from "svelte/store";
import { Render, Subscribe } from "@humanspeak/svelte-headless-table";
import * as Table from "$lib/components/ui/table";
import { createPaymentTable } from "./columns";
import type { Payment } from "./schema";
let { data }: { data: Payment[] } = $props();
const store = readable(data);
const { table, columns } = createPaymentTable(store);
const { headerRows, pageRows, tableAttrs, tableBodyAttrs } =
table.createViewModel(columns);
</script>
<div class="rounded-md border">
<Table.Root {...$tableAttrs}>
<Table.Header>
{#each $headerRows as headerRow}
<Subscribe rowAttrs={headerRow.attrs()} let:rowAttrs>
<Table.Row {...rowAttrs}>
{#each headerRow.cells as cell (cell.id)}
<Subscribe attrs={cell.attrs()} let:attrs>
<Table.Head {...attrs}>
<Render of={cell.render()} />
</Table.Head>
</Subscribe>
{/each}
</Table.Row>
</Subscribe>
{/each}
</Table.Header>
<Table.Body {...$tableBodyAttrs}>
{#each $pageRows as row (row.id)}
<Subscribe rowAttrs={row.attrs()} let:rowAttrs>
<Table.Row {...rowAttrs}>
{#each row.cells as cell (cell.id)}
<Subscribe attrs={cell.attrs()} let:attrs>
<Table.Cell {...attrs}>
<Render of={cell.render()} />
</Table.Cell>
</Subscribe>
{/each}
</Table.Row>
</Subscribe>
{/each}
</Table.Body>
</Table.Root>
</div><!-- src/routes/payments/data-table.svelte -->
<script lang="ts">
import { readable } from "svelte/store";
import { Render, Subscribe } from "@humanspeak/svelte-headless-table";
import * as Table from "$lib/components/ui/table";
import { createPaymentTable } from "./columns";
import type { Payment } from "./schema";
let { data }: { data: Payment[] } = $props();
const store = readable(data);
const { table, columns } = createPaymentTable(store);
const { headerRows, pageRows, tableAttrs, tableBodyAttrs } =
table.createViewModel(columns);
</script>
<div class="rounded-md border">
<Table.Root {...$tableAttrs}>
<Table.Header>
{#each $headerRows as headerRow}
<Subscribe rowAttrs={headerRow.attrs()} let:rowAttrs>
<Table.Row {...rowAttrs}>
{#each headerRow.cells as cell (cell.id)}
<Subscribe attrs={cell.attrs()} let:attrs>
<Table.Head {...attrs}>
<Render of={cell.render()} />
</Table.Head>
</Subscribe>
{/each}
</Table.Row>
</Subscribe>
{/each}
</Table.Header>
<Table.Body {...$tableBodyAttrs}>
{#each $pageRows as row (row.id)}
<Subscribe rowAttrs={row.attrs()} let:rowAttrs>
<Table.Row {...rowAttrs}>
{#each row.cells as cell (cell.id)}
<Subscribe attrs={cell.attrs()} let:attrs>
<Table.Cell {...attrs}>
<Render of={cell.render()} />
</Table.Cell>
</Subscribe>
{/each}
</Table.Row>
</Subscribe>
{/each}
</Table.Body>
</Table.Root>
</div>Page Component
Finally, use the table in your page:
<!-- src/routes/payments/+page.svelte -->
<script lang="ts">
import DataTable from "./data-table.svelte";
import type { Payment } from "./schema";
// In a real app, this would come from +page.server.ts
const payments: Payment[] = [
{
id: "728ed52f",
amount: 100,
status: "pending",
email: "m@example.com",
},
{
id: "489e1d42",
amount: 125,
status: "processing",
email: "example@gmail.com",
},
// ... more data
];
</script>
<div class="container mx-auto py-10">
<DataTable data={payments} />
</div><!-- src/routes/payments/+page.svelte -->
<script lang="ts">
import DataTable from "./data-table.svelte";
import type { Payment } from "./schema";
// In a real app, this would come from +page.server.ts
const payments: Payment[] = [
{
id: "728ed52f",
amount: 100,
status: "pending",
email: "m@example.com",
},
{
id: "489e1d42",
amount: 125,
status: "processing",
email: "example@gmail.com",
},
// ... more data
];
</script>
<div class="container mx-auto py-10">
<DataTable data={payments} />
</div>Cell Formatting
You can format cell values by providing a custom cell function:
table.column({
header: "Amount",
accessor: "amount",
cell: ({ value }) => {
const formatted = new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
}).format(value);
return formatted;
},
}),table.column({
header: "Amount",
accessor: "amount",
cell: ({ value }) => {
const formatted = new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
}).format(value);
return formatted;
},
}),For more complex formatting with custom styles, use createRender:
import StatusBadge from "./status-badge.svelte";
table.column({
header: "Status",
accessor: "status",
cell: ({ value }) => {
return createRender(StatusBadge, { status: value });
},
}),import StatusBadge from "./status-badge.svelte";
table.column({
header: "Status",
accessor: "status",
cell: ({ value }) => {
return createRender(StatusBadge, { status: value });
},
}),<!-- status-badge.svelte -->
<script lang="ts">
import { Badge } from "$lib/components/ui/badge";
export let status: string;
const variants = {
pending: "secondary",
processing: "outline",
success: "default",
failed: "destructive",
} as const;
</script>
<Badge variant={variants[status] ?? "default"}>
{status}
</Badge><!-- status-badge.svelte -->
<script lang="ts">
import { Badge } from "$lib/components/ui/badge";
export let status: string;
const variants = {
pending: "secondary",
processing: "outline",
success: "default",
failed: "destructive",
} as const;
</script>
<Badge variant={variants[status] ?? "default"}>
{status}
</Badge>Row Actions
Add a dropdown menu for row actions using createRender:
<!-- src/routes/payments/data-table-actions.svelte -->
<script lang="ts">
import Ellipsis from "lucide-svelte/icons/ellipsis";
import * as DropdownMenu from "$lib/components/ui/dropdown-menu";
import { Button } from "$lib/components/ui/button";
export let id: string;
</script>
<DropdownMenu.Root>
<DropdownMenu.Trigger asChild let:builder>
<Button
variant="ghost"
builders={[builder]}
size="icon"
class="relative h-8 w-8 p-0"
>
<span class="sr-only">Open menu</span>
<Ellipsis class="h-4 w-4" />
</Button>
</DropdownMenu.Trigger>
<DropdownMenu.Content>
<DropdownMenu.Group>
<DropdownMenu.Label>Actions</DropdownMenu.Label>
<DropdownMenu.Item on:click={() => navigator.clipboard.writeText(id)}>
Copy payment ID
</DropdownMenu.Item>
</DropdownMenu.Group>
<DropdownMenu.Separator />
<DropdownMenu.Item>View customer</DropdownMenu.Item>
<DropdownMenu.Item>View payment details</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Root><!-- src/routes/payments/data-table-actions.svelte -->
<script lang="ts">
import Ellipsis from "lucide-svelte/icons/ellipsis";
import * as DropdownMenu from "$lib/components/ui/dropdown-menu";
import { Button } from "$lib/components/ui/button";
export let id: string;
</script>
<DropdownMenu.Root>
<DropdownMenu.Trigger asChild let:builder>
<Button
variant="ghost"
builders={[builder]}
size="icon"
class="relative h-8 w-8 p-0"
>
<span class="sr-only">Open menu</span>
<Ellipsis class="h-4 w-4" />
</Button>
</DropdownMenu.Trigger>
<DropdownMenu.Content>
<DropdownMenu.Group>
<DropdownMenu.Label>Actions</DropdownMenu.Label>
<DropdownMenu.Item on:click={() => navigator.clipboard.writeText(id)}>
Copy payment ID
</DropdownMenu.Item>
</DropdownMenu.Group>
<DropdownMenu.Separator />
<DropdownMenu.Item>View customer</DropdownMenu.Item>
<DropdownMenu.Item>View payment details</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Root>Add the actions column:
import DataTableActions from "./data-table-actions.svelte";
table.column({
header: "",
accessor: ({ id }) => id,
cell: ({ value }) => {
return createRender(DataTableActions, { id: value });
},
}),import DataTableActions from "./data-table-actions.svelte";
table.column({
header: "",
accessor: ({ id }) => id,
cell: ({ value }) => {
return createRender(DataTableActions, { id: value });
},
}),Pagination
Add pagination using the addPagination plugin:
import { createTable, createRender } from "@humanspeak/svelte-headless-table";
import { addPagination } from "@humanspeak/svelte-headless-table/plugins";
const table = createTable(data, {
page: addPagination(),
});import { createTable, createRender } from "@humanspeak/svelte-headless-table";
import { addPagination } from "@humanspeak/svelte-headless-table/plugins";
const table = createTable(data, {
page: addPagination(),
});Extract the pagination state from the view model:
const { headerRows, pageRows, tableAttrs, tableBodyAttrs, pluginStates } =
table.createViewModel(columns);
const { hasNextPage, hasPreviousPage, pageIndex } = pluginStates.page;const { headerRows, pageRows, tableAttrs, tableBodyAttrs, pluginStates } =
table.createViewModel(columns);
const { hasNextPage, hasPreviousPage, pageIndex } = pluginStates.page;Add pagination controls to your table component:
<div class="flex items-center justify-end space-x-4 py-4">
<Button
variant="outline"
size="sm"
on:click={() => ($pageIndex = $pageIndex - 1)}
disabled={!$hasPreviousPage}
>
Previous
</Button>
<Button
variant="outline"
size="sm"
on:click={() => ($pageIndex = $pageIndex + 1)}
disabled={!$hasNextPage}
>
Next
</Button>
</div><div class="flex items-center justify-end space-x-4 py-4">
<Button
variant="outline"
size="sm"
on:click={() => ($pageIndex = $pageIndex - 1)}
disabled={!$hasPreviousPage}
>
Previous
</Button>
<Button
variant="outline"
size="sm"
on:click={() => ($pageIndex = $pageIndex + 1)}
disabled={!$hasNextPage}
>
Next
</Button>
</div>Sorting
Add sorting with the addSortBy plugin:
import { addPagination, addSortBy } from "@humanspeak/svelte-headless-table/plugins";
const table = createTable(data, {
page: addPagination(),
sort: addSortBy(),
});import { addPagination, addSortBy } from "@humanspeak/svelte-headless-table/plugins";
const table = createTable(data, {
page: addPagination(),
sort: addSortBy(),
});Create a sortable header component:
<!-- src/routes/payments/data-table-sort-button.svelte -->
<script lang="ts">
import ArrowUpDown from "lucide-svelte/icons/arrow-up-down";
import { Button } from "$lib/components/ui/button";
export let label: string;
</script>
<Button variant="ghost" on:click>
{label}
<ArrowUpDown class="ml-2 h-4 w-4" />
</Button><!-- src/routes/payments/data-table-sort-button.svelte -->
<script lang="ts">
import ArrowUpDown from "lucide-svelte/icons/arrow-up-down";
import { Button } from "$lib/components/ui/button";
export let label: string;
</script>
<Button variant="ghost" on:click>
{label}
<ArrowUpDown class="ml-2 h-4 w-4" />
</Button>Update your column definitions to use sortable headers:
import DataTableSortButton from "./data-table-sort-button.svelte";
table.column({
header: (cell, { pluginStates }) => {
const { sort } = pluginStates;
return createRender(DataTableSortButton, { label: "Email" });
},
accessor: "email",
}),import DataTableSortButton from "./data-table-sort-button.svelte";
table.column({
header: (cell, { pluginStates }) => {
const { sort } = pluginStates;
return createRender(DataTableSortButton, { label: "Email" });
},
accessor: "email",
}),Wire up the click handler in the header:
{#each headerRow.cells as cell (cell.id)}
<Subscribe attrs={cell.attrs()} let:attrs props={cell.props()} let:props>
<Table.Head {...attrs}>
{#if cell.id === "email"}
<Button variant="ghost" on:click={props.sort.toggle}>
<Render of={cell.render()} />
</Button>
{:else}
<Render of={cell.render()} />
{/if}
</Table.Head>
</Subscribe>
{/each}{#each headerRow.cells as cell (cell.id)}
<Subscribe attrs={cell.attrs()} let:attrs props={cell.props()} let:props>
<Table.Head {...attrs}>
{#if cell.id === "email"}
<Button variant="ghost" on:click={props.sort.toggle}>
<Render of={cell.render()} />
</Button>
{:else}
<Render of={cell.render()} />
{/if}
</Table.Head>
</Subscribe>
{/each}Filtering
Add table-wide filtering with the addTableFilter plugin:
import {
addPagination,
addSortBy,
addTableFilter,
} from "@humanspeak/svelte-headless-table/plugins";
const table = createTable(data, {
page: addPagination(),
sort: addSortBy(),
filter: addTableFilter({
fn: ({ filterValue, value }) =>
value.toLowerCase().includes(filterValue.toLowerCase()),
}),
});import {
addPagination,
addSortBy,
addTableFilter,
} from "@humanspeak/svelte-headless-table/plugins";
const table = createTable(data, {
page: addPagination(),
sort: addSortBy(),
filter: addTableFilter({
fn: ({ filterValue, value }) =>
value.toLowerCase().includes(filterValue.toLowerCase()),
}),
});Extract the filter state:
const { filterValue } = pluginStates.filter;const { filterValue } = pluginStates.filter;Add a filter input:
<div class="flex items-center py-4">
<Input
class="max-w-sm"
placeholder="Filter emails..."
type="text"
bind:value={$filterValue}
/>
</div><div class="flex items-center py-4">
<Input
class="max-w-sm"
placeholder="Filter emails..."
type="text"
bind:value={$filterValue}
/>
</div>Column Visibility
Add column visibility controls with the addHiddenColumns plugin:
import {
addPagination,
addSortBy,
addTableFilter,
addHiddenColumns,
} from "@humanspeak/svelte-headless-table/plugins";
const table = createTable(data, {
page: addPagination(),
sort: addSortBy(),
filter: addTableFilter({
fn: ({ filterValue, value }) =>
value.toLowerCase().includes(filterValue.toLowerCase()),
}),
hide: addHiddenColumns(),
});import {
addPagination,
addSortBy,
addTableFilter,
addHiddenColumns,
} from "@humanspeak/svelte-headless-table/plugins";
const table = createTable(data, {
page: addPagination(),
sort: addSortBy(),
filter: addTableFilter({
fn: ({ filterValue, value }) =>
value.toLowerCase().includes(filterValue.toLowerCase()),
}),
hide: addHiddenColumns(),
});Extract the hidden columns state:
const { hiddenColumnIds } = pluginStates.hide;const { hiddenColumnIds } = pluginStates.hide;Create a columns visibility dropdown:
<DropdownMenu.Root>
<DropdownMenu.Trigger asChild let:builder>
<Button variant="outline" class="ml-auto" builders={[builder]}>
Columns
<ChevronDown class="ml-2 h-4 w-4" />
</Button>
</DropdownMenu.Trigger>
<DropdownMenu.Content>
{#each flatColumns as col}
{#if col.id !== "actions"}
<DropdownMenu.CheckboxItem
checked={!$hiddenColumnIds.includes(col.id)}
on:click={() => {
if ($hiddenColumnIds.includes(col.id)) {
$hiddenColumnIds = $hiddenColumnIds.filter((id) => id !== col.id);
} else {
$hiddenColumnIds = [...$hiddenColumnIds, col.id];
}
}}
>
{col.header}
</DropdownMenu.CheckboxItem>
{/if}
{/each}
</DropdownMenu.Content>
</DropdownMenu.Root><DropdownMenu.Root>
<DropdownMenu.Trigger asChild let:builder>
<Button variant="outline" class="ml-auto" builders={[builder]}>
Columns
<ChevronDown class="ml-2 h-4 w-4" />
</Button>
</DropdownMenu.Trigger>
<DropdownMenu.Content>
{#each flatColumns as col}
{#if col.id !== "actions"}
<DropdownMenu.CheckboxItem
checked={!$hiddenColumnIds.includes(col.id)}
on:click={() => {
if ($hiddenColumnIds.includes(col.id)) {
$hiddenColumnIds = $hiddenColumnIds.filter((id) => id !== col.id);
} else {
$hiddenColumnIds = [...$hiddenColumnIds, col.id];
}
}}
>
{col.header}
</DropdownMenu.CheckboxItem>
{/if}
{/each}
</DropdownMenu.Content>
</DropdownMenu.Root>Row Selection
Add row selection with the addSelectedRows plugin:
import {
addPagination,
addSortBy,
addTableFilter,
addHiddenColumns,
addSelectedRows,
} from "@humanspeak/svelte-headless-table/plugins";
const table = createTable(data, {
page: addPagination(),
sort: addSortBy(),
filter: addTableFilter({
fn: ({ filterValue, value }) =>
value.toLowerCase().includes(filterValue.toLowerCase()),
}),
hide: addHiddenColumns(),
select: addSelectedRows(),
});import {
addPagination,
addSortBy,
addTableFilter,
addHiddenColumns,
addSelectedRows,
} from "@humanspeak/svelte-headless-table/plugins";
const table = createTable(data, {
page: addPagination(),
sort: addSortBy(),
filter: addTableFilter({
fn: ({ filterValue, value }) =>
value.toLowerCase().includes(filterValue.toLowerCase()),
}),
hide: addHiddenColumns(),
select: addSelectedRows(),
});Create a checkbox component for row selection:
<!-- src/routes/payments/data-table-checkbox.svelte -->
<script lang="ts">
import { Checkbox } from "$lib/components/ui/checkbox";
export let checked: boolean = false;
</script>
<Checkbox bind:checked /><!-- src/routes/payments/data-table-checkbox.svelte -->
<script lang="ts">
import { Checkbox } from "$lib/components/ui/checkbox";
export let checked: boolean = false;
</script>
<Checkbox bind:checked />Add a select column:
import DataTableCheckbox from "./data-table-checkbox.svelte";
table.column({
header: (_, { pluginStates }) => {
const { allPageRowsSelected } = pluginStates.select;
return createRender(DataTableCheckbox, {
checked: allPageRowsSelected,
});
},
accessor: "id",
cell: ({ row }, { pluginStates }) => {
const { getRowState } = pluginStates.select;
const { isSelected } = getRowState(row);
return createRender(DataTableCheckbox, {
checked: isSelected,
});
},
}),import DataTableCheckbox from "./data-table-checkbox.svelte";
table.column({
header: (_, { pluginStates }) => {
const { allPageRowsSelected } = pluginStates.select;
return createRender(DataTableCheckbox, {
checked: allPageRowsSelected,
});
},
accessor: "id",
cell: ({ row }, { pluginStates }) => {
const { getRowState } = pluginStates.select;
const { isSelected } = getRowState(row);
return createRender(DataTableCheckbox, {
checked: isSelected,
});
},
}),Display the selection count:
<div class="flex-1 text-sm text-muted-foreground">
{Object.keys($selectedDataIds).length} of {$rows.length} row(s) selected.
</div><div class="flex-1 text-sm text-muted-foreground">
{Object.keys($selectedDataIds).length} of {$rows.length} row(s) selected.
</div>Complete Example
Here’s a complete example combining all features:
// columns.ts
import { createTable, createRender } from "@humanspeak/svelte-headless-table";
import {
addPagination,
addSortBy,
addTableFilter,
addHiddenColumns,
addSelectedRows,
} from "@humanspeak/svelte-headless-table/plugins";
import type { Payment } from "./schema";
import type { Readable } from "svelte/store";
import DataTableActions from "./data-table-actions.svelte";
import DataTableCheckbox from "./data-table-checkbox.svelte";
export function createPaymentTable(data: Readable<Payment[]>) {
const table = createTable(data, {
page: addPagination(),
sort: addSortBy({ disableMultiSort: true }),
filter: addTableFilter({
fn: ({ filterValue, value }) =>
value.toLowerCase().includes(filterValue.toLowerCase()),
}),
hide: addHiddenColumns(),
select: addSelectedRows(),
});
const columns = table.createColumns([
table.column({
header: (_, { pluginStates }) => {
const { allPageRowsSelected } = pluginStates.select;
return createRender(DataTableCheckbox, {
checked: allPageRowsSelected,
});
},
accessor: "id",
cell: ({ row }, { pluginStates }) => {
const { getRowState } = pluginStates.select;
const { isSelected } = getRowState(row);
return createRender(DataTableCheckbox, {
checked: isSelected,
});
},
plugins: {
sort: { disable: true },
filter: { exclude: true },
},
}),
table.column({
header: "Status",
accessor: "status",
plugins: {
sort: { disable: true },
filter: { exclude: true },
},
}),
table.column({
header: "Email",
accessor: "email",
}),
table.column({
header: "Amount",
accessor: "amount",
cell: ({ value }) => {
const formatted = new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
}).format(value);
return formatted;
},
plugins: {
filter: { exclude: true },
},
}),
table.column({
header: "",
accessor: ({ id }) => id,
cell: ({ value }) => {
return createRender(DataTableActions, { id: value });
},
plugins: {
sort: { disable: true },
filter: { exclude: true },
},
}),
]);
return { table, columns };
}// columns.ts
import { createTable, createRender } from "@humanspeak/svelte-headless-table";
import {
addPagination,
addSortBy,
addTableFilter,
addHiddenColumns,
addSelectedRows,
} from "@humanspeak/svelte-headless-table/plugins";
import type { Payment } from "./schema";
import type { Readable } from "svelte/store";
import DataTableActions from "./data-table-actions.svelte";
import DataTableCheckbox from "./data-table-checkbox.svelte";
export function createPaymentTable(data: Readable<Payment[]>) {
const table = createTable(data, {
page: addPagination(),
sort: addSortBy({ disableMultiSort: true }),
filter: addTableFilter({
fn: ({ filterValue, value }) =>
value.toLowerCase().includes(filterValue.toLowerCase()),
}),
hide: addHiddenColumns(),
select: addSelectedRows(),
});
const columns = table.createColumns([
table.column({
header: (_, { pluginStates }) => {
const { allPageRowsSelected } = pluginStates.select;
return createRender(DataTableCheckbox, {
checked: allPageRowsSelected,
});
},
accessor: "id",
cell: ({ row }, { pluginStates }) => {
const { getRowState } = pluginStates.select;
const { isSelected } = getRowState(row);
return createRender(DataTableCheckbox, {
checked: isSelected,
});
},
plugins: {
sort: { disable: true },
filter: { exclude: true },
},
}),
table.column({
header: "Status",
accessor: "status",
plugins: {
sort: { disable: true },
filter: { exclude: true },
},
}),
table.column({
header: "Email",
accessor: "email",
}),
table.column({
header: "Amount",
accessor: "amount",
cell: ({ value }) => {
const formatted = new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
}).format(value);
return formatted;
},
plugins: {
filter: { exclude: true },
},
}),
table.column({
header: "",
accessor: ({ id }) => id,
cell: ({ value }) => {
return createRender(DataTableActions, { id: value });
},
plugins: {
sort: { disable: true },
filter: { exclude: true },
},
}),
]);
return { table, columns };
}