# Accordion Numbered Editorial — React Accordion Component

> A magazine-style accordion with oversized serif numerals, a rule that draws across the open row and a plus that folds into a minus.

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

## Install

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

The file is written to `components/pixelpapa/accordion-numbered-editorial.tsx`. To install manually, run `npm install 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 { AnimatePresence, motion } from "motion/react";
import { useId, useState } from "react";
import { cn } from "@/lib/utils";

type EditorialItem = { title: string; body: string; meta: [string, string][] };

type EditorialAccordionProps = {
  items: EditorialItem[];
  defaultOpen?: number | null;
  className?: string;
};

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

export function EditorialAccordion({ items, defaultOpen = 0, className }: EditorialAccordionProps) {
  const uid = useId();
  const [open, setOpen] = useState<number | null>(defaultOpen);

  return (
    <div className={cn("border-b border-border", className)}>
      {items.map((item, i) => {
        const isOpen = open === i;
        return (
          <div key={item.title} className="relative border-t border-border">
            <motion.span aria-hidden className="absolute -top-px left-0 h-px w-full origin-left bg-primary" initial={false} animate={{ scaleX: isOpen ? 1 : 0 }} transition={{ duration: 0.6, ease }} />
            <h3>
              <button
                type="button"
                aria-expanded={isOpen}
                aria-controls={`${uid}-${i}`}
                onClick={() => setOpen(isOpen ? null : i)}
                className="group flex w-full items-baseline gap-5 py-4 text-left outline-none focus-visible:bg-muted/60"
              >
                <span className={cn("w-12 shrink-0 font-serif text-3xl tabular-nums transition-colors duration-300", isOpen ? "text-primary italic" : "text-muted-foreground/50 group-hover:text-muted-foreground")}>
                  {String(i + 1).padStart(2, "0")}
                </span>
                <span className="flex-1 font-serif text-[26px] leading-tight text-foreground transition-transform duration-300 ease-out group-hover:translate-x-1.5">{item.title}</span>
                <span className="relative size-4 shrink-0 self-center text-foreground" aria-hidden>
                  <span className="absolute top-1/2 left-0 h-[1.5px] w-full -translate-y-1/2 bg-current" />
                  <span className={cn("absolute top-0 left-1/2 h-full w-[1.5px] -translate-x-1/2 bg-current transition-transform duration-300", isOpen && "rotate-90")} />
                </span>
              </button>
            </h3>
            <AnimatePresence initial={false}>
              {isOpen && (
                <motion.div id={`${uid}-${i}`} role="region" initial={{ height: 0 }} animate={{ height: "auto" }} exit={{ height: 0 }} transition={{ duration: 0.45, ease }} className="overflow-hidden">
                  <motion.div
                    initial={{ opacity: 0, y: 12 }}
                    animate={{ opacity: 1, y: 0, transition: { duration: 0.4, delay: 0.08 } }}
                    exit={{ opacity: 0, transition: { duration: 0.15 } }}
                    className="grid gap-6 pr-9 pb-6 pl-[68px] sm:grid-cols-[1fr_auto]"
                  >
                    <p className="max-w-sm text-sm leading-relaxed text-pretty text-muted-foreground">{item.body}</p>
                    <dl className="grid grid-cols-2 gap-x-6 gap-y-1 self-start text-xs sm:grid-cols-1">
                      {item.meta.map(([k, v]) => (
                        <div key={k} className="flex gap-2">
                          <dt className="w-16 tracking-wider text-muted-foreground/70 uppercase">{k}</dt>
                          <dd className="font-medium text-foreground">{v}</dd>
                        </div>
                      ))}
                    </dl>
                  </motion.div>
                </motion.div>
              )}
            </AnimatePresence>
          </div>
        );
      })}
    </div>
  );
}

const process: EditorialItem[] = [
  { title: "Listen before sketching", body: "Two weeks inside your team: support tickets, sales calls and the spreadsheet everyone is afraid to delete.", meta: [["Length", "2 weeks"], ["Output", "Field notes"]] },
  { title: "Name the one job", body: "We write a single sentence the product must be best at, then cut every idea that does not serve it.", meta: [["Length", "3 days"], ["Output", "Brief"]] },
  { title: "Prototype in the real thing", body: "No slide decks. You get a working build on your own data, updated every Friday.", meta: [["Length", "5 weeks"], ["Output", "Live build"]] },
  { title: "Hand over, stay close", body: "Documented components, a recorded walkthrough and ninety days of office hours.", meta: [["Length", "Ongoing"], ["Output", "Design system"]] },
];

export default function AccordionNumberedEditorialDemo() {
  return (
    <div className="h-[400px] w-full max-w-xl">
      <EditorialAccordion items={process} />
    </div>
  );
}
```

## AI prompt

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

What it is: A magazine-style accordion with oversized serif numerals, a rule that draws across the open row and a plus that folds into a minus.
Category: Accordions. Depends on: motion.

1. Install it with the shadcn CLI:
   npx shadcn@latest add "https://pixelpapa.com/r/accordion-numbered-editorial.json"
   (If the CLI is unavailable, fetch that URL, write files[0].content to components/pixelpapa/accordion-numbered-editorial.tsx and install the listed dependencies.)
2. Import it from "@/components/pixelpapa/accordion-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 accordions

- [Accordion Horizontal Panels](https://pixelpapa.com/components/accordions/accordion-horizontal-panels.md) (Pro): Side-by-side panels where the chosen one springs wide to reveal its story while the others fold down to a vertical title.
- [Accordion Nested Tree](https://pixelpapa.com/components/accordions/accordion-nested-tree.md) (Pro): A nested tree with indent guides where branches unfold with animated height and staggered children, fully navigable with the arrow keys.
- [Accordion Smooth Height](https://pixelpapa.com/components/accordions/accordion-smooth-height.md): A single-open accordion that animates to its content's natural height while the chevron turns and the answer settles in from a blur.
- [Accordion With Icons Cards](https://pixelpapa.com/components/accordions/accordion-with-icons-cards.md): Separate accordion cards with icon tiles; the open card lifts on a spring, its icon fills with colour and the plus turns into a close mark.
