Skip to main content
Urbicon UI

Client Processing

The table sorts, filters, searches and pages your rows in the browser. Pass an array, and every control on the page works.

Hand over the rows

processing: 'client' means the table does the work. Search, filters, sorting, grouping and paging all happen in the browser, on the array you passed. The items prop is the short way to say it:

An array and some columns

Type in the search field, sort a column, page through. Nothing else to wire.
Employee
Role
Department
Location
Emma Wilson
Staff Engineer
Platform
Berlin
Liam Chen
Product Designer
Design
Hamburg
Sofia Martinez
Engineering Manager
Platform
Munich
James Park
Frontend Developer
Product
Remote
Aisha Patel
Data Scientist
Data
Berlin
<script lang="ts">
  import { Table } from '@urbicon-ui/table';
  import { employees } from './data';

  const columns = [
    { accessor: 'name', title: 'Employee' },
    { accessor: 'role', title: 'Role' },
    { accessor: 'department', title: 'Department', groupable: true },
    { accessor: 'location', title: 'Location' }
  ];
</script>

<Table {columns} items={employees} viewDefaults={{ pageSize: 5 }} />

Every column sorts and answers the search field unless it opts out; groupable is the one you opt into (Column Configuration). viewDefaults sets where the view the table owns starts. Once that state has to live somewhere the reader can share, hand in a view of your own from createTableView instead (URL State); a table takes one or the other, never both.

You don't need source for this. Reach for it once a loading or an error state comes into it.

Rows that arrive with the page are the same case. A SvelteKit load has them ready before the page renders, so the first screen is server-rendered and the table takes over from there:

Every row, in one load

No fetch in the browser, no empty first paint.
// +page.server.ts
export const load = async () => ({ employees: await db.employees.findMany() });

// +page.svelte
<script lang="ts">
  import { Table } from '@urbicon-ui/table';
  import { columns } from './columns';

  let { data } = $props();
</script>

<Table {columns} items={data.employees} />

While the rows are loading

When you fetch the rows yourself, source carries the two states the table renders for you:

Your fetch, the table's states

<script lang="ts">
  import { Table } from '@urbicon-ui/table';

  let items = $state<Employee[]>([]);
  let loading = $state(true);
  let error = $state<string | null>(null);

  fetchEmployees()
    .then((rows) => (items = rows))
    .catch((e) => (error = e.message))
    .finally(() => (loading = false));
</script>

<Table {columns} source={{ processing: 'client', items, loading, error }} />

The table shows its loading row while loading is true, in place of the rows and with the pager hidden until it clears. Its error state carries whatever error holds as the message. You decide when each applies; the search field and the rest of the toolbar stay put throughout.

source wins over items

Both props reach the same rows. When you pass both, the table reads source and the items prop does nothing.

When the browser runs out

The whole set sits in memory, and every sort touches all of it. A few thousand rows is comfortable. A hundred thousand is a tab that stops responding.

Two ways out, and they answer different questions. If the rows still fit and only the rendering hurts, keep client processing and draw fewer of them: Virtual Scrolling. If the set is too large to send at all, the backend sorts and pages it instead: Server Processing.