<!-- Protocore Design System — StickyAccordion -->
# StickyAccordion

- **Category:** Navigation (`navigation`)
- **Slug:** `navigation/sticky-accordion`
- **Status:** stable
- **Platforms:** web
- **Import:** `import { StickyAccordion } from "@protocore/pds";`
- **Docs:** https://pds.protocore.io/components/navigation/sticky-accordion

> Sticky-rail accordion — a pinned left rail (eyebrow, headline, illustration) beside a scrolling column of numbered rows that open one at a time.

## When to use it

**StickyAccordion** is a landing-page deep-dive for *one idea, several facets* — a product whose capabilities you want to narrate while a headline and illustration stay anchored beside them.

- A plain FAQ or settings stack with no rail? Use the lighter **Accordion**.
- Presenting several distinct pillars side-by-side? Use **TabbedShowcase**.
- An ordered process with progress? Use **Steps**.
- Feed the `art` slot a **LineArt** field and keep row bodies to a sentence.

## Props

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `art` | `ReactNode` | no | — | Illustration anchored to the bottom of the sticky rail (e.g. a `<LineArt>`). |
| `className` | `string` | no | — | — |
| `defaultOpen` | `number` | no | `0` | Uncontrolled initial open index. Default 0. |
| `eyebrow` | `ReactNode` | no | — | Mono eyebrow at the top of the sticky rail. |
| `items` | `AccordionItem[]` | yes | — | The expandable numbered rows. |
| `onOpenChange` | `((index: number) => void)` | no | — | Fires with the next open index whenever a row is toggled. |
| `open` | `number` | no | — | Controlled open index. Pair with `onOpenChange`. |
| `rail` | `ReactNode` | no | — | Fully custom rail content (overrides eyebrow/title/art). |
| `style` | `CSSProperties` | no | — | — |
| `title` | `ReactNode` | no | — | Display headline in the sticky rail. |

## Examples

### Basics

Give the rail an `eyebrow`, `title`, and `art`, then pass the expandable `items`. The rail stays pinned while the numbered rows scroll; clicking a row reveals its body, one open at a time. It needs a tall container to show the sticky effect.

```tsx
import { StickyAccordion, LineArt } from "@protocore/pds";

export default function StickyAccordionBasics() {
  return (
    <div style={{ height: 520 }}>
      <StickyAccordion
        eyebrow="Asset Issuance"
        title="Connect to 150+ supported blockchains."
        art={<LineArt variant="nexus" animate={false} />}
        items={[
          {
            title: "Asset Orchestration",
            body: "Issue, move, and redeem tokenized assets at scale.",
          },
          {
            title: "Unified Liquidity",
            body: "Route native value across chains without wrapping.",
          },
          {
            title: "Programmable Settlement",
            body: "Compose message and value delivery in one packet.",
          },
          {
            title: "Observability",
            body: "Trace every message from source emit to destination lands.",
          },
        ]}
      />
    </div>
  );
}
```

### Controlled

Own the open row with `open` + `onOpenChange` to sync it with scroll position, analytics, or a deep link; otherwise use `defaultOpen`.

```tsx
import { useState } from "react";
import { StickyAccordion, LineArt, Badge } from "@protocore/pds";

const ITEMS = [
  { title: "Endpoints", body: "Immutable contracts that send and receive messages." },
  { title: "Executor", body: "Delivers messages and prepays destination gas." },
  { title: "DVN", body: "Independently attests to message validity." },
];

export default function StickyAccordionControlled() {
  const [open, setOpen] = useState(0);
  return (
    <div style={{ display: "grid", gap: "var(--pds-space-4)" }}>
      <Badge tone="info">open: [ 0{open + 1} ]</Badge>
      <div style={{ height: 460 }}>
        <StickyAccordion
          eyebrow="Protocol"
          title="The three roles that move a message."
          art={<LineArt variant="chain" animate={false} />}
          open={open}
          onOpenChange={setOpen}
          items={ITEMS}
        />
      </div>
    </div>
  );
}
```

## Do & don't

**Do**

- Give it a tall container so the sticky rail has room to pin.
- Keep all rows about one theme — the rail states the single idea.
- Use a LineArt illustration anchored in the art slot.
- Keep row bodies short; this is a narrative surface, not a document.

**Don't**

- Don't use it for unrelated FAQ entries — that's a plain Accordion.
- Don't put more than ~6 rows in the scrolling column.
- Don't nest interactive forms inside a row body.
- Don't rely on it on very short pages where nothing scrolls past the rail.

## Accessibility

**Keyboard**

| Keys | Action |
| --- | --- |
| `Tab` | Move focus across the numbered row buttons |
| `Enter / Space` | Open the focused row (closing the previously open one) |

**Notes**

- Each row is a real `<button>` with `aria-expanded` reflecting whether its body is shown.
- The `[ 0N ]` row index is `aria-hidden` decoration; the row title is the button's accessible name.
- The rail is presentational — the interactive control is the row list.

## Related

`accordion`, `tabbed-showcase`, `steps`, `page-header`

---

© Protocore. All rights reserved. Use of the Protocore Design System requires prior written authorization from Protocore (contact@protocore.io). These machine-readable materials must not be ingested into ML-training datasets or derivative design systems. See https://pds.protocore.io/legal/
