# Accordion With Icons Cards — React Accordion Component

> 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.

- Category: [Accordions](https://pixelpapa.com/components/accordions.md)
- Tags: accordion, cards, icons, lift, disclosure
- Dependencies: lucide-react, motion (plus React and Tailwind CSS v4)
- Last updated: 2026-11-13
- Live preview: https://pixelpapa.com/components/accordions/accordion-with-icons-cards

## Install

```bash
npx shadcn@latest add "https://pixelpapa.com/r/accordion-with-icons-cards.json"
```

The file is written to `components/pixelpapa/accordion-with-icons-cards.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 { Globe, Plus, ShieldCheck, Workflow, Zap, type LucideIcon } from "lucide-react";
import { AnimatePresence, motion } from "motion/react";
import { useId, useState } from "react";
import { cn } from "@/lib/utils";

type CardItem = { icon: LucideIcon; title: string; summary: string; body: string };

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

const spring = { type: "spring", stiffness: 360, damping: 30 } as const;

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

  return (
    <div className={cn("flex flex-col gap-2.5", className)}>
      {items.map(({ icon: Icon, title, summary, body }, i) => {
        const isOpen = open === i;
        return (
          <motion.div
            key={title}
            initial={false}
            animate={{
              y: isOpen ? -2 : 0,
              scale: isOpen ? 1 : 0.985,
              boxShadow: isOpen ? "0 24px 48px -20px rgb(40 50 120 / 0.35)" : "0 0 0 0 rgb(40 50 120 / 0)",
            }}
            transition={spring}
            className={cn("rounded-2xl border bg-card transition-colors duration-300", isOpen ? "border-primary/40" : "border-border")}
          >
            <button
              type="button"
              aria-expanded={isOpen}
              aria-controls={`${uid}-${i}`}
              onClick={() => setOpen(isOpen ? null : i)}
              className="flex w-full items-center gap-4 rounded-2xl p-3.5 text-left outline-none focus-visible:ring-2 focus-visible:ring-ring"
            >
              <span className={cn("flex size-11 shrink-0 items-center justify-center rounded-xl transition-colors duration-300", isOpen ? "bg-primary text-primary-foreground" : "bg-muted text-muted-foreground")}>
                <motion.span animate={{ rotate: isOpen ? [0, -14, 10, 0] : 0 }} transition={{ duration: 0.5 }}>
                  <Icon className="size-5" />
                </motion.span>
              </span>
              <span className="min-w-0 flex-1">
                <span className="block font-medium text-card-foreground">{title}</span>
                <span className="block truncate text-sm text-muted-foreground">{summary}</span>
              </span>
              <span className={cn("flex size-8 shrink-0 items-center justify-center rounded-full border border-border text-muted-foreground transition-transform duration-300", isOpen && "rotate-[135deg] text-foreground")}>
                <Plus className="size-4" />
              </span>
            </button>
            <AnimatePresence initial={false}>
              {isOpen && (
                <motion.div id={`${uid}-${i}`} role="region" initial={{ height: 0, opacity: 0 }} animate={{ height: "auto", opacity: 1 }} exit={{ height: 0, opacity: 0 }} transition={spring} className="overflow-hidden">
                  <p className="pr-6 pb-5 pl-[76px] text-sm leading-relaxed text-pretty text-muted-foreground">{body}</p>
                </motion.div>
              )}
            </AnimatePresence>
          </motion.div>
        );
      })}
    </div>
  );
}

const features: CardItem[] = [
  { icon: Zap, title: "Instant previews", summary: "A live URL for every branch", body: "Push a commit and get a shareable link in under a minute, seeded with production-like data and torn down when the branch merges." },
  { icon: ShieldCheck, title: "Guardrails built in", summary: "Checks before anything ships", body: "Type errors, broken links and accessibility regressions block the deploy and tell you which line to fix." },
  { icon: Workflow, title: "Pipelines you can read", summary: "One file, no YAML archaeology", body: "Describe build steps in plain TypeScript. Run the same pipeline locally with one command before you push." },
  { icon: Globe, title: "Edge in 34 regions", summary: "Close to every customer", body: "Static assets and server functions are replicated worldwide, with automatic failover when a region has a bad day." },
];

export default function AccordionWithIconsCardsDemo() {
  return (
    <div className="h-[410px] w-full max-w-lg">
      <CardAccordion items={features} />
    </div>
  );
}
```

## AI prompt

```text
Add the "Accordion With Icons Cards" component from PixelPapa UI to this project.

What it is: 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.
Category: Accordions. Depends on: lucide-react, motion.

1. Install it with the shadcn CLI:
   npx shadcn@latest add "https://pixelpapa.com/r/accordion-with-icons-cards.json"
   (If the CLI is unavailable, fetch that URL, write files[0].content to components/pixelpapa/accordion-with-icons-cards.tsx and install the listed dependencies.)
2. Import it from "@/components/pixelpapa/accordion-with-icons-cards" 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 Numbered Editorial](https://pixelpapa.com/components/accordions/accordion-numbered-editorial.md): A magazine-style accordion with oversized serif numerals, a rule that draws across the open row and a plus that folds into a minus.
- [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.
