Documentation
Header Renderer
Customize column headers with headerRenderer.
Add a header renderer
Set headerRenderer on a column to customize the header cell. Use header.label and other column fields from the props.
React TSX
Copy
const StatusHeader = ({ header }) => (<span style={{ fontWeight: 600 }}>{header.label}</span>);const columns: ReactColumnDef[] = [{ accessor: "status", label: "Status", width: 120, headerRenderer: StatusHeader },];
Vue SFC
Copy
import { h } from "vue";const StatusHeader = ({ header }) =>h("span", { style: { fontWeight: "600" } }, header.label);const columns: VueColumnDef[] = [{ accessor: "status", label: "Status", width: 120, headerRenderer: StatusHeader },];
Angularstatus-header.component.ts
Copy
@Component({standalone: true,selector: "app-status-header",template: `<span style="font-weight:600">{{ header.label }}</span>`,})export class StatusHeaderComponent {@Input() header!: { label: string };}// column def{ accessor: "status", label: "Status", width: 120, headerRenderer: StatusHeaderComponent }
Svelte
Copy
<!-- StatusHeader.svelte --><script lang="ts">import type { HeaderRendererProps } from "@simple-table/svelte";let { header }: HeaderRendererProps = $props();</script><span style="font-weight:600">{header.label}</span><!-- column def -->{ accessor: "status", label: "Status", width: 120, headerRenderer: StatusHeader }
Solid TSX
Copy
const StatusHeader = (props) => (<span style={{ "font-weight": "600" }}>{props.header.label}</span>);const columns: SolidColumnDef[] = [{ accessor: "status", label: "Status", width: 120, headerRenderer: StatusHeader },];
TypeScript
Copy
const StatusHeader = ({ header }) => {const span = document.createElement("span");span.style.fontWeight = "600";span.textContent = header.label;return span;};const columns: ColumnDef[] = [{ accessor: "status", label: "Status", width: 120, headerRenderer: StatusHeader },];
Reuse built-in components
Arrange components (labelContent, sortIcon, filterIcon, collapseIcon) so sort/filter keep working without reimplementing them.
React TSX
Copy
{accessor: "name",label: "Name",sortable: true,filterable: true,headerRenderer: ({ components }) => (<>{components?.labelContent}{components?.sortIcon}{components?.filterIcon}</>),}
Vue SFC
Copy
import { h } from "vue";{accessor: "name",label: "Name",sortable: true,filterable: true,headerRenderer: ({ components }) =>h("div", { style: { display: "flex", gap: "4px", alignItems: "center" } }, [components?.labelContent,components?.sortIcon,components?.filterIcon,].filter(Boolean)),}
Angularheader-layout.component.ts — receive @Input() components
Copy
// Template: place labelContent, sortIcon, filterIcon in your order{accessor: "name",label: "Name",sortable: true,filterable: true,headerRenderer: HeaderLayoutComponent,}
Svelte
Copy
<!-- HeaderLayout.svelte — place components.labelContent / sortIcon / filterIcon --><script lang="ts">import type { HeaderRendererProps } from "@simple-table/svelte";let { components }: HeaderRendererProps = $props();</script><!-- column def -->{ accessor: "name", label: "Name", sortable: true, filterable: true, headerRenderer: HeaderLayout }
Solid TSX
Copy
{accessor: "name",label: "Name",sortable: true,filterable: true,headerRenderer: (props) => (<>{props.components?.labelContent}{props.components?.sortIcon}{props.components?.filterIcon}</>),}
TypeScript
Copy
{accessor: "name",label: "Name",sortable: true,filterable: true,headerRenderer: ({ components }) => {const row = document.createElement("div");row.style.display = "flex";row.style.gap = "4px";row.style.alignItems = "center";if (components?.labelContent) {if (typeof components.labelContent === "string") {row.append(components.labelContent);} else {row.appendChild(components.labelContent);}}if (components?.sortIcon instanceof Node) row.appendChild(components.sortIcon);if (components?.filterIcon instanceof Node) row.appendChild(components.filterIcon);return row;},}
Hide all headers
To hide the header row entirely, set hideHeader on the table. See the API Reference.
Example
Custom header controls with built-in sort/filter icons. Use Code or StackBlitz for the full example.
React TSX
Copy
1import { useState } from "react";2import { SimpleTable } from "@simple-table/react";3import type { Theme, ReactColumnDef, HeaderRendererProps } from "@simple-table/react";4import { headerRendererConfig } from "./header-renderer.demo-data";5import "@simple-table/react/styles.css";67/**8 * Stateful header control. Pin should survive built-in sort/filter icon refreshes.9 * If core remounts the React headerRenderer on sort, the pin snaps back off.10 */11const StatefulHeader = ({ header, components }: HeaderRendererProps) => {12 const [pinned, setPinned] = useState(false);13 const [clicks, setClicks] = useState(0);1415 return (16 <div17 style={{18 display: "flex",19 alignItems: "center",20 gap: 6,21 width: "100%",22 minWidth: 0,23 padding: "0 4px",24 }}25 >26 <span27 style={{28 fontWeight: 600,29 overflow: "hidden",30 textOverflow: "ellipsis",31 whiteSpace: "nowrap",32 minWidth: 0,33 flex: 1,34 }}35 >36 {header.label}37 </span>38 <button39 type="button"40 aria-label={pinned ? `Unpin ${header.label}` : `Pin ${header.label}`}41 aria-pressed={pinned}42 title="Toggle pin, then sort — pin should stay on"43 onClick={(event) => {44 event.stopPropagation();45 setPinned((value) => !value);46 setClicks((n) => n + 1);47 }}48 style={{49 display: "inline-flex",50 alignItems: "center",51 justifyContent: "center",52 gap: 2,53 flexShrink: 0,54 height: 22,55 padding: "0 6px",56 border: "1px solid",57 borderColor: pinned ? "#6366f1" : "#d1d5db",58 borderRadius: 6,59 background: pinned ? "#eef2ff" : "#fff",60 color: pinned ? "#4f46e5" : "#6b7280",61 cursor: "pointer",62 fontSize: 12,63 lineHeight: 1,64 fontWeight: 600,65 }}66 >67 {pinned ? "★" : "☆"} {clicks}68 </button>69 {components?.filterIcon}70 {components?.sortIcon}71 </div>72 );73};7475const headers: ReactColumnDef[] = headerRendererConfig.headers.map((h) => ({76 ...h,77 // Keep built-in sorting so sort icon refresh goes through core's header path.78 sortable: h.sortable ?? true,79 filterable: h.accessor === "role" || h.accessor === "department",80 headerRenderer: StatefulHeader,81}));8283const HeaderRendererDemo = ({84 height = "400px",85 theme,86}: {87 height?: string | number;88 theme?: Theme;89}) => {90 return (91 <div style={{ display: "flex", flexDirection: "column", gap: 8, height: "100%" }}>92 <p style={{ margin: 0, fontSize: 13, color: "#6b7280", lineHeight: 1.4 }}>93 Click a header ★ to pin it, then sort that column. The pin (and click count) should stay —94 if the header remounts on sort, both reset.95 </p>96 <SimpleTable97 columns={headers}98 rows={headerRendererConfig.rows}99 height={height}100 theme={theme}101 columnResizing102 selectableCells103 />104 </div>105 );106};107108export default HeaderRendererDemo;
Vue SFC
Copy
1<script setup lang="ts">2import { ref, computed, defineComponent, h } from "vue";3import { SimpleTable } from "@simple-table/vue";4import type { Theme, VueColumnDef, Row } from "@simple-table/vue";5import { headerRendererConfig } from "./header-renderer.demo-data";6import "@simple-table/vue/styles.css";78const props = withDefaults(defineProps<{ height?: string | number; theme?: Theme }>(), {9 height: "400px",10});1112type SortDir = "asc" | "desc" | null;13const CYCLE: SortDir[] = ["asc", "desc", null];1415const sortAccessor = ref<string | null>(null);16const sortDirection = ref<SortDir>(null);1718const sortedData = computed(() => {19 if (!sortAccessor.value || !sortDirection.value) return [...headerRendererConfig.rows];20 const acc = sortAccessor.value;21 const dir = sortDirection.value;22 return [...headerRendererConfig.rows].sort((a, b) => {23 const aVal = a[acc];24 const bVal = b[acc];25 if (aVal === bVal) return 0;26 const cmp =27 typeof aVal === "number" && typeof bVal === "number"28 ? aVal - bVal29 : String(aVal).localeCompare(String(bVal));30 return dir === "asc" ? cmp : -cmp;31 });32});3334function makeSortableHeader(col: VueColumnDef) {35 return defineComponent({36 name: `SortHeader-${String(col.accessor)}`,37 setup() {38 return () => {39 const isSorted = sortAccessor.value === col.accessor;40 const dir = isSorted ? sortDirection.value : null;41 const indicator = dir === "asc" ? " ▲" : dir === "desc" ? " ▼" : "";42 const handleClick = () => {43 if (!isSorted) {44 sortAccessor.value = col.accessor as string;45 sortDirection.value = "asc";46 return;47 }48 const idx = CYCLE.indexOf(dir);49 const next = CYCLE[(idx + 1) % CYCLE.length];50 if (next) {51 sortAccessor.value = col.accessor as string;52 sortDirection.value = next;53 } else {54 sortAccessor.value = null;55 sortDirection.value = null;56 }57 };58 return h(59 "div",60 {61 onClick: handleClick,62 style: {63 cursor: "pointer",64 userSelect: "none",65 fontWeight: "600",66 display: "flex",67 alignItems: "center",68 gap: "4px",69 },70 },71 [72 h("span", col.label as string),73 indicator74 ? h("span", { style: { fontSize: "10px", color: "#6366f1" } }, indicator)75 : null,76 ],77 );78 };79 },80 });81}8283const headers = computed(() =>84 headerRendererConfig.headers.map((col) => ({85 ...col,86 sortable: false,87 headerRenderer: makeSortableHeader(col),88 })),89);90</script>9192<template>93 <SimpleTable94 :columns="headers"95 :rows="sortedData as Row[]"96 :height="props.height"97 :theme="props.theme"98 />99</template>
Angularheader-demo-sort.ts
Copy
1import { signal } from "@angular/core";23export type HeaderDemoSortDir = "asc" | "desc" | null;45const CYCLE: HeaderDemoSortDir[] = ["asc", "desc", null];67export const headerDemoSortAccessor = signal<string | null>(null);8export const headerDemoSortDirection = signal<HeaderDemoSortDir>(null);910export function cycleHeaderDemoSort(accessor: string): void {11 const currentAcc = headerDemoSortAccessor();12 const dir = headerDemoSortDirection();13 if (currentAcc !== accessor) {14 headerDemoSortAccessor.set(accessor);15 headerDemoSortDirection.set("asc");16 return;17 }18 const idx = CYCLE.indexOf(dir);19 const next = CYCLE[(idx + 1) % CYCLE.length]!;20 if (next) {21 headerDemoSortAccessor.set(accessor);22 headerDemoSortDirection.set(next);23 } else {24 headerDemoSortAccessor.set(null);25 headerDemoSortDirection.set(null);26 }27}282930// header-renderer-demo.component.ts31import { Component, computed, Input } from "@angular/core";32import { SimpleTableComponent } from "@simple-table/angular";33import type { AngularColumnDef, Row, Theme } from "@simple-table/angular";34import { headerDemoSortAccessor, headerDemoSortDirection } from "./header-demo-sort";35import { headerRendererConfig } from "./header-renderer.demo-data";36import { HeaderSortableHeaderComponent } from "./header-sortable-header.component";37import "@simple-table/angular/styles.css";3839@Component({40 selector: "header-renderer-demo",41 standalone: true,42 imports: [SimpleTableComponent],43 template: `44 <simple-table45 [rows]="sortedData()"46 [columns]="headers()"47 [height]="height"48 [theme]="theme"49 ></simple-table>50 `,51})52export class HeaderRendererDemoComponent {53 @Input() height: string | number = "400px";54 @Input() theme?: Theme;5556 readonly sortedData = computed(() => {57 const acc = headerDemoSortAccessor();58 const dir = headerDemoSortDirection();59 if (!acc || !dir) return [...headerRendererConfig.rows];60 return [...headerRendererConfig.rows].sort((a, b) => {61 const aVal = a[acc];62 const bVal = b[acc];63 if (aVal === bVal) return 0;64 const cmp =65 typeof aVal === "number" && typeof bVal === "number"66 ? (aVal as number) - (bVal as number)67 : String(aVal).localeCompare(String(bVal));68 return dir === "asc" ? cmp : -cmp;69 });70 });7172 readonly headers = computed((): AngularColumnDef[] =>73 headerRendererConfig.headers.map((h) => ({74 ...h,75 sortable: false,76 headerRenderer: HeaderSortableHeaderComponent,77 })),78 );79}808182// header-renderer.demo-data.ts83// Self-contained demo table setup for this example.84import type { AngularColumnDef, Row } from "@simple-table/angular";858687export const headerRendererData: Row[] = [88 { id: 1, name: "Alice Johnson", email: "alice@example.com", role: "Engineer", salary: 125000, department: "Engineering" },89 { id: 2, name: "Bob Martinez", email: "bob@example.com", role: "Designer", salary: 98000, department: "Design" },90 { id: 3, name: "Clara Chen", email: "clara@example.com", role: "PM", salary: 115000, department: "Product" },91 { id: 4, name: "David Kim", email: "david@example.com", role: "Engineer", salary: 132000, department: "Engineering" },92 { id: 5, name: "Elena Rossi", email: "elena@example.com", role: "Analyst", salary: 89000, department: "Analytics" },93 { id: 6, name: "Frank Müller", email: "frank@example.com", role: "Engineer", salary: 118000, department: "Engineering" },94 { id: 7, name: "Grace Park", email: "grace@example.com", role: "Designer", salary: 105000, department: "Design" },95 { id: 8, name: "Henry Patel", email: "henry@example.com", role: "Lead", salary: 145000, department: "Engineering" },96];9798export const headerRendererHeaders: AngularColumnDef[] = [99 { accessor: "id", label: "ID", width: 60, type: "number", sortable: true },100 { accessor: "name", label: "Employee Name", width: 180, type: "string", sortable: true },101 { accessor: "email", label: "Email Address", width: 200, type: "string" },102 { accessor: "role", label: "Job Role", width: 130, type: "string", sortable: true },103 { accessor: "salary", label: "Annual Salary", width: 140, type: "number", sortable: true },104 { accessor: "department", label: "Department", width: 150, type: "string", sortable: true },105];106107export const headerRendererConfig = {108 headers: headerRendererHeaders,109 rows: headerRendererData,110 tableProps: {111 selectableCells: true,112 columnResizing: true,113 },114} as const;115116117// header-sortable-header.component.ts118import { Component, computed, Input, signal } from "@angular/core";119import type { HeaderRendererProps } from "@simple-table/angular";120import { cycleHeaderDemoSort, headerDemoSortAccessor, headerDemoSortDirection } from "./header-demo-sort";121122@Component({123 standalone: true,124 selector: "demo-header-sortable",125 template: `126 <div127 style="cursor:pointer;user-select:none;font-weight:600;display:flex;align-items:center;gap:4px;"128 (click)="onClick()"129 (keydown)="onKeydown($event)"130 role="button"131 tabindex="0"132 >133 <span>{{ header.label }}</span>134 @if (indicator()) {135 <span style="font-size:10px;color:#6366f1;">{{ indicator() }}</span>136 }137 </div>138 `,139})140export class HeaderSortableHeaderComponent {141 private readonly accessorStr = signal("");142143 @Input({ required: true }) set header(h: HeaderRendererProps["header"]) {144 this._header = h;145 this.accessorStr.set(String(h.accessor));146 }147 get header(): HeaderRendererProps["header"] {148 return this._header;149 }150 private _header!: HeaderRendererProps["header"];151152 @Input() accessor?: HeaderRendererProps["accessor"];153 @Input() colIndex?: HeaderRendererProps["colIndex"];154 @Input() components?: HeaderRendererProps["components"];155156 readonly indicator = computed(() => {157 headerDemoSortAccessor();158 headerDemoSortDirection();159 const acc = this.accessorStr();160 const isSorted = headerDemoSortAccessor() === acc;161 const dir = isSorted ? headerDemoSortDirection() : null;162 return dir === "asc" ? " ▲" : dir === "desc" ? " ▼" : "";163 });164165 onClick(): void {166 cycleHeaderDemoSort(this.accessorStr());167 }168169 onKeydown(e: KeyboardEvent): void {170 if (e.key === "Enter") this.onClick();171 }172}173
SvelteHeaderRendererDemo.svelte
Copy
1<script lang="ts">2 import { SimpleTable } from "@simple-table/svelte";3 import type { Theme, SvelteColumnDef } from "@simple-table/svelte";4 import { headerRendererConfig } from "./header-renderer.demo-data";5 import HeaderSortableHeader from "./HeaderSortableHeader.svelte";6 import { headerDemoSortAccessor, headerDemoSortDirection } from "./header-sort-store";7 import "@simple-table/svelte/styles.css";89 let { height = "400px", theme }: { height?: string | number; theme?: Theme } = $props();1011 const sortedData = $derived.by(() => {12 const acc = $headerDemoSortAccessor;13 const dir = $headerDemoSortDirection;14 if (!acc || !dir) return [...headerRendererConfig.rows];15 return [...headerRendererConfig.rows].sort((a, b) => {16 const aVal = a[acc];17 const bVal = b[acc];18 if (aVal === bVal) return 0;19 const cmp =20 typeof aVal === "number" && typeof bVal === "number"21 ? (aVal as number) - (bVal as number)22 : String(aVal).localeCompare(String(bVal));23 return dir === "asc" ? cmp : -cmp;24 });25 });2627 const headers = $derived(28 headerRendererConfig.headers.map((h) => ({29 ...h,30 sortable: false,31 headerRenderer: HeaderSortableHeader,32 })),33 );34</script>3536<SimpleTable columns={headers} rows={sortedData} {height} {theme} />373839// HeaderSortableHeader.svelte40<script lang="ts">41 import type { HeaderRendererProps } from "@simple-table/svelte";42 import {43 headerDemoSortAccessor,44 headerDemoSortDirection,45 cycleHeaderDemoSort,46 } from "./header-sort-store";4748 let { header }: HeaderRendererProps = $props();49 const accessor = $derived(String(header.accessor));50 const isSorted = $derived($headerDemoSortAccessor === accessor);51 const dir = $derived(isSorted ? $headerDemoSortDirection : null);52 const indicator = $derived(dir === "asc" ? " ▲" : dir === "desc" ? " ▼" : "");53</script>5455<div56 style="cursor:pointer;user-select:none;font-weight:600;display:flex;align-items:center;gap:4px;"57 onclick={() => cycleHeaderDemoSort(accessor)}58 onkeydown={(e) => e.key === "Enter" && cycleHeaderDemoSort(accessor)}59 role="button"60 tabindex="0"61>62 <span>{header.label}</span>63 {#if indicator}64 <span style="font-size:10px;color:#6366f1;">{indicator}</span>65 {/if}66</div>67
Solid TSX
Copy
1import { createSignal, createMemo } from "solid-js";2import {SimpleTable} from "@simple-table/solid";import type { Theme, SolidColumnDef, HeaderRendererProps } from "@simple-table/solid";3import { headerRendererConfig } from "./header-renderer.demo-data";4import "@simple-table/solid/styles.css";56type SortDir = "asc" | "desc" | null;7const CycleOrder: SortDir[] = ["asc", "desc", null];89export default function HeaderRendererDemo(props: { height?: string | number; theme?: Theme }) {10 const [sortAccessor, setSortAccessor] = createSignal<string | null>(null);11 const [sortDirection, setSortDirection] = createSignal<SortDir>(null);1213 const sortedData = createMemo(() => {14 const acc = sortAccessor();15 const dir = sortDirection();16 if (!acc || !dir) return [...headerRendererConfig.rows];17 return [...headerRendererConfig.rows].sort((a, b) => {18 const aVal = a[acc];19 const bVal = b[acc];20 if (aVal === bVal) return 0;21 const cmp = typeof aVal === "number" && typeof bVal === "number"22 ? aVal - bVal23 : String(aVal).localeCompare(String(bVal));24 return dir === "asc" ? cmp : -cmp;25 });26 });2728 const headers = createMemo((): SolidColumnDef[] =>29 headerRendererConfig.headers.map((h) => ({30 ...h,31 sortable: false,32 headerRenderer: ({ accessor }: HeaderRendererProps) => {33 const isSorted = sortAccessor() === accessor;34 const dir = isSorted ? sortDirection() : null;35 const indicator = dir === "asc" ? " ▲" : dir === "desc" ? " ▼" : "";3637 const handleClick = () => {38 if (!isSorted) {39 setSortAccessor(accessor as string);40 setSortDirection("asc");41 return;42 }43 const idx = CycleOrder.indexOf(dir);44 const next = CycleOrder[(idx + 1) % CycleOrder.length];45 setSortAccessor(next ? (accessor as string) : null);46 setSortDirection(next);47 };4849 return (50 <div51 onClick={handleClick}52 style={{53 cursor: "pointer",54 "user-select": "none",55 "font-weight": "600",56 display: "flex",57 "align-items": "center",58 gap: "4px",59 }}60 >61 <span>{h.label}</span>62 {indicator && (63 <span style={{ "font-size": "10px", color: "#6366f1" }}>{indicator}</span>64 )}65 </div>66 );67 },68 }))69 );7071 return (72 <SimpleTable73 columns={headers()}74 rows={sortedData()}75 height={props.height ?? "400px"}76 theme={props.theme}77 />78 );79}
TypeScriptHeaderRendererDemo.ts
Copy
1import { SimpleTableVanilla } from "simple-table-core";2import type { Theme, ColumnDef, Row } from "simple-table-core";3import { headerRendererConfig } from "./header-renderer.demo-data";4import "simple-table-core/styles.css";56type SortDir = "asc" | "desc" | null;7const CYCLE: SortDir[] = ["asc", "desc", null];89export function renderHeaderRendererDemo(10 container: HTMLElement,11 options?: { height?: string | number; theme?: Theme }12): SimpleTableVanilla {13 let sortAccessor: string | null = null;14 let sortDirection: SortDir = null;1516 function getSortedData(): Row[] {17 if (!sortAccessor || !sortDirection) return [...headerRendererConfig.rows];18 const acc = sortAccessor;19 const dir = sortDirection;20 return [...headerRendererConfig.rows].sort((a, b) => {21 const aVal = a[acc];22 const bVal = b[acc];23 if (aVal === bVal) return 0;24 const cmp = typeof aVal === "number" && typeof bVal === "number"25 ? (aVal as number) - (bVal as number)26 : String(aVal).localeCompare(String(bVal));27 return dir === "asc" ? cmp : -cmp;28 });29 }3031 function buildHeaders(): ColumnDef[] {32 return headerRendererConfig.headers.map((h) => ({33 ...h,34 sortable: false,35 headerRenderer: () => {36 const isSorted = sortAccessor === h.accessor;37 const dir = isSorted ? sortDirection : null;38 const indicator = dir === "asc" ? " ▲" : dir === "desc" ? " ▼" : "";3940 const wrapper = document.createElement("div");41 Object.assign(wrapper.style, {42 cursor: "pointer",43 userSelect: "none",44 fontWeight: "600",45 display: "flex",46 alignItems: "center",47 gap: "4px",48 });49 wrapper.addEventListener("click", () => {50 if (!isSorted) {51 sortAccessor = h.accessor as string;52 sortDirection = "asc";53 } else {54 const idx = CYCLE.indexOf(dir);55 const next = CYCLE[(idx + 1) % CYCLE.length];56 if (next) {57 sortAccessor = h.accessor as string;58 sortDirection = next;59 } else {60 sortAccessor = null;61 sortDirection = null;62 }63 }64 table.update({ columns: buildHeaders(), rows: getSortedData() });65 });6667 const label = document.createElement("span");68 label.textContent = h.label;69 wrapper.appendChild(label);7071 if (indicator) {72 const ind = document.createElement("span");73 Object.assign(ind.style, { fontSize: "10px", color: "#6366f1" });74 ind.textContent = indicator;75 wrapper.appendChild(ind);76 }7778 return wrapper;79 },80 }));81 }8283 const table = new SimpleTableVanilla(container, {84 columns: buildHeaders(),85 rows: getSortedData(),86 height: options?.height ?? "400px",87 theme: options?.theme,88 });8990 return table;91}929394// header-renderer.demo-data.ts95// Self-contained demo table setup for this example.96import type { ColumnDef, Row } from "simple-table-core";979899export const headerRendererData: Row[] = [100 { id: 1, name: "Alice Johnson", email: "alice@example.com", role: "Engineer", salary: 125000, department: "Engineering" },101 { id: 2, name: "Bob Martinez", email: "bob@example.com", role: "Designer", salary: 98000, department: "Design" },102 { id: 3, name: "Clara Chen", email: "clara@example.com", role: "PM", salary: 115000, department: "Product" },103 { id: 4, name: "David Kim", email: "david@example.com", role: "Engineer", salary: 132000, department: "Engineering" },104 { id: 5, name: "Elena Rossi", email: "elena@example.com", role: "Analyst", salary: 89000, department: "Analytics" },105 { id: 6, name: "Frank Müller", email: "frank@example.com", role: "Engineer", salary: 118000, department: "Engineering" },106 { id: 7, name: "Grace Park", email: "grace@example.com", role: "Designer", salary: 105000, department: "Design" },107 { id: 8, name: "Henry Patel", email: "henry@example.com", role: "Lead", salary: 145000, department: "Engineering" },108];109110export const headerRendererHeaders: ColumnDef[] = [111 { accessor: "id", label: "ID", width: 60, type: "number", sortable: true },112 { accessor: "name", label: "Employee Name", width: 180, type: "string", sortable: true },113 { accessor: "email", label: "Email Address", width: 200, type: "string" },114 { accessor: "role", label: "Job Role", width: 130, type: "string", sortable: true },115 { accessor: "salary", label: "Annual Salary", width: 140, type: "number", sortable: true },116 { accessor: "department", label: "Department", width: 150, type: "string", sortable: true },117];118119export const headerRendererConfig = {120 headers: headerRendererHeaders,121 rows: headerRendererData,122 tableProps: {123 selectableCells: true,124 columnResizing: true,125 },126} as const;127
Props
Header Renderer Configuration
| Property | Required | Description | Example |
|---|---|---|---|
Property | Required | Description | Example |
ColumnDef.headerRenderer | Optional | Custom header content. Framework adapters accept components or render functions; vanilla returns a string or DOM node. |
Renderer arguments
HeaderRendererProps
| Property | Required | Description | Example |
|---|---|---|---|
Property | Required | Description | Example |
accessor | Required | The column accessor/key identifying which column this header belongs to | |
colIndexnumber | Required | The zero-based index of the column within the table | |
header | Required | The complete ColumnDef containing all configuration for this column including label, width, and other properties | |
componentsHeaderRendererComponents | Optional | Object containing pre-rendered header components (sortIcon, filterIcon, collapseIcon, labelContent) that can be positioned anywhere in your custom header renderer. This gives you complete control over the layout and order of header elements. |