Drawer

Drawer creates accessible off-canvas panels for navigation, filters, carts, settings, profile menus, and modal-like side panels. The visual structure is controlled by CSS — the FrontAlign runtime handles opening, closing, focus management, backdrop behavior, body scroll locking, keyboard support, and lifecycle events automatically.

Drawer is suited for mobile menus, dashboard side panels, shopping carts, notification panels, advanced filters, quick settings, and any interface that needs a temporary surface above the page.


Getting Started

Add fa-component="drawer" to the drawer root and use a trigger with fa-toggle="drawer". The trigger points to the drawer through data-target. No manual event listeners are needed.

<button class="button is-primary" fa-toggle="drawer" data-target="#site-drawer">
  Open drawer
</button>

<aside id="site-drawer" class="drawer">
  <div class="drawer-panel">
    <div class="drawer-header">
      <h3 class="drawer-title">Menu</h3>
      <button class="button drawer-close" aria-label="Close drawer"></button>
    </div>

    <div class="drawer-body">
      <a href="/docs">Documentation</a>
      <a href="/components">Components</a>
      <a href="/examples">Examples</a>
    </div>
  </div>
</aside>

Start Drawer

End Drawer

Top Drawer

Bottom Drawer

Runtime

The Javascript runtime is required for the Drawer component to function correctly. After new FrontAlign() runs on the client, delegated drawer behavior is activated.

The runtime listens for clicks on [fa-toggle="drawer"], reads the data-target selector, finds the matching .drawer.

Structure

The drawer markup is intentionally small. The root represents the overlay layer, and .drawer-panel represents the visible surface.

ElementClass / attributePurpose
Triggerfa-toggle="drawer"Marks an element as a drawer opener.
Target selectordata-targetPoints the trigger to the drawer root.
RootdrawerCreates the overlay and backdrop layer.
Paneldrawer-panelVisible drawer surface. Backdrop clicks outside this element close the drawer.
Positionis-startSlides the drawer from the left side (default).
Positionis-endSlides the drawer from the right side.
Positionis-topSlides the drawer from the top edge.
Positionis-bottomSlides the drawer from the bottom edge.
Modifierhas-no-backdropRemoves the backdrop layer while keeping the drawer interactive.
Close buttondrawer-closeDismisses the closest drawer.

Basic Example

This

<button class="button is-primary" fa-toggle="drawer" data-target="#productFilters">
  Open Filters
</button>

<div class="drawer-panel"id="productFilters">

  <div class="drawer-header">
    <h3 class="drawer-title">Filters</h3>
    <button class="drawer-close" aria-label="Close filters"></button>
  </div>

  <div class="drawer-body">
    <form fa-component="form">

      <div class="group">
        <label class="form-label">Search</label>
        <input type="search" class="form-input" placeholder="Search products..." />
      </div>

      <div class="group">
        <label class="form-label">Category</label>
        <div class="group-inline is-flex flex-column gap-2">
          <div class="group-checkbox">
            <input type="checkbox" class="form-input-checkbox" id="cat-electronics" />
            <label class="form-label" for="cat-electronics">Electronics</label>
          </div>
          <div class="group-checkbox">
            <input type="checkbox" class="form-input-checkbox" id="cat-furniture" />
            <label class="form-label" for="cat-furniture">Furniture</label>
          </div>
          <div class="group-checkbox">
            <input type="checkbox" class="form-input-checkbox" id="cat-accessories" />
            <label class="form-label" for="cat-accessories">Accessories</label>
          </div>
        </div>
      </div>

      <div class="group">
        <label class="form-label">Brand</label>
        <select class="form-select">
          <option>All Brands</option>
          <option>Apple</option>
          <option>Samsung</option>
          <option>Sony</option>
        </select>
      </div>

      <div class="group">
        <label class="form-label">Price Range</label>
        <input type="range" class="form-input-range" min="0" max="100" value="10" />
      </div>

      <div class="group">
        <label class="form-label">Availability</label>
        <div class="group-inline is-flex flex-column gap-2">
          <div class="group-radio">
            <input type="radio" class="form-input-radio" name="availability" id="avail-instock" />
            <label class="form-label" for="avail-instock">In Stock</label>
          </div>
          <div class="group-radio">
            <input type="radio" class="form-input-radio" name="availability" id="avail-all" checked />
            <label class="form-label" for="avail-all">All Products</label>
          </div>
        </div>
      </div>

    </form>
  </div>

  <div class="drawer-footer">
    <button class="button is-light" type="button">Reset</button>
    <button class="button is-primary" type="submit">Apply Filters</button>
  </div>

</div>

Filters

Configuration Attributes

Drawer uses a small set of stable runtime selectors and state classes.

Attribute / selectorDefaultBehavior
fa-toggle="drawer"Required on triggerOpens the drawer connected through data-target.
data-targetRequired for external triggerStores the CSS selector of the drawer root.
has-no-backdropOptional classPrevents body scroll locking for drawers that should not behave like a blocking overlay.
openedRuntime classApplied to the drawer root while it is visible.

Close Button

Use .drawer-close inside the drawer panel when the user should be able to dismiss the panel from inside the drawer.

<aside id="account-drawer" class="drawer" fa-component="drawer">
  <div class="drawer-panel">
    <div class="drawer-header">
      <h3 class="drawer-title">Account</h3>
      <button class="button drawer-close" aria-label="Close drawer"></button>
    </div>

    <div class="drawer-body">
      <p>Manage your profile, billing, and notification preferences.</p>
    </div>
  </div>
</aside>

The runtime finds the closest .drawer from the close button and removes the opened state class.

RTL Support

Drawer supports right-to-left layouts automatically through the global dir="rtl" attribute.

In RTL mode, the default drawer opens from the right side, while .is-end opens from the left side. Top and bottom drawers keep their vertical behavior.

<html dir="rtl">
  <body>
    <button class="button is-primary" fa-toggle="drawer" data-target="#rtl-drawer">
      Open RTL drawer
    </button>

    <div id="rtl-drawer" class="drawer">
      <div class="drawer-panel">
        <div class="drawer-header">
          <h3 class="drawer-title">RTL Drawer</h3>
          <button class="drawer-close" aria-label="Close drawer"></button>
        </div>

        <div class="drawer-body">
          Drawer direction is automatically mirrored.
        </div>
      </div>
    </div>
  </body>
</html>

Runtime Behavior

Drawer is initialized through the FrontAlign runtime.

When new FrontAlign() runs, the delegated component system listens for drawer triggers, close buttons, backdrop clicks, and Escape key presses.

BehaviorDescription
Delegated eventsDrawer triggers and close buttons do not need individual listeners.
Target lookupThe runtime reads data-target and opens the matching drawer.
Open lockAn internal lock prevents repeated open or close actions while transitions are running.
Focus trapTab and Shift+Tab stay inside the active drawer while it is open.
ESC closePressing Escape closes the active drawer.
Backdrop closeClicking outside .drawer-panel closes the active drawer.
Body scroll lockThe ScrollBarHelper locks page scrolling and compensates scrollbar width unless has-no-backdrop is used.
Accessibility attributesThe runtime applies role="dialog", aria-modal="true", and tabindex="-1" while open.

Drawer Without Backdrop

Use .has-no-backdrop when the drawer should stay above the page without dimming or blocking the entire interface.

<button class="button is-primary" fa-toggle="drawer" data-target="#settings-drawer">
  Open drawer
</button>

<div id="settings-drawer" class="drawer has-no-backdrop is-end">
  <div class="drawer-panel">
    <div class="drawer-header">
      <h3 class="drawer-title">Settings</h3>
      <button class="drawer-close" aria-label="Close drawer"></button>
    </div>

    <div class="drawer-body">
      <p>This drawer opens without a backdrop overlay.</p>
    </div>
  </div>
</div>

Settings

This drawer opens without a backdrop overlay.


Lifecycle Events

Drawer dispatches lifecycle events before and after opening or closing.

The open and close events are cancelable. Call event.preventDefault() to stop the action.

EventCancelableWhen it fires
fa.drawer.openYesBefore a drawer opens.
fa.drawer.openedNoAfter the drawer transition finishes and focus is trapped.
fa.drawer.closeYesBefore a drawer closes.
fa.drawer.closedNoAfter the drawer transition finishes and body scroll is restored.

Each event includes drawer in event.detail.

document.addEventListener('fa.drawer.opened', (event) => {
  const { drawer } = event.detail;

  console.log('Drawer opened:', drawer);
});

Prevent Opening

Use the cancelable fa.drawer.open event when a drawer should not open under certain conditions.

document.addEventListener('fa.drawer.open', (event) => {
  const { drawer } = event.detail;

  if (drawer.dataset.locked === 'true') {
    event.preventDefault();
  }
});

Prevent Closing

Use the cancelable fa.drawer.close event when a drawer contains unsaved changes.

document.addEventListener('fa.drawer.close', (event) => {
  const { drawer } = event.detail;

  if (drawer.dataset.dirty === 'true') {
    event.preventDefault();
  }
});

React Usage

For React and Next.js projects, use the React integration layer instead of manually wiring drawer behavior inside every component.

import { useDrawer } from 'frontalign/react';

export function AppDrawer() {
  useDrawer();

  return (
    <>
      <button className="button is-primary" fa-toggle="drawer" data-target="#app-drawer">
        Open drawer
      </button>

      <aside id="app-drawer" className="drawer">
        <div className="drawer-panel">
          <div className="drawer-header">
            <h3 className="drawer-title">Application menu</h3>
            <button className="button drawer-close" aria-label="Close drawer" />
          </div>

          <div className="drawer-body">
            <a href="/dashboard">Dashboard</a>
            <a href="/settings">Settings</a>
            <a href="/billing">Billing</a>
          </div>
        </div>
      </aside>
    </>
  );
}

Continue with the React Integration guide for provider setup, hooks, runtime helpers, and Next.js usage.

Accessibility

Drawer automatically receives modal semantics while it is open.

Recommended practices:

  • Use a real <button> for drawer triggers when possible.
  • Add a visible title inside the drawer panel.
  • Add aria-label to icon-only .drawer-close buttons.
  • Keep focusable content inside .drawer-panel.
  • Do not place interactive controls outside the panel if the drawer is modal.
  • Keep the DOM order logical so focus moves naturally through the drawer content.
  • Use Escape and backdrop close as secondary exits, not the only visible close method.
<button class="button" fa-toggle="drawer" data-target="#accessible-drawer">
  Open settings
</button>

<aside id="accessible-drawer" class="drawer"  aria-labelledby="settings-title">
  <div class="drawer-panel">
    <div class="drawer-header">
      <h3 id="settings-title" class="drawer-title">Settings</h3>
      <button class="button drawer-close" aria-label="Close settings drawer"></button>
    </div>

    <div class="drawer-body">
      <button class="button is-primary">Save changes</button>
    </div>
  </div>
</aside>

The runtime adds role="dialog", aria-modal="true", and tabindex="-1" when the drawer opens, then removes the modal attributes when it closes.

Notes

  • Use fa-toggle="drawer" on the trigger and data-target for the drawer selector.
  • The drawer root should include fa-component="drawer".
  • The visible surface must use .drawer-panel.
  • The runtime applies the opened class to the drawer root.
  • .drawer-close closes the closest drawer.
  • Clicking outside .drawer-panel closes the active drawer.
  • Escape closes the active drawer.
  • Customize drawer width by overriding the --drawer-width CSS custom property on .drawer-panel for larger desktop layouts.

FrontAlign