Web InnoventixFreeCode

Bordered Accordion

Original · free

Bordered card-style accordion panels with rounded corners.

byWeb InnoventixReact + Tailwind
accordionborderedaccordions
Open

Copy prompt gives Claude Code, Cursor or v0 a ready-to-paste prompt that recreates this exact component.

npx shadcn@latest add https://webinnoventix.com/r/accordion-bordered.json
accordion-bordered.tsx
"use client";

import { useId, useState } from "react";

type FaqItem = {
  q: string;
  a: string;
};

const ITEMS: FaqItem[] = [
  {
    q: "Do you build on a template or from scratch?",
    a: "Every build starts from a blank canvas mapped to your goals. We design the layout, write the markup, and hand-tune the CSS so nothing ships that you did not ask for. No bloated page-builder plugins, no leftover demo content, no theme you have to fight for the next three years.",
  },
  {
    q: "How long until my site goes live?",
    a: "A focused marketing site takes two to four weeks from kickoff to launch. Larger builds with custom integrations run four to eight. You get a week-by-week schedule on day one, and we review real progress together at the end of every sprint so there are no surprises at the finish line.",
  },
  {
    q: "Will the site actually rank on Google?",
    a: "Technical SEO is baked in, not bolted on. Clean semantic HTML, fast Core Web Vitals, structured data, and a sensible internal-linking plan ship with the build. Rankings still depend on content and competition, but we make sure the foundation never holds you back.",
  },
  {
    q: "What happens after launch?",
    a: "You own every file, credential, and asset the day we hand off. If you want us to stay, we offer monthly care plans for updates, monitoring, and content. If you would rather run it yourself, we record a short walkthrough so your team can edit with confidence.",
  },
  {
    q: "Can you work with my existing brand and copy?",
    a: "Absolutely. Bring your logo, palette, and voice and we design around them. If any of that is missing or dated, we can refine it as part of the project so the finished site feels deliberate from the first pixel to the footer.",
  },
];

function Chevron() {
  return (
    <svg
      viewBox="0 0 24 24"
      aria-hidden="true"
      className="h-5 w-5 shrink-0 text-indigo-500 transition-transform duration-300 ease-out group-aria-expanded:rotate-180 dark:text-indigo-400"
    >
      <path
        d="M6 9l6 6 6-6"
        fill="none"
        stroke="currentColor"
        strokeWidth="2"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
    </svg>
  );
}

export default function AccordionBordered() {
  const [openIndex, setOpenIndex] = useState<number | null>(0);
  const baseId = useId();

  const toggle = (index: number) => {
    setOpenIndex((current) => (current === index ? null : index));
  };

  return (
    <section className="relative w-full bg-slate-50 px-4 py-20 sm:px-6 sm:py-28 dark:bg-slate-950">
      <style>{`
        @keyframes accbd-fade-in {
          from { opacity: 0; transform: translateY(-4px); }
          to { opacity: 1; transform: translateY(0); }
        }
        @media (prefers-reduced-motion: reduce) {
          .accbd-panel { transition: none !important; }
          .accbd-body { animation: none !important; }
        }
      `}</style>

      <div className="mx-auto max-w-2xl">
        <div className="mb-10 text-center">
          <span className="inline-flex items-center rounded-full border border-indigo-200 bg-indigo-50 px-3 py-1 text-xs font-semibold uppercase tracking-wide text-indigo-700 dark:border-indigo-500/30 dark:bg-indigo-500/10 dark:text-indigo-300">
            FAQ
          </span>
          <h2 className="mt-4 text-3xl font-bold tracking-tight text-slate-900 sm:text-4xl dark:text-white">
            Questions worth answering
          </h2>
          <p className="mt-3 text-base text-slate-600 dark:text-slate-400">
            Everything clients ask before we start a build, in plain language.
          </p>
        </div>

        <div className="flex flex-col gap-3">
          {ITEMS.map((item, index) => {
            const isOpen = openIndex === index;
            const headerId = `${baseId}-header-${index}`;
            const panelId = `${baseId}-panel-${index}`;

            return (
              <div
                key={item.q}
                className={`overflow-hidden rounded-2xl border bg-white transition-colors duration-300 dark:bg-slate-900 ${
                  isOpen
                    ? "border-indigo-300 shadow-sm shadow-indigo-100 dark:border-indigo-500/50 dark:shadow-none"
                    : "border-slate-200 hover:border-slate-300 dark:border-slate-800 dark:hover:border-slate-700"
                }`}
              >
                <h3>
                  <button
                    type="button"
                    id={headerId}
                    aria-expanded={isOpen}
                    aria-controls={panelId}
                    onClick={() => toggle(index)}
                    className="group flex w-full items-center justify-between gap-4 px-5 py-4 text-left focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white sm:px-6 dark:focus-visible:ring-offset-slate-900"
                  >
                    <span className="text-base font-semibold text-slate-900 sm:text-lg dark:text-white">
                      {item.q}
                    </span>
                    <Chevron />
                  </button>
                </h3>

                <div
                  id={panelId}
                  role="region"
                  aria-labelledby={headerId}
                  className="accbd-panel grid transition-[grid-template-rows] duration-300 ease-out"
                  style={{ gridTemplateRows: isOpen ? "1fr" : "0fr" }}
                >
                  <div className="overflow-hidden">
                    <p className="accbd-body px-5 pb-5 text-sm leading-relaxed text-slate-600 sm:px-6 sm:text-base dark:text-slate-400" style={{ animation: isOpen ? "accbd-fade-in 0.35s ease-out" : "none" }}>
                      {item.a}
                    </p>
                  </div>
                </div>
              </div>
            );
          })}
        </div>
      </div>
    </section>
  );
}

Dependencies

None (React + Tailwind only).

Licence

Built by Web Innoventix. Free for personal and commercial use, no attribution required.

Built by Web Innoventix

Need a custom interface, a full website, or SEO that gets you cited by AI? We design, build and rank it end to end.

Get a free quote

Similar components

Browse all →