InicioBlogsProyectosMarcadoresPoemasImágenesSobre

How to make a Dropdown Component in Next.js using TailwindCSS

In this blog, we will make a functional react component that you can easily integrate in our next.js project.

15th May, 2024
10 min read
NextJs
ReactJs
How to make a Dropdown Component in Next.js using TailwindCSS

Let’s start with a fresh Next.js project. We are going to use bun as our package manager (because it’s super fast, even faster pnpm). In your vs code terminal, navigate to your desired folder and run:

bun create next-app my-dropdown

Now navigate to the folder my-dropdown. Inside the src folder we will create a new components folder, where we will create our first dropdown component.

So create a file named dropdown.tsx and start putting in some stuffs. Let’s create the basic structure of the dropdown component:

"use client";

interface DropdownProps {}

export function Dropdown({}: DropdownProps) {
  return <div>Dropdown</div>;
}

We want to make sure that the component is type-safe. So let’s create an interface and a DropdownItem type. Create a new folder called types inside src, then create a new index.ts file.

export type DropdownItem = {
  label: string;
  value: string;
};

Now in our dropdown component again -

"use client";
import { DropdownItem } from "@/types";

interface DropdownProps {
  options: DropdownItem[];
}

export function Dropdown({ options }: DropdownProps) {
  return <div>Dropdown</div>;
}

Now that we have the basic structure, let’s put in some design -

"use client";
import { DropdownItem } from "@/types";

interface DropdownProps {
  options: DropdownItem[];
  name: string;
}

export function Dropdown({ options }: DropdownProps) {
  return (
    <div>
      <div className="relative inline-block text-left">
        <div>
          <button
            type="button"
            className="inline-flex w-full justify-center gap-x-1.5 rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50"
            id="menu-button"
            aria-expanded="true"
            aria-haspopup="true"
          >
            Options
            <svg
              className="-mr-1 h-5 w-5 text-gray-400"
              viewBox="0 0 20 20"
              fill="currentColor"
              aria-hidden="true"
            >
              <path
                fill-rule="evenodd"
                d="M5.23 7.21a.75.75 0 011.06.02L10 11.168l3.71-3.938a.75.75 0 111.08 1.04l-4.25 4.5a.75.75 0 01-1.08 0l-4.25-4.5a.75.75 0 01.02-1.06z"
                clip-rule="evenodd"
              />
            </svg>
          </button>
        </div>
        <div
          className="absolute right-0 z-10 mt-2 w-56 origin-top-right rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none"
          role="menu"
          aria-orientation="vertical"
          aria-labelledby="menu-button"
          tabIndex={-1}
        >
          <div className="py-1" role="none">
            <a
              href="#"
              className="text-gray-700 block px-4 py-2 text-sm"
              role="menuitem"
              tabIndex={-1}
              id="menu-item-0"
            >
              Account settings
            </a>
            <a
              href="#"
              className="text-gray-700 block px-4 py-2 text-sm"
              role="menuitem"
              tabIndex={-1}
              id="menu-item-1"
            >
              Support
            </a>
            <a
              href="#"
              className="text-gray-700 block px-4 py-2 text-sm"
              role="menuitem"
              tabIndex={-1}
              id="menu-item-2"
            >
              License
            </a>
            <form method="POST" action="#" role="none">
              <button
                type="submit"
                className="text-gray-700 block w-full px-4 py-2 text-left text-sm"
                role="menuitem"
                tabIndex={-1}
                id="menu-item-3"
              >
                Sign out
              </button>
            </form>
          </div>
        </div>
      </div>
    </div>
  );
}

Now go to page.tsx and render your component and test the work so far:

import { Dropdown } from "@/components/dropdown";

export default function Home() {
  const options = [
    { label: "Account settings", value: "account" },
    { label: "Support", value: "support" },
    { label: "License", value: "license" },
  ];
  return (
    <div className="flex justify-center items-center h-screen">
      <Dropdown name="Options" options={options} />
    </div>
  );
}

Run the application with -

bun dev

You should now be able to see the current state:

Untitled.png

Great work so far! Now let’s my our dropdown dynamic. Navigate to the dropdown component and do the following:

"use client";
import { DropdownItem } from "@/types";
import Link from "next/link";
import { useState } from "react";

interface DropdownProps {
  options: DropdownItem[];
  name: string;
}

export function Dropdown({ options, name }: DropdownProps) {
  const [isOpen, setIsOpen] = useState(false);
  return (
    <div>
      <div className="relative inline-block text-left">
        <div>
          <button
            onClick={() => setIsOpen(!isOpen)}
            type="button"
            className="inline-flex w-full justify-center gap-x-1.5 rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50"
            id="menu-button"
            aria-expanded="true"
            aria-haspopup="true"
          >
            {name}
            <svg
              className="-mr-1 h-5 w-5 text-gray-400"
              viewBox="0 0 20 20"
              fill="currentColor"
              aria-hidden="true"
            >
              <path
                fill-rule="evenodd"
                d="M5.23 7.21a.75.75 0 011.06.02L10 11.168l3.71-3.938a.75.75 0 111.08 1.04l-4.25 4.5a.75.75 0 01-1.08 0l-4.25-4.5a.75.75 0 01.02-1.06z"
                clip-rule="evenodd"
              />
            </svg>
          </button>
        </div>
        {isOpen && (
          <div
            className="absolute right-0 z-10 mt-2 w-56 origin-top-right rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none"
            role="menu"
            aria-orientation="vertical"
            aria-labelledby="menu-button"
            tabIndex={-1}
          >
            <div className="py-1" role="none">
              {options.map((option) => (
                <Link
                  key={option.value}
                  href={`/${option.value}`}
                  className="text-gray-700 block px-4 py-2 text-sm"
                  role="menuitem"
                  tabIndex={-1}
                  id="menu-item-0"
                >
                  {option.label}
                </Link>
              ))}
            </div>
          </div>
        )}
      </div>
    </div>
  );
}

And that’s it. You can now define your own options and dropdown name and test the component out!

Here’s the final result:

ScreenRecording2024-05-15at7.24.54PM-ezgif.com-video-to-gif-converter.gif

Thank you for reading this far! Good luck!