Skip to content

Load Cards

Start with the server-side overview. A loader returns ordinary application cards; Kanban does not need pre-rendered board rows.

Interactive example · supplied HTTP demo backend

Load cards from your backend

A local HTTP demo backend returns pages of canonical cards.

Try it: Refresh normally, then try a slow connection and watch the loading state.

Starting demo backend…

HTTP reads: 0 · writes: 0

What to notice: Kanban receives cards and total-count metadata; the app owns the endpoint.

TypeScript source

The supplied backend handlers use MSW simulation in the browser. Replace the transport with your application endpoint.

remote-board.ts
import type { KanbanConfig, KanbanCardMoveDetail } from '@revolist/kanban';
import type { WorkCard } from './first-board';
import { boardConfig, configureBoard } from './first-board';
// apiUrl is YOUR endpoint. The documentation supplies an isolated HTTP demo backend.
export function remoteBoard(apiUrl: string): KanbanConfig<WorkCard> {
return { ...boardConfig, remote: {
chunkSize: 6,
query: { retry: false, refetchOnWindowFocus: false },
async loadData(skip, limit, _order, _single, _multi, _quick, _temporal, _ast, _execution, context) {
const response = await fetch(`${apiUrl}/cards?skip=${skip}&limit=${limit}`, { signal: context?.signal });
if (!response.ok) throw new Error(`Card loading failed: HTTP ${response.status}`);
return response.json(); // { data: Card[], total: number }
},
} };
}
// Read-only setup; add app-owned write handling before enabling moves.
export async function createServerBoard(container: HTMLElement, apiUrl: string) {
const grid = document.createElement('revo-grid');
grid.style.cssText = 'display:block;width:100%;height:360px';
container.append(grid);
try {
// configureBoard waits for grid initialization before activating the remote loader.
const kanban = await configureBoard(grid, { ...remoteBoard(apiUrl), readOnly: true }, []);
return { grid, kanban, destroy() { grid.remove(); } };
} catch (error) { grid.remove(); throw error; }
}
export async function saveMove(apiUrl: string, detail: KanbanCardMoveDetail, signal?: AbortSignal) {
const response = await fetch(`${apiUrl}/moves`, {
method: 'POST', signal, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ changedCards: detail.changedCards }),
});
if (!response.ok) throw new Error(`Move save could not be confirmed: HTTP ${response.status}`);
return response.json();
}
// grid.kanban = remoteBoard(apiUrl);
// grid.addEventListener('kanbancardmove', event => { void saveMove(apiUrl, event.detail); });
// In your app, pause conflicting moves while saving; on failure refresh before more edits.
// Pagination, refresh and Query policy belong to remote. There is no remote.commit method.
Download remote-board.ts

Try it: load the example and refresh it. Watch the loading message and returned card count. This is a simulated backend; replace its URL with your application’s endpoint.

Start with Your First Board; these recipes extend its grid and config.

grid.kanban = {
columns: config.columns,
remote: {
chunkSize: 50,
loadData: async (skip, limit, order, single, multi, quick, temporal, ast, execution, context) => {
const response = await fetch(`/api/cards?skip=${skip}&limit=${limit}`, {
signal: context?.signal,
});
if (!response.ok) throw new Error('Unable to load cards');
return response.json(); // { data, total?, hasMore? }
},
},
};

Return either a card array or an object with data, optional total and optional hasMore:

return { data: cards, total: 24, hasMore: true };

Cards must have the configured ID, status, rank and optional lane fields. Use stable server ordering so offsets mean the same thing between requests. Do not return Kanban’s projected grid rows.

Listen to kanbanremoteloadstart, kanbanremoteloadsuccess and kanbanremoteloaderror for application feedback. A ready plugin exposes getRemoteState() with loading, next offset, continuation, total and error state. See Events and Methods.

The shared loader signature keeps cancellation context at argument ten. A typed rest-argument callback avoids naming unused filter arguments:

import type { KanbanRemoteConfig } from '@revolist/kanban';
const loadData: KanbanRemoteConfig<Card>['loadData'] = async (...args) => {
const [skip, limit] = args;
const response = await fetch(`/api/cards?skip=${skip}&limit=${limit}`, {
signal: args[9]?.signal,
});
if (!response.ok) throw new Error('Unable to load cards');
return response.json();
};

Card is your application card type. Forward the signal to network work so leaving or replacing the board can release a pending read.

The live client is apps/portal/src/components/kanban/KanbanRemote.astro; its backend is examples/components/src/components/kanban/docs/backend.ts. The backend validates pages and move batches before applying them, and each mounted example has its own seeded store. Teardown aborts requests and disposes that session. Production APIs need application-owned authentication, durable storage, ordering and concurrency control.

Continue with load pages, read errors and save changes. See the Kanban API for exact types.