Replies: 5 comments 9 replies
-
@LoicUV Thanks for the good idea via meta! I experimented a bit based on your solution and got it to work automatically, but didn't test on large nesting: <thead>
{table.getHeaderGroups().map((headerGroup) => (
<tr key={headerGroup.id}>
{headerGroup.headers.map((header) => {
const columnRelativeDepth = header.depth - header.column.depth;
if (
!header.isPlaceholder &&
columnRelativeDepth > 1 &&
header.id === header.column.id
) {
return null;
}
let rowSpan = 1;
if (header.isPlaceholder) {
const leafs = header.getLeafHeaders();
rowSpan = leafs[leafs.length - 1].depth - header.depth;
}
return (
<th
key={header.id}
colSpan={header.colSpan}
rowSpan={rowSpan}
>
{flexRender(
header.column.columnDef.header,
header.getContext(),
)}
</th>
);
})}
</tr>
))}
</thead> My logic is that columns have the smallest depth, relative to the nested columns. And if the column depth is more than 1 greater than the column depth, it means that there is a placeholder and we don't display this header. The P.S. I couldn't figure out how to use the built-in rowSpan either. Do you have any new information about it? |
Beta Was this translation helpful? Give feedback.
-
@Hristy-A thanks! I use the following code to implement this interesting feature in vue import { FlexRender } from "@tanstack/vue-table";
export const Thead = ({ table }) => {
return <thead>
{table.getHeaderGroups().map((headerGroup) => (
<tr key={headerGroup.id}>
{headerGroup.headers.map((header) => {
const columnRelativeDepth = header.depth - header.column.depth;
if (
columnRelativeDepth > 1
) {
return null;
}
let rowSpan = 1;
if (header.isPlaceholder) {
const leafs = header.getLeafHeaders();
rowSpan = leafs[leafs.length - 1].depth - header.depth;
}
return (
<th
data-columnRelativeDepth={columnRelativeDepth}
key={header.id}
colspan={header.colSpan}
rowspan={rowSpan}
>
<FlexRender
render={header.column.columnDef.header}
props={header.getContext()}
/>
</th>
);
})}
</tr>
))}
</thead>
} |
Beta Was this translation helpful? Give feedback.
-
hello. import * as React from "react";
import ReactDOM from "react-dom/client";
import {
ColumnDef,
createColumnHelper,
flexRender,
getCoreRowModel,
useReactTable,
} from "@tanstack/react-table";
declare module "@tanstack/table-core" {
// @ts-expect-error
interface ColumnMeta<TData extends RowData, TValue> {
rowSpan?: number;
}
}
type Person = {
test?: string,
firstName: string;
lastName: string;
age: number;
visits: number;
status: string;
progress: number;
};
const defaultData: Person[] = [
{
test: "aaa",
firstName: "tanner",
lastName: "linsley",
age: 24,
visits: 100,
status: "In Relationship",
progress: 50,
},
{
test: "bbb",
firstName: "tandy",
lastName: "miller",
age: 40,
visits: 40,
status: "Single",
progress: 80,
},
{
test: "ccc",
firstName: "joe",
lastName: "dirte",
age: 45,
visits: 20,
status: "Complicated",
progress: 10,
},
];
const columnHelper = createColumnHelper<Person>();
const columns = [
columnHelper.accessor("test", {
cell: (info) => info.getValue(),
meta: {
rowSpan: 3,
},
}),
columnHelper.group({
id: "hello",
header: () => <span>Hello</span>,
columns: [
columnHelper.accessor("firstName", {
cell: (info) => info.getValue(),
meta: {
rowSpan: 2,
},
}),
columnHelper.accessor((row) => row.lastName, {
id: "lastName",
cell: (info) => info.getValue(),
header: () => <span>Last Name</span>,
meta: {
rowSpan: 2,
},
}),
],
}),
columnHelper.group({
header: "Info",
columns: [
columnHelper.accessor("age", {
header: () => "Age",
meta: {
rowSpan: 2,
},
}),
columnHelper.group({
header: "More Info",
columns: [
columnHelper.accessor("visits", {
header: () => <span>Visits</span>,
}),
columnHelper.accessor("status", {
header: "Status",
}),
columnHelper.accessor("progress", {
header: "Profile Progress",
}),
],
}),
],
}),
];
export default function FlexibleHeaderTable() {
const [data, setData] = React.useState(() => [...defaultData]);
const rerender = React.useReducer(() => ({}), {})[1];
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
});
return (
<div className="p-2">
<table style={{ borderCollapse: 'collapse', width: '100%', border: '1px solid #e5e7eb' }}>
<thead>
{table.getHeaderGroups().map((headerGroup) => (
<tr key={headerGroup.id} >
{headerGroup.headers.map((header) => {
const columnRelativeDepth = header.depth - header.column.depth;
if (
columnRelativeDepth > 1
) {
return null;
}
let rowSpan = 1;
if (header.isPlaceholder) {
const leafs = header.getLeafHeaders();
rowSpan = leafs[leafs.length - 1].depth - header.depth;
}
return (
<th
key={header.id}
colSpan={header.colSpan}
rowSpan={rowSpan}
style={{
padding: '12px',
textAlign: 'left',
fontSize: '0.75rem',
fontWeight: 'medium',
color: '#6b7280',
textTransform: 'uppercase',
letterSpacing: '0.05em',
backgroundColor: '#f9fafb',
border: '1px solid #e5e7eb',
}}
>
{flexRender(
header.column.columnDef.header,
header.getContext()
)}
</th>
);
})}
</tr>
))}
</thead>
<tbody>
{table.getRowModel().rows.map((row) => (
<tr key={row.id}>
{row.getVisibleCells().map((cell) => (
<td key={cell.id} style={{
padding: '12px',
whiteSpace: 'nowrap',
fontSize: '0.875rem',
color: '#6b7280',
border: '1px solid #e5e7eb',
}}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</td>
))}
</tr>
))}
</tbody>
</table>
</div>
);
} |
Beta Was this translation helpful? Give feedback.
-
Repo: https://github.com/jacksonkasi1/FlexiSheet |
Beta Was this translation helpful? Give feedback.
-
Hey everyone! Just solved this issue and packaged it into a tiny npm library. It's really simple to use — you can rowspan both the header and footer, with table and non-table tr/th elements. Check it out!:
|
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Hi folks 👋
I'm having a bit of trouble understanding how
rowSpan
works for headers, especially when working with header groups.The column group demo renders header groups by looping over

table.getHeaderGroups()
, creating a<tr>
for each group, looping over group headers and returningnull
inside<th>
when the headerisPlaceholder
value istrue
, which results in empty header cells depending on the structureIdeally it should look like (at least in my use case)

To achieve that I'd have to
<th>
with a value of the depth found<th>
for the headers that are already "covered" by the rowSpan of the parentDoes someone know how to achieve this in a simpler way ?
Couldn't it possible to manually declare a rowSpan in columnDef, which would be taken into account when building the groups ?
I found here that header supports
rowSpan
, but I'm not sure how it's calculated, it always seem to be 0.What do you think ?
Edit :
I managed to make it work by adding a custom
rowSpan
in column meta in combination with the following render logic :But it feels kinda wrong and displays some header twice if you don't supply a
rowSpan
when needed.Here's a codesandbox if you want to tinker with it.
Beta Was this translation helpful? Give feedback.
All reactions