Skip to content

Cell Types & Registry

Every column (and individual cell) has a cell type that controls how values are formatted, aligned, and rendered on the canvas. The CellTypeRegistry manages all type renderers and supports custom types.

Live Demo
All cell types in one table: string, number (right-aligned), boolean (checkbox), date, progress bar, and star rating.
View source code
CellTypesDemo.tsx
import { useRef, useEffect } from 'react';
import { Spreadsheet } from '@witqq/spreadsheet-react';
import type { SpreadsheetRef } from '@witqq/spreadsheet-react';
import type { ColumnDef } from '@witqq/spreadsheet';
import { DemoWrapper } from './DemoWrapper';
import { useSiteTheme } from './useSiteTheme';
const columns: ColumnDef[] = [
{ key: 'id', title: 'ID', width: 50, type: 'number' },
{ key: 'text', title: 'String', width: 120, type: 'string' },
{ key: 'amount', title: 'Number', width: 100, type: 'number' },
{ key: 'active', title: 'Boolean', width: 80, type: 'boolean' },
{ key: 'created', title: 'Date', width: 110, type: 'date' },
{ key: 'progress', title: 'Progress', width: 140, type: 'progressBar' },
{ key: 'rating', title: 'Rating', width: 120, type: 'rating' },
];
const data = [
{
id: 1,
text: 'Hello World',
amount: 1234.56,
active: true,
created: '2025-01-15',
progress: 85,
rating: 5,
},
{
id: 2,
text: 'Test Data',
amount: -42,
active: false,
created: '2024-06-01',
progress: 45,
rating: 3,
},
{
id: 3,
text: 'Long text that will be truncated in the cell',
amount: 0,
active: true,
created: '2023-12-25',
progress: 10,
rating: 1,
},
{
id: 4,
text: '',
amount: 99999,
active: false,
created: '2025-03-01',
progress: 100,
rating: 4,
},
{
id: 5,
text: 'Final Row',
amount: 500,
active: true,
created: '2025-02-14',
progress: 62,
rating: 2,
},
];
export function CellTypesDemo() {
const { witTheme } = useSiteTheme();
const tableRef = useRef<SpreadsheetRef>(null);
useEffect(() => {
const engine = tableRef.current?.getInstance();
if (!engine) return;
const registry = engine.getCellTypeRegistry();
registry.register('progressBar', {
format: (value) => `${value}%`,
align: 'left',
render: (ctx, value, x, y, width, height, theme) => {
const pct = Math.max(0, Math.min(100, Number(value) || 0));
const barWidth = (width - 8) * (pct / 100);
const barHeight = 12;
const barY = y + (height - barHeight) / 2;
ctx.fillStyle = theme.colors.cellBorder;
ctx.fillRect(x + 4, barY, width - 8, barHeight);
ctx.fillStyle = pct >= 80 ? '#63be7b' : pct >= 50 ? '#ffeb84' : '#f8696b';
ctx.fillRect(x + 4, barY, barWidth, barHeight);
ctx.fillStyle = theme.colors.cellText;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(`${pct}%`, x + width / 2, y + height / 2);
},
});
registry.register('rating', {
format: (value) => ''.repeat(Number(value) || 0),
align: 'center',
render: (ctx, value, x, y, width, height) => {
const rating = Math.max(0, Math.min(5, Math.round(Number(value) || 0)));
const starSize = 14;
const totalWidth = starSize * 5;
const startX = x + (width - totalWidth) / 2;
const centerY = y + height / 2 + 5;
ctx.font = `${starSize}px sans-serif`;
for (let i = 0; i < 5; i++) {
ctx.fillStyle = i < rating ? '#f4b400' : '#e0e0e0';
ctx.fillText('', startX + i * starSize, centerY);
}
},
});
engine.requestRender();
}, []);
return (
<DemoWrapper
title="Live Demo"
description="All cell types in one table: string, number (right-aligned), boolean (checkbox), date, progress bar, and star rating."
height={300}
>
<Spreadsheet
theme={witTheme}
ref={tableRef}
columns={columns}
data={data}
showRowNumbers
style={{ width: '100%', height: '100%' }}
/>
</DemoWrapper>
);
}
type CellType =
| 'string'
| 'number'
| 'boolean'
| 'date'
| 'datetime'
| 'select'
| 'dynamicSelect'
| 'formula'
| 'link'
| 'image'
| 'progressBar'
| 'rating'
| 'badge'
| 'custom';

Set the type on a column definition:

const columns: ColumnDef[] = [
{ key: 'name', title: 'Name', width: 200, type: 'string' },
{ key: 'price', title: 'Price', width: 100, type: 'number' },
{ key: 'active', title: 'Active', width: 80, type: 'boolean' },
{ key: 'created', title: 'Created', width: 120, type: 'date' },
];

Each cell type maps to a renderer:

interface CellTypeRenderer {
format(value: CellValue, cellData?: CellData, row?: number, col?: number): string;
align: CellAlignment; // 'left' | 'center' | 'right'
render?: (
ctx: CanvasRenderingContext2D,
cellData: CellData,
x: number,
y: number,
width: number,
height: number,
theme: SpreadsheetTheme,
row?: number,
col?: number
) => void;
measureHeight?: (
ctx: CanvasRenderingContext2D,
cellData: CellData,
width: number,
theme: SpreadsheetTheme,
row?: number,
col?: number
) => number;
getHitZones?: (
cellData: CellData,
width: number,
height: number,
theme?: SpreadsheetTheme,
row?: number,
col?: number
) => HitZone[];
}
MethodDescription
format(value, cellData?, row?, col?)Convert a CellValue to display string. Optional cellData provides full cell context
alignText alignment within the cell
renderOptional custom canvas drawing — receives CellData instead of raw value
measureHeightOptional height measurement for auto row sizing
getHitZonesOptional sub-cell interactive zone declarations for hit testing
TypeAlignmentFormatting
stringleftReturns value as-is (String(value))
numberrightLocale-formatted number (toLocaleString())
booleancenterRenders a checkbox glyph via custom render
datelefttoLocaleDateString() for Date values. See Date Editors
class CellTypeRegistry {
get(type: CellType): CellTypeRenderer;
register(type: CellType, renderer: CellTypeRenderer): void;
detectType(value: CellValue): CellType;
addDecorator(registration: CellDecoratorRegistration): void;
removeDecorator(id: string): void;
getDecorators(row: number, col: number, cellData: CellData): CellDecorator[];
}
  • get(type) — returns the renderer for a type (falls back to string if not found)
  • register(type, renderer) — register or override a type renderer
  • detectType(value) — auto-detect type from a value (number, boolean, Date, or string)
  • addDecorator(registration) — register a cell decorator. Replaces existing decorator with same ID
  • removeDecorator(id) — remove a decorator by ID
  • getDecorators(row, col, cellData) — get all decorators that apply to a cell
engine.getCellTypeRegistry().register('progressBar', {
format: (value) => `${value}%`,
align: 'left',
render: (ctx, cellData, x, y, width, height, theme) => {
const pct = Math.max(0, Math.min(100, Number(cellData.value) || 0));
const barWidth = (width - 8) * (pct / 100);
const barHeight = 12;
const barY = y + (height - barHeight) / 2;
// Background track
ctx.fillStyle = theme.colors.cellBorder;
ctx.fillRect(x + 4, barY, width - 8, barHeight);
// Filled portion
ctx.fillStyle = pct >= 80 ? '#63be7b' : pct >= 50 ? '#ffeb84' : '#f8696b';
ctx.fillRect(x + 4, barY, barWidth, barHeight);
// Label
ctx.fillStyle = theme.colors.cellText;
ctx.textAlign = 'center';
ctx.fillText(`${pct}%`, x + width / 2, y + height / 2 + 4);
},
});
engine.getCellTypeRegistry().register('rating', {
format: (value) => ''.repeat(Number(value) || 0),
align: 'center',
render: (ctx, cellData, x, y, width, height, theme) => {
const rating = Math.max(0, Math.min(5, Math.round(Number(cellData.value) || 0)));
const starSize = 14;
const totalWidth = starSize * 5;
const startX = x + (width - totalWidth) / 2;
const centerY = y + height / 2 + 5;
ctx.font = `${starSize}px sans-serif`;
for (let i = 0; i < 5; i++) {
ctx.fillStyle = i < rating ? '#f4b400' : '#e0e0e0';
ctx.fillText('', startX + i * starSize, centerY);
}
},
});

Use the custom type in a column:

const columns: ColumnDef[] = [
{ key: 'progress', title: 'Progress', width: 150, type: 'progressBar' },
{ key: 'rating', title: 'Rating', width: 120, type: 'rating' },
];