Creating Elegant Clickable Table Rows in Next.js

Srujan GurramSrujan Gurram
3 min read

The Challenge

We've all been there - you're building a data-heavy application with tables, and you want users to be able to click anywhere on a row to navigate to a detail page. But wait, what about those action buttons in the last column? How do you make the entire row clickable without breaking the functionality of those buttons?

Let's explore a clean, reusable solution that solves this common UI challenge using two simple components:

export const LinkBox: React.FC<
  React.HTMLAttributes<HTMLDivElement> & {
    element?: ElementType;
  }
> = ({ children, className, element: Element = "div", ...props }) => {
  return (
    <Element
      {...props}
      className={cn(
        "relative",
        "[&_a:not(.link-overlay)]:relative [&_a:not(.link-overlay)]:z-10",
        "[&_button]:relative [&_button]:z-10",
        className,
      )}
    >
      {children}
    </Element>
  );
};

The LinkOverlay Component

export const LinkOverlay: React.FC<LinkOverlayProps> = ({
  children,
  href,
  className,
  target,
  ...props
}) => {
  return (
    <Link
      href={href}
      target={target}
      rel={target === "_blank" ? "noopener noreferrer" : ""}
      {...props}
      className={cn(
        "link-overlay static before:absolute before:inset-0 before:z-0",
        className,
      )}
    >
      {children}
    </Link>
  );
};

โœจ How It Works

This solution uses some CSS magic to create a clickable overlay while preserving the functionality of nested interactive elements:

  1. LinkBox creates a positioning context and ensures any buttons or links inside have a higher z-index

  2. LinkOverlay uses a pseudo-element that stretches across the entire parent container

  3. When a user clicks anywhere in the row, they navigate to the detail page

  4. When they click on a button, the button's click event takes precedence

๐Ÿš€ Putting It All Together

Here's how you can implement clickable table rows:

<TableBody>
  {items.map((item) => (
    <LinkBox key={item.id} element={TableRow}>
      <TableCell className="font-medium">
        <LinkOverlay href={`/items/${item.id}`}>
          {item.name}
        </LinkOverlay>
      </TableCell>

      <TableCell>{item.category}</TableCell>
      <TableCell>{item.status}</TableCell>

      <TableCell className="text-right">
        <div className="flex justify-end gap-2">
          <Button
            variant="outline"
            size="sm"
            onClick={() => handleEditClick(item)}
          >
            Edit
          </Button>
          <Button
            variant="outline"
            size="sm"
            onClick={() => handleDeleteClick(item)}
          >
            Delete
          </Button>
        </div>
      </TableCell>
    </LinkBox>
  ))}
</TableBody>

๐Ÿ” The Magic in the Details

What makes this approach special:

  • Flexibility: The LinkBox can wrap any element, not just table rows

  • Simplicity: The API is straightforward and easy to implement

  • Accessibility: Maintains proper semantic HTML structure

  • Reusability: Can be used across your entire application

๐Ÿ› ๏ธ Implementation Tips

  1. Place the LinkOverlay in the most logical cell (usually the name or title)

  2. Make sure your buttons have proper event handlers

  3. Use the element prop to specify what HTML element the LinkBox should render as

๐ŸŽ‰ Conclusion

This pattern solves a common UI challenge in a clean, reusable way. No more hacky solutions or compromising on user experience! Your tables can now have fully clickable rows while maintaining the functionality of nested buttons.

Next time you're building a data table, give this approach a try - your users (and fellow developers) will thank you! ๐Ÿ‘

1
Subscribe to my newsletter

Read articles from Srujan Gurram directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Srujan Gurram
Srujan Gurram

I am a Student and a Web Developer