mui table style manage hook
11/18/2025
import { SxProps, Theme } from '@mui/material';
import { useMemo } from 'react';
interface UseTableStylesParams {
/** Index of the column to stick (null for no sticky) */
stickyColumn?: number | null;
/** Whether to stick the header */
stickyHeader?: boolean;
/** Header background color */
headerBackgroundColor?: string;
/** Header text color */
headerTextColor?: string;
/** Body background color */
bodyBackgroundColor?: string;
/** Border color */
borderColor?: string;
/** Hover background color */
hoverBackgroundColor?: string;
/** TableContainer max height */
maxHeight?: string;
/** TableContainer min height */
minHeight?: string;
// Custom sx
customSx?: {
container?: SxProps<Theme>;
table?: SxProps<Theme>;
tableHead?: SxProps<Theme>;
headerCell?: SxProps<Theme>;
bodyCell?: SxProps<Theme>;
tableRow?: SxProps<Theme>;
};
}
interface TableStyles {
container: SxProps<Theme>;
table: SxProps<Theme>;
tableHead: SxProps<Theme>;
headerCell: (index: number) => SxProps<Theme>;
bodyCell: (index: number) => SxProps<Theme>;
tableRow: SxProps<Theme>;
}
const DEFAULT_PARAMS: Required<Omit<UseTableStylesParams, 'customSx'>> = {
stickyColumn: null,
stickyHeader: true,
headerBackgroundColor: '#F2F4F6',
headerTextColor: '#5C5F66',
bodyBackgroundColor: '#FFFFFF',
borderColor: '#e5e5e5',
hoverBackgroundColor: '#def6e5',
maxHeight: '50vh',
minHeight: '30vh',
};
/**
* Hook for using table styles consistently
* @param params - Style configuration parameters
* @returns Style objects to apply to table components
*
* @example
* const styles = useTableStyles({
* stickyColumn: 0,
* stickyHeader: true,
* customSx: {
* table: {
* minWidth: 1000,
* th: { height: 40 },
* },
* container: {
* maxHeight: '70vh',
* },
* }
* });
*
* <TableContainer sx={styles.container}>
* <Table sx={styles.table}>
* ...
* </Table>
* </TableContainer>
*/
export const useTableStyles = (params: UseTableStylesParams = {}): TableStyles => {
const config = { ...DEFAULT_PARAMS, ...params };
const { customSx = {} } = params;
const styles = useMemo<TableStyles>(
() => ({
container: {
maxHeight: config.maxHeight,
minHeight: config.minHeight,
overflowX: 'auto',
borderRadius: 'none',
'&::-webkit-scrollbar': {
width: '0.85rem',
height: '0.85rem',
},
'&::-webkit-scrollbar-thumb': {
width: '0.5rem',
background: '#fff',
borderRadius: '.5rem',
cursor: 'pointer',
},
'&::-webkit-scrollbar-track': {
background: '#efefef',
},
'&::-webkit-scrollbar-corner': {
background: '#efefef',
},
...customSx.container,
} as SxProps<Theme>,
table: {
borderCollapse: 'unset',
borderTop: `1px solid ${config.borderColor}`,
borderLeft: `1px solid ${config.borderColor}`,
...customSx.table,
} as SxProps<Theme>,
tableHead: {
position: config.stickyHeader ? 'sticky' : 'static',
top: '0',
zIndex: 3,
backgroundColor: config.headerBackgroundColor,
overflow: 'inherit',
...customSx.tableHead,
} as SxProps<Theme>,
headerCell: (index: number) => {
const isStickyColumn = config.stickyColumn !== null && config.stickyColumn >= index;
return {
borderRight: `1px solid ${config.borderColor}`,
borderBottom: `1px solid ${config.borderColor}`,
boxSizing: 'border-box' as const,
backgroundColor: config.headerBackgroundColor,
color: config.headerTextColor,
fontSize: '.825rem',
whiteSpace: 'nowrap',
...(isStickyColumn && {
position: 'sticky' as const,
left: 0,
zIndex: 4,
backgroundColor: config.headerBackgroundColor,
}),
...customSx.headerCell,
} as SxProps<Theme>;
},
bodyCell: (index: number) => {
const isStickyColumn = config.stickyColumn !== null && config.stickyColumn >= index;
return {
borderBottom: `1px solid ${config.borderColor}`,
borderRight: `1px solid ${config.borderColor}`,
boxSizing: 'border-box' as const,
whiteSpace: 'nowrap',
...(isStickyColumn && {
position: 'sticky' as const,
left: 0,
zIndex: 1,
backgroundColor: config.bodyBackgroundColor,
}),
...customSx.bodyCell,
} as SxProps<Theme>;
},
tableRow: {
'&:hover': {
background: config.hoverBackgroundColor,
cursor: 'pointer',
},
...customSx.tableRow,
} as SxProps<Theme>,
}),
[config, customSx]
);
return styles;
};
in the jsx
<TableContainer component='div' sx={tableStyles.container}>
<Table sx={tableStyles.table}>
<TableHead sx={tableStyles.tableHead}>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header, index) => (
<TableCell key={header.id} sx={tableStyles.headerCell(index)}>
{flexRender(header.column.columnDef.header, header.getContext())}
</TableCell>
))}
</TableRow>
))}
</TableHead>
<TableBody>
{table.getRowModel().rows.map((row) => (
<TableRow key={row.id} sx={tableStyles.tableRow} onClick={(event) => handleRowClick(event, row.original)}>
{row.getVisibleCells().map((cell, index) => (
<TableCell key={cell.id} sx={tableStyles.bodyCell(index)}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</TableCell>
))}
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
