BorderedAccordion

accordions

Expand/collapse panels for FAQs and dense settings.

It is a React component (often forwardRef) styled with Tailwind utility classes so you can drop it into layouts or compose it with other kit pieces.

blogs/components/accordions/BorderedAccordion.tsx

Preview

Our plans start at $9.99/mo for standard.
24/7 priority email support for all users.

Source

import React, { useState, forwardRef } from "react";
import { ChevronDown } from "lucide-react";

export interface AccordionItem {
  title: React.ReactNode;
  content: React.ReactNode;
}

export interface AccordionProps extends React.HTMLAttributes<HTMLDivElement> {
  items?: AccordionItem[];
}

export const BorderedAccordion = forwardRef<HTMLDivElement, AccordionProps>(
  (
    {
      items = [
        {
          title: "Pricing",
          content: "Our plans start at $9.99/mo for standard.",
        },
        {
          title: "Support",
          content: "24/7 priority email support for all users.",
        },
      ],
      className = "",
      ...props
    },
    ref,
  ) => {
    const [openIdx, setOpenIdx] = useState<number | null>(0);

    return (
      <div ref={ref} className={`w-72 space-y-2 ${className}`} {...props}>
        {items.map((item, idx) => {
          const isOpen = openIdx === idx;
          return (
            <div
              key={idx}
              className={`border rounded-lg transition-colors duration-200 ${
                isOpen
                  ? "border-blue-200 bg-blue-50/30"
                  : "border-zinc-200 bg-white"
              }`}
            >
              <button
                onClick={() => setOpenIdx(isOpen ? null : idx)}
                className="w-full px-4 py-3 flex items-center justify-between text-left text-sm font-medium text-zinc-800 focus:outline-none"
              >
                {item.title}
                <ChevronDown
                  className={`w-4 h-4 text-zinc-400 transition-transform duration-300 ${
                    isOpen ? "rotate-180" : ""
                  }`}
                />
              </button>
              <div
                className={`overflow-hidden transition-all duration-300 ease-in-out px-4 ${
                  isOpen
                    ? "max-h-40 opacity-100 pb-3"
                    : "max-h-0 opacity-0 pb-0"
                }`}
              >
                <div className="text-sm text-zinc-600">{item.content}</div>
              </div>
            </div>
          );
        })}
      </div>
    );
  },
);
BorderedAccordion.displayName = "BorderedAccordion";