I have a table using @tanstack/react-table
and styled with ShadCN Table Component with a few adjustments for the sticky functionality and ShadCN Tooltip Component
I have all of the Thead
(th
) components within a TableHeader
(thead
) sticky to the top. So when a user scrolls a table vertically, the Thead
(th
) sticks to the top.
I have the first columns TableCell
‘s (td
) sticky to the left. So when a user scrolls the table horizontally, that column’s data sticks to the left.
The problem is, that to make this work, I need to add a z-index
‘s across various elements, so Tooltips either get stuck behind Thead
‘s or other TableCell
‘s. So, not really sure how to have both the sticky Thead
& TableCell
along with Tooltips
‘s
Here is a StackBlitz Environment to recreate the issue if you want to play around with it?
Tanstack Column
const Header: React.FC<{ title: string }> = ({ title }) => (
<div className="font-bold min-w-[300px] bg-slate-300 h-16 flex justify-center items-center">
{title}
</div>
);
//////
{
accessorFn: (row) => `${row.firstName} ${row.lastName}`,
id: 'fullName',
header: () => <Header title="Name" />,
cell: (info) => {
return (
<div className="font-bold min-w-[300px]">
<TooltipProvider>
<Tooltip defaultOpen={info.row.index === 0}>
<TooltipTrigger asChild>
<span>{info.getValue()}</span>
</TooltipTrigger>
<TooltipContent className="max-w-[200px] w-full min-w-[150px]">
<p>This</p>
<p>Is</p>
<p>A</p>
<p>Tooltip</p>
<p>Test</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
);
},
footer: (props) => props.column.id,
meta: {
sticky: true,
stickyLeft: true,
},
},
table.tsx
import { cn } from '@/lib/utils';
import { type ColumnMeta } from '@tanstack/react-table';
import * as React from 'react';
export interface TableProps extends React.HTMLAttributes<HTMLTableElement> {
tableClassName?: string;
}
const Table = React.forwardRef<HTMLTableElement, TableProps>(({ className, tableClassName, ...props }, ref) => (
<div className={cn('w-full overflow-x-auto data-table-container border rounded-md', className)}>
<table ref={ref} className={cn('w-full caption-bottom text-sm relative data-table', tableClassName)} {...props} />
</div>
));
Table.displayName = 'Table';
const TableHeader = React.forwardRef<HTMLTableSectionElement, React.HTMLAttributes<HTMLTableSectionElement>>(({ className, ...props }, ref) => (
<thead ref={ref} className={cn(className)} {...props} />
));
TableHeader.displayName = 'TableHeader';
const TableBody = React.forwardRef<HTMLTableSectionElement, React.HTMLAttributes<HTMLTableSectionElement>>(({ className, ...props }, ref) => (
<tbody ref={ref} className={cn(className)} {...props} />
));
TableBody.displayName = 'TableBody';
const TableFooter = React.forwardRef<HTMLTableSectionElement, React.HTMLAttributes<HTMLTableSectionElement>>(({ className, ...props }, ref) => (
<tfoot ref={ref} className={cn('bg-primary font-medium text-primary-foreground', className)} {...props} />
));
TableFooter.displayName = 'TableFooter';
export interface TableRowProps extends React.HTMLAttributes<HTMLTableRowElement> {
isLoading?: boolean;
noHover?: boolean;
}
const TableRow = React.forwardRef<HTMLTableRowElement, TableRowProps>(({ className, isLoading, noHover, ...props }, ref) => (
<tr
ref={ref}
className={cn('transition-colors', !isLoading && '[&>*]:data-[state=selected]:bg-muted', !noHover && !isLoading && '[&>*]:hover:bg-muted', className)}
{...props}
/>
));
TableRow.displayName = 'TableRow';
export interface TableHeadProps<TData, TValue> extends React.ThHTMLAttributes<HTMLTableCellElement>, ColumnMeta<TData, TValue> {
isLoading?: boolean;
last?: boolean;
}
const TableHead = React.forwardRef<HTMLTableCellElement, TableHeadProps<any, any>>(
({ className, sticky, stickyLeft, stickyRight, isLoading, last, ...props }, ref) => {
return (
<th
ref={ref}
className={cn(
'h-10 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px] relative',
sticky && 'sticky -top-[1px] z-[7] bg-background',
stickyLeft && 'sticky -left-[1px] z-[9] bg-background',
stickyRight && 'sticky -right-[1px] z-[9] bg-background',
stickyRight && last && 'z-[8]',
isLoading && 'min-w-[100px]',
className,
)}
{...props}
/>
);
},
);
TableHead.displayName = 'TableHead';
export interface TableCellProps<TData, TValue> extends React.TdHTMLAttributes<HTMLTableCellElement>, ColumnMeta<TData, TValue> {}
const TableCell = React.forwardRef<HTMLTableCellElement, TableCellProps<any, any>>(({ className, sticky, stickyLeft, stickyRight, ...props }, ref) => (
<td
ref={ref}
className={cn(
'table-cell px-4 py-2 align-middle [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px] relative',
sticky && 'sticky -right-[1px] z-[5] bg-background',
stickyLeft && 'sticky -left-[1px] z-[6] bg-background',
stickyRight && 'sticky -right-[1px] z-[5] bg-background',
className,
)}
{...props}
/>
));
TableCell.displayName = 'TableCell';
const TableCaption = React.forwardRef<HTMLTableCaptionElement, React.HTMLAttributes<HTMLTableCaptionElement>>(({ className, ...props }, ref) => (
<caption ref={ref} className={cn('mt-4 text-sm text-muted-foreground', className)} {...props} />
));
TableCaption.displayName = 'TableCaption';
export { Table, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption };
Any help would be greatly appreciated!
2
Answers
In case anyone comes across a similar issue, while using shadcn-ui/radix-ui/react-tooltip.
You can wrap a the tooltip
Content
in aPortal
StackBlitz Fork
You could look at increasing the
z-index
of the cell when it is hovered, since this is presumably when the tooltip shows:Though you will still get some janky behavior such as when the cell being hovered is partially behind a different cell that should be on top:
See a Stackblitz fork of this solution
Otherwise, you could consider using Portals to render the tooltip element outside the table such that it would be easier to organize elements in the z-stack, though I’m not sure how compatible the Shadcn/Radix Tooltip component would be with this approach.