# Single-Open React Accordion With aria-controls

A shadcn/ui block from the [@weinc](https://we.inc/components?utm_source=shadcn.exchange) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/weinc/single-open-react-accordion-with-aria-controls).

- Page, with a live preview: https://shadcn.exchange/r/weinc/single-open-react-accordion-with-aria-controls
- Screenshot: https://shadcn.exchange/shot/weinc/single-open-react-accordion-with-aria-controls/light.png
- Registry item: https://we.inc/r/single-open-react-accordion-with-aria-controls.json
- Listed: 2026-10-29, source last changed 2026-10-29

## Install

```bash
npx shadcn@latest add https://shadcn.exchange/r/weinc/single-open-react-accordion-with-aria-controls.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Files

### `components/we-inc/single-open-react-accordion-with-aria-controls.tsx`

```tsx
import { useId, useState } from "react";

const sections = [
  { title: "Return window", body: "Unused gear can come back within 60 days of delivery. Tents and sleeping bags that have been slept in once are still accepted if they are clean and dry." },
  { title: "Repairs before replacements", body: "Torn seams, stuck zips and snapped poles are fixed free in our Kendal workshop for the life of the product. Most repairs are posted back inside ten working days." },
  { title: "How refunds are paid", body: "Refunds go back to the card you paid with within three working days of the parcel reaching us. Store credit is added instantly if you prefer it." },
  { title: "Items we cannot take back", body: "Worn base layers, opened gas canisters and anything cut to length, such as guy line or webbing, cannot be returned for hygiene and safety reasons." },
];

export default function SingleOpenReactAccordionWithAriaControls() {
  const [open, setOpen] = useState<number | null>(0);
  const base = useId();
  return (
    <section className="soa bg-white px-5 py-16 sm:py-20">
      <style>{`
        .soa-panel { display: grid; grid-template-rows: 0fr; transition: grid-template-rows .3s cubic-bezier(.2,.7,.2,1); }
        .soa-panel[data-open] { grid-template-rows: 1fr; }
        @media (prefers-reduced-motion: reduce) { .soa-panel, .soa-chev { transition: none !important; } }
      `}</style>
      <div className="mx-auto max-w-2xl">
        <h2 className="text-2xl font-semibold tracking-tight text-slate-900 sm:text-3xl">Returns and repairs</h2>
        <p className="mt-2 text-[15px] text-slate-600">Fellside Outfitters. One section opens at a time.</p>
        <div className="mt-8 space-y-3">
          {sections.map((s, i) => {
            const isOpen = open === i;
            const btn = `${base}-b${i}`;
            const panel = `${base}-p${i}`;
            return (
              <div key={s.title} className={`rounded-xl border transition-colors ${isOpen ? "border-emerald-600 bg-emerald-50/60" : "border-slate-200 bg-white"}`}>
                <h3>
                  <button
                    id={btn}
                    type="button"
                    aria-expanded={isOpen}
                    aria-controls={panel}
                    onClick={() => setOpen(isOpen ? null : i)}
                    className="flex min-h-[56px] w-full items-center justify-between gap-4 rounded-xl px-5 py-4 text-left text-base font-semibold text-slate-900 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-emerald-700"
                  >
                    {s.title}
                    <svg className={`soa-chev h-5 w-5 shrink-0 transition-transform duration-300 ${isOpen ? "rotate-180 text-emerald-700" : "text-slate-500"}`} viewBox="0 0 20 20" fill="none" stroke="currentColor" strokeWidth="1.8" aria-hidden="true"><path d="M5 7.5l5 5 5-5" strokeLinecap="round" strokeLinejoin="round" /></svg>
                  </button>
                </h3>
                <div id={panel} role="region" aria-labelledby={btn} data-open={isOpen ? "" : undefined} className="soa-panel">
                  <div className="overflow-hidden" {...(isOpen ? {} : { inert: "" })}>
                    <p className="px-5 pb-5 text-[15px] leading-relaxed text-slate-700">{s.body}</p>
                  </div>
                </div>
              </div>
            );
          })}
        </div>
      </div>
    </section>
  );
}
```
