Drawer
A container component that slides in from the left or right. Typically containing menus, search or other content.

Documentation
Installation
npm install @vrembem/drawer
Styles
@use "@vrembem/drawer";
JavaScript
import Drawer from '@vrembem/drawer';
const drawer = new Drawer({ autoInit: true });
Markup
Drawers are composed using classes for styling and data attributes for JavaScript functionality. To link a drawer toggle, open or close trigger to a drawer, use a unique identifier as the values for both the trigger and drawer's respective data attributes. Close buttons can be left value-less if placed inside a drawer element they're meant to close.
data-drawer="[unique-id]"
data-drawer-dialog
data-drawer-toggle="[unique-id]"
data-drawer-open="[unique-id]"
data-drawer-close="[unique-id]"
(or value-less if inside drawer)
<div class="drawer__wrapper">
<aside data-drawer="[unique-id]" class="drawer">
<div data-drawer-dialog class="drawer__dialog">
<button data-drawer-close>...</button>
</div>
</aside>
<div class="drawer__main">
<button data-drawer-toggle="[unique-id]">...</button>
<button data-drawer-open="[unique-id]">...</button>
<button data-drawer-close="[unique-id]">...</button>
</div>
</div>
Drawer dialogs are the actual dialog element within a drawer and are defined using a the value-less data-drawer-dialog
attribute. The dialog component is a great fit for composing a drawer’s content.
<aside data-drawer="[unique-id]" class="drawer">
<div data-drawer-dialog class="drawer__dialog dialog">
<div class="dialog__header">
...
<button data-drawer-close>...</button>
</div>
<div class="dialog__body">
...
</div>
<div class="dialog__footer">
...
</div>
</div>
</aside>
data-drawer-breakpoint
In cases where you'd like a drawer to switch to a drawer modal on a specific breakpoint, use the data-drawer-breakpoint
data attribute with either a breakpoint key or a specific pixel value.
<aside data-drawer="[unique-id]" data-drawer-breakpoint="md" class="drawer">
...
</aside>
<aside data-drawer="[unique-id]" data-drawer-breakpoint="900px" class="drawer">
...
</aside>
A custom breakpoints object can be passed in using the breakpoints
option. Otherwise, default values are set via the core variables module.
const drawer = new Drawer({
breakpoints: {
xs: '480px',
sm: '620px',
md: '760px',
lg: '990px',
xl: '1380px'
}
});
data-drawer-focus
Drawer dialogs are given focus on open by default as long as the setTabindex
option is set to true
or if the drawer dialog has tabindex="-1"
set manually. If focus on a specific element inside a drawer is preferred, give it the data-drawer-focus
attribute. The focus in either case is returned to the trigger element once the drawer is closed.
<div cass="drawer__wrapper">
<aside data-drawer="[unique-id]" class="drawer">
<div data-drawer-dialog class="drawer__dialog">
...
</div>
</aside>
<aside data-drawer="[unique-id]" class="drawer">
<div data-drawer-dialog class="drawer__dialog">
<button data-drawer-focus>...</button>
</div>
</aside>
<div class="drawer__main">
<button data-drawer-toggle="[unique-id]">...</button>
</div>
</div>
Drawer State
By default, the state of a drawer is saved to local storage and applied persistently under the "DrawerState" local storage variable. Set stateSave: false
to disable save state. Use stateKey: "[CUSTOM-KEY]"
to change the key that save state is stored under.
Behavior and Accessibility
Drawers when in their modal context follow a set of patterns expected from other modals on the web. Here's what to expect:
- When a drawer modal is opened, focus is moved to the dialog or an element inside.
- Drawer modals provide standard methods for the user to close such as using the
esc
key or clicking outside the dialog. - While the drawer modal is active, contents obscured by the drawer modal are inaccessible to all users.
- When a drawer modal is closed, focus is returned to the initial trigger element that activated the dialog.
To take full advantage of drawer modal's accessibility features, it's recommened to that you set the selectorInert
option to all elements that are ouside the drawer modal (most likely the drawer__main
element). All elements that match the selectorInert
selector will be given the inert
attribute as well as aria-hidden="true"
when a modal is opened.
Inert is not currently widly supportted by all browsers. Consider using a polyfill such as wicg-inert or Google's inert-polyfill.
Example
Here's an example where we want the [role="main"]
content area to be inaccessible while drawer modals are open. We also want to disable other scrollable elements using the selectorOverflow
option.
const drawer = new Drawer({
autoInit: true,
selectorInert: '[role="main"]',
selectorOverflow: 'body, [role="main"]'
});
Modifiers
drawer_modal
Convert a drawer into it’s modal state with the drawer_modal
modifier class. Only one modal can be open at a time.
<div class="drawer__wrapper">
<aside data-drawer="[unique-id]" class="drawer drawer_modal">
...
</aside>
<div class="drawer__main">
<button data-drawer-toggle="[unique-id]">
...
</button>
</div>
</div>
drawer_pos_[value]
Drawers can slide in from the left or right using the position modifiers:
drawer_pos_left
drawer_pos_right
<div class="drawer__wrapper">
<aside data-drawer="[unique-id]" class="drawer drawer_pos_left">
...
</aside>
<aside data-drawer="[unique-id]" class="drawer drawer_pos_right">
...
</aside>
<div class="drawer__main">
<button data-drawer-toggle="[unique-id]">
...
</button>
<button data-drawer-toggle="[unique-id]">
...
</button>
</div>
</div>
If a position modifier is not provided, the drawer will appear based on it’s location in the DOM relative to the main content area and other drawers.
Customization
Sass Variables
Variable | Default | Description |
---|
$prefix-block | null | String to prefix blocks with. |
$prefix-element | "__" | String to prefix element with. |
$prefix-modifier | "_" | String to prefix modifier with. |
$prefix-modifier-value | "_" | String to prefix modifier values with. |
$width | 18em | The width of drawers. |
$max-width | 100% | The max-width of drawers. |
$border | null | Border applied to drawer items with position modifiers. Shown on side of drawers facing drawer main. |
$sep-border | null | Border color applied to dialog components within drawer items. |
$background | core.$shade | Background color applied to drawer items. |
$box-shadow | none | Box shadow applied to drawer items. |
$travel | 5em | Distance that drawers travel during their transition. |
$transition-duration | core.$transition-duration | Duration of drawer transition. |
$transition-timing-function | core.$transition-timing-function | Timing function used for drawer transitions. |
$wrapper-height | 100% | The height given to drawer wrapper element. |
$modal-zindex | 900 | Modal z-index to help control the stack order. Should be highest priority as modal. |
$modal-width | $width | The width of modal drawers. |
$modal-max-width | 80% | The max-width of modal drawers. |
$modal-sep-border | null | Border color applied to dialog components within modal drawer items. |
$modal-background | core.$white | Background color applied to modal drawer items. |
$modal-box-shadow | core.$box-shadow-24dp | Box shadow applied to modal drawer items. |
$modal-screen-background | core.$night | Background color of modal screen. |
$modal-screen-background-alpha | 0.8 | The alpha channel for the modal screen. |
JavaScript Options
Key | Default | Description |
---|
autoInit | false | Automatically initializes the instance. |
dataDrawer | 'drawer' | Data attribute for a drawer. |
dataDialog | 'drawer-dialog' | Data attribute for a drawer dialog. |
dataToggle | 'drawer-toggle' | Data attribute for a drawer toggle trigger. |
dataOpen | 'drawer-open' | Data attribute for a drawer open trigger. |
dataClose | 'drawer-close' | Data attribute for a drawer close trigger. |
dataBreakpoint | 'drawer-breakpoint' | Data attribute for setting a drawer's breakpoint. |
dataFocus | 'drawer-focus' | Data attribute for setting a drawer's focus element. |
stateOpen | 'is-opened' | Class used for open state. |
stateOpening | 'is-opening' | Class used for transitioning to open state. |
stateClosing | 'is-closing' | Class used for transitioning to closed state. |
stateClosed | 'is-closed' | Class used for closed state. |
classModal | 'drawer_modal' | Class used for toggling the drawer modal state. |
selectorInert | null | Applies inert and aria-hidden attributes to all matching elements when a modal drawer is opened. |
selectorOverflow | null | Applies overflow:hidden styles on all matching elements when a modal drawer is opened. |
breakpoints | core.breakpoints | An object with key/value pairs defining a breakpoints set. |
customEventPrefix | 'drawer:' | Prefix to be used on custom events. |
stateSave | true | Toggles the save state feature. |
stateKey | "DrawerState" | Defines the localStorage key where drawer states are saved. |
setTabindex | true | Whether or not to set tabindex="-1" on all drawer dialog elements on init. |
transition | true | Toggle the transition animation for the drawer. Set to false to disable. |
Events
drawer:opened
Emits when the drawer has opened.drawer:closed
Emits when the drawer has closed.drawer:breakpoint
Emits when the drawer has hit a breakpoint.drawer:toModal
Emits when the drawer is switched to it's modal state.drawer:toDefault
Emits when the drawer is switched to it's default state.
API
drawer.init()
Initializes the drawer instance. During initialization, the following processes are run:
- Runs
stateSet()
to apply the initial state for all drawers. - Runs
setTabindex()
to apply tabindex for all drawer dialogs. - Runs
breakpoint.init()
to initialize all breakpoints for drawers. - Adds the
click
event listener to the document. - Adds the
keyup
event listener for closing modal drawers with the esc
key.
const drawer = new Drawer();
drawer.init();
drawer.destroy()
Destroys and cleans up the drawer instantiation. During cleanup, the following processes are run:
- Runs
breakpoint.destroy()
to remove all active breakpoints. - Clears the stored
drawer.memory
object. - Clears the stored
drawer.state
object. - Removes the saved state from local storage.
- Removes the
click
event listener from the document. - Removes the
keyup
event listener from the document.
const drawer = new Drawer();
drawer.init();
drawer.destroy();
drawer.toggle(key)
Toggles a drawer when provided the drawer key and returns a promise that resolves to the drawer object once the transition has finished.
Parameters
key [String]
A unique key that matches the value of a drawer data-drawer
attribute.
Returns
Promise
The returned promise value will either be the HTML object
of the drawer that was toggled, or error
if a drawer was not found.
<div class="drawer" data-drawer="drawer-key">...</div>
drawer.toggle('drawer-key');
drawer.toggle('drawer-key').then((result) => {
console.log(result);
});
drawer.open(key)
Opens a drawer when provided the drawer key and returns a promise that resolves to the drawer object once the transition has finished.
Parameters
key [String]
A unique key that matches the value of a drawer data-drawer
attribute.
Returns
Promise
The returned promise value will either be the HTML object
of the drawer that was opened, or error
if a drawer was not found.
<div class="drawer" data-drawer="drawer-key">...</div>
drawer.open('drawer-key');
drawer.open('drawer-key').then((result) => {
console.log(result);
});
drawer.close(key)
Closes a drawer when provided the drawer key and returns a promise that resolves to the drawer object once the transition has finished.
Parameters
key [String]
A unique key that matches the value of a drawer data-drawer
attribute.
Returns
Promise
The returned promise value will either be the HTML object
of the drawer that was closed, or error
if a drawer was not found.
<div class="drawer" data-drawer="drawer-key">...</div>
drawer.close('drawer-key');
drawer.close('drawer-key').then((result) => {
console.log(result);
});
drawer.getDrawer(key)
Returns a drawer that matches the provided unique drawer key.
Parameters
key [String]
A unique key that matches the value of a drawer data-drawer
attribute.
Returns
HTML object
The matching drawer element.
<div class="drawer" data-drawer="drawer-key">...</div>
const el = drawer.getDrawer('drawer-key');
console.log(el);
drawer.setTabindex
Sets the tabindex="-1"
attribute on all drawer dialogs. This makes it possible to set focus on the dialog when opened but won't allow users to focus it using the keyboard. This is ran automatically on drawer.init()
if the setTabindex
option is set to true
.
<aside data-drawer="[unique-id]" class="drawer">
<div data-drawer-dialog class="drawer__dialog">
...
</div>
</aside>
drawer.setTabindex();
<aside data-drawer="[unique-id]" class="drawer">
<div data-drawer-dialog class="drawer__dialog" tabindex="-1">
...
</div>
</aside>
drawer.breakpoint.init()
Initializes the drawer breakpoint feature. During initialization, all drawers with data-drawer-breakpoint
are retrieved and a MediaQueryList
is created for each. Each MediaQueryList
and it's associated drawer key is stored in the drawer.mediaQueryLists
array. This is ran automatically on drawer.init()
.
<aside data-drawer="drawer-1" data-drawer-breakpoint="420px" class="drawer">
...
</aside>
<aside data-drawer="drawer-2" data-drawer-breakpoint="740px" class="drawer">
...
</aside>
drawer.breakpoint.init();
console.log(drawer.mediaQueryLists);
[{
mql: MediaQueryList
drawer: 'drawer-1'
}, {
mql: MediaQueryList
drawer: 'drawer-2'
}]
drawer.breakpoint.destroy()
Destroys the drawer breakpoint feature. This process involves removeing all attached media match listeners from the stored MediaQueryList
s and then clearing the stored array. This is ran automatically on drawer.destroy()
.
drawer.breakpoint.destroy();
console.log(drawer.mediaQueryLists);
null
drawer.breakpoint.check()
Force a check of any drawers that meet their breakpoint condition. If their state doesn't match the current breakpoint condition, they'll be updated. This is useful when used with frameworks that dynamically re-render components on the fly.
<aside data-drawer="[unique-id]" data-drawer-breakpoint="sm" class="drawer">
...
</aside>
drawer.breakpoint.check();
<aside data-drawer="[unique-id]" data-drawer-breakpoint="sm" class="drawer drawer_modal">
...
</aside>
drawer.switchToModal(key)
Switches a drawer to it's modal state.
Parameters
key [String]
A unique key that matches the value of a drawer data-drawer
attribute.
<div class="drawer" data-drawer="drawer-key">...</div>
drawer.switchToModal('drawer-key');
<div class="drawer drawer_modal" data-drawer="drawer-key">...</div>
drawer.switchToDefault(key)
Switches a drawer to it's default non-modal state.
Parameters
key [String]
A unique key that matches the value of a drawer data-drawer
attribute.
<div class="drawer drawer_modal" data-drawer="drawer-key">...</div>
drawer.switchToDefault('drawer-key');
<div class="drawer" data-drawer="drawer-key">...</div>
drawer.stateSet()
Sets the current saved state of all drawer elements based on the values set in localStorage and updates the instance drawer.state
object.
<aside data-drawer="drawer-1" class="drawer is-opened">...</aside>
<aside data-drawer="drawer-2" class="drawer is-opened">...</aside>
drawer.stateSet();
<aside data-drawer="drawer-1" class="drawer is-closed">...</aside>
<aside data-drawer="drawer-2" class="drawer is-closed">...</aside>
drawer.stateSave(target)
Saves the current state of drawers to localStorage and drawer's drawer.state
object. This is useful when state becomes out of sync or the DOM is re-rendered in a way that breaks current state.
Parameters
HTML Element [Object]
(Default: null) A specific target to save state. If nothing is passed, all drawers in the DOM will have their state saved.
<aside data-drawer="drawer-1" class="drawer is-closed">...</aside>
<aside data-drawer="drawer-2" class="drawer is-opened">...</aside>
console.log(drawer.state);
drawer.stateSave();
console.log(drawer.state);
drawer.stateClear()
Clears the existing saved states in both localStorage and drawer's drawer.state
object.
console.log(drawer.state);
drawer.stateClear();
console.log(drawer.state);