Themes and Styling
Keep the board visually quiet and let card titles carry the meaning. Use RevoGrid’s matching theme and Kanban tokens rather than placing another bordered card inside the managed shell.
Choose a comfortable card density
Card row height controls the space reserved for each card.
Try it: Compare compact and comfortable cards. Use the site theme selector for dark mode.
What to notice: The layout changes without changing tasks or workflow.
TypeScript source
Install the packages from Getting Started, then use this source in your app.
import type { KanbanConfig } from '@revolist/kanban';import type { WorkCard } from './first-board';
export const minimalCard: Partial<KanbanConfig<WorkCard>> = { customization: { cardContent: (h, { card }) => h('strong', undefined, card.title) }, cardRowHeight: 88,};export const detailedCard: Partial<KanbanConfig<WorkCard>> = { card: { titleField: 'title', descriptionField: 'description', priorityField: 'priority', tagsField: 'tags', progressField: 'progress', startDateField: 'startDate', endDateField: 'endDate', assignees: card => [{ id: card.owner, label: card.owner ?? 'Unassigned' }] }, cardRowHeight: 220,};export const customContent: NonNullable<KanbanConfig<WorkCard>['customization']> = { cardContent: (h, { card }) => h('div', { class: 'kanban-doc-card' }, [h('strong', undefined, card.title), h('span', { class: 'kanban-doc-card__meta' }, `${card.owner} · ${card.priority}`)]),};// Apply with grid.kanban = { ...grid.kanban, ...minimalCard }.// Kanban retains ownership of the surrounding drag/selection/keyboard shell.import { defineCustomElements } from '@revolist/revogrid/loader';import { KanbanPlugin, type KanbanConfig } from '@revolist/kanban';import '@revolist/revogrid-pro/styles.css';import '@revolist/kanban/styles.css';
export interface WorkCard { id: string; title: string; status: string; order: number; description?: string; team?: string; owner?: string; priority?: string; tags?: string[]; progress?: number; startDate?: string; endDate?: string;}export const demoCards: WorkCard[] = [ { id: 'task-1', title: 'Research customer needs', description: 'Collect feedback for the next release.', status: 'todo', team: 'product', owner: 'Ana', priority: 'normal', order: 1000 }, { id: 'task-2', title: 'Build onboarding', description: 'Make the first session useful.', status: 'doing', team: 'product', owner: 'Mia', priority: 'high', order: 1000 }, { id: 'task-3', title: 'Improve API errors', description: 'Return actionable validation messages.', status: 'todo', team: 'platform', owner: 'Rui', priority: 'normal', order: 2000 }, { id: 'task-4', title: 'Release checklist', description: 'Ready for the next deployment.', status: 'done', team: 'platform', owner: 'Sam', priority: 'normal', order: 1000 },];export const boardConfig: KanbanConfig<WorkCard> = { columns: [{ prop: 'todo', name: 'To do', size: 210 }, { prop: 'doing', name: 'In progress', size: 210 }, { prop: 'done', name: 'Done', size: 210 }], card: { titleField: 'title' }, cardRowHeight: 88, addCard: false, swimlaneColumn: false,};
export async function configureBoard( grid: HTMLRevoGridElement, config: KanbanConfig<WorkCard> = boardConfig, cards: readonly WorkCard[] = demoCards, extraPlugins: HTMLRevoGridElement['plugins'] = [],) { await defineCustomElements(); grid.plugins = [KanbanPlugin, ...extraPlugins]; // The packaged editor and history install automatically. grid.columns = [{ prop: 'title', name: 'Title' }, { prop: 'status', name: 'Status' }, { prop: 'owner', name: 'Owner' }]; // Cached remote data can hydrate immediately: finish initial grid setup first. grid.kanban = config.remote ? { ...config, remote: false } : config; grid.source = cards.map(card => ({ ...card })); await grid.componentOnReady(); const plugins = await grid.getPlugins(); const kanban = plugins.find((plugin): plugin is KanbanPlugin<WorkCard> => plugin instanceof KanbanPlugin); if (!kanban) throw new Error('Kanban could not start.'); if (config.remote) grid.kanban = config; return kanban;}
// Give your container a width and mount once after it exists in the DOM.export async function createKanbanBoard(container: HTMLElement) { await defineCustomElements(); const grid = document.createElement('revo-grid'); grid.style.cssText = 'display:block;width:100%;height:400px'; container.append(grid); try { const kanban = await configureBoard(grid); return { grid, kanban, destroy() { grid.remove(); } }; } catch (error) { grid.remove(); throw error; }}Try it: change the example’s appearance and inspect focus and selection as well as its idle cards. A dark page does not automatically set a grid theme.
Swimlane header templates
Section titled “Swimlane header templates”Set swimlaneTemplate on an explicit swimlane when that lane needs to own its
complete header layout. This works in both column and top swimlane layouts:
grid.kanban = { ...config, swimlaneField: 'workstream', swimlaneLayout: 'top', swimlanes: [ { id: 'product', title: 'Product experience', collapsible: true, swimlaneTemplate: (h, context) => h('div', { class: 'workstream-header' }, [ h('span', { class: 'workstream-header__icon', 'aria-hidden': 'true' }, '◈'), h('span', { class: 'workstream-header__kicker' }, 'WORKSTREAM'), h('strong', undefined, context.swimlane.title), h('span', null, `${context.visibleCount} / ${context.totalCount}`), h('button', { type: 'button', onClick: context.toggleCollapsed, }, context.swimlane.collapsed ? 'Expand' : 'Collapse'), ]), }, ],};swimlaneTemplate fully replaces the managed title, count, WIP badges, and
collapse button for that lane. Render the pieces your product needs and call
context.toggleCollapsed() when the custom header exposes collapse. The action
is guarded by the lane’s collapsible setting. Kanban continues to own the
outer group row, lane identity, configured style, and accessibility label.
A definition-owned template takes precedence over
customization.swimlaneHeader. Lanes without one keep the board-wide custom
content or the default header.
Workflow header templates
Section titled “Workflow header templates”Set columnTemplate and columnProperties directly on a workflow definition,
using the same shape as a RevoGrid column:
grid.kanban = { ...config, columns: [ { prop: 'active', name: 'Active', size: 280, wipLimit: 4, columnProperties: () => ({ class: 'workflow-header-cell', style: { backgroundColor: 'var(--active-header)' }, }), columnTemplate: (h, props, additionalData) => { const { column, visibleCount, totalCount, overWipLimit, toggleCollapsed } = props.kanban; const name = column.name ?? String(column.prop); return h('div', { class: overWipLimit ? 'workflow-header workflow-header--over' : 'workflow-header' }, [ h('strong', undefined, name), h('span', null, `${visibleCount} / ${totalCount}`), h('button', { type: 'button', onClick: toggleCollapsed }, 'Toggle'), additionalData?.workspaceName ? h('small', undefined, additionalData.workspaceName) : null, ]); }, }, ],};columnTemplate fully replaces the inner managed workflow header. Kanban does
not append its default name, count, WIP badge, or collapse button, so render the
pieces your product needs and call props.kanban.toggleCollapsed() when the
custom header exposes collapse. Application additionalData is passed through
unchanged. RevoGrid’s outer resizable header remains managed.
columnProperties is composed with Kanban’s required header properties through
the shared Pro merging behavior: classes and styles combine, while required
workflow identity and interaction attributes win conflicts. Collapsed body
rails deliberately render name (or String(prop)) instead of invoking the
header template.
Import the standalone Kanban stylesheet once:
import '@revolist/kanban/styles.css';Prefer Kanban CSS variables and classes returned by card rules or customization hooks. Avoid changing managed-shell focus, selection, and drag-state elements in ways that hide their state.
Kanban follows the grid’s material, compact, darkMaterial, and
darkCompact themes automatically. Its defaults derive from RevoGrid’s public
--revo-grid-* tokens, while the semantic --kanban-board-background,
--kanban-surface,
--kanban-card-background, --kanban-border, --kanban-text,
--kanban-muted, --kanban-accent, and --kanban-accent-strong variables let
the host tune either theme without replacing component styles. Drag previews
copy the resolved Kanban tokens, so a body-mounted preview retains the active
dark theme.
Next steps
Section titled “Next steps”Continue with card design, labels and language and accessibility. See the Kanban API for exact types and signatures.