Introduction
Have you ever seen one of those impressive sticky headers that have a base color until they become "sticky", at which point they change color to aid visibility?
Inspiration
The initial idea for this post came from Spotify's table, which has a header with a transparent background which fades to gray once it becomes pinned to the top.
I don't have any footage of it, but check the video below to get a better idea of what I mean.
For the solution we will be using the Intersection Observer API to track when the table header intersects with the top of the viewport.
The inspiration for the solution came from here. I just converted it to a React hook to consume in a Next app and added some Tailwind classes to pretty it up a bit.
Setup
For this demo we will be using shadcn/ui, which has a great-looking Data Table
component.
The setup is pretty straightforward. Run through the install instructions. Then in the root folder of your app run the following commands to install the required dependencies for the Data Table component:
pnpx shadcn-ui add table
pnpm add @tanstack/react-table
This will install some basic components into the @/components/ui/table
directory, such as TableHead
and TableBody
. These components are used to build the full table.
We import these component in our complete table component, Table
, as per the shadcn/ui docs.
Intersection Observer Hook
As stated above, we essentially want to change the color of the table's header when it intersects with the top of the page. We do this by creating a useIntersectionObserver
hook which returns a ref to the table header. When the header intersects with the top of the page, we toggle an is-pinned
class to the header.
import { useEffect, useRef } from "react";
export const useIntersectionObserver = () => {
const ref = useRef(null);
useEffect(() => {
const node = ref?.current;
if (!node || typeof IntersectionObserver !== "function") {
return;
}
const observer = new IntersectionObserver(
([entry]) => {
console.log(entry);
if (entry)
entry.target.classList.toggle(
"is-pinned",
entry.intersectionRatio < 1
);
},
{ threshold: [1], rootMargin: "10px" }
);
observer.observe(node);
return () => {
observer.disconnect();
};
}, []);
return [ref];
};
Add ref to Table header
We now move back to the Table
component and add the ref to the TableHeader
component, also adding a few Tailwind classes to make the header sticky and to transition the color change.
<TableHeader ref={ref} className="sticky -top-[1px] transition-colors">
Add CSS
Finally, we add the CSS to change the color of the header when it becomes pinned to the top of the page.
.is-pinned {
@apply bg-gray-800 shadow-white;
}
Conclusion
And that's it! We now have a table header that changes color when it becomes pinned to the top of the page. You can see the full code here