# FAQ Numbered Editorial — React FAQ Section

> Magazine-style numbered questions in a serif: a rule draws across the open row, the label rolls from Read to Close and a short answer sits beside the long one.

- Category: [FAQs](https://pixelpapa.com/components/faqs.md)
- Tags: faq, editorial, serif, numbered, accordion
- Dependencies: lucide-react, motion (plus React and Tailwind CSS v4)
- Last updated: 2026-11-13
- Live preview: https://pixelpapa.com/components/faqs/faq-numbered-editorial

## Install

```bash
npx shadcn@latest add "https://pixelpapa.com/r/faq-numbered-editorial.json"
```

The file is written to `components/pixelpapa/faq-numbered-editorial.tsx`. To install manually, run `npm install lucide-react motion` and copy the source below into your project. It imports `cn` from `@/lib/utils`, which every shadcn project has.

## Source

```tsx
"use client";

import { ArrowUpRight, Plus } from "lucide-react";
import { AnimatePresence, motion, useReducedMotion } from "motion/react";
import { useId, useState } from "react";
import { cn } from "@/lib/utils";

type EditorialFaq = {
  question: string;
  /** One-line verdict, set large beside the full answer. */
  short: string;
  answer: string;
};

const defaultItems: EditorialFaq[] = [
  {
    question: "What does a typical engagement look like?",
    short: "Six to ten weeks, one team, no hand-offs.",
    answer:
      "We open with a one-week discovery sprint, then design and build in two-week cycles with a working demo every second Friday. The people you meet on the first call are the people doing the work.",
  },
  {
    question: "How do you price a project?",
    short: "A fixed fee per phase, agreed up front.",
    answer:
      "Every phase has a fixed price and a written scope. If the scope changes we re-quote before doing the work, so an invoice never surprises you. Most projects land between €28k and €90k.",
  },
  {
    question: "Do you work with early-stage startups?",
    short: "Yes. About half our clients are pre-Series A.",
    answer:
      "For teams under ten people we run a compressed four-week format: brand, product design and a launch site, scoped to what you can actually ship before your next raise.",
  },
  {
    question: "Who owns the work when we are done?",
    short: "You do. All of it.",
    answer:
      "Design files, source code and font licences transfer to you with the final invoice. We only ask for permission to show the work in our portfolio, and you are free to say no.",
  },
  {
    question: "Can you work alongside our in-house team?",
    short: "Gladly. We prefer it.",
    answer:
      "We join your stand-ups, commit to your repository and document as we go, so nothing lives only in our heads when the engagement ends.",
  },
  {
    question: "When could you start?",
    short: "Next opening: the week of 18 January.",
    answer:
      "We take on two projects at a time. A 20% deposit holds your slot and is fully refundable until two weeks before kickoff.",
  },
];

const ease = [0.22, 1, 0.36, 1] as const;

export default function FaqNumberedEditorial({ items = defaultItems }: { items?: EditorialFaq[] }) {
  const [open, setOpen] = useState<number | null>(0);
  const reduce = useReducedMotion() ?? false;
  const uid = useId();
  const spring = reduce ? { duration: 0 } : { type: "spring" as const, stiffness: 220, damping: 30 };

  return (
    <section className="bg-background px-6 py-20 sm:py-24">
      <div className="mx-auto max-w-6xl">
        <header className="grid gap-6 pb-10 lg:grid-cols-12 lg:items-end lg:gap-8">
          <div className="lg:col-span-8">
            <p className="font-mono text-xs tracking-[0.18em] text-muted-foreground uppercase">
              Maren Studio <span className="mx-2 text-border">/</span> Frequently asked
            </p>
            <h2 className="mt-5 font-serif text-5xl leading-[1.02] tracking-tight text-balance text-foreground sm:text-7xl">
              Asked often, <em className="text-primary">answered plainly.</em>
            </h2>
          </div>
          <p className="max-w-sm text-pretty text-muted-foreground lg:col-span-4 lg:pb-2">
            Six things founders ask on the first call. If yours is missing, write to us. A partner replies, not a form.
          </p>
        </header>

        <ol className="border-t border-foreground">
          {items.map((item, i) => {
            const isOpen = open === i;
            return (
              <motion.li
                key={item.question}
                initial={reduce ? { opacity: 0 } : { opacity: 0, y: 20 }}
                whileInView={{ opacity: 1, y: 0 }}
                viewport={{ once: true }}
                transition={{ duration: 0.5, delay: reduce ? 0 : i * 0.05, ease }}
                className="relative border-b border-border"
              >
                {/* Ink rule that draws across the open row. */}
                <motion.span
                  aria-hidden
                  initial={false}
                  animate={{ scaleX: isOpen ? 1 : 0 }}
                  transition={{ duration: reduce ? 0 : 0.6, ease }}
                  className="absolute inset-x-0 -bottom-px h-px origin-left bg-foreground"
                />
                <h3>
                  <button
                    type="button"
                    id={`${uid}-q-${i}`}
                    aria-expanded={isOpen}
                    aria-controls={`${uid}-a-${i}`}
                    onClick={() => setOpen(isOpen ? null : i)}
                    className="group grid w-full grid-cols-[2.75rem_minmax(0,1fr)_auto] items-baseline gap-x-4 py-6 text-left outline-none focus-visible:ring-2 focus-visible:ring-ring sm:py-7 lg:grid-cols-12 lg:gap-x-8"
                  >
                    <span
                      className={cn(
                        "font-serif text-3xl tabular-nums transition-colors duration-300 sm:text-4xl lg:col-span-2 lg:text-5xl",
                        isOpen ? "text-primary" : "text-muted-foreground/45 group-hover:text-foreground",
                      )}
                    >
                      {String(i + 1).padStart(2, "0")}
                    </span>
                    <span className="font-serif text-2xl leading-snug text-balance text-foreground transition-transform duration-300 ease-out group-hover:translate-x-1.5 motion-reduce:transform-none sm:text-3xl lg:col-span-8">
                      {item.question}
                    </span>
                    <span className="flex items-center justify-end gap-3 self-center lg:col-span-2">
                      <span className="relative hidden h-4 w-11 overflow-hidden font-mono text-[11px] leading-4 tracking-[0.16em] text-muted-foreground uppercase sm:block">
                        <AnimatePresence mode="popLayout" initial={false}>
                          <motion.span
                            key={isOpen ? "close" : "read"}
                            initial={{ y: reduce ? 0 : "100%", opacity: 0 }}
                            animate={{ y: 0, opacity: 1 }}
                            exit={{ y: reduce ? 0 : "-100%", opacity: 0 }}
                            transition={{ duration: reduce ? 0 : 0.3, ease }}
                            className="absolute inset-0 text-right"
                          >
                            {isOpen ? "Close" : "Read"}
                          </motion.span>
                        </AnimatePresence>
                      </span>
                      <motion.span
                        aria-hidden
                        initial={false}
                        animate={{ rotate: isOpen ? 45 : 0 }}
                        transition={spring}
                        className={cn(
                          "flex size-9 items-center justify-center rounded-full border transition-colors duration-300",
                          isOpen
                            ? "border-foreground bg-foreground text-background"
                            : "border-border text-foreground group-hover:border-foreground",
                        )}
                      >
                        <Plus className="size-4" strokeWidth={1.5} />
                      </motion.span>
                    </span>
                  </button>
                </h3>

                <motion.div
                  id={`${uid}-a-${i}`}
                  role="region"
                  aria-labelledby={`${uid}-q-${i}`}
                  aria-hidden={!isOpen}
                  initial={false}
                  animate={{ height: isOpen ? "auto" : 0 }}
                  transition={spring}
                  className="overflow-hidden"
                >
                  <div className="grid gap-x-8 gap-y-4 pb-9 pl-[3.75rem] lg:grid-cols-12 lg:pl-0">
                    <motion.p
                      initial={false}
                      animate={isOpen ? { opacity: 1, y: 0 } : { opacity: 0, y: 12 }}
                      transition={{ duration: reduce ? 0 : 0.45, delay: isOpen && !reduce ? 0.08 : 0, ease }}
                      className="font-serif text-xl leading-snug text-balance text-primary italic sm:text-2xl lg:col-span-4 lg:col-start-3"
                    >
                      {item.short}
                    </motion.p>
                    <motion.p
                      initial={false}
                      animate={isOpen ? { opacity: 1, y: 0 } : { opacity: 0, y: 12 }}
                      transition={{ duration: reduce ? 0 : 0.45, delay: isOpen && !reduce ? 0.16 : 0, ease }}
                      className="text-base leading-relaxed text-pretty text-muted-foreground lg:col-span-5"
                    >
                      {item.answer}
                    </motion.p>
                  </div>
                </motion.div>
              </motion.li>
            );
          })}
        </ol>

        <div className="mt-8 flex flex-wrap items-center justify-between gap-4">
          <p className="font-mono text-xs tracking-[0.16em] text-muted-foreground uppercase">
            {String(items.length).padStart(2, "0")} questions <span className="mx-2 text-border">/</span> Revised
            November 2026
          </p>
          <a
            href="#"
            className="group inline-flex items-center gap-2 font-serif text-xl text-foreground italic underline decoration-border underline-offset-[6px] transition-colors hover:decoration-primary"
          >
            Book an intro call
            <ArrowUpRight className="size-4 transition-transform duration-300 group-hover:translate-x-0.5 group-hover:-translate-y-0.5" />
          </a>
        </div>
      </div>
    </section>
  );
}
```

## AI prompt

```text
Add the "FAQ Numbered Editorial" component from PixelPapa UI to this project.

What it is: Magazine-style numbered questions in a serif: a rule draws across the open row, the label rolls from Read to Close and a short answer sits beside the long one.
Category: FAQs. Depends on: lucide-react, motion.

1. Install it with the shadcn CLI:
   npx shadcn@latest add "https://pixelpapa.com/r/faq-numbered-editorial.json"
   (If the CLI is unavailable, fetch that URL, write files[0].content to components/pixelpapa/faq-numbered-editorial.tsx and install the listed dependencies.)
2. Import it from "@/components/pixelpapa/faq-numbered-editorial" and place it where I describe below.
3. Keep its animation timings and accessibility attributes. Replace the demo copy and data with content that fits my product, and use my existing theme tokens (bg-background, text-foreground, bg-primary, border-border) rather than hard-coded colours.

Where to use it: <describe the page and position here>
```

## More faqs

- [FAQ Chat Style](https://pixelpapa.com/components/faqs/faq-chat-style.md) (Pro): A support chat window where tapping a suggested question sends it as a bubble, shows a typing indicator, then types the answer in word by word.
- [FAQ Grid Cards](https://pixelpapa.com/components/faqs/faq-grid-cards.md): A grid of question cards that flip over in 3D on a spring to show the answer on the back; cards peek on hover and a control flips the whole deck in a wave.
- [FAQ Search Filter](https://pixelpapa.com/components/faqs/faq-search-filter.md) (Pro): Type to filter questions live: matches are highlighted in both question and answer, rows reflow with layout animation and topic chips update their counts.
- [FAQ Smooth Accordion](https://pixelpapa.com/components/faqs/faq-smooth-accordion.md): Single-column accordion whose panels spring open to their natural height while the plus icon folds into a minus and the open card lifts.
- [FAQ Two Column Categories](https://pixelpapa.com/components/faqs/faq-two-column-categories.md) (Pro): Category tabs on the left with a sliding highlight; the question list on the right swaps with a stagger and each row expands in place.
- [FAQ With Contact Card](https://pixelpapa.com/components/faqs/faq-with-contact-card.md) (Pro): Accordion beside a sticky support card; marking an answer as unhelpful makes the card hop, pulse and rewrite itself around that question.
