# React Offcanvas

A React offcanvas component on Bootstrap's markup: a side panel opened from state, four placements, backdrop and scroll options, a responsive sidebar mode.

An offcanvas is a panel that slides in from an edge of the window. Use it for content that should be close at hand without a page of its own, such as filters, a settings form or a long menu. The AdminKit sidebar uses it as the menu sheet on small screens.

`Offcanvas` renders Bootstrap's offcanvas from React state, without Bootstrap's JavaScript or react-bootstrap. It is in the free and PRO versions of [AdminKit for React](/react/).

It works like the [modal](/react/docs/components/modal/). The parent owns the open state: `show` opens the panel, and `onHide` asks to close it. While open, the panel locks the page scroll and keeps the focus inside. Unlike the modal, it stays in the DOM when it is hidden, as in Bootstrap.

## Example

Inside `Offcanvas`, write a header and a body. They become the contents of `.offcanvas`.

```html
<button class="btn btn-primary" type="button" data-bs-toggle="offcanvas" data-bs-target="#offcanvasFilters" aria-controls="offcanvasFilters">
  Filters
</button>

<div class="offcanvas offcanvas-start" tabindex="-1" id="offcanvasFilters" aria-labelledby="offcanvasFiltersLabel">
  <div class="offcanvas-header">
    <h2 class="offcanvas-title h5" id="offcanvasFiltersLabel">Filters</h2>
    <button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button>
  </div>
  <div class="offcanvas-body">
    <p>Show only the orders that match.</p>
    <button type="button" class="btn btn-primary" data-bs-dismiss="offcanvas">Apply</button>
  </div>
</div>
```
```tsx code
import { useState } from "react";
import { CloseButton } from "@/components/bootstrap/close-button";
import { Offcanvas } from "@/components/bootstrap/offcanvas";

export function OrderFilters() {
  const [show, setShow] = useState(false);
  const close = () => setShow(false);

  return (
    <>
      <button
        className="btn btn-primary"
        type="button"
        aria-controls="offcanvasFilters"
        onClick={() => setShow(true)}
      >
        Filters
      </button>
      <Offcanvas
        id="offcanvasFilters"
        show={show}
        onHide={close}
        aria-labelledby="offcanvasFiltersLabel"
      >
        <div className="offcanvas-header">
          <h2 className="offcanvas-title h5" id="offcanvasFiltersLabel">Filters</h2>
          <CloseButton onClick={close} />
        </div>
        <div className="offcanvas-body">
          <p>Show only the orders that match.</p>
          <button type="button" className="btn btn-primary" onClick={close}>Apply</button>
        </div>
      </Offcanvas>
    </>
  );
}
```

`onHide` runs on Escape and on a click on the backdrop. Close the panel there by setting `show` to `false`.

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `show` | `boolean` | | Whether the panel is open |
| `onHide` | `() => void` | | Called on Escape and on a backdrop click; set `show` to false here |
| `onShown` | `() => void` | | Called when the slide-in transition has ended |
| `onHidden` | `() => void` | | Called when the slide-out transition has ended |
| `placement` | `"start" \| "end" \| "top" \| "bottom"` | `"start"` | The edge it slides in from |
| `responsive` | `"sm" \| "md" \| "lg" \| "xl" \| "xxl"` | | A sheet only below this breakpoint, part of the page from it up |
| `backdrop` | `boolean \| "static"` | `true` | `true`: a backdrop that closes the panel on click; `"static"`: a backdrop that does not; `false`: none |
| `scroll` | `boolean` | `false` | Keeps the page scrollable while the panel is open |
| `keyboard` | `boolean` | `true` | Escape closes the panel |
| `as` | `ElementType` | `"div"` | The element, such as `"nav"` or `"aside"` |

Other props, such as `id`, `className`, `ref` and `aria-labelledby`, go to the `.offcanvas` element. The component is in `src/components/bootstrap/offcanvas.tsx`.

## Placement

Pick the edge that suits the content. `placement` adds `.offcanvas-start` (left), `.offcanvas-end` (right), `.offcanvas-top` or `.offcanvas-bottom`.

```html
<button class="btn btn-primary" type="button" data-bs-toggle="offcanvas" data-bs-target="#offcanvasActivity" aria-controls="offcanvasActivity">
  Activity
</button>

<div class="offcanvas offcanvas-end" tabindex="-1" id="offcanvasActivity" aria-labelledby="offcanvasActivityLabel">
  <div class="offcanvas-header">
    <h2 class="offcanvas-title h5" id="offcanvasActivityLabel">Activity</h2>
    <button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button>
  </div>
  <div class="offcanvas-body">No new activity today.</div>
</div>
```
```tsx code
<Offcanvas
  id="offcanvasActivity"
  placement="end"
  show={show}
  onHide={close}
  aria-labelledby="offcanvasActivityLabel"
>
  <div className="offcanvas-header">
    <h2 className="offcanvas-title h5" id="offcanvasActivityLabel">Activity</h2>
    <CloseButton onClick={close} />
  </div>
  <div className="offcanvas-body">No new activity today.</div>
</Offcanvas>
```

## Backdrop and scrolling

By default the panel has a backdrop and the page cannot scroll. Sometimes the user should keep working on the page while the panel is open. `scroll` keeps the page scrollable, and `backdrop={false}` removes the backdrop.

```html
<button class="btn btn-primary" type="button" data-bs-toggle="offcanvas" data-bs-target="#offcanvasNotes" aria-controls="offcanvasNotes">
  Notes
</button>

<div class="offcanvas offcanvas-start" data-bs-scroll="true" data-bs-backdrop="false" tabindex="-1" id="offcanvasNotes" aria-labelledby="offcanvasNotesLabel">
  <div class="offcanvas-header">
    <h2 class="offcanvas-title h5" id="offcanvasNotesLabel">Notes</h2>
    <button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button>
  </div>
  <div class="offcanvas-body">Scroll the page: it stays scrollable while these notes are open.</div>
</div>
```
```tsx code
<Offcanvas
  id="offcanvasNotes"
  scroll
  backdrop={false}
  show={show}
  onHide={close}
  aria-labelledby="offcanvasNotesLabel"
>
  <div className="offcanvas-header">
    <h2 className="offcanvas-title h5" id="offcanvasNotesLabel">Notes</h2>
    <CloseButton onClick={close} />
  </div>
  <div className="offcanvas-body">Scroll the page: it stays scrollable while these notes are open.</div>
</Offcanvas>
```

`backdrop="static"` shows the backdrop but ignores clicks on it, so only Escape and your own buttons close the panel.

## Responsive sidebar

Some panels should be a sheet on small screens and part of the page on large ones. With `responsive`, the element gets `.offcanvas-lg` (or another breakpoint) instead of `.offcanvas`. Below that breakpoint it is a sheet. From that breakpoint up, it is part of the page. The sidebar in `src/components/sidebar/sidebar.tsx` works this way, with `as="nav"` and `responsive="lg"`. The navbar toggle opens it through the layout context.

```tsx
<Offcanvas
  as="nav"
  id="sidebar"
  responsive="lg"
  show={sheetOpen}
  onHide={() => setSheetOpen(false)}
  aria-label="Sidebar"
>
  ...
</Offcanvas>
```

## Accessibility

- While open, the panel has `role="dialog"` and `aria-modal="true"`. Point `aria-labelledby` at its title, or give it an `aria-label`.
- Once it is visible, the focus moves to the panel, and Tab and Shift+Tab stay inside it. When it closes, the focus returns to the element that had it. As in Bootstrap, a panel with `scroll` and `backdrop={false}` does not trap the focus.
- Escape closes the panel, unless `keyboard={false}`.
- `CloseButton` has `aria-label="Close"`. Set `aria-controls` on the button that opens the panel.

## Related

- [Next.js offcanvas](/nextjs/docs/components/offcanvas/): the same component in AdminKit for Next.js
- [Bootstrap 5 offcanvas](/bootstrap/docs/components/offcanvas/): the offcanvas of the HTML version
- [Modal](/react/docs/components/modal/): a dialog in the middle of the window
- [Offcanvas demo](https://react-demo.adminkit.io/ui-offcanvas): panels from every edge, with and without a backdrop

---

Canonical HTML page: https://adminkit.io/react/docs/components/offcanvas/

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
