Next.js Bootstrap Collapse
A Next.js Bootstrap collapse client component: a panel that opens and closes from React state with Bootstrap's height transition, rendered in the App Router.
This component is only available in AdminKit PRO. Learn more.
Collapse keeps secondary content out of the way until the user wants it, such as order details or a second address. A click on your toggle slides the panel open with Bootstrap's height animation. The accordion and the sidebar's nested menus are built on it. Collapse is part of the PRO version of AdminKit for Next.js.
It needs no Bootstrap JavaScript. A boolean prop switches the panel between Bootstrap's classes: .collapse when hidden, .collapsing while the height animates, and .collapse.show when open.
In the App Router, pages are server components: they render on the server and have no state. Collapse is a client component, from a file that starts with "use client". The open state and the button that changes it live in your code, so they need a client component too. Put the toggle and the panel in a small client component, and render that from your server component page.
Example
"use client";
import { useState } from "react";
import { Collapse } from "@/components/bootstrap/collapse";
export function OrderDetails() {
const [open, setOpen] = useState(false);
return (
<>
<button
className="btn btn-primary"
type="button"
aria-expanded={open}
aria-controls="orderDetails"
onClick={() => setOpen(!open)}
>
Order details
</button>
<Collapse in={open} id="orderDetails">
<div className="card card-body mt-3">
Ordered on May 4, paid by credit card, shipped to Amsterdam.
</div>
</Collapse>
</>
);
}Collapse always renders its children, so a closed panel is in the HTML that Next.js builds, hidden by .collapse (display: none). The browser's first render matches that HTML, and nothing animates when the page loads.
The panel content does not have to live in the client component. It can come from the server component, as children of your client component. The server renders it, and only the toggle logic runs in the browser:
"use client";
import { type ReactNode, useState } from "react";
import { Collapse } from "@/components/bootstrap/collapse";
export function DetailsToggle({ id, label, children }: { id: string; label: string; children: ReactNode }) {
const [open, setOpen] = useState(false);
return (
<>
<button className="btn btn-light" type="button" aria-expanded={open} aria-controls={id} onClick={() => setOpen(!open)}>
{label}
</button>
<Collapse in={open} id={id}>
<div className="pt-3">{children}</div>
</Collapse>
</>
);
}Props
| Prop | Type | Default | Description |
|---|---|---|---|
in | boolean | Required. Shown (true) or collapsed (false) | |
as | ElementType | "div" | The element to render, such as "ul" for a nested list |
onEntered | () => void | Called when the opening transition has ended | |
onExited | () => void | Called when the closing transition has ended |
Other props, such as id, className, ref and aria-*, go to the collapsing element. Only changes of in after the first render animate. When the user prefers reduced motion, there is no transition, and onEntered and onExited run on the next frame. The component is in src/components/bootstrap/collapse.tsx.
Padding and margins
The height animation runs on the Collapse element itself, so padding or margins on it make the transition jump at the end. Put them on a child instead, as .card and mt-3 do above. It animates the height only: .collapse-horizontal does nothing.
Several panels
To open two panels from one button, derive that button's state from both. Every Collapse still has its own in:
"use client";
import { useState } from "react";
import { Collapse } from "@/components/bootstrap/collapse";
export function Addresses() {
const [billing, setBilling] = useState(false);
const [shipping, setShipping] = useState(false);
const both = billing && shipping;
return (
<>
<p>
<button
className="btn btn-primary me-2"
type="button"
aria-expanded={billing}
aria-controls="panelBilling"
onClick={() => setBilling(!billing)}
>
Billing
</button>
<button
className="btn btn-primary me-2"
type="button"
aria-expanded={shipping}
aria-controls="panelShipping"
onClick={() => setShipping(!shipping)}
>
Shipping
</button>
<button
className="btn btn-secondary"
type="button"
aria-expanded={both}
aria-controls="panelBilling panelShipping"
onClick={() => {
setBilling(!both);
setShipping(!both);
}}
>
Both addresses
</button>
</p>
<div className="row">
<div className="col">
<Collapse in={billing} id="panelBilling">
<div className="card card-body">Billing: 12 Market Street, Amsterdam</div>
</Collapse>
</div>
<div className="col">
<Collapse in={shipping} id="panelShipping">
<div className="card card-body">Shipping: 4 Harbor Road, Rotterdam</div>
</Collapse>
</div>
</div>
</>
);
}In the Bootstrap preview, "Both addresses" toggles each panel. The component above opens both, or closes both when both are open.
Accessibility
Collapseadds no ARIA attributes. Setaria-expandedandaria-controlson the toggle yourself.- Prefer a
<button>as the toggle. A link toggle needsrole="button"andevent.preventDefault()inonClick. - While collapsed, the content is
display: none: out of the tab order and skipped by screen readers.
Related
- React Bootstrap collapse: the same component in AdminKit for React (Vite)
- Bootstrap 5 collapse: the collapse of the HTML version
- Accordion: collapsing sections, one open at a time unless you set
alwaysOpen - General UI demo: a card accordion built with
Collapse
Last updated on
Close button
A Next.js Bootstrap close button: Bootstrap's .btn-close with an aria-label, for the modals, offcanvas sheets and alerts of your App Router pages.
Dropdowns
Next.js Bootstrap dropdown menus that server component pages can render as is: split buttons, alignment, auto close, keyboard support and next/link items.