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.
| Element | Class / attribute | Purpose |
|---|---|---|
| Trigger | fa-toggle="drawer" | Marks an element as a drawer opener. |
| Target selector | data-target | Points the trigger to the drawer root. |
| Root | drawer | Creates the overlay and backdrop layer. |
| Panel | drawer-panel | Visible drawer surface. Backdrop clicks outside this element close the drawer. |
| Position | is-start | Slides the drawer from the left side (default). |
| Position | is-end | Slides the drawer from the right side. |
| Position | is-top | Slides the drawer from the top edge. |
| Position | is-bottom | Slides the drawer from the bottom edge. |
| Modifier | has-no-backdrop | Removes the backdrop layer while keeping the drawer interactive. |
| Close button | drawer-close | Dismisses 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 / selector | Default | Behavior |
|---|---|---|
fa-toggle="drawer" | Required on trigger | Opens the drawer connected through data-target. |
data-target | Required for external trigger | Stores the CSS selector of the drawer root. |
has-no-backdrop | Optional class | Prevents body scroll locking for drawers that should not behave like a blocking overlay. |
opened | Runtime class | Applied 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.
| Behavior | Description |
|---|---|
| Delegated events | Drawer triggers and close buttons do not need individual listeners. |
| Target lookup | The runtime reads data-target and opens the matching drawer. |
| Open lock | An internal lock prevents repeated open or close actions while transitions are running. |
| Focus trap | Tab and Shift+Tab stay inside the active drawer while it is open. |
| ESC close | Pressing Escape closes the active drawer. |
| Backdrop close | Clicking outside .drawer-panel closes the active drawer. |
| Body scroll lock | The ScrollBarHelper locks page scrolling and compensates scrollbar width unless has-no-backdrop is used. |
| Accessibility attributes | The 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.
| Event | Cancelable | When it fires |
|---|---|---|
fa.drawer.open | Yes | Before a drawer opens. |
fa.drawer.opened | No | After the drawer transition finishes and focus is trapped. |
fa.drawer.close | Yes | Before a drawer closes. |
fa.drawer.closed | No | After 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-labelto icon-only.drawer-closebuttons. - 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 anddata-targetfor the drawer selector. - The drawer root should include
fa-component="drawer". - The visible surface must use
.drawer-panel. - The runtime applies the
openedclass to the drawer root. .drawer-closecloses the closest drawer.- Clicking outside
.drawer-panelcloses the active drawer. - Escape closes the active drawer.
- Customize drawer width by overriding the
--drawer-widthCSS custom property on.drawer-panelfor larger desktop layouts.