# Offcanvas Widget

> Slide-in panel triggered by button / icon click. Panel can contain saved Elementor template or custom content. 4 slide directions (left / right / top / bottom). Uses Pro's `Helper` (not Lite's).

**Class file:** `includes/Elements/Offcanvas.php` (1,568 lines)
**Slug:** `offcanvas` (widget id `eael-offcanvas`)
**Public docs:** <https://essential-addons.com/elementor/docs/offcanvas/>
**Pro-shared:** Pro-only. Pro's own `Helper`.

## Overview

Off-canvas drawer that slides in from a screen edge. Trigger element configurable (button / icon / hamburger). Panel content can be a saved Elementor template or inline HTML. Optional overlay backdrop dimming the page. Common use: mobile menus, cart drawers, sidebars triggered from a header button.

## Pro vs Lite

Pro-only. Uses Pro's `Helper` (atypical; most widgets use Lite's).

## File Map

| File | Role |
| --- | --- |
| `includes/Elements/Offcanvas.php` | Widget class (1,568 lines) |
| `src/css/view/offcanvas.scss` → `assets/front-end/css/view/offcanvas.min.css` | Panel + overlay styling |
| `src/js/view/offcanvas.js` → `assets/front-end/js/view/offcanvas.min.js` | Open / close handlers |
| `config.php` entry `'offcanvas'` | Self CSS + self JS |

## Architecture

- **Composes Pro's `Helper`** (line 14) — atypical.
- **No vendor libs** — pure CSS transform for slide-in, JS for state toggle.
- **Single template render** — slide direction is a CSS modifier class.
- **Trigger + Panel both render in DOM** — panel is fixed-position, hidden via `transform: translateX(-100%)` etc.
- **Backdrop overlay** optional — adds `<div class="eael-offcanvas-overlay">` that fades in with the panel.
- **Saved-template support** — content can be a `template_id` resolved via Elementor's frontend builder.

## Render Output

```html
<div class="eael-offcanvas-wrap">
  <button class="eael-offcanvas-trigger"
          data-target="#eael-offcanvas-{element-id}"
          [?] data-direction="left|right|top|bottom">
    [?] <i class="hamburger-icon"></i>
    [?] {Custom trigger text}
  </button>
</div>
<!-- Panel rendered outside trigger DOM, body-attached -->
<div class="eael-offcanvas-overlay"></div>
<aside class="eael-offcanvas-panel eael-offcanvas-panel-{direction}"
       id="eael-offcanvas-{element-id}">
  <button class="eael-offcanvas-close">&times;</button>
  <div class="eael-offcanvas-content">
    [?] {inline HTML}
    [?] {rendered Elementor template}
  </div>
</aside>
```

## Controls Reference

| Control id | Tab → Section | Type | Purpose |
| --- | --- | --- | --- |
| Trigger type | Content → Trigger | CHOOSE | `button` / `icon` / `hamburger` |
| Trigger label / icon | Content → Trigger | TEXT / ICONS | Per-type |
| Content type | Content → Panel | SELECT | `inline` / `template` |
| Inline content | Content → Panel | WYSIWYG | When type = inline |
| Template ID | Content → Panel | SELECT | When type = template |
| Slide direction | Content → Panel | CHOOSE | `left` / `right` / `top` / `bottom` |
| Panel width / height | Content → Panel | SLIDER | Per-direction |
| Overlay enable / color / opacity | Content → Overlay | SWITCHER + COLOR + SLIDER | Backdrop |
| Animation duration | Content → Animation | SLIDER | Slide transition speed |
| Close on overlay click | Content → Behavior | SWITCHER | Dismiss UX |
| Close on Escape | Content → Behavior | SWITCHER | Keyboard dismiss |

## Conditional Dependencies

```text
trigger_type variations show appropriate controls
content_type = 'template' → template SELECT
content_type = 'inline' → WYSIWYG
overlay_enable = 'yes' → overlay color/opacity visible
```

## JavaScript Lifecycle

```js
$trigger.on( 'click', function() {
    var $target = $( $trigger.data( 'target' ) );
    $target.addClass( 'open' );
    $overlay.addClass( 'visible' );
} );

$close.on( 'click', closeHandler );
$overlay.on( 'click', closeHandler ); // if enabled
$( document ).on( 'keydown', function( e ) { if ( e.key === 'Escape' ) closeHandler(); } );
```

## Hooks & Filters

Standard widget render. Saved-template render uses Elementor's frontend builder API.

## Common Issues

| Symptom | Cause | Fix |
| --- | --- | --- |
| Panel doesn't slide | CSS transition / transform missing | Verify Pro CSS loaded |
| Multiple offcanvas widgets conflict | All share `.eael-offcanvas-overlay` class? | Verify unique IDs / scoped state |
| Saved template renders empty | Template ID invalid | Re-pick template |
| Esc doesn't close on mobile | No physical Esc key on touch | Expected; tap overlay instead |
| Body scrolls when panel open | No body-lock | Add `overflow: hidden` to `body` on open |

## Known Limitations

- **No focus trap** — Tab navigates outside the panel when open. Accessibility gap.
- **No `aria-modal` / `aria-hidden`** state management — screen readers may announce both panel and underlying content
- **Body scroll-lock not enforced** — page scrolls behind the open panel
- **No `prefers-reduced-motion`**
- **Saved template renders in DOM at page load** — even if user never opens — bulky pages

## Cross-References

- Sibling: [`lightbox.md`](lightbox.md), [`one-page-navigation.md`](one-page-navigation.md)
- Shared patterns: [`_patterns.md`](_patterns.md)
