DEV Community

Cover image for Build an Expandable / Collapsible Data Table with 2 shadcn/ui Components
Marc Seitz
Marc Seitz

Posted on

Build an Expandable / Collapsible Data Table with 2 shadcn/ui Components

What you will find in this article?

In the modern digital age, the way we present data to users significantly impacts their experience. A responsive, well-designed table not only provides clarity but also enhances user engagement. This article will guide you on building a data table that expands and reveals more data rows and is an essential component for swift data viewing.

Of course, there is Tanstack table, but it's overkill for most of the simpler usecases. Therefore, we are using shadcn/ui (only two components).

We will use Next.js and Tailwindcss ensuring our table looks stylish and performs optimally.

cool gif

Papermark - Your open-source DocSend alternative.

Before we begin, allow me to introduce Papermark. It's an open-source project for securely sharing documents with built-in real-time, page-by-page analytics.

If you're loving the concept, I'd greatly appreciate a star! And, as always, your thoughts and feedback in the comments section are highly valued ❤️

https://github.com/mfts/papermark

Papermark Links Table

Setup the project

It's time to set up our project environment. We'll start with a Next.js app and then layer in our responsive table components.

Set up tea

It's a good idea to have a package manager handy, like tea. It'll handle your development environment and simplify your (programming) life!



sh <(curl https://tea.xyz)

# --- OR ---
# using brew
brew install teaxyz/pkgs/tea-cli


Enter fullscreen mode Exit fullscreen mode

tea frees you to focus on your code, as it takes care of installing node, npm, vercel and any other packages you may need. The best part is, tea installs all packages in a dedicated directory (default: ~/.tea), keeping your system files neat and tidy.

Set up Next.js with TypeScript and Tailwindcss

We will use create-next-app to generate a new Next.js project. We will also be using TypeScript and Tailwind CSS, so make sure to select those options when prompted.



npx create-next-app

# ---
# you'll be asked the following prompts
What is your project named?  my-app
Would you like to add TypeScript with this project?  Y/N
# select `Y` for typescript
Would you like to use ESLint with this project?  Y/N
# select `Y` for ESLint
Would you like to use Tailwind CSS with this project? Y/N
# select `Y` for Tailwind CSS
Would you like to use the `src/ directory` with this project? Y/N
# select `N` for `src/` directory
What import alias would you like configured? `@/*`
# enter `@/*` for import alias


Enter fullscreen mode Exit fullscreen mode

Building the Expandable Table

Having set up the environment, we can now focus on crafting our responsive, expandable table.

In this section, we will utilize the components from shadcn/ui to create a responsive and expandable table. shadcn/ui provides us with modular and highly customizable components, making it easier to develop user-friendly interfaces.

#1 Table Structure and Styling

Using the Table component from shadcn/ui, we'll design a table that's both responsive and visually appealing.

Head over to ui.shadcn.com and grab the <Table /> component.



# run this command in your Next.js project
npx shadcn-ui@latest add table


Enter fullscreen mode Exit fullscreen mode


// components/links-table.tsx
import {
  Table, TableBody, TableCell, TableHead, TableHeader, TableRow,
} from "@/components/ui/table";

export default function LinksTable() {
  const links = [...] // these are link objects, we'll get there later

  return (
    <div className="w-full sm:p-4">
      <h2 className="p-4">All links</h2>
      <div className="rounded-md sm:border">
        <Table>
          <TableHeader>
            <TableRow>
              <TableHead className="font-medium">Name</TableHead>
              <TableHead className="font-medium">Link</TableHead>
              <TableHead className="font-medium">Views</TableHead>
            </TableRow>
          </TableHeader>
          <TableBody>
            {links ? (
              links.map((link) => (
                <TableRow key={link.id}>
                  <TableCell>{link.name}</TableCell>
                  <TableCell>{link.id}</TableCell>
                  <TableCell>{link.viewCount}</TableCell>
                </TableRow>
              ))
            ) : null}
          </TableBody>
        </Table>
      </div>
    </div>
  );
}


Enter fullscreen mode Exit fullscreen mode

#2 Table Interactivity

The logic behind the table's expandability is crucial. We need to ensure smooth transitions and ease of access to data.

For this, we are using the second shadcn/ui component: <Collapsible />.

Head over to ui.shadcn.com and grab the Collapsible component.



# run this command in your Next.js project
npx shadcn-ui@latest add collapsible


Enter fullscreen mode Exit fullscreen mode

We are creating a new component that will contain our expanded data of visitors for each link



// components/links-visitors.tsx
import { TableCell, TableRow } from "@/components/ui/table";
import { Gauge } from "@/components/ui/gauge";

export default function LinksVisitors({linkId}: {linkId: string) {
  const visitors = [...] // these are the visitor objects based on the linkId

  return (
    <>
      {visitors ? ( 
        visitors.map((visitor) => (
          <TableRow key={visitor.id}>
            <TableCell>{visitor.name}</TableCell>
            <TableCell>{visitor.totalDuration}</TableCell>
            <TableCell>
              <Gauge value={view.completionRate} />
            </TableCell>
          </TableRow>
        ))
      ) : null}
    </>
  );
}


Enter fullscreen mode Exit fullscreen mode

Small excursion ✨

Now, you might ask yourself, what is this <Gauge /> component?

It's a beautiful Vercel UI component that indicates a status from 0 to 100. I recreated it with Tailwindcss and made it open-source 🎉

Gauge Component

More detail and source code in this tweet: https://twitter.com/mfts0/status/1691110169319559169


Ok back to our expanding table. Let's add the <Collapsible /> component to our first component, the links-table



// components/links-table.tsx
import {
  Table, TableBody, TableCell, TableHead, TableHeader, TableRow,
} from "@/components/ui/table";

import {
  Collapsible, CollapsibleContent, CollapsibleTrigger,
} from "@/components/ui/collapsible";

import LinksVisitors from "@/components/links-visitors.tsx"

export default function LinksTable() {
  const links = [...] // these are link objects, we'll get there later

  return (
    <div className="w-full sm:p-4">
      <h2 className="p-4">All links</h2>
      <div className="rounded-md sm:border">
        <Table>
          <TableHeader>
            <TableRow>
              <TableHead className="font-medium">Name</TableHead>
              <TableHead className="font-medium">Link</TableHead>
              <TableHead className="font-medium">Views</TableHead>
            </TableRow>
          </TableHeader>
          <TableBody>
            {links ? (
              links.map((link) => (
                <Collapsible key={link.id} asChild>
                  <>
                    <TableRow>
                      <TableCell>{link.name}</TableCell>
                      <TableCell>{link.id}</TableCell>
                      <TableCell>{link.viewCount}
                        <CollapsibleTrigger asChild>
                          <div>{link.viewCount}</div>
                        </CollapsibleTrigger>
                      </TableCell>
                    </TableRow>
                    <CollapsibleContent asChild>
                      <LinksVisitors linkId={link.id} />
                    </CollapsibleContent>
                  </>
                </Collapsible>
              ))
            ) : null}
          </TableBody>
        </Table>
      </div>
    </div>
  );
}


Enter fullscreen mode Exit fullscreen mode

It's important to note that we have to wrap the TableRow in a fragment (<></>) and use the property asChild on the <Collapsible /> component in order for everything to function properly.

And there you have it, an impeccably designed, responsive expandable (or collapsible) table, all set to elevate your data presentation!

Collapsed table view

Collapsed Table View

Expanded table view

Expanded Table View

Conclusion

You've successfully built a responsive, expandable/collapsible table for quick data viewing using Next.js and Tailwindcss. While this article provides a foundational guide, you can further enhance the table, adding sorting functionalities, filters, and more!

Thank you for joining me on this journey. I'm Marc, an advocate for open-source software. I'm passionately working on papermark.io - your go-to open-source alternative to DocSend.

Help me out!

If you found this guide enlightening and have a clearer vision for building simple data tables, please give us a star! Your feedback in the comments propels us forward ❤️

https://github.com/mfts/papermark

Cat please

Top comments (9)

Collapse
 
shnai0 profile image
Iuliia Shnai

Is this live on Papermark already?

Collapse
 
mfts profile image
Marc Seitz

It's live on Papermark 🎉

Collapse
 
mvesto profile image
Matthew

Nice post! What did you use to record / edit the videos and gifs?

Collapse
 
mfts profile image
Marc Seitz • Edited

Screen.studio

Collapse
 
espennilsen profile image
Espen Nilsen

Thanks for sharing this, I get the following error when using fragments: Error: React.Children.only expected to receive a single React element child.

Solved this by using div, but that screws up the table for me obviously.

Collapse
 
espennilsen profile image
Espen Nilsen

It was Next.js server component issue, changed it to a client component and it now works as intended.

Collapse
 
kaitakami profile image
Kai Takami

You just saved me so much time @espennilsen

Thread Thread
 
mfts profile image
Marc Seitz

Love it!

Collapse
 
siddu_srinivasmamidala_c profile image
siddu srinivas mamidala

can i know where is this live & its repo?