Skip to content
CodeAtlas UI

Display

DataTable

Table with global search, column sorting, pagination and a column visibility toggle, built on TanStack Table.

DataTable lives in its own entry point so that TanStack Table stays out of bundles that do not need it. Install the optional peer first.

bun add @tanstack/svelte-table
<script lang="ts">
  import { DataTable, createCaColumnHelper } from "@codeatlasdev/ui/table";

  const col = createCaColumnHelper<User>();
  const columns = col.columns([
    col.accessor("name", { header: "Name", meta: { label: "Name" } }),
  ]);
</script>

<DataTable {columns} data={users} />

There are two modes. In simple mode you pass columns and data and the table searches, sorts and paginates locally. In controlled mode you pass globalFilter, pagination and sorting together with their change handlers, and feed it one page of data plus total, which is what you want for server-side data.

Simple mode

Pass `columns` and `data`. Search, sort and pagination are managed automatically.

12 results
IDNameEmailRoleStatus
1Ana Souza[email protected]Adminactive
2Bruno Lima[email protected]Developeractive
3Carla Mendes[email protected]Designerinactive
4Diego Ferreira[email protected]Developeractive
5Elena Castro[email protected]Managerpending
6Felipe Rocha[email protected]Developeractive
7Gabriela Nunes[email protected]Designeractive
8Henrique Lopes[email protected]Admininactive
9Isabela Torres[email protected]Developeractive
10Joao Almeida[email protected]Managerpending
Rows
1 / 2 · 12
Code
svelte
<script lang="ts">
  import { DataTable, createCaColumnHelper } from '@codeatlasdev/ui/table';

  interface User {
    id: number;
    name: string;
    email: string;
    role: string;
    status: 'active' | 'inactive' | 'pending';
  }

  const data: User[] = [
    { id: 1, name: 'Ana Souza', email: '[email protected]', role: 'Admin', status: 'active' },
    { id: 2, name: 'Bruno Lima', email: '[email protected]', role: 'Developer', status: 'active' },
    { id: 3, name: 'Carla Mendes', email: '[email protected]', role: 'Designer', status: 'inactive' },
    { id: 4, name: 'Diego Ferreira', email: '[email protected]', role: 'Developer', status: 'active' },
    { id: 5, name: 'Elena Castro', email: '[email protected]', role: 'Manager', status: 'pending' },
    { id: 6, name: 'Felipe Rocha', email: '[email protected]', role: 'Developer', status: 'active' },
    { id: 7, name: 'Gabriela Nunes', email: '[email protected]', role: 'Designer', status: 'active' },
    { id: 8, name: 'Henrique Lopes', email: '[email protected]', role: 'Admin', status: 'inactive' },
    { id: 9, name: 'Isabela Torres', email: '[email protected]', role: 'Developer', status: 'active' },
    { id: 10, name: 'Joao Almeida', email: '[email protected]', role: 'Manager', status: 'pending' },
    { id: 11, name: 'Karina Bastos', email: '[email protected]', role: 'Developer', status: 'active' },
    { id: 12, name: 'Lucas Carvalho', email: '[email protected]', role: 'Designer', status: 'active' },
  ];

  const col = createCaColumnHelper<User>();
  const columns = col.columns([
    col.accessor('id', { header: 'ID', meta: { label: 'ID' } }),
    col.accessor('name', { header: 'Name', meta: { label: 'Name' } }),
    col.accessor('email', { header: 'Email', meta: { label: 'Email' } }),
    col.accessor('role', { header: 'Role', meta: { label: 'Role' } }),
    col.accessor('status', { header: 'Status', meta: { label: 'Status' } }),
  ]);
</script>

<div class="wrap"><DataTable {columns} {data} /></div>

<style>
  .wrap { width: 100%; min-width: 0; }
</style>

Controlled mode

Own the state to drive server-side filtering, sorting and pagination.

12 results
IDNameEmailRoleStatus
1Ana Souza[email protected]Adminactive
2Bruno Lima[email protected]Developeractive
3Carla Mendes[email protected]Designerinactive
4Diego Ferreira[email protected]Developeractive
5Elena Castro[email protected]Managerpending
Rows
1 / 3 · 12
Code
svelte
<script lang="ts">
  import { DataTable, createCaColumnHelper } from '@codeatlasdev/ui/table';
  import type { PaginationState, SortingState, Updater } from '@codeatlasdev/ui/table';

  interface User {
    id: number;
    name: string;
    email: string;
    role: string;
    status: 'active' | 'inactive' | 'pending';
  }

  const allData: User[] = [
    { id: 1, name: 'Ana Souza', email: '[email protected]', role: 'Admin', status: 'active' },
    { id: 2, name: 'Bruno Lima', email: '[email protected]', role: 'Developer', status: 'active' },
    { id: 3, name: 'Carla Mendes', email: '[email protected]', role: 'Designer', status: 'inactive' },
    { id: 4, name: 'Diego Ferreira', email: '[email protected]', role: 'Developer', status: 'active' },
    { id: 5, name: 'Elena Castro', email: '[email protected]', role: 'Manager', status: 'pending' },
    { id: 6, name: 'Felipe Rocha', email: '[email protected]', role: 'Developer', status: 'active' },
    { id: 7, name: 'Gabriela Nunes', email: '[email protected]', role: 'Designer', status: 'active' },
    { id: 8, name: 'Henrique Lopes', email: '[email protected]', role: 'Admin', status: 'inactive' },
    { id: 9, name: 'Isabela Torres', email: '[email protected]', role: 'Developer', status: 'active' },
    { id: 10, name: 'Joao Almeida', email: '[email protected]', role: 'Manager', status: 'pending' },
    { id: 11, name: 'Karina Bastos', email: '[email protected]', role: 'Developer', status: 'active' },
    { id: 12, name: 'Lucas Carvalho', email: '[email protected]', role: 'Designer', status: 'active' },
  ];

  const col = createCaColumnHelper<User>();
  const columns = col.columns([
    col.accessor('id', { header: 'ID', meta: { label: 'ID' } }),
    col.accessor('name', { header: 'Name', meta: { label: 'Name' } }),
    col.accessor('email', { header: 'Email', meta: { label: 'Email' } }),
    col.accessor('role', { header: 'Role', meta: { label: 'Role' } }),
    col.accessor('status', { header: 'Status', meta: { label: 'Status' } }),
  ]);

  let globalFilter = $state('');
  let pagination = $state<PaginationState>({ pageIndex: 0, pageSize: 5 });
  let sorting = $state<SortingState>([]);

  // Stand-in for a server: filter, sort, then slice the requested page.
  const filtered = $derived.by(() => {
    const q = globalFilter.toLowerCase();
    let rows = q
      ? allData.filter((r) =>
          [r.name, r.email, r.role, r.status].some((v) => v.toLowerCase().includes(q)),
        )
      : allData;
    if (sorting.length > 0) {
      const { id, desc } = sorting[0];
      rows = [...rows].sort((a, b) => {
        const av = String(a[id as keyof User]);
        const bv = String(b[id as keyof User]);
        return desc ? bv.localeCompare(av) : av.localeCompare(bv);
      });
    }
    return rows;
  });

  const pageData = $derived(
    filtered.slice(
      pagination.pageIndex * pagination.pageSize,
      (pagination.pageIndex + 1) * pagination.pageSize,
    ),
  );

  const onGlobalFilterChange = (v: string) => {
    globalFilter = v;
    pagination = { ...pagination, pageIndex: 0 };
  };
  const onPaginationChange = (v: Updater<PaginationState>) => {
    pagination = typeof v === 'function' ? v(pagination) : v;
  };
  const onSortingChange = (v: Updater<SortingState>) => {
    sorting = typeof v === 'function' ? v(sorting) : v;
  };
</script>

<div class="wrap">
  <DataTable
    {columns}
    data={pageData}
    total={filtered.length}
    {globalFilter}
    {onGlobalFilterChange}
    {pagination}
    {onPaginationChange}
    {sorting}
    {onSortingChange}
    searchPlaceholder="Search by name, email, role…"
  />
</div>

<style>
  .wrap { width: 100%; min-width: 0; }
</style>

Props

PropTypeDefaultDescription
columnsColumnDef[]–Column definitions created with `createCaColumnHelper`. Required.
dataTData[]–Row data. In simple mode pass all rows; in controlled mode pass the current page. Required.
totalnumberdata.lengthTotal row count after filtering, used to compute the page count. Ignored in simple mode.
globalFilterstringundefinedSearch value. Passing it switches the table to controlled mode.
onGlobalFilterChange(v: string) => voidundefinedCalled when the user types in the search box.
paginationPaginationStateundefinedControlled `{ pageIndex, pageSize }`.
onPaginationChange(v: Updater<PaginationState>) => voidundefinedCalled when the page or page size changes.
sortingSortingStateundefinedControlled sorting, `[{ id, desc }]`.
onSortingChange(v: Updater<SortingState>) => voidundefinedCalled when the user clicks a column header.
labels{ noResults?, noResultsSub?, clearSearch? }undefinedOverrides the strings of the no-results state.
searchPlaceholderstringundefinedPlaceholder of the search input.
isLoadingbooleanundefinedShows a loading indicator in the toolbar.
isExportingbooleanundefinedShows the export button as busy.
onExport() => voidundefinedEnables the export button in the toolbar.
onRowClick(row: TData) => voidundefinedCalled when a row is clicked.
hasActiveFiltersbooleanundefinedTreats the table as filtered, which suppresses the empty state.
emptyStateSnippetundefinedCustom content shown when there is no data and no active filter.
childrenSnippetundefinedFilter chips such as `DataTableFacetedFilter`, rendered in the toolbar.
classstring''Extra class names on the wrapper.