Web InnoventixFreeCode

Big Questions Accordion

Original · free

A large-type editorial FAQ accordion.

byWeb InnoventixReact + Tailwind
accordionbigquestionsaccordions
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-big-questions.json
accordion-big-questions.tsx
"use client";

import { useId, useRef, useState, type KeyboardEvent } from "react";

type QA = {
  q: string;
  a: string;
  tag: string;
};

const FAQS: QA[] = [
  {
    tag: "Timeline",
    q: "How long before we see real results?",
    a: "Most sites feel faster and cleaner within the first two weeks. Meaningful search movement — new rankings, more qualified traffic — typically lands between month three and month six. We work in shippable increments, so you never wait months to see the first proof it is working.",
  },
  {
    tag: "Pricing",
    q: "What does a project actually cost?",
    a: "We scope every engagement to the outcome you need, not an hourly meter. A focused landing page rebuild starts around a fixed project fee; ongoing growth work runs on a flat monthly retainer. You get the full number, in writing, before we begin — no surprise line items and no lock-in.",
  },
  {
    tag: "Process",
    q: "Do I have to hand over my whole website?",
    a: "No. We work inside your existing stack and access only what the task requires. You keep ownership of every account, domain, and repository. If we build something new, it ships to you documented and portable — the work is yours the day we finish.",
  },
  {
    tag: "Scope",
    q: "Can you take over a project someone else started?",
    a: "Often, yes. We begin with a short audit of what exists, tell you honestly what is worth keeping, and give you a plan to finish or replace the rest. Inherited codebases are one of the most common ways clients reach us — you are not the first, and we will not judge the last team's shortcuts.",
  },
  {
    tag: "Support",
    q: "What happens after the site goes live?",
    a: "Launch is a milestone, not the finish line. Every build ships with a thirty-day fix window at no charge, and you can move onto a maintenance plan for monitoring, updates, and monthly improvements. If you would rather run it in-house, we hand off clean documentation so your team is never stuck.",
  },
];

export default function AccordionBigQuestions() {
  const [open, setOpen] = useState<number | null>(0);
  const baseId = useId();
  const btnRefs = useRef<(HTMLButtonElement | null)[]>([]);

  const toggle = (i: number) => setOpen((cur) => (cur === i ? null : i));

  const onKeyDown = (e: KeyboardEvent, i: number) => {
    const last = FAQS.length - 1;
    let next: number | null = null;
    if (e.key === "ArrowDown") next = i === last ? 0 : i + 1;
    else if (e.key === "ArrowUp") next = i === 0 ? last : i - 1;
    else if (e.key === "Home") next = 0;
    else if (e.key === "End") next = last;
    if (next !== null) {
      e.preventDefault();
      btnRefs.current[next]?.focus();
    }
  };

  return (
    <section className="relative w-full overflow-hidden bg-white px-6 py-24 text-slate-900 sm:py-32 dark:bg-slate-950 dark:text-slate-100">
      <style>{`
        @keyframes bq-reveal-abq {
          from { opacity: 0; transform: translateY(6px); }
          to { opacity: 1; transform: translateY(0); }
        }
        .bq-panel-abq[data-open="true"] .bq-inner-abq {
          animation: bq-reveal-abq 320ms cubic-bezier(0.22, 1, 0.36, 1) both;
        }
        @media (prefers-reduced-motion: reduce) {
          .bq-panel-abq[data-open="true"] .bq-inner-abq { animation: none; }
          .bq-chev-abq { transition: none !important; }
          .bq-grid-abq { transition: none !important; }
        }
      `}</style>

      {/* soft ambient accents */}
      <div
        aria-hidden
        className="pointer-events-none absolute -left-32 top-0 h-96 w-96 rounded-full bg-indigo-200/40 blur-3xl dark:bg-indigo-500/10"
      />
      <div
        aria-hidden
        className="pointer-events-none absolute -right-24 bottom-0 h-80 w-80 rounded-full bg-violet-200/40 blur-3xl dark:bg-violet-500/10"
      />

      <div className="relative mx-auto max-w-4xl">
        <div className="mb-14 max-w-2xl sm:mb-20">
          <span className="inline-flex items-center gap-2 rounded-full border border-slate-200 bg-slate-50 px-3 py-1 text-xs font-medium uppercase tracking-widest text-slate-500 dark:border-slate-800 dark:bg-slate-900 dark:text-slate-400">
            <span className="h-1.5 w-1.5 rounded-full bg-indigo-500" />
            Frequently asked
          </span>
          <h2 className="mt-6 text-4xl font-semibold tracking-tight text-slate-900 sm:text-6xl dark:text-white">
            The big questions,{" "}
            <span className="text-indigo-600 dark:text-indigo-400">
              answered plainly.
            </span>
          </h2>
          <p className="mt-5 text-lg leading-relaxed text-slate-600 dark:text-slate-400">
            No fine print, no runaround. Here is exactly how we work, what it
            costs, and what you own when it is done.
          </p>
        </div>

        <div className="divide-y divide-slate-200 border-y border-slate-200 dark:divide-slate-800 dark:border-slate-800">
          {FAQS.map((item, i) => {
            const isOpen = open === i;
            const panelId = `${baseId}-panel-${i}`;
            const headerId = `${baseId}-header-${i}`;
            return (
              <div key={item.q} className="group">
                <h3>
                  <button
                    ref={(el) => {
                      btnRefs.current[i] = el;
                    }}
                    id={headerId}
                    type="button"
                    aria-expanded={isOpen}
                    aria-controls={panelId}
                    onClick={() => toggle(i)}
                    onKeyDown={(e) => onKeyDown(e, i)}
                    className="flex w-full items-start gap-5 py-7 text-left outline-none transition-colors sm:py-9 focus-visible:ring-2 focus-visible:ring-indigo-500/70 focus-visible:ring-offset-4 focus-visible:ring-offset-white dark:focus-visible:ring-offset-slate-950"
                  >
                    <span className="mt-2 hidden w-10 shrink-0 font-mono text-sm text-slate-400 tabular-nums sm:block dark:text-slate-600">
                      {String(i + 1).padStart(2, "0")}
                    </span>
                    <span className="flex-1">
                      <span className="mb-2 block text-xs font-semibold uppercase tracking-widest text-indigo-600 dark:text-indigo-400">
                        {item.tag}
                      </span>
                      <span
                        className={`block text-2xl font-medium leading-snug tracking-tight transition-colors sm:text-3xl ${
                          isOpen
                            ? "text-slate-900 dark:text-white"
                            : "text-slate-700 group-hover:text-slate-900 dark:text-slate-300 dark:group-hover:text-white"
                        }`}
                      >
                        {item.q}
                      </span>
                    </span>
                    <span
                      aria-hidden
                      className={`bq-chev-abq mt-2 flex h-9 w-9 shrink-0 items-center justify-center rounded-full border transition-all duration-300 ${
                        isOpen
                          ? "rotate-45 border-indigo-500 bg-indigo-500 text-white dark:border-indigo-400 dark:bg-indigo-500"
                          : "border-slate-300 text-slate-500 group-hover:border-slate-400 group-hover:text-slate-700 dark:border-slate-700 dark:text-slate-400 dark:group-hover:border-slate-500 dark:group-hover:text-slate-200"
                      }`}
                    >
                      <svg
                        viewBox="0 0 24 24"
                        fill="none"
                        stroke="currentColor"
                        strokeWidth="2"
                        strokeLinecap="round"
                        className="h-4 w-4"
                      >
                        <path d="M12 5v14M5 12h14" />
                      </svg>
                    </span>
                  </button>
                </h3>

                <div
                  id={panelId}
                  role="region"
                  aria-labelledby={headerId}
                  hidden={!isOpen}
                  data-open={isOpen}
                  className="bq-panel-abq bq-grid-abq grid transition-[grid-template-rows] duration-300 ease-out"
                  style={{ gridTemplateRows: isOpen ? "1fr" : "0fr" }}
                >
                  <div className="overflow-hidden">
                    <div className="bq-inner-abq pb-9 pl-0 pr-4 sm:pl-[3.75rem]">
                      <p className="max-w-2xl text-lg leading-relaxed text-slate-600 dark:text-slate-400">
                        {item.a}
                      </p>
                    </div>
                  </div>
                </div>
              </div>
            );
          })}
        </div>

        <div className="mt-14 flex flex-col items-start gap-4 rounded-2xl bg-slate-50 p-8 sm:flex-row sm:items-center sm:justify-between dark:bg-slate-900">
          <div>
            <p className="text-lg font-medium text-slate-900 dark:text-white">
              Still have a question?
            </p>
            <p className="mt-1 text-slate-600 dark:text-slate-400">
              Ask us directly — we reply to every message within one business
              day.
            </p>
          </div>
          <a
            href="#contact"
            className="inline-flex shrink-0 items-center gap-2 rounded-full bg-indigo-600 px-6 py-3 text-sm font-semibold text-white transition-colors hover:bg-indigo-500 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500/70 focus-visible:ring-offset-2 focus-visible:ring-offset-slate-50 dark:focus-visible:ring-offset-slate-900"
          >
            Talk to us
            <svg
              viewBox="0 0 24 24"
              fill="none"
              stroke="currentColor"
              strokeWidth="2"
              strokeLinecap="round"
              strokeLinejoin="round"
              className="h-4 w-4"
              aria-hidden
            >
              <path d="M5 12h14M13 6l6 6-6 6" />
            </svg>
          </a>
        </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 →