Properties and callbacks used to configure columns.
Column definitions configure how data is displayed, formatted, and interacted with in the grid. Each column extends TanStack Table's ColumnDef with additional DataTable-specific properties.
import { DataTableColumnDef } from '@workspace/data-table';const columns: DataTableColumnDef<User>[] = [
{
accessorKey: 'name',
header: 'Name',
},
{
accessorKey: 'email',
header: 'Email',
},
];string{
accessorKey: 'name',
header: 'Name',
}
// Nested property access
{
accessorKey: 'user.address.city',
header: 'City',
}(row: TData) => TValue{
id: 'fullName',
accessorFn: (row) => `${row.firstName} ${row.lastName}`,
header: 'Full Name',
}string{
id: 'customId',
accessorFn: (row) => row.data,
header: 'Custom',
}string | ((ctx: HeaderContext) => ReactNode)// String header
{
accessorKey: 'name',
header: 'Name',
}
// Custom header component
{
accessorKey: 'status',
header: ({ column }) => (
<div className="flex items-center gap-2">
Status
<Badge>New</Badge>
</div>
),
}(ctx: CellContext) => ReactNodestring | ((ctx: HeaderContext) => ReactNode)number150number80number800booleantruebooleantruebooleantruebooleantruestring | string[]DataTableColumnDef<TData, any>[]SortingFn<TData>FilterFn<TData>DataTableColumnMeta<TData, TValue>accessorKey for simple field accessaccessorFn for computed valuesid for display columnsmeta for display formatting{
accessorKey: 'status',
header: 'Status',
cell: ({ getValue }) => {
const status = getValue();
return <Badge variant={status}>{status}</Badge>;
},
}{
accessorKey: 'amount',
header: 'Amount',
footer: ({ table }) => {
const total = table.getFilteredRowModel().rows
.reduce((sum, row) => sum + row.original.amount, 0);
return `Total: $${total.toFixed(2)}`;
},
}{
accessorKey: 'email',
header: 'Email',
size: 200,
}{
accessorKey: 'id',
header: 'ID',
size: 60,
minSize: 50,
}{
accessorKey: 'description',
header: 'Description',
maxSize: 400,
}{
accessorKey: 'id',
header: 'ID',
enableSorting: false,
}{
accessorKey: 'internalId',
header: 'Internal ID',
enableHiding: false, // Always visible
}{
accessorKey: 'actions',
header: 'Actions',
enableResizing: false,
}{
accessorKey: 'name',
header: 'Name',
enablePinning: true,
}{
accessorKey: 'price',
header: 'Price',
type: 'currency',
}
// Multiple types
{
accessorKey: 'total',
header: 'Total',
type: ['currency', 'editable'],
}{
header: 'User Info',
columns: [
{ accessorKey: 'firstName', header: 'First Name' },
{ accessorKey: 'lastName', header: 'Last Name' },
],
}{
accessorKey: 'status',
header: 'Status',
sortingFn: (rowA, rowB, columnId) => {
const order = ['pending', 'active', 'completed'];
return order.indexOf(rowA.getValue(columnId)) -
order.indexOf(rowB.getValue(columnId));
},
}{
accessorKey: 'tags',
header: 'Tags',
filterFn: (row, columnId, filterValue) => {
const tags = row.getValue(columnId) as string[];
return tags.some(tag => tag.includes(filterValue));
},
}{
accessorKey: 'amount',
header: 'Amount',
meta: {
cellAlign: 'right',
valueFormatter: (value) => `$${value.toFixed(2)}`,
filterType: 'number',
},
}{
accessorKey: 'name',
header: 'Name',
size: 200,
enableSorting: true,
}{
accessorKey: 'price',
header: 'Price',
size: 120,
meta: {
cellAlign: 'right',
valueFormatter: (value) => `$${value.toFixed(2)}`,
filterType: 'number',
enableFilterOperators: true,
},
}{
accessorKey: 'status',
header: 'Status',
size: 120,
cell: ({ getValue }) => {
const status = getValue() as string;
return (
<Badge variant={status === 'active' ? 'default' : 'secondary'}>
{status}
</Badge>
);
},
meta: {
filterType: 'select',
filterOptions: [
{ label: 'Active', value: 'active' },
{ label: 'Inactive', value: 'inactive' },
],
},
}{
id: 'fullName',
accessorFn: (row) => `${row.firstName} ${row.lastName}`,
header: 'Full Name',
size: 200,
cell: ({ getValue }) => (
<div className="font-medium">{getValue()}</div>
),
}{
header: 'Address',
columns: [
{
accessorKey: 'address.street',
header: 'Street',
size: 200,
},
{
accessorKey: 'address.city',
header: 'City',
size: 150,
},
{
accessorKey: 'address.zip',
header: 'ZIP',
size: 100,
},
],
}{
accessorKey: 'quantity',
header: 'Quantity',
size: 100,
meta: {
editable: true,
editType: 'number',
validate: (value) => {
if (value < 0) return 'Must be positive';
if (value > 1000) return 'Maximum is 1000';
return undefined;
},
},
}{
id: 'actions',
header: 'Actions',
size: 100,
enableSorting: false,
enableHiding: false,
cell: ({ row }) => (
<div className="flex gap-2">
<Button size="sm" onClick={() => edit(row.original)}>
Edit
</Button>
<Button size="sm" variant="destructive" onClick={() => del(row.original)}>
Delete
</Button>
</div>
),
}type User = {
id: string;
name: string;
email: string;
age: number;
};
const columns: DataTableColumnDef<User>[] = [
{
accessorKey: 'name', // Type-safe: must be keyof User
header: 'Name',
},
{
accessorKey: 'invalid', // Error: 'invalid' not in User
header: 'Invalid',
},
];{
accessorKey: 'status',
header: 'Status',
cell: ({ getValue, row }) => {
const status = getValue(); // Type: unknown
const user = row.original; // Type: User
return <Badge>{status}</Badge>;
},
}