Documentation
Nested Tables
Give each hierarchy level its own columns — expand a row to open a full child table.
- 1
Define child columns
Unlike row grouping, each level can have its own column set.TypeScriptCopyconst divisionColumns: ReactColumnDef[] = [{ accessor: "divisionId", label: "Division ID", width: 120 },{ accessor: "revenue", label: "Revenue", width: 120, type: "number" },{ accessor: "headcount", label: "Headcount", width: 110, type: "number" },{ accessor: "location", label: "Location", width: "1fr" },];TypeScriptCopyconst divisionColumns: AngularColumnDef[] = [{ accessor: "divisionId", label: "Division ID", width: 120 },{ accessor: "revenue", label: "Revenue", width: 120, type: "number" },{ accessor: "headcount", label: "Headcount", width: 110, type: "number" },{ accessor: "location", label: "Location", width: "1fr" },];TypeScriptCopyconst divisionColumns: VueColumnDef[] = [{ accessor: "divisionId", label: "Division ID", width: 120 },{ accessor: "revenue", label: "Revenue", width: 120, type: "number" },{ accessor: "headcount", label: "Headcount", width: 110, type: "number" },{ accessor: "location", label: "Location", width: "1fr" },];TypeScriptCopyconst divisionColumns: SvelteColumnDef[] = [{ accessor: "divisionId", label: "Division ID", width: 120 },{ accessor: "revenue", label: "Revenue", width: 120, type: "number" },{ accessor: "headcount", label: "Headcount", width: 110, type: "number" },{ accessor: "location", label: "Location", width: "1fr" },];TypeScriptCopyconst divisionColumns: SolidColumnDef[] = [{ accessor: "divisionId", label: "Division ID", width: 120 },{ accessor: "revenue", label: "Revenue", width: 120, type: "number" },{ accessor: "headcount", label: "Headcount", width: 110, type: "number" },{ accessor: "location", label: "Location", width: "1fr" },];TypeScriptCopyconst divisionColumns: ColumnDef[] = [{ accessor: "divisionId", label: "Division ID", width: 120 },{ accessor: "revenue", label: "Revenue", width: 120, type: "number" },{ accessor: "headcount", label: "Headcount", width: 110, type: "number" },{ accessor: "location", label: "Location", width: "1fr" },]; - 2
Add nestedTable to an expandable column
Setexpandable: trueandnestedTablewith those child columns.TypeScriptCopy{accessor: "companyName",label: "Company",width: 200,expandable: true,nestedTable: {columns: divisionColumns,},}TypeScriptCopy{accessor: "companyName",label: "Company",width: 200,expandable: true,nestedTable: {columns: divisionColumns,},}TypeScriptCopy{accessor: "companyName",label: "Company",width: 200,expandable: true,nestedTable: {columns: divisionColumns,},}TypeScriptCopy{accessor: "companyName",label: "Company",width: 200,expandable: true,nestedTable: {columns: divisionColumns,},}TypeScriptCopy{accessor: "companyName",label: "Company",width: 200,expandable: true,nestedTable: {columns: divisionColumns,},}TypeScriptCopy{accessor: "companyName",label: "Company",width: 200,expandable: true,nestedTable: {columns: divisionColumns,},} - 3
Shape nested data
Nest child arrays under therowGroupingkeys. Child fields match the nested table columns.TypeScriptCopy{id: "co-1",companyName: "Acme Corp",divisions: [{divisionId: "D-1",revenue: 1200000,headcount: 42,location: "Austin",},],}TypeScriptCopy{id: "co-1",companyName: "Acme Corp",divisions: [{divisionId: "D-1",revenue: 1200000,headcount: 42,location: "Austin",},],}TypeScriptCopy{id: "co-1",companyName: "Acme Corp",divisions: [{divisionId: "D-1",revenue: 1200000,headcount: 42,location: "Austin",},],}TypeScriptCopy{id: "co-1",companyName: "Acme Corp",divisions: [{divisionId: "D-1",revenue: 1200000,headcount: 42,location: "Austin",},],}TypeScriptCopy{id: "co-1",companyName: "Acme Corp",divisions: [{divisionId: "D-1",revenue: 1200000,headcount: 42,location: "Austin",},],}TypeScriptCopy{id: "co-1",companyName: "Acme Corp",divisions: [{divisionId: "D-1",revenue: 1200000,headcount: 42,location: "Austin",},],} - 4
Wire rowGrouping on the parent table
PassrowGroupingandgetRowIdso expansion stays stable.React TSXCopy<SimpleTablecolumns={companyColumns}rows={rows}rowGrouping={["divisions"]}getRowId={({ row }) => String(row.id)}/>AngularCopy<simple-table[columns]="companyColumns"[rows]="rows"[rowGrouping]="['divisions']"[getRowId]="getRowId"></simple-table>Vue SFCCopy<SimpleTable:columns="companyColumns":rows="rows":row-grouping="['divisions']":get-row-id="(ctx) => String(ctx.row.id)"/>SvelteCopy<SimpleTablecolumns={companyColumns}{rows}rowGrouping={["divisions"]}getRowId={({ row }) => String(row.id)}/>Solid TSXCopy<SimpleTablecolumns={companyColumns}rows={rows()}rowGrouping={["divisions"]}getRowId={({ row }) => String(row.id)}/>TypeScriptCopynew SimpleTableVanilla(container, {columns: companyColumns,rows,rowGrouping: ["divisions"],getRowId: ({ row }) => String(row.id),});
Patterns
Configure the nested table
nestedTable accepts most SimpleTable props (selection, resizing, theme, pagination, and more). rows and state renderers come from the parent and are not set here.
TypeScript
Copy
{accessor: "companyName",label: "Company",expandable: true,nestedTable: {columns: divisionColumns,enableRowSelection: true,columnResizing: true,autoExpandColumns: true,},}
TypeScript
Copy
{accessor: "companyName",label: "Company",expandable: true,nestedTable: {columns: divisionColumns,enableRowSelection: true,columnResizing: true,autoExpandColumns: true,},}
TypeScript
Copy
{accessor: "companyName",label: "Company",expandable: true,nestedTable: {columns: divisionColumns,enableRowSelection: true,columnResizing: true,autoExpandColumns: true,},}
TypeScript
Copy
{accessor: "companyName",label: "Company",expandable: true,nestedTable: {columns: divisionColumns,enableRowSelection: true,columnResizing: true,autoExpandColumns: true,},}
TypeScript
Copy
{accessor: "companyName",label: "Company",expandable: true,nestedTable: {columns: divisionColumns,enableRowSelection: true,columnResizing: true,autoExpandColumns: true,},}
TypeScript
Copy
{accessor: "companyName",label: "Company",expandable: true,nestedTable: {columns: divisionColumns,enableRowSelection: true,columnResizing: true,autoExpandColumns: true,},}
Multi-level nesting
Add nestedTable on a column inside the child columns, and extend rowGrouping accordingly.
TypeScript
Copy
// Parent column{accessor: "companyName",expandable: true,nestedTable: {columns: [{accessor: "divisionName",expandable: true,nestedTable: { columns: teamColumns },},// ...],},}// Parent tablerowGrouping: ["divisions", "teams"]
TypeScript
Copy
// Parent column{accessor: "companyName",expandable: true,nestedTable: {columns: [{accessor: "divisionName",expandable: true,nestedTable: { columns: teamColumns },},// ...],},}// Parent tablerowGrouping: ["divisions", "teams"]
TypeScript
Copy
// Parent column{accessor: "companyName",expandable: true,nestedTable: {columns: [{accessor: "divisionName",expandable: true,nestedTable: { columns: teamColumns },},// ...],},}// Parent tablerowGrouping: ["divisions", "teams"]
TypeScript
Copy
// Parent column{accessor: "companyName",expandable: true,nestedTable: {columns: [{accessor: "divisionName",expandable: true,nestedTable: { columns: teamColumns },},// ...],},}// Parent tablerowGrouping: ["divisions", "teams"]
TypeScript
Copy
// Parent column{accessor: "companyName",expandable: true,nestedTable: {columns: [{accessor: "divisionName",expandable: true,nestedTable: { columns: teamColumns },},// ...],},}// Parent tablerowGrouping: ["divisions", "teams"]
TypeScript
Copy
// Parent column{accessor: "companyName",expandable: true,nestedTable: {columns: [{accessor: "divisionName",expandable: true,nestedTable: { columns: teamColumns },},// ...],},}// Parent tablerowGrouping: ["divisions", "teams"]
Lazy-load nested rows
Use onRowGroupExpand on the parent (or on a nested level) to fetch children when a row expands — same helpers as row grouping (setLoading, setError, setEmpty).
React TSX
Copy
<SimpleTablecolumns={columns}rows={rows}rowGrouping={["stores", "products"]}getRowId={({ row }) => String(row.id)}onRowGroupExpand={async ({ row, isExpanded, groupingKey, setLoading, setError, setEmpty, rowIndexPath }) => {if (!isExpanded) return;setLoading(true);try {const children = await fetchChildren(row.id, groupingKey);setLoading(false);if (children.length === 0) {setEmpty(true, "No data");return;}// update rows using rowIndexPath / groupingKey} catch (error) {setLoading(false);setError(error.message);}}}/>
Angular
Copy
<simple-table[columns]="columns"[rows]="rows"[rowGrouping]="['stores', 'products']"[getRowId]="getRowId"(rowGroupExpand)="handleRowGroupExpand($event)"></simple-table>
Vue SFC
Copy
<SimpleTable:columns="columns":rows="rows":row-grouping="['stores', 'products']":get-row-id="(ctx) => String(ctx.row.id)":on-row-group-expand="handleRowGroupExpand"/>
Svelte
Copy
<SimpleTable{columns}{rows}rowGrouping={["stores", "products"]}getRowId={({ row }) => String(row.id)}onRowGroupExpand={handleRowGroupExpand}/>
Solid TSX
Copy
<SimpleTablecolumns={columns}rows={rows()}rowGrouping={["stores", "products"]}getRowId={({ row }) => String(row.id)}onRowGroupExpand={async ({ row, isExpanded, groupingKey, setLoading, setError, setEmpty, rowIndexPath }) => {if (!isExpanded) return;setLoading(true);try {const children = await fetchChildren(row.id, groupingKey);setLoading(false);if (children.length === 0) {setEmpty(true, "No data");return;}// update rows using rowIndexPath / groupingKey} catch (error) {setLoading(false);setError(error.message);}}}/>
TypeScript
Copy
new SimpleTableVanilla(container, {columns,rows,rowGrouping: ["stores", "products"],getRowId: ({ row }) => String(row.id),onRowGroupExpand: async ({ row, isExpanded, groupingKey, setLoading, setError, setEmpty, rowIndexPath }) => {if (!isExpanded) return;setLoading(true);try {const children = await fetchChildren(row.id, groupingKey);setLoading(false);if (children.length === 0) {setEmpty(true, "No data");return;}// update rows using rowIndexPath / groupingKey} catch (error) {setLoading(false);setError(error.message);}},});
Example
Pre-loaded divisions under each company.
React TSX
Copy
1import { useMemo } from "react";2import { SimpleTable } from "@simple-table/react";3import type { Theme } from "@simple-table/react";4import {5 nestedTablesConfig,6 generateNestedTablesData,7} from "./nested-tables.demo-data";8import "@simple-table/react/styles.css";910const NestedTablesDemo = ({ height = "500px", theme }: { height?: string | number; theme?: Theme }) => {11 const sampleData = useMemo(() => generateNestedTablesData(25), []);1213 return (14 <SimpleTable15 autoExpandColumns={nestedTablesConfig.tableProps.autoExpandColumns}16 columns={nestedTablesConfig.headers}17 rows={sampleData}18 rowGrouping={nestedTablesConfig.tableProps.rowGrouping}19 getRowId={({ row }) => row.id}20 expandAll={nestedTablesConfig.tableProps.expandAll}21 columnResizing={nestedTablesConfig.tableProps.columnResizing}22 height={height}23 theme={theme}24 />25 );26};2728export default NestedTablesDemo;
Angularnested-tables-demo.component.ts
Copy
1import { Component, Input } from "@angular/core";2import {SimpleTableComponent} from "@simple-table/angular";import type { AngularColumnDef, GetRowIdParams, Theme } from "@simple-table/angular";3import { nestedTablesConfig, generateNestedTablesData } from "./nested-tables.demo-data";4import "@simple-table/angular/styles.css";5import type { NestedCompany } from "./nested-tables.demo-data";67@Component({8 selector: "nested-tables-demo",9 standalone: true,10 imports: [SimpleTableComponent],11 template: `12 <simple-table13 [autoExpandColumns]="true"14 [columns]="headers"15 [rows]="sampleData"16 [rowGrouping]="grouping"17 [getRowId]="getRowId"18 [expandAll]="false"19 [columnResizing]="true"20 [height]="height"21 [theme]="theme"22 ></simple-table>23 `,24})25export class NestedTablesDemoComponent {26 @Input() height: string | number = "500px";27 @Input() theme?: Theme;2829 readonly headers: AngularColumnDef<NestedCompany>[] = nestedTablesConfig.headers;30 readonly sampleData = generateNestedTablesData(25);31 readonly grouping = ["divisions"];32 readonly getRowId = ({ row }: GetRowIdParams<NestedCompany>) => String(row.id);33}343536// nested-tables.demo-data.ts37// Self-contained demo table setup for this example.38import type { AngularColumnDef } from "@simple-table/angular";3940export interface NestedDivision {41 divisionId: string;42 divisionName: string;43 revenue: string;44 profitMargin: string;45 headcount: number;46 location: string;47}4849export interface NestedCompany {50 id: number;51 companyName: string;52 industry: string;53 founded: number;54 headquarters: string;55 stockSymbol: string;56 marketCap: string;57 ceo: string;58 revenue: string;59 employees: number;60 divisions: NestedDivision[];61}6263const industries = ["Technology", "Financial Services", "Healthcare", "Manufacturing", "Retail", "Energy", "Telecommunications", "Pharmaceuticals", "Automotive", "Aerospace", "Biotechnology", "E-commerce"];64const cities = ["San Francisco, CA", "New York, NY", "Boston, MA", "Seattle, WA", "Austin, TX", "Chicago, IL", "Los Angeles, CA", "Denver, CO", "Miami, FL", "Atlanta, GA", "Portland, OR", "Dallas, TX"];65const firstNames = ["Jane", "John", "Emily", "Michael", "Sarah", "David", "Lisa", "Robert", "Maria", "James", "Jennifer", "William", "Patricia", "Richard", "Linda"];66const lastNames = ["Smith", "Johnson", "Williams", "Brown", "Jones", "Garcia", "Miller", "Davis", "Rodriguez", "Martinez", "Anderson", "Taylor", "Thomas", "Moore"];67const divisionTypes = ["Cloud Services", "AI Research", "Consumer Products", "Investment Banking", "Retail Banking", "Research & Development", "Operations", "Sales & Marketing", "Customer Success", "Engineering", "Product Development", "Analytics", "Infrastructure", "Security", "Data Science"];68const companyNames = ["TechCorp", "FinanceHub", "HealthTech", "GlobalSystems", "InnovateLabs", "FutureTech", "DataWorks", "CloudFirst", "SmartSolutions", "NextGen", "PrimeVentures", "AlphaGroup", "BetaSystems", "GammaIndustries", "DeltaCorp"];69const suffixes = ["Global", "Inc", "Solutions", "Systems", "Ventures", "Group", "Industries", "Technologies"];7071const randomElement = <T,>(arr: T[]): T => arr[Math.floor(Math.random() * arr.length)];72const randomInt = (min: number, max: number): number => Math.floor(Math.random() * (max - min + 1)) + min;7374const generateDivision = (divisionIndex: number, companyIndex: number): NestedDivision => ({75 divisionId: `DIV-${String(companyIndex * 10 + divisionIndex).padStart(3, "0")}`,76 divisionName: randomElement(divisionTypes),77 revenue: `$${randomInt(5, 25)}B`,78 profitMargin: `${randomInt(15, 50)}%`,79 headcount: randomInt(50, 500),80 location: randomElement(cities),81});8283const generateCompany = (companyIndex: number): NestedCompany => {84 const divisions = Array.from({ length: randomInt(3, 7) }, (_, i) => generateDivision(i, companyIndex));85 return {86 id: companyIndex + 1,87 companyName: `${randomElement(companyNames)} ${randomElement(suffixes)}`,88 industry: randomElement(industries),89 founded: randomInt(1985, 2020),90 headquarters: randomElement(cities),91 stockSymbol: Array.from({ length: 4 }, () => String.fromCharCode(65 + randomInt(0, 25))).join(""),92 marketCap: `$${randomInt(10, 200)}B`,93 ceo: `${randomElement(firstNames)} ${randomElement(lastNames)}`,94 revenue: `$${randomInt(5, 60)}B`,95 employees: randomInt(5000, 100000),96 divisions,97 };98};99100export const generateNestedTablesData = (count: number = 25): NestedCompany[] =>101 Array.from({ length: count }, (_, i) => generateCompany(i));102103export const nestedTablesDivisionHeaders: AngularColumnDef<NestedDivision>[] = [104 { accessor: "divisionId", label: "Division ID", width: 120 },105 { accessor: "revenue", label: "Revenue", width: 120 },106 { accessor: "profitMargin", label: "Profit Margin", width: 130 },107 { accessor: "headcount", label: "Headcount", width: 110, type: "number" },108 { accessor: "location", label: "Location", width: "1fr" },109];110111export const nestedTablesHeaders: AngularColumnDef<NestedCompany>[] = [112 {113 accessor: "companyName",114 label: "Company",115 width: 200,116 expandable: true,117 nestedTable: {118 columns: nestedTablesDivisionHeaders,119 },120 },121 { accessor: "stockSymbol", label: "Symbol", width: 100 },122 { accessor: "marketCap", label: "Market Cap", width: 120 },123 { accessor: "revenue", label: "Revenue", width: 120 },124 { accessor: "employees", label: "Employees", width: 120, type: "number" },125];126127export const nestedTablesConfig = {128 headers: nestedTablesHeaders,129 tableProps: {130 rowGrouping: ["divisions"],131 expandAll: false,132 columnResizing: true,133 autoExpandColumns: true,134 },135};136
Vue SFC
Copy
1<template>2 <SimpleTable3 :auto-expand-columns="nestedTablesConfig.tableProps.autoExpandColumns"4 :columns="nestedTablesConfig.headers"5 :rows="sampleData"6 :row-grouping="nestedTablesConfig.tableProps.rowGrouping"7 :get-row-id="getRowId"8 :expand-all="nestedTablesConfig.tableProps.expandAll"9 :column-resizing="nestedTablesConfig.tableProps.columnResizing"10 :height="height"11 :theme="theme"12 />13</template>1415<script setup lang="ts">16import { computed } from "vue";17import { SimpleTable } from "@simple-table/vue";18import type { Theme, GetRowIdParams } from "@simple-table/vue";19import {20 nestedTablesConfig,21 generateNestedTablesData,22 type NestedCompany,23} from "./nested-tables.demo-data";24import "@simple-table/vue/styles.css";2526withDefaults(defineProps<{ height?: string | number; theme?: Theme }>(), { height: "500px" });2728const sampleData = computed(() => generateNestedTablesData(25));2930const getRowId = ({ row }: GetRowIdParams<NestedCompany>) => row.id;31</script>
Svelte
Copy
1<script lang="ts">2 import { SimpleTable } from "@simple-table/svelte";3 import type { Theme, GetRowIdParams } from "@simple-table/svelte";4 import {5 nestedTablesConfig,6 generateNestedTablesData,7 type NestedCompany,8 } from "./nested-tables.demo-data";9 import "@simple-table/svelte/styles.css";1011 let { height = "500px", theme }: { height?: string | number; theme?: Theme } = $props();1213 const sampleData = generateNestedTablesData(25);14 const getRowId = ({ row }: GetRowIdParams<NestedCompany>) => row.id;15</script>1617<SimpleTable18 autoExpandColumns={nestedTablesConfig.tableProps.autoExpandColumns}19 columns={nestedTablesConfig.headers}20 rows={sampleData}21 rowGrouping={nestedTablesConfig.tableProps.rowGrouping}22 {getRowId}23 expandAll={nestedTablesConfig.tableProps.expandAll}24 columnResizing={nestedTablesConfig.tableProps.columnResizing}25 {height}26 {theme}27/>
Solid TSX
Copy
1import { createMemo } from "solid-js";2import { SimpleTable } from "@simple-table/solid";3import type { Theme } from "@simple-table/solid";4import { nestedTablesConfig, generateNestedTablesData } from "./nested-tables.demo-data";5import "@simple-table/solid/styles.css";67export default function NestedTablesDemo(props: { height?: string | number; theme?: Theme }) {8 const sampleData = createMemo(() => generateNestedTablesData(25));910 return (11 <SimpleTable12 autoExpandColumns={nestedTablesConfig.tableProps.autoExpandColumns}13 columns={nestedTablesConfig.headers}14 rows={sampleData()}15 rowGrouping={nestedTablesConfig.tableProps.rowGrouping}16 getRowId={({ row }) => row.id}17 expandAll={nestedTablesConfig.tableProps.expandAll}18 columnResizing={nestedTablesConfig.tableProps.columnResizing}19 height={props.height ?? "500px"}20 theme={props.theme}21 />22 );23}
TypeScriptNestedTablesDemo.ts
Copy
1import { SimpleTableVanilla } from "simple-table-core";2import type { NestedCompany } from "./nested-tables.demo-data";3import type { Theme, GetRowIdParams } from "simple-table-core";4import { nestedTablesConfig, generateNestedTablesData } from "./nested-tables.demo-data";5import "simple-table-core/styles.css";67const getRowId = ({ row }: GetRowIdParams<NestedCompany>) => row.id;89export function renderNestedTablesDemo(10 container: HTMLElement,11 options?: { height?: string | number; theme?: Theme }12): SimpleTableVanilla<NestedCompany> {13 const sampleData = generateNestedTablesData(25);1415 return new SimpleTableVanilla(container, {16 autoExpandColumns: nestedTablesConfig.tableProps.autoExpandColumns,17 columns: nestedTablesConfig.headers,18 rows: sampleData,19 rowGrouping: nestedTablesConfig.tableProps.rowGrouping,20 getRowId,21 expandAll: nestedTablesConfig.tableProps.expandAll,22 columnResizing: nestedTablesConfig.tableProps.columnResizing,23 height: options?.height ?? "500px",24 theme: options?.theme,25 });26}272829// nested-tables.demo-data.ts30// Self-contained demo table setup for this example.31import type { ColumnDef } from "simple-table-core";3233export interface NestedDivision {34 divisionId: string;35 divisionName: string;36 revenue: string;37 profitMargin: string;38 headcount: number;39 location: string;40}4142export interface NestedCompany {43 id: number;44 companyName: string;45 industry: string;46 founded: number;47 headquarters: string;48 stockSymbol: string;49 marketCap: string;50 ceo: string;51 revenue: string;52 employees: number;53 divisions: NestedDivision[];54}5556const industries = ["Technology", "Financial Services", "Healthcare", "Manufacturing", "Retail", "Energy", "Telecommunications", "Pharmaceuticals", "Automotive", "Aerospace", "Biotechnology", "E-commerce"];57const cities = ["San Francisco, CA", "New York, NY", "Boston, MA", "Seattle, WA", "Austin, TX", "Chicago, IL", "Los Angeles, CA", "Denver, CO", "Miami, FL", "Atlanta, GA", "Portland, OR", "Dallas, TX"];58const firstNames = ["Jane", "John", "Emily", "Michael", "Sarah", "David", "Lisa", "Robert", "Maria", "James", "Jennifer", "William", "Patricia", "Richard", "Linda"];59const lastNames = ["Smith", "Johnson", "Williams", "Brown", "Jones", "Garcia", "Miller", "Davis", "Rodriguez", "Martinez", "Anderson", "Taylor", "Thomas", "Moore"];60const divisionTypes = ["Cloud Services", "AI Research", "Consumer Products", "Investment Banking", "Retail Banking", "Research & Development", "Operations", "Sales & Marketing", "Customer Success", "Engineering", "Product Development", "Analytics", "Infrastructure", "Security", "Data Science"];61const companyNames = ["TechCorp", "FinanceHub", "HealthTech", "GlobalSystems", "InnovateLabs", "FutureTech", "DataWorks", "CloudFirst", "SmartSolutions", "NextGen", "PrimeVentures", "AlphaGroup", "BetaSystems", "GammaIndustries", "DeltaCorp"];62const suffixes = ["Global", "Inc", "Solutions", "Systems", "Ventures", "Group", "Industries", "Technologies"];6364const randomElement = <T,>(arr: T[]): T => arr[Math.floor(Math.random() * arr.length)];65const randomInt = (min: number, max: number): number => Math.floor(Math.random() * (max - min + 1)) + min;6667const generateDivision = (divisionIndex: number, companyIndex: number): NestedDivision => ({68 divisionId: `DIV-${String(companyIndex * 10 + divisionIndex).padStart(3, "0")}`,69 divisionName: randomElement(divisionTypes),70 revenue: `$${randomInt(5, 25)}B`,71 profitMargin: `${randomInt(15, 50)}%`,72 headcount: randomInt(50, 500),73 location: randomElement(cities),74});7576const generateCompany = (companyIndex: number): NestedCompany => {77 const divisions = Array.from({ length: randomInt(3, 7) }, (_, i) => generateDivision(i, companyIndex));78 return {79 id: companyIndex + 1,80 companyName: `${randomElement(companyNames)} ${randomElement(suffixes)}`,81 industry: randomElement(industries),82 founded: randomInt(1985, 2020),83 headquarters: randomElement(cities),84 stockSymbol: Array.from({ length: 4 }, () => String.fromCharCode(65 + randomInt(0, 25))).join(""),85 marketCap: `$${randomInt(10, 200)}B`,86 ceo: `${randomElement(firstNames)} ${randomElement(lastNames)}`,87 revenue: `$${randomInt(5, 60)}B`,88 employees: randomInt(5000, 100000),89 divisions,90 };91};9293export const generateNestedTablesData = (count: number = 25): NestedCompany[] =>94 Array.from({ length: count }, (_, i) => generateCompany(i));9596export const nestedTablesDivisionHeaders: ColumnDef<NestedDivision>[] = [97 { accessor: "divisionId", label: "Division ID", width: 120 },98 { accessor: "revenue", label: "Revenue", width: 120 },99 { accessor: "profitMargin", label: "Profit Margin", width: 130 },100 { accessor: "headcount", label: "Headcount", width: 110, type: "number" },101 { accessor: "location", label: "Location", width: "1fr" },102];103104export const nestedTablesHeaders: ColumnDef<NestedCompany>[] = [105 {106 accessor: "companyName",107 label: "Company",108 width: 200,109 expandable: true,110 nestedTable: {111 columns: nestedTablesDivisionHeaders,112 },113 },114 { accessor: "stockSymbol", label: "Symbol", width: 100 },115 { accessor: "marketCap", label: "Market Cap", width: 120 },116 { accessor: "revenue", label: "Revenue", width: 120 },117 { accessor: "employees", label: "Employees", width: 120, type: "number" },118];119120export const nestedTablesConfig = {121 headers: nestedTablesHeaders,122 tableProps: {123 rowGrouping: ["divisions"],124 expandAll: false,125 columnResizing: true,126 autoExpandColumns: true,127 },128};129
Dynamic loading example
Divisions load when a company row expands.
React TSX
Copy
1import { useState, useCallback } from "react";2import { SimpleTable } from "@simple-table/react";3import type { Theme, OnRowGroupExpandProps } from "@simple-table/react";4import {5 dynamicNestedTablesConfig,6 dynamicNestedTablesData,7 fetchDivisionsForCompany8} from "./dynamic-nested-tables.demo-data";9import type { DynamicCompany } from "./dynamic-nested-tables.demo-data";10import "@simple-table/react/styles.css";1112const DynamicNestedTablesDemo = ({13 height = "500px",14 theme15}: {16 height?: string | number;17 theme?: Theme;18}) => {19 const [rows, setRows] = useState<DynamicCompany[]>([...dynamicNestedTablesData]);2021 const handleCompanyExpand = useCallback(22 async ({23 row,24 groupingKey,25 isExpanded,26 rowIndexPath,27 setLoading,28 setError,29 setEmpty30 }: OnRowGroupExpandProps<DynamicCompany>) => {31 if (!isExpanded) return;32 try {33 if (groupingKey === "divisions") {34 if (row.divisions && row.divisions.length > 0) return;35 setLoading(true);36 const divisions = await fetchDivisionsForCompany(row.id);37 if (divisions.length === 0) {38 setEmpty(true, "No divisions found for this company");39 return;40 }41 setRows((prevRows) => {42 const newRows = [...prevRows];43 const companyIndex = rowIndexPath[0];44 newRows[companyIndex] = { ...newRows[companyIndex], divisions };45 return newRows;46 });47 }48 } catch (error) {49 setLoading(false);50 setError(error instanceof Error ? error.message : "Failed to load divisions");51 }52 },53 [],54 );5556 return (57 <SimpleTable58 autoExpandColumns={dynamicNestedTablesConfig.tableProps.autoExpandColumns}59 columns={dynamicNestedTablesConfig.headers}60 expandAll={dynamicNestedTablesConfig.tableProps.expandAll}61 height={height}62 rowGrouping={dynamicNestedTablesConfig.tableProps.rowGrouping}63 getRowId={({ row }) => row.id}64 rows={rows}65 onRowGroupExpand={handleCompanyExpand}66 theme={theme}67 />68 );69};7071export default DynamicNestedTablesDemo;
Angulardynamic-nested-tables-demo.component.ts
Copy
1import { Component, Input } from "@angular/core";2import {SimpleTableComponent} from "@simple-table/angular";import type { AngularColumnDef, GetRowIdParams, OnRowGroupExpandProps, Theme } from "@simple-table/angular";3import {4 dynamicNestedTablesConfig,5 dynamicNestedTablesData,6 fetchDivisionsForCompany,7} from "./dynamic-nested-tables.demo-data";8import type { DynamicCompany } from "./dynamic-nested-tables.demo-data";9import "@simple-table/angular/styles.css";1011@Component({12 selector: "dynamic-nested-tables-demo",13 standalone: true,14 imports: [SimpleTableComponent],15 template: `16 <simple-table17 [autoExpandColumns]="tableProps.autoExpandColumns"18 [columns]="headers"19 [expandAll]="tableProps.expandAll"20 [height]="height"21 [rowGrouping]="tableProps.rowGrouping"22 [getRowId]="getRowId"23 [rows]="rows"24 [onRowGroupExpand]="handleCompanyExpand"25 [theme]="theme"26 ></simple-table>27 `,28})29export class DynamicNestedTablesDemoComponent {30 @Input() height: string | number = "500px";31 @Input() theme?: Theme;3233 headers: AngularColumnDef<DynamicCompany>[] = dynamicNestedTablesConfig.headers;34 rows: DynamicCompany[] = [...dynamicNestedTablesData];35 readonly tableProps = dynamicNestedTablesConfig.tableProps;3637 handleCompanyExpand = async ({38 row,39 groupingKey,40 isExpanded,41 rowIndexPath,42 setLoading,43 setError,44 setEmpty,45 }: OnRowGroupExpandProps<DynamicCompany>) => {46 if (!isExpanded) return;47 try {48 if (groupingKey === "divisions") {49 const company = row as DynamicCompany;50 if (company.divisions && company.divisions.length > 0) return;51 setLoading(true);52 const divisions = await fetchDivisionsForCompany(company.id);53 if (divisions.length === 0) {54 setEmpty(true, "No divisions found for this company");55 return;56 }57 const newRows = [...this.rows];58 newRows[rowIndexPath[0]] = { ...newRows[rowIndexPath[0]], divisions };59 this.rows = newRows;60 }61 } catch (error) {62 setLoading(false);63 setError(error instanceof Error ? error.message : "Failed to load divisions");64 }65 };6667 getRowId = ({ row }: GetRowIdParams<DynamicCompany>) => row.id;68}697071// dynamic-nested-tables.demo-data.ts72// Self-contained demo table setup for this example.73import type { AngularColumnDef } from "@simple-table/angular";7475export interface DynamicCompany {76 id: string;77 companyName: string;78 industry: string;79 revenue: string;80 employees: number;81 divisions?: DynamicDivision[];82}8384export interface DynamicDivision {85 id: string;86 divisionName: string;87 revenue: string;88 profitMargin: string;89 headcount: number;90 location: string;91}9293const simulateDelay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));9495export const fetchDivisionsForCompany = async (companyId: string): Promise<DynamicDivision[]> => {96 await simulateDelay(800);97 const divisionCount = Math.floor(Math.random() * 3) + 2;98 const divisionNames = [99 "Cloud Services",100 "AI Research",101 "Consumer Products",102 "Investment Banking",103 "Operations",104 "Engineering",105 ];106 const locations = [107 "San Francisco, CA",108 "New York, NY",109 "Boston, MA",110 "Seattle, WA",111 "Austin, TX",112 "Chicago, IL",113 ];114115 return Array.from({ length: divisionCount }, (_, i) => ({116 id: `${companyId}-div-${i}`,117 divisionName: divisionNames[i % divisionNames.length],118 revenue: `$${Math.floor(Math.random() * 50) + 10}M`,119 profitMargin: `${Math.floor(Math.random() * 30) + 10}%`,120 headcount: Math.floor(Math.random() * 400) + 50,121 location: locations[i % locations.length],122 }));123};124125export const dynamicNestedTablesData: DynamicCompany[] = [126 {127 id: "comp-1",128 companyName: "TechCorp Global",129 industry: "Technology",130 revenue: "$250M",131 employees: 1200,132 },133 {134 id: "comp-2",135 companyName: "FinanceHub Inc",136 industry: "Financial Services",137 revenue: "$180M",138 employees: 850,139 },140 {141 id: "comp-3",142 companyName: "HealthTech Solutions",143 industry: "Healthcare",144 revenue: "$320M",145 employees: 1500,146 },147 {148 id: "comp-4",149 companyName: "RetailMax Corporation",150 industry: "Retail",151 revenue: "$420M",152 employees: 2100,153 },154 {155 id: "comp-5",156 companyName: "EnergyFlow Systems",157 industry: "Energy",158 revenue: "$560M",159 employees: 1800,160 },161 {162 id: "comp-6",163 companyName: "MediaVision Studios",164 industry: "Entertainment",165 revenue: "$290M",166 employees: 950,167 },168 {169 id: "comp-7",170 companyName: "AutoDrive Industries",171 industry: "Automotive",172 revenue: "$680M",173 employees: 3200,174 },175 {176 id: "comp-8",177 companyName: "CloudNet Services",178 industry: "Technology",179 revenue: "$195M",180 employees: 720,181 },182 {183 id: "comp-9",184 companyName: "HealthCare Solutions",185 industry: "Healthcare",186 revenue: "$380M",187 employees: 1300,188 },189 {190 id: "comp-10",191 companyName: "EducationTech Innovations",192 industry: "Education",193 revenue: "$240M",194 employees: 1050,195 },196 {197 id: "comp-11",198 companyName: "EnergyFlow Systems",199 industry: "Energy",200 revenue: "$560M",201 employees: 1800,202 },203 {204 id: "comp-12",205 companyName: "EnergyFlow Systems",206 industry: "Energy",207 revenue: "$560M",208 employees: 1800,209 },210 {211 id: "comp-13",212 companyName: "EnergyFlow Systems",213 industry: "Energy",214 revenue: "$560M",215 employees: 1800,216 },217 {218 id: "comp-14",219 companyName: "EnergyFlow Systems",220 industry: "Energy",221 revenue: "$560M",222 employees: 1800,223 },224 {225 id: "comp-15",226 companyName: "EnergyFlow Systems",227 industry: "Energy",228 revenue: "$560M",229 employees: 1800,230 },231];232233export const dynamicNestedTablesDivisionHeaders: AngularColumnDef<DynamicDivision>[] = [234 { accessor: "divisionName", label: "Division", width: 200 },235 { accessor: "revenue", label: "Revenue", width: 120 },236 { accessor: "profitMargin", label: "Profit Margin", width: 130 },237 { accessor: "headcount", label: "Headcount", width: 110, type: "number" },238 { accessor: "location", label: "Location", width: 180 },239];240241export const dynamicNestedTablesCompanyHeaders: AngularColumnDef<DynamicCompany>[] = [242 {243 accessor: "companyName",244 label: "Company",245 width: 200,246 expandable: true,247 nestedTable: {248 columns: dynamicNestedTablesDivisionHeaders,249 expandAll: false,250 autoExpandColumns: true,251 },252 },253 { accessor: "industry", label: "Industry", width: 150 },254 { accessor: "revenue", label: "Revenue", width: 120 },255 { accessor: "employees", label: "Employees", width: 120, type: "number" },256];257258export const dynamicNestedTablesConfig = {259 headers: dynamicNestedTablesCompanyHeaders,260 rows: dynamicNestedTablesData,261 tableProps: {262 rowGrouping: ["divisions"],263 expandAll: false,264 autoExpandColumns: true,265 },266};267
Vue SFC
Copy
1<template>2 <SimpleTable3 :auto-expand-columns="dynamicNestedTablesConfig.tableProps.autoExpandColumns"4 :columns="dynamicNestedTablesConfig.headers"5 :expand-all="dynamicNestedTablesConfig.tableProps.expandAll"6 :height="height"7 :row-grouping="dynamicNestedTablesConfig.tableProps.rowGrouping"8 :get-row-id="getRowId"9 :rows="rows"10 :on-row-group-expand="handleCompanyExpand"11 :theme="theme"12 />13</template>1415<script setup lang="ts">16import { ref } from "vue";17import { SimpleTable } from "@simple-table/vue";18import type { Theme, OnRowGroupExpandProps, GetRowIdParams } from "@simple-table/vue";19import {20 dynamicNestedTablesConfig,21 dynamicNestedTablesData,22 fetchDivisionsForCompany,23} from "./dynamic-nested-tables.demo-data";24import type { DynamicCompany } from "./dynamic-nested-tables.demo-data";25import "@simple-table/vue/styles.css";2627withDefaults(defineProps<{ height?: string | number; theme?: Theme }>(), { height: "500px" });2829const rows = ref<DynamicCompany[]>([...dynamicNestedTablesData]);3031const getRowId = ({ row }: GetRowIdParams<DynamicCompany>) => row.id;3233async function handleCompanyExpand({34 row,35 groupingKey,36 isExpanded,37 rowIndexPath,38 setLoading,39 setError,40 setEmpty,41}: OnRowGroupExpandProps<DynamicCompany>) {42 if (!isExpanded) return;43 try {44 if (groupingKey === "divisions") {45 if (row.divisions && row.divisions.length > 0) return;46 setLoading(true);47 const divisions = await fetchDivisionsForCompany(row.id);48 if (divisions.length === 0) {49 setEmpty(true, "No divisions found for this company");50 return;51 }52 const newRows = [...rows.value];53 newRows[rowIndexPath[0]] = { ...newRows[rowIndexPath[0]], divisions };54 rows.value = newRows;55 }56 } catch (error) {57 setLoading(false);58 setError(error instanceof Error ? error.message : "Failed to load divisions");59 }60}61</script>
Svelte
Copy
1<script lang="ts">2 import { SimpleTable } from "@simple-table/svelte";3 import type { Theme, OnRowGroupExpandProps, GetRowIdParams } from "@simple-table/svelte";4 import {5 dynamicNestedTablesConfig,6 dynamicNestedTablesData,7 fetchDivisionsForCompany,8 } from "./dynamic-nested-tables.demo-data";9 import type { DynamicCompany } from "./dynamic-nested-tables.demo-data";10 import "@simple-table/svelte/styles.css";1112 let { height = "500px", theme }: { height?: string | number; theme?: Theme } = $props();1314 let rows = $state<DynamicCompany[]>([...dynamicNestedTablesData]);15 const getRowId = ({ row }: GetRowIdParams<DynamicCompany>) => row.id;1617 async function handleCompanyExpand({18 row,19 groupingKey,20 isExpanded,21 rowIndexPath,22 setLoading,23 setError,24 setEmpty,25 }: OnRowGroupExpandProps<DynamicCompany>) {26 if (!isExpanded) return;27 try {28 if (groupingKey === "divisions") {29 if (row.divisions && row.divisions.length > 0) return;30 setLoading(true);31 const divisions = await fetchDivisionsForCompany(row.id);32 if (divisions.length === 0) {33 setEmpty(true, "No divisions found for this company");34 return;35 }36 const newRows = [...rows];37 newRows[rowIndexPath[0]] = { ...newRows[rowIndexPath[0]], divisions };38 rows = newRows;39 }40 } catch (error) {41 setLoading(false);42 setError(error instanceof Error ? error.message : "Failed to load divisions");43 }44 }45</script>4647<SimpleTable48 autoExpandColumns={dynamicNestedTablesConfig.tableProps.autoExpandColumns}49 columns={dynamicNestedTablesConfig.headers}50 expandAll={dynamicNestedTablesConfig.tableProps.expandAll}51 {getRowId}52 {height}53 rowGrouping={dynamicNestedTablesConfig.tableProps.rowGrouping}54 rows={rows}55 onRowGroupExpand={handleCompanyExpand}56 {theme}57/>
Solid TSX
Copy
1import { createSignal } from "solid-js";2import { SimpleTable } from "@simple-table/solid";3import type { Theme, OnRowGroupExpandProps } from "@simple-table/solid";4import {5 dynamicNestedTablesConfig,6 dynamicNestedTablesData,7 fetchDivisionsForCompany,8} from "./dynamic-nested-tables.demo-data";9import type { DynamicCompany } from "./dynamic-nested-tables.demo-data";10import "@simple-table/solid/styles.css";1112export default function DynamicNestedTablesDemo(props: { height?: string | number; theme?: Theme }) {13 const [rows, setRows] = createSignal<DynamicCompany[]>([...dynamicNestedTablesData]);1415 const handleCompanyExpand = async ({16 row,17 groupingKey,18 isExpanded,19 rowIndexPath,20 setLoading,21 setError,22 setEmpty,23 }: OnRowGroupExpandProps<DynamicCompany>) => {24 if (!isExpanded) return;25 try {26 if (groupingKey === "divisions") {27 if (row.divisions && row.divisions.length > 0) return;28 setLoading(true);29 const divisions = await fetchDivisionsForCompany(row.id);30 if (divisions.length === 0) {31 setEmpty(true, "No divisions found for this company");32 return;33 }34 setRows((prev) => {35 const newRows = [...prev];36 newRows[rowIndexPath[0]] = { ...newRows[rowIndexPath[0]], divisions };37 return newRows;38 });39 }40 } catch (error) {41 setLoading(false);42 setError(error instanceof Error ? error.message : "Failed to load divisions");43 }44 };4546 return (47 <SimpleTable48 autoExpandColumns={dynamicNestedTablesConfig.tableProps.autoExpandColumns}49 columns={dynamicNestedTablesConfig.headers}50 expandAll={dynamicNestedTablesConfig.tableProps.expandAll}51 height={props.height ?? "500px"}52 rowGrouping={dynamicNestedTablesConfig.tableProps.rowGrouping}53 getRowId={({ row }) => row.id}54 rows={rows()}55 onRowGroupExpand={handleCompanyExpand}56 theme={props.theme}57 />58 );59}
TypeScriptDynamicNestedTablesDemo.ts
Copy
1import { SimpleTableVanilla } from "simple-table-core";2import type { Theme, OnRowGroupExpandProps, GetRowIdParams } from "simple-table-core";3import {4 dynamicNestedTablesConfig,5 dynamicNestedTablesData,6 fetchDivisionsForCompany,7} from "./dynamic-nested-tables.demo-data";8import type { DynamicCompany } from "./dynamic-nested-tables.demo-data";9import "simple-table-core/styles.css";1011const getRowId = ({ row }: GetRowIdParams<DynamicCompany>) => row.id;1213export function renderDynamicNestedTablesDemo(14 container: HTMLElement,15 options?: { height?: string | number; theme?: Theme }16): SimpleTableVanilla<DynamicCompany> {17 let rows: DynamicCompany[] = [...dynamicNestedTablesData];1819 const handleCompanyExpand = async ({20 row,21 groupingKey,22 isExpanded,23 rowIndexPath,24 setLoading,25 setError,26 setEmpty,27 }: OnRowGroupExpandProps<DynamicCompany>) => {28 if (!isExpanded) return;29 try {30 if (groupingKey === "divisions") {31 const company = row;32 if (company.divisions && company.divisions.length > 0) return;33 setLoading(true);34 const divisions = await fetchDivisionsForCompany(company.id);35 if (divisions.length === 0) {36 setEmpty(true, "No divisions found for this company");37 return;38 }39 rows[rowIndexPath[0]] = { ...rows[rowIndexPath[0]], divisions };40 table.updateConfig({ rows: [...rows] });41 }42 } catch (error) {43 setLoading(false);44 setError(error instanceof Error ? error.message : "Failed to load divisions");45 }46 };4748 const table = new SimpleTableVanilla(container, {49 autoExpandColumns: dynamicNestedTablesConfig.tableProps.autoExpandColumns,50 columns: dynamicNestedTablesConfig.headers,51 expandAll: dynamicNestedTablesConfig.tableProps.expandAll,52 height: options?.height ?? "500px",53 rowGrouping: dynamicNestedTablesConfig.tableProps.rowGrouping,54 getRowId,55 rows: rows,56 onRowGroupExpand: handleCompanyExpand,57 theme: options?.theme,58 });5960 return table;61}626364// dynamic-nested-tables.demo-data.ts65// Self-contained demo table setup for this example.66import type { ColumnDef } from "simple-table-core";6768export interface DynamicCompany {69 id: string;70 companyName: string;71 industry: string;72 revenue: string;73 employees: number;74 divisions?: DynamicDivision[];75}7677export interface DynamicDivision {78 id: string;79 divisionName: string;80 revenue: string;81 profitMargin: string;82 headcount: number;83 location: string;84}8586const simulateDelay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));8788export const fetchDivisionsForCompany = async (companyId: string): Promise<DynamicDivision[]> => {89 await simulateDelay(800);90 const divisionCount = Math.floor(Math.random() * 3) + 2;91 const divisionNames = [92 "Cloud Services",93 "AI Research",94 "Consumer Products",95 "Investment Banking",96 "Operations",97 "Engineering",98 ];99 const locations = [100 "San Francisco, CA",101 "New York, NY",102 "Boston, MA",103 "Seattle, WA",104 "Austin, TX",105 "Chicago, IL",106 ];107108 return Array.from({ length: divisionCount }, (_, i) => ({109 id: `${companyId}-div-${i}`,110 divisionName: divisionNames[i % divisionNames.length],111 revenue: `$${Math.floor(Math.random() * 50) + 10}M`,112 profitMargin: `${Math.floor(Math.random() * 30) + 10}%`,113 headcount: Math.floor(Math.random() * 400) + 50,114 location: locations[i % locations.length],115 }));116};117118export const dynamicNestedTablesData: DynamicCompany[] = [119 {120 id: "comp-1",121 companyName: "TechCorp Global",122 industry: "Technology",123 revenue: "$250M",124 employees: 1200,125 },126 {127 id: "comp-2",128 companyName: "FinanceHub Inc",129 industry: "Financial Services",130 revenue: "$180M",131 employees: 850,132 },133 {134 id: "comp-3",135 companyName: "HealthTech Solutions",136 industry: "Healthcare",137 revenue: "$320M",138 employees: 1500,139 },140 {141 id: "comp-4",142 companyName: "RetailMax Corporation",143 industry: "Retail",144 revenue: "$420M",145 employees: 2100,146 },147 {148 id: "comp-5",149 companyName: "EnergyFlow Systems",150 industry: "Energy",151 revenue: "$560M",152 employees: 1800,153 },154 {155 id: "comp-6",156 companyName: "MediaVision Studios",157 industry: "Entertainment",158 revenue: "$290M",159 employees: 950,160 },161 {162 id: "comp-7",163 companyName: "AutoDrive Industries",164 industry: "Automotive",165 revenue: "$680M",166 employees: 3200,167 },168 {169 id: "comp-8",170 companyName: "CloudNet Services",171 industry: "Technology",172 revenue: "$195M",173 employees: 720,174 },175 {176 id: "comp-9",177 companyName: "HealthCare Solutions",178 industry: "Healthcare",179 revenue: "$380M",180 employees: 1300,181 },182 {183 id: "comp-10",184 companyName: "EducationTech Innovations",185 industry: "Education",186 revenue: "$240M",187 employees: 1050,188 },189 {190 id: "comp-11",191 companyName: "EnergyFlow Systems",192 industry: "Energy",193 revenue: "$560M",194 employees: 1800,195 },196 {197 id: "comp-12",198 companyName: "EnergyFlow Systems",199 industry: "Energy",200 revenue: "$560M",201 employees: 1800,202 },203 {204 id: "comp-13",205 companyName: "EnergyFlow Systems",206 industry: "Energy",207 revenue: "$560M",208 employees: 1800,209 },210 {211 id: "comp-14",212 companyName: "EnergyFlow Systems",213 industry: "Energy",214 revenue: "$560M",215 employees: 1800,216 },217 {218 id: "comp-15",219 companyName: "EnergyFlow Systems",220 industry: "Energy",221 revenue: "$560M",222 employees: 1800,223 },224];225226export const dynamicNestedTablesDivisionHeaders: ColumnDef<DynamicDivision>[] = [227 { accessor: "divisionName", label: "Division", width: 200 },228 { accessor: "revenue", label: "Revenue", width: 120 },229 { accessor: "profitMargin", label: "Profit Margin", width: 130 },230 { accessor: "headcount", label: "Headcount", width: 110, type: "number" },231 { accessor: "location", label: "Location", width: 180 },232];233234export const dynamicNestedTablesCompanyHeaders: ColumnDef<DynamicCompany>[] = [235 {236 accessor: "companyName",237 label: "Company",238 width: 200,239 expandable: true,240 nestedTable: {241 columns: dynamicNestedTablesDivisionHeaders,242 expandAll: false,243 autoExpandColumns: true,244 },245 },246 { accessor: "industry", label: "Industry", width: 150 },247 { accessor: "revenue", label: "Revenue", width: 120 },248 { accessor: "employees", label: "Employees", width: 120, type: "number" },249];250251export const dynamicNestedTablesConfig = {252 headers: dynamicNestedTablesCompanyHeaders,253 rows: dynamicNestedTablesData,254 tableProps: {255 rowGrouping: ["divisions"],256 expandAll: false,257 autoExpandColumns: true,258 },259};260
Props
Nested Tables Configuration
| Property | Required | Description | Example |
|---|---|---|---|
Property | Required | Description | Example |
ColumnDef.nestedTable | Optional | Renders an independent child table on expand. Requires expandable: true and matching rowGrouping. | |
nestedTable.columns | Required | Column defs for the nested table — can differ entirely from the parent. | |
rowGroupingstring[] | Optional | Nested array property names that define the hierarchy (same as row grouping). | |
getRowId(props: { row: Row }) => string | number | null | undefined | Optional | Stable row id so expansion survives sort and data updates. | |
onRowGroupExpand | Optional | Lazy-load children on expand. Can be set per nesting level. |