# Next.js Bootstrap Accordion

A Next.js Bootstrap accordion for the App Router that a server component page can render as is: flush and always-open variants, no Bootstrap JavaScript.

> This component is only available in **AdminKit PRO**. [Learn more](/pricing/#nextjs).

An accordion keeps a long FAQ or help page short: the reader sees the headers and opens the part they need. AdminKit builds it from four components, `Accordion`, `AccordionItem`, `AccordionHeader` and `AccordionBody`. They render Bootstrap's accordion markup and open items from React state, without Bootstrap's JavaScript. The accordion is part of the PRO version of [AdminKit for Next.js](/nextjs/).

In the App Router, pages are server components: they render on the server and have no state. The accordion needs state to know which items are open, so its components are client components, from a file that starts with `"use client"`. You don't need a client component of your own, though. `Accordion` holds the open items itself, and its props are only strings and booleans, which a server component can pass. Your page renders it directly, needs no `"use client"` and keeps its `metadata` export.

## Example

Every item has an `eventKey`. Pass one to `defaultActiveKey` to open that item on the first render. Opening another item then closes it.

```html
<div class="accordion" id="accordionFaq">
  <div class="accordion-item">
    <h2 class="accordion-header">
      <button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#faqShipping" aria-expanded="true" aria-controls="faqShipping">
        When do orders ship?
      </button>
    </h2>
    <div id="faqShipping" class="accordion-collapse collapse show" data-bs-parent="#accordionFaq">
      <div class="accordion-body">Orders ship within two business days.</div>
    </div>
  </div>
  <div class="accordion-item">
    <h2 class="accordion-header">
      <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#faqReturns" aria-expanded="false" aria-controls="faqReturns">
        Can I return an order?
      </button>
    </h2>
    <div id="faqReturns" class="accordion-collapse collapse" data-bs-parent="#accordionFaq">
      <div class="accordion-body">Return any order within 30 days for a full refund.</div>
    </div>
  </div>
  <div class="accordion-item">
    <h2 class="accordion-header">
      <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#faqPayment" aria-expanded="false" aria-controls="faqPayment">
        Which payment methods do you accept?
      </button>
    </h2>
    <div id="faqPayment" class="accordion-collapse collapse" data-bs-parent="#accordionFaq">
      <div class="accordion-body">Credit cards, PayPal and bank transfer.</div>
    </div>
  </div>
</div>
```
```tsx code title="src/app/(dashboard)/help/page.tsx"
import {
  Accordion,
  AccordionBody,
  AccordionHeader,
  AccordionItem,
} from "@/components/bootstrap/accordion";
import { pageMetadata } from "@/lib/site";

export const metadata = pageMetadata("/help");

export default function HelpPage() {
  return (
    <>
      <h1 className="h3 mb-3">Help</h1>
      <Accordion defaultActiveKey="shipping">
        <AccordionItem eventKey="shipping">
          <AccordionHeader>When do orders ship?</AccordionHeader>
          <AccordionBody>Orders ship within two business days.</AccordionBody>
        </AccordionItem>
        <AccordionItem eventKey="returns">
          <AccordionHeader>Can I return an order?</AccordionHeader>
          <AccordionBody>Return any order within 30 days for a full refund.</AccordionBody>
        </AccordionItem>
        <AccordionItem eventKey="payment">
          <AccordionHeader>Which payment methods do you accept?</AccordionHeader>
          <AccordionBody>Credit cards, PayPal and bank transfer.</AccordionBody>
        </AccordionItem>
      </Accordion>
    </>
  );
}
```

`pageMetadata()` reads the page's title and description from `src/data/pages.json`, so a new page needs an entry there (see [Add a page](/nextjs/docs/getting-started/project-structure/#add-a-page)).

The HTML that Next.js builds already has the items of `defaultActiveKey` open (`.collapse.show`) and the others closed. The `id`s come from `useId`, so they are the same on the server and in the browser. When the browser takes over that HTML (hydration), its markup matches the server's: nothing shifts, and there is no hydration mismatch.

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `defaultActiveKey` | `string \| string[]` | | The item or items open on the first render; all closed without it |
| `alwaysOpen` | `boolean` | `false` | Items stay open when another item opens |

Other props of `Accordion`, such as `className` and `id`, go to the `.accordion` element. The four components are in `src/components/bootstrap/accordion.tsx`.

- `AccordionItem`: a required `eventKey` (`string`), unique in its accordion. Other props go to `.accordion-item`.
- `AccordionHeader`: `as` (`"h2"` to `"h6"`, `"h2"` by default) sets the heading around the button, `className` styles that heading, `children` is the button text.
- `AccordionBody`: its props, `className` included, go to `.accordion-body`, not to the collapsing wrapper around it.

Once the accordion is on the page, no prop opens an item from outside. To drive a panel from your own state, use [Collapse](/nextjs/docs/components/collapse/) in a client component. The parts throw an error when they are used outside an `Accordion`.

## Flush

To fit the accordion edge to edge in a card, add `accordion-flush`. It drops the outer borders and the rounded corners:

```html
<div class="accordion accordion-flush" id="accordionFlush">
  <div class="accordion-item">
    <h2 class="accordion-header">
      <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#flushAccount" aria-expanded="false" aria-controls="flushAccount">
        Account
      </button>
    </h2>
    <div id="flushAccount" class="accordion-collapse collapse" data-bs-parent="#accordionFlush">
      <div class="accordion-body">Change your name, email and password.</div>
    </div>
  </div>
  <div class="accordion-item">
    <h2 class="accordion-header">
      <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#flushBilling" aria-expanded="false" aria-controls="flushBilling">
        Billing
      </button>
    </h2>
    <div id="flushBilling" class="accordion-collapse collapse" data-bs-parent="#accordionFlush">
      <div class="accordion-body">Update your payment method and download invoices.</div>
    </div>
  </div>
</div>
```
```tsx code
<Accordion className="accordion-flush">
  <AccordionItem eventKey="account">
    <AccordionHeader>Account</AccordionHeader>
    <AccordionBody>Change your name, email and password.</AccordionBody>
  </AccordionItem>
  <AccordionItem eventKey="billing">
    <AccordionHeader>Billing</AccordionHeader>
    <AccordionBody>Update your payment method and download invoices.</AccordionBody>
  </AccordionItem>
</Accordion>
```

## Always open

When readers need several sections open at once, add `alwaysOpen`. Pass a list to `defaultActiveKey` to open more than one at the start:

```html
<div class="accordion">
  <div class="accordion-item">
    <h2 class="accordion-header">
      <button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#openProfile" aria-expanded="true" aria-controls="openProfile">
        Profile
      </button>
    </h2>
    <div id="openProfile" class="accordion-collapse collapse show">
      <div class="accordion-body">Your photo and display name.</div>
    </div>
  </div>
  <div class="accordion-item">
    <h2 class="accordion-header">
      <button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#openNotifications" aria-expanded="true" aria-controls="openNotifications">
        Notifications
      </button>
    </h2>
    <div id="openNotifications" class="accordion-collapse collapse show">
      <div class="accordion-body">Choose which emails you receive.</div>
    </div>
  </div>
</div>
```
```tsx code
<Accordion alwaysOpen defaultActiveKey={["profile", "notifications"]}>
  <AccordionItem eventKey="profile">
    <AccordionHeader>Profile</AccordionHeader>
    <AccordionBody>Your photo and display name.</AccordionBody>
  </AccordionItem>
  <AccordionItem eventKey="notifications">
    <AccordionHeader>Notifications</AccordionHeader>
    <AccordionBody>Choose which emails you receive.</AccordionBody>
  </AccordionItem>
</Accordion>
```

## Accessibility

- Each header is a `<button>` inside a heading, with `aria-expanded` and `aria-controls` set to the body's `id`.
- Use `as` to match the page outline, for example `as="h3"` when the accordion sits under an `h2`.
- Tab moves between the header buttons. Enter and Space open and close an item.
- A closed body is `display: none` (`.collapse`). Its links and fields leave the tab order, and screen readers skip it.

## Related

- [React Bootstrap accordion](/react/docs/components/accordion/): the same component in AdminKit for React (Vite)
- [Bootstrap 5 accordion](/bootstrap/docs/components/accordion/): the accordion of the HTML version
- [Collapse](/nextjs/docs/components/collapse/): one panel that shows and hides from your own state
- [General UI demo](https://nextjs-demo.adminkit.io/ui-general): a card accordion built with `Collapse`

---

Canonical HTML page: https://adminkit.io/nextjs/docs/components/accordion/

Site overview for agents: https://adminkit.io/llms.txt (expanded: https://adminkit.io/llms-full.txt) · Documentation: https://adminkit.io/bootstrap/docs/ · Questions: support@adminkit.io
