Skip to content

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.

Interactive example

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.

Loading example…

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.

card-design.ts
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.
Download card-design.ts

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.

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.

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.

Continue with card design, labels and language and accessibility. See the Kanban API for exact types and signatures.