Headless UI for building powerful tables & datagrids for TS/JS
—
Comprehensive column definition and management system supporting accessor functions, display columns, grouped columns, and various column types with full TypeScript type safety.
Utility for creating strongly-typed column definitions with proper type inference and accessor patterns.
/**
* Creates a typed column helper for building column definitions
* @returns ColumnHelper instance with type-safe methods
*/
function createColumnHelper<TData extends RowData>(): ColumnHelper<TData>;
interface ColumnHelper<TData extends RowData> {
/** Create accessor column with automatic type inference */
accessor<TAccessor extends AccessorFn<TData> | DeepKeys<TData>>(
accessor: TAccessor,
column: ColumnDefBase<TData, any>
): ColumnDef<TData, any>;
/** Create display-only column with no data access */
display(column: DisplayColumnDef<TData>): DisplayColumnDef<TData, unknown>;
/** Create grouped column with sub-columns */
group(column: GroupColumnDef<TData>): GroupColumnDef<TData, unknown>;
}Usage Examples:
import { createColumnHelper } from "@tanstack/table-core";
type Person = {
firstName: string;
lastName: string;
age: number;
address: {
street: string;
city: string;
};
};
const columnHelper = createColumnHelper<Person>();
// Accessor column with key
const firstNameColumn = columnHelper.accessor('firstName', {
header: 'First Name',
cell: info => info.getValue(),
});
// Accessor column with function
const fullNameColumn = columnHelper.accessor(
row => `${row.firstName} ${row.lastName}`,
{
id: 'fullName',
header: 'Full Name',
cell: info => info.getValue(),
}
);
// Deep accessor for nested properties
const cityColumn = columnHelper.accessor('address.city', {
header: 'City',
cell: info => info.getValue(),
});
// Display column
const actionsColumn = columnHelper.display({
id: 'actions',
header: 'Actions',
cell: props => (
<button onClick={() => console.log(props.row.original)}>
Edit
</button>
),
});
// Group column
const nameGroup = columnHelper.group({
header: 'Name',
columns: [firstNameColumn, lastNameColumn],
});Core column definition types supporting different column patterns and behaviors.
/** Union type of all possible column definitions */
type ColumnDef<TData extends RowData, TValue = unknown> =
| AccessorKeyColumnDef<TData, TValue>
| AccessorFnColumnDef<TData, TValue>
| DisplayColumnDef<TData, TValue>
| GroupColumnDef<TData, TValue>;
/** Base column definition with common properties */
interface ColumnDefBase<TData extends RowData, TValue = unknown> {
/** Unique identifier for the column */
id?: string;
/** Header content for the column */
header?: ColumnDefTemplate<HeaderContext<TData, TValue>>;
/** Footer content for the column */
footer?: ColumnDefTemplate<HeaderContext<TData, TValue>>;
/** Cell content renderer */
cell?: ColumnDefTemplate<CellContext<TData, TValue>>;
/** Custom meta information for the column */
meta?: ColumnMeta<TData, TValue>;
/** Enable/disable this specific column */
enableHiding?: boolean;
}
/** Column definition using property key accessor */
interface AccessorKeyColumnDef<TData extends RowData, TValue = unknown>
extends ColumnDefBase<TData, TValue> {
/** Property key to access from row data */
accessorKey: DeepKeys<TData>;
/** Optional accessor function override */
accessorFn?: AccessorFn<TData, TValue>;
}
/** Column definition using function accessor */
interface AccessorFnColumnDef<TData extends RowData, TValue = unknown>
extends ColumnDefBase<TData, TValue> {
/** Function to extract value from row data */
accessorFn: AccessorFn<TData, TValue>;
id: string;
}
/** Display column with no data access */
interface DisplayColumnDef<TData extends RowData, TValue = unknown>
extends ColumnDefBase<TData, TValue> {
id: string;
}
/** Group column containing sub-columns */
interface GroupColumnDef<TData extends RowData, TValue = unknown>
extends ColumnDefBase<TData, TValue> {
/** Array of sub-columns */
columns: ColumnDef<TData, any>[];
id?: string;
}The core column interface providing access to column properties and methods.
interface Column<TData extends RowData, TValue = unknown> extends
CoreColumn<TData, TValue>,
VisibilityColumn,
ColumnOrderColumn,
ColumnPinningColumn,
FacetedColumn<TData>,
ColumnFiltersColumn<TData>,
GlobalFilterColumn,
SortingColumn<TData>,
GroupingColumn<TData>,
ColumnSizingColumn {}
interface CoreColumn<TData extends RowData, TValue = unknown> {
/** Unique column identifier */
id: string;
/** Depth of the column in nested structure */
depth: number;
/** Original column definition */
columnDef: ColumnDef<TData, TValue>;
/** Sub-columns if this is a group column */
columns: Column<TData, TValue>[];
/** Parent column if this is a sub-column */
parent?: Column<TData, TValue>;
/** Accessor function for extracting values */
accessorFn?: AccessorFn<TData, TValue>;
/** Get flattened array of all sub-columns */
getFlatColumns(): Column<TData, TValue>[];
/** Get array of leaf columns (no sub-columns) */
getLeafColumns(): Column<TData, TValue>[];
}Usage Examples:
// Access column properties
const column = table.getColumn('firstName');
console.log(column.id); // 'firstName'
console.log(column.depth); // 0
// Get nested columns
const groupColumn = table.getColumn('nameGroup');
const leafColumns = groupColumn.getLeafColumns();
// Check if column has sub-columns
const hasSubColumns = column.columns.length > 0;Functions for extracting values from row data with strong typing support.
/** Function type for accessing row data values */
type AccessorFn<TData extends RowData, TValue = unknown> = (
originalRow: TData,
index: number
) => TValue;
/** Utility type for deep property key access */
type DeepKeys<T> = T extends Record<string, any>
? {
[K in keyof T]-?: K extends string | number
? T[K] extends Record<string, any>
? `${K}` | `${K}.${DeepKeys<T[K]>}`
: `${K}`
: never;
}[keyof T]
: never;
/** Utility type for extracting deep property values */
type DeepValue<T, TProp extends DeepKeys<T>> = T extends Record<string, any>
? TProp extends `${infer TKey}.${infer TRest}`
? TKey extends keyof T
? TRest extends DeepKeys<T[TKey]>
? DeepValue<T[TKey], TRest>
: never
: never
: TProp extends keyof T
? T[TProp]
: never
: never;Usage Examples:
type User = {
profile: {
personal: {
name: string;
age: number;
};
contact: {
email: string;
};
};
};
// Deep key examples
type NameKey = 'profile.personal.name'; // Valid
type EmailKey = 'profile.contact.email'; // Valid
// Accessor function examples
const nameAccessor: AccessorFn<User, string> = (row, index) =>
row.profile.personal.name;
const ageAccessor: AccessorFn<User, number> = (row, index) =>
row.profile.personal.age;
// Using with column helper
const nameColumn = columnHelper.accessor('profile.personal.name', {
header: 'Name',
cell: info => info.getValue(), // Type: string
});Flexible templating system for column headers, cells, and footers.
/** Template type that can be a value or function */
type ColumnDefTemplate<TProps> =
| string
| ((props: TProps) => any);
/** Template rendering function */
type StringOrTemplateHeader<TData extends RowData, TValue = unknown> =
| string
| ((props: HeaderContext<TData, TValue>) => any);Usage Examples:
// String template
const simpleColumn = columnHelper.accessor('name', {
header: 'Name',
cell: 'Default Cell Content',
});
// Function template
const complexColumn = columnHelper.accessor('status', {
header: ({ column }) => (
<button onClick={() => column.toggleSorting()}>
Status {column.getIsSorted() === 'asc' ? '↑' : column.getIsSorted() === 'desc' ? '↓' : ''}
</button>
),
cell: ({ getValue, row }) => {
const status = getValue();
return (
<span className={`status-${status}`}>
{status} ({row.original.id})
</span>
);
},
});Custom metadata system for attaching additional information to columns.
/** Custom metadata interface for columns */
interface ColumnMeta<TData extends RowData, TValue = unknown> {
/** Any custom properties */
[key: string]: any;
}Usage Examples:
const columnWithMeta = columnHelper.accessor('price', {
header: 'Price',
meta: {
type: 'currency',
format: 'USD',
validation: {
required: true,
min: 0,
},
className: 'text-right font-mono',
},
cell: ({ getValue, column }) => {
const value = getValue();
const meta = column.columnDef.meta;
if (meta?.type === 'currency') {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: meta.format || 'USD',
}).format(value);
}
return value;
},
});
// Access meta in rendering
const meta = column.columnDef.meta;Internal column definition resolution and processing.
/** Resolved column definition after processing */
interface ColumnDefResolved<TData extends RowData, TValue = unknown>
extends ColumnDefBase<TData, TValue> {
/** Resolved accessor function */
accessorFn?: AccessorFn<TData, TValue>;
/** Resolved column ID */
id: string;
}
/** Creates a resolved column from definition */
function createColumn<TData extends RowData, TValue = unknown>(
table: Table<TData>,
columnDef: ColumnDef<TData, TValue>,
depth: number,
parent?: Column<TData, any>
): Column<TData, TValue>;This system automatically resolves column definitions, generates IDs when missing, and creates the appropriate accessor functions based on the column type.
Install with Tessl CLI
npx tessl i tessl/npm-tanstack--table-core