0

I'm working on a custom layout using MUI where I want to display exam data in a compact scrollable table. The idea is to keep the Exam Name and Percentage columns static (not scrollable), while making the attendance data (Date, Day, Status) vertically scrollable.

Here’s the structure I’m aiming for:

  • Exam name (T1, T2, T3...) – static
  • Percentage – static
  • Attendance records for each exam – scrollable (with Date, Day, Status)
  • Each exam block (T1, T2...) is separated, with scrollable data in the center
  • The status is shown using MUI <Chip />s

Everything works except the header columns (Date, Day, Status) are not properly aligned with their respective data rows inside the scrollable area.

What I've tried:

  • Gave fixed widths to each column container
  • Used display: flex with nested Boxes for layout
  • Used overflowY: auto for the scrollable part
  • Tried padding/margin adjustments to sync widths
  • Tried wrapping header and data rows in shared Box containers

Problem:

Despite all that, the scrollable data columns do not align properly with the fixed headers above them — the cells shift slightly depending on padding/scroll/zoom.

Desired Output:

T1 ───────| Date | Day | Status | Percentage
          |------|-----|--------|
          | ...  | ... |  ✅     |
          | ...  | ... |  ❌     | ← scrollable vertically
T2 ───────| ...  | ... | ...    | ...
...

Tools:

  • React
  • MUI v5
  • CSS Modules (optional)

Question:

How can I ensure perfect alignment between the header columns and the scrollable data columns below them? Is there a better way to structure this layout using MUI Grid, Table, or Flex?

Any guidance or working example would be greatly appreciated!

CODE:

import {
  Box,
  Table,
  TableBody,
  TableCell,
  TableContainer,
  TableHead,
  TableRow,
  Paper,
  Chip,
  Typography,
} from '@mui/material';

const AttendanceTable = ({ data }) => {
  return (
    <Box
      sx={{
        width: '100%',
        borderRadius: '16px',
        p: '1.5rem',
        bgcolor: 'background.default',
      }}
    >
      <Typography variant="h3" sx={{ mb: 2 }}>
        Attendance
      </Typography>
      <TableContainer
        component={Paper}
        sx={{
          minWidth: '800px',
          overflowY: 'auto',
          boxShadow: 'none',
          border: 'none',
          borderRadius: '9px',

          '&::-webkit-scrollbar': {
            width: '6px',
          },
          '&::-webkit-scrollbar-track': {
            background: 'transparent',
            borderRadius: '8px',
          },
          '&::-webkit-scrollbar-thumb': {
            backgroundColor: 'rgba(0,0,0,0.3)',
            borderRadius: '8px',
          },
          '&::-webkit-scrollbar-thumb:hover': {
            backgroundColor: 'rgba(0,0,0,0.4)',
          },

          scrollbarWidth: 'thin',
          scrollbarColor: 'rgba(0,0,0,0.3) transparent',
        }}
      >
        <Table
          size="small"
          sx={{
            '& td, & th': {
              border: 'none',
            },
          }}
        >
          <TableHead>
            <TableRow
              sx={{
                backgroundColor: 'background.paper',
                borderBottom: '2px solid',
                borderColor: 'divider',
              }}
            >
              {['Exam', 'Date', 'Day', 'Status', 'Percentage'].map(
                (heading, idx) => (
                  <TableCell
                    key={idx}
                    sx={{
                      textAlign: 'center',
                      fontWeight: 'bold',
                      py: 1.5,
                      px: 2,
                    }}
                  >
                    {heading}
                  </TableCell>
                )
              )}
            </TableRow>
          </TableHead>
          <TableBody>
            {data.exams.map((exam, examIndex) => {
              const total = exam.attendanceRecords.length;
              const present = exam.attendanceRecords.filter(
                (r) => r.status === 'Present'
              ).length;
              const percentage = ((present / total) * 100).toFixed(2);

              return (
                <TableRow
                  key={`exam-${examIndex}`}
                  sx={{
                    borderBottom: '2px solid',
                    borderColor: 'divider',
                  }}
                >
                  {/* Fixed Exam Column */}
                  <TableCell
                    sx={{
                      textAlign: 'center',
                      py: 1.5,
                      px: 2,
                      verticalAlign: 'top',
                      width: '90px',
                      fontWeight: '500',
                      whiteSpace: 'nowrap',
                    }}
                  >
                    {exam.exam}
                  </TableCell>

                  {/* Scrollable Date/Day/Status Block */}
                  <TableCell colSpan={3} sx={{ p: 0 }}>
                    <Box
                      sx={{
                        maxHeight: '180px',
                        overflowY: 'auto',
                        pr: 1,

                        '&::-webkit-scrollbar': {
                          width: '4px',
                        },
                        '&::-webkit-scrollbar-thumb': {
                          backgroundColor: 'rgba(0, 0, 0, 0.3)',
                          borderRadius: '4px',
                        },
                        scrollbarWidth: 'thin',
                        scrollbarColor: 'rgba(0,0,0,0.3) transparent',
                      }}
                    >
                      {exam.attendanceRecords.map((record, idx) => (
                        <TableRow
                          key={`record-${examIndex}-${idx}`}
                          sx={{
                            backgroundColor: 'background.paper',
                            borderBottom:
                              idx === exam.attendanceRecords.length - 1
                                ? 'none'
                                : '1px solid',
                            borderColor: 'divider',
                          }}
                        >
                          <TableCell
                            sx={{
                              textAlign: 'center',
                              py: 1.5,
                              px: 2,
                            }}
                          >
                            {record.date}
                          </TableCell>
                          <TableCell
                            sx={{
                              textAlign: 'center',
                              py: 1.5,
                              px: 2,
                            }}
                          >
                            {record.day}
                          </TableCell>
                          <TableCell
                            sx={{
                              textAlign: 'center',
                              py: 1.5,
                              px: 2,
                            }}
                          >
                            <Chip
                              label={record.status}
                              color={
                                record.status === 'Present'
                                  ? 'success'
                                  : 'error'
                              }
                              size="small"
                            />
                          </TableCell>
                        </TableRow>
                      ))}
                    </Box>
                  </TableCell>

                  {/* Fixed Percentage Column */}
                  <TableCell
                    sx={{
                      textAlign: 'center',
                      py: 1.5,
                      px: 2,
                      verticalAlign: 'top',
                      whiteSpace: 'nowrap',
                      width: '90px',
                      fontWeight: '500',
                    }}
                  >
                    {percentage}%
                  </TableCell>
                </TableRow>
              );
            })}
          </TableBody>
        </Table>
      </TableContainer>
    </Box>
  );
};

export default AttendanceTable;

Demo Data

export const demoSubjectAttendance = {
[![enter image description here][1]][1]
  exams: [
    {
      exam: 'T1',
      startDate: '2025-07-01',
      endDate: '2025-07-05',
      percentage: '66.67%', // 2/3
      attendanceRecords: [
        {
          date: '2025-07-01',
          day: 'Tuesday',
          status: 'Present',
        },
        {
          date: '2025-07-02',
          day: 'Wednesday',
          status: 'Absent',
        },
        {
          date: '2025-07-03',
          day: 'Thursday',
          status: 'Present',
        },
        {
          date: '2025-07-03',
          day: 'Thursday',
          status: 'Present',
        },
        {
          date: '2025-07-03',
          day: 'Thursday',
          status: 'Present',
        },
        {
          date: '2025-07-03',
          day: 'Thursday',
          status: 'Present',
        },
        {
          date: '2025-07-03',
          day: 'Thursday',
          status: 'Present',
        },
        {
          date: '2025-07-03',
          day: 'Thursday',
          status: 'Present',
        },
        {
          date: '2025-07-03',
          day: 'Thursday',
          status: 'Present',
        },
        {
          date: '2025-07-03',
          day: 'Thursday',
          status: 'Present',
        },
        {
          date: '2025-07-03',
          day: 'Thursday',
          status: 'Present',
        },
        {
          date: '2025-07-03',
          day: 'Thursday',
          status: 'Present',
        },
        {
          date: '2025-07-03',
          day: 'Thursday',
          status: 'Present',
        },
        {
          date: '2025-07-03',
          day: 'Thursday',
          status: 'Present',
        },
        {
          date: '2025-07-03',
          day: 'Thursday',
          status: 'Present',
        },
        {
          date: '2025-07-03',
          day: 'Thursday',
          status: 'Present',
        },
        {
          date: '2025-07-03',
          day: 'Thursday',
          status: 'Present',
        },
        {
          date: '2025-07-03',
          day: 'Thursday',
          status: 'Present',
        },
        {
          date: '2025-07-03',
          day: 'Thursday',
          status: 'Present',
        },
        {
          date: '2025-07-03',
          day: 'Thursday',
          status: 'Present',
        },
      ],
    },
    {
      exam: 'T2',
      startDate: '2025-08-01',
      endDate: '2025-08-05',
      percentage: '100.00%', // 3/3
      attendanceRecords: [
        {
          date: '2025-08-01',
          day: 'Friday',
          status: 'Present',
        },
        {
          date: '2025-08-02',
          day: 'Saturday',
          status: 'Present',
        },
        {
          date: '2025-08-03',
          day: 'Sunday',
          status: 'Present',
        },
      ],
    },
    {
      exam: 'T3',
      startDate: '2025-09-10',
      endDate: '2025-09-15',
      percentage: '66.67%', // 2/3
      attendanceRecords: [
        {
          date: '2025-09-10',
          day: 'Wednesday',
          status: 'Absent',
        },
        {
          date: '2025-09-11',
          day: 'Thursday',
          status: 'Present',
        },
        {
          date: '2025-09-12',
          day: 'Friday',
          status: 'Present',
        },
      ],
    },
  ],
};

UI Image

UI Image req

1 Answer 1

0

You could consider using a another table component to maintain alignment and enable a scrollbar.
Please check the demo for reference.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.