Live Updates
Non-disruptive real-time data updates with buffering and user-controlled application.
Overview
Writing rows straight into a filtered, sorted, paged table is disorienting: rows jump
position mid-read, disappear behind an active filter, or land in a selection the reader is
still building. enableLiveUpdates buffers them instead.
A banner above the rows counts what is waiting (“3 new, 2 updated”), and the reader
merges it when they are ready.
You push changes from any data source — WebSocket, SSE, or polling — through three methods
on the table context: pushInsert, pushUpdate, and pushDelete.
Demo
The panel below plays the role of your backend. Push a few events — the LiveUpdateBanner appears above the rows and counts
what is pending. Apply merges the buffer into the table, Dismiss drops it. The demo passes autoApplyOnNavigation={false} so pending changes always
wait for your click.
Simulated server feed
Stand-in for a WebSocket/SSE handler — each button calls the corresponding push method.
Order | Customer | Status | Total | Placed |
|---|---|---|---|---|
ORD-1001 | Aurora Labs | delivered | 1240 | 2026-07-08 |
ORD-1002 | Baltic Trade | shipped | 380 | 2026-07-09 |
ORD-1003 | Cobalt Works | paid | 2150 | 2026-07-10 |
ORD-1004 | Delta Foods | pending | 95 | 2026-07-11 |
ORD-1005 | Ember Studio | paid | 640 | 2026-07-11 |
ORD-1006 | Fjord Analytics | shipped | 1780 | 2026-07-12 |
ORD-1007 | Granite & Co | pending | 420 | 2026-07-12 |
ORD-1008 | Helix Health | pending | 310 | 2026-07-13 |
Enabling live updates
Enable live updates
<Table
{items}
{columns}
enableLiveUpdates
/>Push changes from your data source
<script>
import { Table, type TableContext } from '@urbicon-ui/table';
let table = $state<TableContext | null>(null);
$effect(() => {
const ctx = table;
if (!ctx) return;
const socket = new WebSocket('wss://api.example.com/orders');
socket.onmessage = (event) => {
const message = JSON.parse(event.data);
if (message.type === 'order:created') ctx.pushInsert(message.order);
if (message.type === 'order:updated') ctx.pushUpdate(message.id, message.changes);
if (message.type === 'order:deleted') ctx.pushDelete(message.id);
};
return () => socket.close();
});
</script>
<Table
{items}
{columns}
enableLiveUpdates
onReady={(context) => (table = context)}
/>Alternative: a feed component inside the table tree
<script>
// LiveFeed.svelte calls getTableContext() — so it must render inside the table
import { Table, SmartFilterBar } from '@urbicon-ui/table';
import LiveFeed from './LiveFeed.svelte';
</script>
<Table {items} {columns} enableLiveUpdates>
{#snippet toolbar()}
<LiveFeed />
<SmartFilterBar />
{/snippet}
</Table>Buffer & merge semantics
The buffer holds at most one pending outcome per row, so a busy feed cannot make it grow
past the number of rows it touched. A second pushInsert replaces the first, consecutive pushUpdate calls merge into one, a pushDelete for a row that is still a pending insert
cancels both, and a pushUpdate for one is folded into the
insert so the row lands already updated. Order of arrival does not matter.
Applying runs deletes, then updates, then inserts. A row that just took an update is highlighted for three seconds, so the change is findable in a long list, and a deleted row leaves the selection along with the table. An update or delete naming a row the table does not hold is skipped, with a warning in dev.
Auto-apply on navigation
With autoApplyOnNavigation (default true), pending changes are merged automatically when
the user changes page, sort, filter, or search — the view is reorganizing anyway, so merging
at that moment is non-disruptive. Set it to false to make the banner the only way changes are applied.
Explicit apply only
<Table
{items}
{columns}
enableLiveUpdates
autoApplyOnNavigation={false}
/>