Skip to main content
pnpm add @winkintel/bootstrap-svelte bootstrap
GitHub

Offcanvas

Build hidden sidebars into your project for navigation, shopping carts, and more with the offcanvas component.


Interactive Playground

Controls

Event Log

  • No events logged

Preview

Code

            
            <script lang="ts">
  let isShown = $state(false);
  let trigger: HTMLElement | null = $state(null);
  
  function toggleOffcanvas() {
    isShown = !isShown;
  }
</script>

<Button bind:elementRef={trigger} onclick={toggleOffcanvas}>
  Toggle Offcanvas
</Button>

<Offcanvas.Root
  placement="start"
  isShown={isShown}
  triggerElements={[trigger]}
  onShow={handleShow}
  onShown={handleShown}
  onHide={handleHide}
  onHidePrevented={handleHidePrevented}
  onHidden={handleHidden}>
  <Offcanvas.Header isDismissible={true}>
    <Offcanvas.Title>Offcanvas Title</Offcanvas.Title>
  </Offcanvas.Header>
  <Offcanvas.Body>
    <p>This is the offcanvas body content. You can place any content here, including forms, text, or other components.</p>
    <div class="d-grid gap-2">
      <Button colorVariant="secondary" onclick={() => isShown = false}>Close</Button>
    </div>
  </Offcanvas.Body>
</Offcanvas.Root>
        

The Offcanvas component is a sidebar that can be toggled to appear from any edge of the viewport. By default, it appears from the left side (start position).

            
            <Button onclick={toggleOffcanvas}>
  Launch Basic Offcanvas
</Button>

<Offcanvas.Root isShown={showOffcanvas} onHidden={() => showOffcanvas = false}>
    <Offcanvas.Header>
        <Offcanvas.Title>Offcanvas</Offcanvas.Title>
    </Offcanvas.Header>
    <Offcanvas.Body>
        <p>Some text as placeholder. Content can be virtually anything.</p>
        <Button colorVariant="secondary" onclick={() => showOffcanvas = false}>Close</Button>
    </Offcanvas.Body>
</Offcanvas.Root>
        

Pass DOM references through triggerElements when a standalone button or link controls the panel. Use bind:this on a native element or bind:elementRef on Button. Your click handler owns visibility; an enabled primary press on a listed element (including its descendants) is not treated as an outside dismissal.

HTML and SVG element references are supported. The list may contain multiple references and null or undefined entries; a null list means no triggers. Remove a reference to revoke ownership; detached elements are ignored and conditional bindings follow replacement elements. No extra listeners or global registrations are retained. Keep references reactive when changing the list. Each panel recognizes only its own list, and only the top overlay handles dismissal.

Native disabled controls (including disabled fieldsets) and elements with aria-disabled="true" do not receive this exemption. Non-primary presses retain the existing outside-press behavior. A cancelled or abandoned primary press on an enabled owner leaves the panel unchanged; ownership never schedules a later toggle. Without a backdrop, outside presses remain ignored.

Ownership does not add click handlers, set ARIA, or raise the trigger above a backdrop. Classes and aria-controls alone do not establish ownership. This example uses z-index: 1042, between a single Offcanvas backdrop (1040) and panel (1045). The panel covers the trigger where they overlap on narrow screens. Stacked overlays receive higher layers, so this fixed value is only suitable for this single-overlay example; consumers control their trigger's stacking context. Keep consumer visibility in sync with dismissals using onHide.

Prevented outside dismissals: 0

            
            <script lang="ts">
    import { Button, Offcanvas } from '@winkintel/bootstrap-svelte';
    let open = $state(false);
    let trigger: HTMLElement | null = $state(null);
</script>

<Button bind:elementRef={trigger} aria-controls="my-panel" aria-expanded={open}
    style="position: relative; z-index: 1042;" onclick={() => open = !open}>
    Toggle Offcanvas
</Button>
<Offcanvas.Root id="my-panel" isShown={open} triggerElements={[trigger]}
    useBackdrop="static" placement="end" onHide={() => open = false}>
    <Offcanvas.Body>Panel content</Offcanvas.Body>
</Offcanvas.Root>
        

Offcanvas supports four different placement options: start (default, left side), end (right side), top, and bottom.

            
            <Button onclick={() => showPlacementStart = true}>
    Offcanvas Start
</Button>
<Offcanvas.Root placement="start" isShown={showPlacementStart} onHidden={() => showPlacementStart = false}>
    <Offcanvas.Header>
        <Offcanvas.Title>Offcanvas Start</Offcanvas.Title>
    </Offcanvas.Header>
    <Offcanvas.Body>Positioned from the left of the screen (or right in RTL languages)</Offcanvas.Body>
</Offcanvas.Root>

<Button onclick={() => showPlacementEnd = true}>
    Offcanvas End
</Button>
<Offcanvas.Root placement="end" isShown={showPlacementEnd} onHidden={() => showPlacementEnd = false}>
    <Offcanvas.Header>
        <Offcanvas.Title>Offcanvas End</Offcanvas.Title>
    </Offcanvas.Header>
    <Offcanvas.Body>Positioned from the right of the screen (or left in RTL languages)</Offcanvas.Body>
</Offcanvas.Root>

<Button onclick={() => showPlacementTop = true}>
    Offcanvas Top
</Button>
<Offcanvas.Root placement="top" isShown={showPlacementTop} onHidden={() => showPlacementTop = false}>
    <Offcanvas.Header>
        <Offcanvas.Title>Offcanvas Top</Offcanvas.Title>
    </Offcanvas.Header>
    <Offcanvas.Body>Positioned from the top of the screen</Offcanvas.Body>
</Offcanvas.Root>

<Button onclick={() => showPlacementBottom = true}>
    Offcanvas Bottom
</Button>
<Offcanvas.Root placement="bottom" isShown={showPlacementBottom} onHidden={() => showPlacementBottom = false}>
    <Offcanvas.Header>
        <Offcanvas.Title>Offcanvas Bottom</Offcanvas.Title>
    </Offcanvas.Header>
    <Offcanvas.Body>Positioned from the bottom of the screen</Offcanvas.Body>
</Offcanvas.Root>
        

You can control the behavior of the backdrop with the useBackdrop prop. It accepts three values: true (default, click outside to dismiss), "static" (backdrop present; outside clicks do not dismiss), or false (no backdrop). With false, outside clicks do not dismiss the panel. Use Escape (when keyboard dismissal is enabled), a dismiss button, the navbar toggler, or the isShown prop to close it. If you need click-away dismissal, enable the backdrop or provide your own outside-click handler.

When the panel is nested in a Navbar.Root, its Navbar.Toggler can open and close it with any backdrop setting, provided the toggler is enabled and remains reachable above the backdrop. A controlling toggler click changes visibility once and does not trigger onHidePrevented. Clicking outside a panel with a static backdrop still triggers that callback and keeps the panel open.

            
            <Offcanvas.Root useBackdrop="static" isShown={showStaticBackdrop} onHidden={() => showStaticBackdrop = false}>
    <Offcanvas.Header>
        <Offcanvas.Title>Static Backdrop</Offcanvas.Title>
    </Offcanvas.Header>
    <Offcanvas.Body>
        <p>Click the close button or press escape to close this offcanvas. Clicking outside won't close it.</p>
        <Button colorVariant="secondary" onclick={() => showStaticBackdrop = false}>Close</Button>
    </Offcanvas.Body>
</Offcanvas.Root>

<Offcanvas.Root useBackdrop={true} isShown={showTrueBackdrop} onHidden={() => showTrueBackdrop = false}>
    <Offcanvas.Header>
        <Offcanvas.Title>True Backdrop</Offcanvas.Title>
    </Offcanvas.Header>
    <Offcanvas.Body>
        <p>This offcanvas has a backdrop that you can click outside to close it.</p>
    </Offcanvas.Body>
</Offcanvas.Root>

<Offcanvas.Root useBackdrop={false} isShown={showFalseBackdrop} onHidden={() => showFalseBackdrop = false}>
    <Offcanvas.Header>
        <Offcanvas.Title>False Backdrop</Offcanvas.Title>
    </Offcanvas.Header>
    <Offcanvas.Body>
        <p>There is no backdrop for this offcanvas. You can still interact with the page content.</p>
        <Button colorVariant="secondary" onclick={() => showFalseBackdrop = false}>Close</Button>
    </Offcanvas.Body>
</Offcanvas.Root>
        

By default, the Offcanvas component prevents scrolling of the main body when it's open. You can allow body scrolling by setting the isBodyScrollable prop to true.

            
            <Offcanvas.Root isBodyScrollable={true} isShown={showScrollable} onHidden={() => showScrollable = false}>
    <Offcanvas.Header>
        <Offcanvas.Title>Scrollable Offcanvas</Offcanvas.Title>
    </Offcanvas.Header>
    <Offcanvas.Body>
        <p>The page body remains scrollable when this offcanvas is open.</p>
        <p>Try scrolling the page while this offcanvas is open.</p>
        <Button colorVariant="secondary" onclick={() => showScrollable = false}>Close</Button>
    </Offcanvas.Body>
</Offcanvas.Root>
        

The Offcanvas component can be configured to behave responsively using the showOnBreakpoint prop. When set, the component will be visible as a regular element at or above the specified breakpoint, and will behave as an offcanvas below it.

Supported breakpoints are sm through xxl. Omit showOnBreakpoint for a standalone dismissible overlay, or to inherit a parent Navbar's mode. For always-inline Navbar content, use Navbar.Root expandOnBreakpoint="xs" and omit the panel's breakpoint. xs is not an Offcanvas breakpoint: remove legacy showOnBreakpoint="xs" usages. Untyped runtime values of xs are treated as omitted, preserving Navbar inheritance without generating an invalid CSS class.

This example will show as a regular sidebar on screens lg and up, but as an offcanvas on smaller screens.

Resize your browser to show the responsive offcanvas toggle.
            
            <Button colorVariant="primary" class="d-lg-none" onclick={() => showResponsive = !showResponsive}>
    Toggle Responsive Offcanvas
</Button>

<Offcanvas.Root showOnBreakpoint="lg" placement="start">
    <Offcanvas.Header>
        <Offcanvas.Title>Responsive Offcanvas</Offcanvas.Title>
    </Offcanvas.Header>
    <Offcanvas.Body>
        <p>This is always visible on lg screens and above, but behaves like a regular offcanvas below lg breakpoint.</p>
        <Button colorVariant="secondary" class="d-lg-none" onclick={() => showResponsive = false}>Close</Button>
    </Offcanvas.Body>
</Offcanvas.Root>
        

Offcanvas.Root Props

NameTypeDefaultDescription
classstring-Additional CSS classes to apply to the component
dir'ltr' | 'rtl''ltr'Text direction, affects the side from which the offcanvas appears
elementRefHTMLElement | nullnullReference to the DOM element
idstringAuto-generatedUnique identifier for the offcanvas
isBodyScrollablebooleanfalseWhen true, allows scrolling the main page body when offcanvas is open
isKeyboardDismissiblebooleantrueWhen true, allows closing the offcanvas with the Escape key
isShownbooleanfalseControls the visibility of the offcanvas
placement'start' | 'end' | 'top' | 'bottom''start'Determines the edge of the screen from which the offcanvas appears
showOnBreakpoint'sm' | 'md' | 'lg' | 'xl' | 'xxl'undefinedInline at or above the selected breakpoint. Omitted inherits a parent Navbar, or keeps a standalone panel as an overlay. Legacy runtime xs is treated as omitted.
triggerElementsreadonly (Element | null | undefined)[] | null[]DOM references to consumer-controlled triggers. Enabled primary presses leave visibility to the consumer click handler.
useBackdrop'static' | booleantrueControls backdrop behavior: true (default, clickable), "static" (not clickable), false (no backdrop or outside-click dismissal)
onHideEventListener-Callback when the offcanvas begins hiding
onHidePreventedEventListener-Callback when the offcanvas is shown, its backdrop is static and a click outside of the offcanvas is performed. The event is also fired when the escape key is pressed and the keyboard option is set to false.
onHiddenEventListener-Callback when the offcanvas has finished hiding
onShowEventListener-Callback when the offcanvas begins showing
onShownEventListener-Callback when the offcanvas has finished showing

Offcanvas.Header Props

NameTypeDefaultDescription
classstring-Additional CSS classes to apply to the component
elementRefHTMLElement | nullnullReference to the DOM element
isDismissiblebooleanfalseWhen true, displays a close button in the header

Offcanvas.Title Props

NameTypeDefaultDescription
classstring-Additional CSS classes to apply to the component
elementRefHTMLElement | nullnullReference to the DOM element
level1 | 2 | 3 | 4 | 5 | 65Heading level to use for the title (h1-h6)

Offcanvas.Body Props

NameTypeDefaultDescription
classstring-Additional CSS classes to apply to the component
elementRefHTMLElement | nullnullReference to the DOM element

CSS Classes

The component applies Bootstrap's offcanvas classes based on the provided props:

  • offcanvas - Base class for the Offcanvas component
  • offcanvas-[breakpoint] - Applied for sm through xxl; there is no offcanvas-xs class
  • offcanvas-[placement] - Position classes (start, end, top, bottom)
  • show - Applied when the offcanvas is visible
  • offcanvas-header - Applied to Offcanvas.Header components
  • offcanvas-title - Applied to Offcanvas.Title components
  • offcanvas-body - Applied to Offcanvas.Body components
  • offcanvas-backdrop - Applied to the backdrop when enabled

Responsive Behavior

The Offcanvas component can be configured for responsive behavior:

  • When showOnBreakpoint is set, the component appears as a regular element on screens at or above the specified breakpoint
  • On screens below the breakpoint, it behaves as a regular offcanvas that must be toggled to appear
  • This enables creating responsive sidebar-like layouts that collapse into an offcanvas on smaller screens