ReactHustle

How to Create React Table Sticky Headers with TailwindCSS

Jasser Mark Arioste

Jasser Mark Arioste

How to Create React Table Sticky Headers with TailwindCSS

Hello, hustler! In this tutorial, you'll learn how to create react table sticky headers and columns with TailwindCSS.  We'll also use NextJS as our react framework as it's one of the frameworks recommended by the react documentation.

Final Output #

Here's the final output of the react table with a sticky header. We're not using any table libraries, only TailwindCSS to add classes.

React Table Sticky Header Tutorial Final Output

Installing TailwindCSS #

In this tutorial, we are using NextJS with TailwindCSS. If you want to follow along from scratch, I already created a NextJS starter template with TailwindCSS preinstalled. We just need to run the following command to install it:

npx create-next-app -e https://github.com/jmarioste/next-tailwind-starter-2 react-table-sticky-header-tutorial

After download and installation, you can run the following command to start the local dev server:

cd react-table-sticky-header-tutorial && yarn dev

If you're using a different framework like Vite or are using an existing NextJS project, make sure you check the tailwind docs on how to install it to your specific framework. 

Creating the <Table/> Component #

Let's create a table component without any data. First, create the file, components/Table.tsx and copy the code below.

// components/Table.tsx
const Table = () => {
  return (
    <table className="w-full relative">
      <thead className="sticky top-0 bg-indigo-500">
        <tr>
          <th className="text-left border text-indigo-50 p-2">ID</th>
          <th className="text-left border text-indigo-50 p-2">Email</th>
          <th className="text-left border text-indigo-50 p-2">Last Name</th>
          <th className="text-left border text-indigo-50 p-2">First Name</th>
        </tr>
      </thead>
      <tbody className="z-0">
        
      </tbody>
    </table>
  );
};
export default Table;
12345678910111213141516171819

Explanation:

In line 4: we use w-full class so that the table takes the full width of its parent element. We also use relative class to make sure that the sticky class works.

In line 5: we use sticky top-0 classes to make the table header stickied to the top of the parent element. We also change the background using bg-indigo-500 otherwise we'll be able to see the table rows. 

Next, let's use it in pages/index.tsx so that we can see the output:

// pages/index.tsx
import type { NextPage } from "next";
import Table from "../components/Table";

const Home: NextPage = () => {
  return (
    <div className="container mx-auto">
      <h1 className="text-2xl">React Table Sticky Header </h1>
      <div className="max-h-[400px] overflow-y-auto">
        <Table />
      </div>
    </div>
  );
};

export default Home;
12345678910111213141516

Explanation:

In line 9, we wrap the <Table/> component and use max-h-[400px] class to set the maxHeight to 400px.

After that, you'll see something like this as the output:

React Table Sticky Header after step 1.

At this point, the sticky header should already work. We only need to add some data.

Rendering Some Data #

For example purposes, I like to use the usehooks-ts library to fetch some data. First, let's install it using the following command:

#yarn
yarn add usehooks-ts

#npm
npm i usehooks-ts
12345

The usehooks library contains some really useful custom hooks and one of them is the useFetch() hook. This hook simplifies fetching data since we don't have to use a useEffect and useState hook.

All right, let's modify the <Table/> component as shown below. I've also added comments so make sure you don't skip them.

// components/Table.tsx
import { useFetch } from "usehooks-ts";

// lines 6-16. we're using dummyjson.com api for getting users data
// since I'm using typescript, we create the type for the data we're expecting
type GetUsersResult = {
  users: {
    id: number;
    firstName: string;
    lastName: string;
    email: string;
  }[];
  total: number;
  skip: number;
  limit: number;
};

const Table = () => {
  // line 21. use the dummyjson.com endpoint and limit it to 15 users.
  // select the fields that we need for this table
  const endpoint =
    "https://dummyjson.com/users?limit=15&&select=firstName,lastName,email";
  const { data } = useFetch<GetUsersResult>(endpoint);
  return (
    <table className="w-full relative">
      <thead className="sticky top-0 bg-indigo-500">
        <tr>
          <th className="text-left border text-indigo-50 p-2">ID</th>
          <th className="text-left border text-indigo-50 p-2">Email</th>
          <th className="text-left border text-indigo-50 p-2">Last Name</th>
          <th className="text-left border text-indigo-50 p-2">First Name</th>
        </tr>
      </thead>
      <tbody className="z-0">
        {/* render the data */}
        {data?.users?.map((user) => {
          return (
            <tr key={user.id} className="bg-indigo-50 odd:bg-white">
              <td className="text-left p-2">{user.id}</td>
              <td className="text-left p-2">{user.email}</td>
              <td className="text-left p-2">{user.firstName}</td>
              <td className="text-left p-2">{user.lastName}</td>
            </tr>
          );
        })}
      </tbody>
    </table>
  );
};

export default Table;
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051

Refactoring <th/> and <td/> #

We have some duplicate code in our component. Let's create custom <Th/> and <Td/> component so that it already contains the classNames.

// components/Table.tsx
import { useFetch } from "usehooks-ts";
import { ComponentPropsWithRef } from "react";

// ... omitted for brevity

export default Table;

const Th = (props: ComponentPropsWithRef<"th">) => {
  return <th {...props} className="text-left border text-indigo-50 p-2" />;
};
const Td = (props: ComponentPropsWithRef<"td">) => {
  return <td {...props} className="border p-2 " />;
};
1234567891011121314

We can now replace our th and td elements with the custom components:

// components/Table.tsx
//...
const Table = () => {
  const endpoint =
    "https://dummyjson.com/users?limit=15&&select=firstName,lastName,email";
  const { data } = useFetch<GetUsersResult>(endpoint);
  return (
    <table className="w-full relative">
      <thead className="sticky top-0 bg-indigo-500">
        <tr>
          <Th>ID</Th>
          <Th>Email</Th>
          <Th>Last Name</Th>
          <Th>First Name</Th>
        </tr>
      </thead>
      <tbody className="z-0">
        {/* render the data */}
        {data?.users?.map((user) => {
          return (
            <tr key={user.id} className="bg-indigo-50 odd:bg-white">
              <Td>{user.id}</Td>
              <Td>{user.email}</Td>
              <Td>{user.firstName}</Td>
              <Td>{user.lastName}</Td>
            </tr>
          );
        })}
      </tbody>
    </table>
  );
};
1234567891011121314151617181920212223242526272829303132

That's basically it!

Full Code and Demo #

The full code can be accessed at GitHub: jmarioste/react-table-sticky-header-tutorial.

You can play with the Demo on Stackblitz: React Table Sticky Header

Conclusion #

You learned how to create a react table with a sticky header by only using TailwindCSS classes. 

If you like this tutorial, please leave a like or share this article. For future tutorials like this, please subscribe to our newsletter or follow me on Twitter.

Resources #

Credits: Image by Makalu from Pixabay

Share this post!

Related Posts

Disclaimer

This content may contain links to products, software and services. Please assume all such links are affiliate links which may result in my earning commissions and fees.
As an Amazon Associate, I earn from qualifying purchases. This means that whenever you buy a product on Amazon from a link on our site, we receive a small percentage of its price at no extra cost to you. This helps us continue to provide valuable content and reviews to you. Thank you for your support!
Donate to ReactHustle