Drawer
A sliding panel that appears from the edge of the screen to display content.
First release: 1.0.0 Latest update: July 1, 2025 Current version: 1.0.0
Import
import { PlusDrawer } from '@plusui/react';import { PlusDrawerComponent } from '@plusui/angular';import { PlusDrawer } from '@plusui/vue';import PlusDrawer from '@plusui/svelte';<script src="https://cdn.jsdelivr.net/npm/@plusui/core"></script>Package
@plusui/react@plusui/angular@plusui/vue@plusui/svelte@plusui/coreDocs
Changelog
Drawer Changelog
Recent changes and updates for the drawer component
v1.0.0
Added Drawer component for side panels
- 4 positions: left, right, top, bottom
- Multiple sizes: sm, md, lg
- Custom header, body, and footer slots
- Smooth animations
- Accessibility support
The Drawer component provides a flexible way to display navigation, forms, details, or other supplementary content in a panel that slides in from the edge of the viewport. It helps manage screen real estate effectively.
Anatomy
Section titled “Anatomy”
Basic Example & Controlling State
Section titled “Basic Example & Controlling State”Use the show() and hide() methods to programmatically open and close the drawer. The isOpen property reflects the current state.
This is the main content of the drawer.
import { PlusDrawer, PlusButton } from '@plusui/react';import { useRef } from 'react';
export default () => { const drawerRef = useRef<HTMLPlusDrawerElement>(null);
const showDrawer = () => drawerRef.current?.show(); const hideDrawer = () => drawerRef.current?.hide();
const handleShow = () => console.log('Drawer shown'); const handleHide = () => console.log('Drawer hidden');
return ( <> <PlusButton onClick={showDrawer}>Open Drawer</PlusButton> <PlusDrawer ref={drawerRef} onPlusDrawerShow={handleShow} onPlusDrawerHide={handleHide} > <div slot="header">Drawer Title</div> <div slot="body"> <p>This is the main content of the drawer.</p> <PlusButton onClick={hideDrawer}>Close Drawer</PlusButton> </div> <div slot="footer">Footer Content</div> </PlusDrawer> </> );};import { Component, ViewChild, ElementRef } from '@angular/core';import type { PlusDrawer } from '@plusui/core';
@Component({ selector: 'app-drawer-basic', template: ` <plus-button (plus-click)="showDrawer()">Open Drawer</plus-button>
<plus-drawer #drawer (plus-drawer-show)="handleShow()" (plus-drawer-hide)="handleHide()"> <div slot="header">Drawer Title</div> <div slot="body"> <p>This is the main content of the drawer.</p> <plus-button (plus-click)="hideDrawer()">Close Drawer</plus-button> </div> <div slot="footer">Footer Content</div> </plus-drawer> `})export class DrawerBasicComponent { @ViewChild('drawer') drawerRef!: ElementRef<PlusDrawer>;
showDrawer() { this.drawerRef.nativeElement.show(); }
hideDrawer() { this.drawerRef.nativeElement.hide(); }
handleShow() { console.log('Drawer shown'); }
handleHide() { console.log('Drawer hidden'); }}<template> <plus-button @plus-click="showDrawer">Open Drawer</plus-button>
<plus-drawer ref="drawerRef" @plus-drawer-show="handleShow" @plus-drawer-hide="handleHide"> <div slot="header">Drawer Title</div> <div slot="body"> <p>This is the main content of the drawer.</p> <plus-button @plus-click="hideDrawer">Close Drawer</plus-button> </div> <div slot="footer">Footer Content</div> </plus-drawer></template>
<script setup>import { ref } from 'vue';
const drawerRef = ref(null);
const showDrawer = () => drawerRef.value?.show();const hideDrawer = () => drawerRef.value?.hide();
const handleShow = () => console.log('Drawer shown');const handleHide = () => console.log('Drawer hidden');</script><script> import { onMount } from 'svelte';
let drawerRef; let openButtonRef; let closeButtonRef;
onMount(() => { if (openButtonRef && closeButtonRef && drawerRef) { openButtonRef.addEventListener('plus-click', () => drawerRef.show()); closeButtonRef.addEventListener('plus-click', () => drawerRef.hide());
drawerRef.addEventListener('plus-drawer-show', () => console.log('Drawer shown')); drawerRef.addEventListener('plus-drawer-hide', () => console.log('Drawer hidden')); } });</script>
<plus-button bind:this={openButtonRef}>Open Drawer</plus-button>
<plus-drawer bind:this={drawerRef}> <div slot="header">Drawer Title</div> <div slot="body"> <p>This is the main content of the drawer.</p> <plus-button bind:this={closeButtonRef}>Close Drawer</plus-button> </div> <div slot="footer">Footer Content</div></plus-drawer><plus-button id="open-button-html">Open Drawer</plus-button>
<plus-drawer id="my-drawer-html"> <div slot="header">Drawer Title</div> <div slot="body"> <p>This is the main content of the drawer.</p> <plus-button id="close-button-html">Close Drawer</plus-button> </div> <div slot="footer">Footer Content</div></plus-drawer>
<script> const drawer = document.getElementById('my-drawer-html'); const openButton = document.getElementById('open-button-html'); const closeButton = document.getElementById('close-button-html');
openButton.addEventListener('plus-click', () => drawer.show()); closeButton.addEventListener('plus-click', () => drawer.hide());
drawer.addEventListener('plus-drawer-show', () => console.log('Drawer shown')); drawer.addEventListener('plus-drawer-hide', () => console.log('Drawer hidden'));</script>Orientation
Section titled “Orientation”The orientation prop determines which edge the drawer slides in from.
import { PlusDrawer, PlusButton } from '@plusui/react';import { useRef, useState } from 'react';
export default () => { const drawerRef = useRef<HTMLPlusDrawerElement>(null); const [orientation, setOrientation] = useState('right');
const openDrawer = (orient: string) => { setOrientation(orient); // Setting state might not be instant, ensure drawer exists before showing requestAnimationFrame(() => drawerRef.current?.show()); };
return ( <> <PlusButton onClick={() => openDrawer('left')}>Left</PlusButton> <PlusButton onClick={() => openDrawer('right')}>Right (Default)</PlusButton> <PlusButton onClick={() => openDrawer('top')}>Top</PlusButton> <PlusButton onClick={() => openDrawer('bottom')}>Bottom</PlusButton>
<PlusDrawer ref={drawerRef} orientation={orientation}> <div slot="header">Orientation Example</div> <div slot="body">Content</div> </PlusDrawer> </> );};import { Component, ViewChild, ElementRef } from '@angular/core';import type { PlusDrawer } from '@plusui/core';
@Component({ selector: 'app-drawer-orientation', template: ` <plus-button (plus-click)="openDrawer('left')">Left</plus-button> <plus-button (plus-click)="openDrawer('right')">Right (Default)</plus-button> <plus-button (plus-click)="openDrawer('top')">Top</plus-button> <plus-button (plus-click)="openDrawer('bottom')">Bottom</plus-button>
<plus-drawer #drawer> <div slot="header">Orientation Example</div> <div slot="body">Content</div> </plus-drawer> `})export class DrawerOrientationComponent { @ViewChild('drawer') drawerRef!: ElementRef<PlusDrawer>;
openDrawer(orientation: 'left' | 'right' | 'top' | 'bottom') { if (this.drawerRef.nativeElement) { this.drawerRef.nativeElement.orientation = orientation; this.drawerRef.nativeElement.show(); } }}<template> <plus-button @plus-click="openDrawer('left')">Left</plus-button> <plus-button @plus-click="openDrawer('right')">Right (Default)</plus-button> <plus-button @plus-click="openDrawer('top')">Top</plus-button> <plus-button @plus-click="openDrawer('bottom')">Bottom</plus-button>
<plus-drawer ref="drawerRef" :orientation="currentOrientation"> <div slot="header">Orientation Example</div> <div slot="body">Content</div> </plus-drawer></template>
<script setup>import { ref, nextTick } from 'vue';
const drawerRef = ref(null);const currentOrientation = ref('right');
const openDrawer = (orientation) => { currentOrientation.value = orientation; // Use nextTick to ensure orientation is updated before showing nextTick(() => { drawerRef.value?.show(); });};</script><script> import { onMount } from 'svelte';
let drawerRef; let currentOrientation = 'right';
onMount(() => { const buttons = ['open-left', 'open-right', 'open-top', 'open-bottom']; const orientations = ['left', 'right', 'top', 'bottom'];
buttons.forEach((buttonId, index) => { const button = document.getElementById(buttonId); if (button && drawerRef) { button.addEventListener('plus-click', () => { drawerRef.orientation = orientations[index]; drawerRef.show(); }); } }); });</script>
<plus-button id="open-left">Left</plus-button><plus-button id="open-right">Right (Default)</plus-button><plus-button id="open-top">Top</plus-button><plus-button id="open-bottom">Bottom</plus-button>
<plus-drawer bind:this={drawerRef} orientation={currentOrientation}> <div slot="header">Orientation Example</div> <div slot="body">Content</div></plus-drawer><plus-button id="open-left-html">Left</plus-button><plus-button id="open-right-html">Right (Default)</plus-button><plus-button id="open-top-html">Top</plus-button><plus-button id="open-bottom-html">Bottom</plus-button>
<plus-drawer id="drawer-orientation-html"> <div slot="header">Orientation Example</div> <div slot="body">Content</div></plus-drawer>
<script> const drawerO = document.getElementById('drawer-orientation-html'); document.getElementById('open-left-html').addEventListener('plus-click', () => { drawerO.orientation = 'left'; drawerO.show(); }); document.getElementById('open-right-html').addEventListener('plus-click', () => { drawerO.orientation = 'right'; drawerO.show(); }); document.getElementById('open-top-html').addEventListener('plus-click', () => { drawerO.orientation = 'top'; drawerO.show(); }); document.getElementById('open-bottom-html').addEventListener('plus-click', () => { drawerO.orientation = 'bottom'; drawerO.show(); });</script>The size prop controls the width (for left/right) or height (for top/bottom) of the drawer.
import { PlusDrawer, PlusButton } from '@plusui/react';import { useRef, useState } from 'react';
export default () => { const drawerRef = useRef<HTMLPlusDrawerElement>(null); const [size, setSize] = useState('md');
const openDrawer = (sz: string) => { setSize(sz); requestAnimationFrame(() => drawerRef.current?.show()); };
return ( <> <PlusButton onClick={() => openDrawer('sm')}>Small</PlusButton> <PlusButton onClick={() => openDrawer('md')}>Medium (Default)</PlusButton> <PlusButton onClick={() => openDrawer('lg')}>Large</PlusButton>
<PlusDrawer ref={drawerRef} size={size}> <div slot="header">Size Example</div> <div slot="body">Content</div> </PlusDrawer> </> );};import { Component, ViewChild, ElementRef } from '@angular/core';import type { PlusDrawer } from '@plusui/core';
@Component({ selector: 'app-drawer-size', template: ` <plus-button (plus-click)="openDrawer('sm')">Small</plus-button> <plus-button (plus-click)="openDrawer('md')">Medium (Default)</plus-button> <plus-button (plus-click)="openDrawer('lg')">Large</plus-button>
<plus-drawer #drawer> <div slot="header">Size Example</div> <div slot="body">Content</div> </plus-drawer> `})export class DrawerSizeComponent { @ViewChild('drawer') drawerRef!: ElementRef<PlusDrawer>;
openDrawer(size: 'sm' | 'md' | 'lg') { if (this.drawerRef.nativeElement) { this.drawerRef.nativeElement.size = size; this.drawerRef.nativeElement.show(); } }}<template> <plus-button @plus-click="openDrawer('sm')">Small</plus-button> <plus-button @plus-click="openDrawer('md')">Medium (Default)</plus-button> <plus-button @plus-click="openDrawer('lg')">Large</plus-button>
<plus-drawer ref="drawerRef" :size="currentSize"> <div slot="header">Size Example</div> <div slot="body">Content</div> </plus-drawer></template>
<script setup>import { ref, nextTick } from 'vue';
const drawerRef = ref(null);const currentSize = ref('md');
const openDrawer = (size) => { currentSize.value = size; nextTick(() => { drawerRef.value?.show(); });};</script><script> import { onMount } from 'svelte';
let drawerRef; let currentSize = 'md';
onMount(() => { const buttons = ['open-sm', 'open-md', 'open-lg']; const sizes = ['sm', 'md', 'lg'];
buttons.forEach((buttonId, index) => { const button = document.getElementById(buttonId); if (button && drawerRef) { button.addEventListener('plus-click', () => { drawerRef.size = sizes[index]; drawerRef.show(); }); } }); });</script>
<plus-button id="open-sm">Small</plus-button><plus-button id="open-md">Medium (Default)</plus-button><plus-button id="open-lg">Large</plus-button>
<plus-drawer bind:this={drawerRef} size={currentSize}> <div slot="header">Size Example</div> <div slot="body">Content</div></plus-drawer><plus-button id="open-sm-html">Small</plus-button><plus-button id="open-md-html">Medium (Default)</plus-button><plus-button id="open-lg-html">Large</plus-button>
<plus-drawer id="drawer-size-html"> <div slot="header">Size Example</div> <div slot="body">Content</div></plus-drawer>
<script> const drawerS = document.getElementById('drawer-size-html'); document.getElementById('open-sm-html').addEventListener('plus-click', () => { drawerS.size = 'sm'; drawerS.show(); }); document.getElementById('open-md-html').addEventListener('plus-click', () => { drawerS.size = 'md'; drawerS.show(); }); document.getElementById('open-lg-html').addEventListener('plus-click', () => { drawerS.size = 'lg'; drawerS.show(); });</script>Customizing Content (Slots)
Section titled “Customizing Content (Slots)”Use the header, body, and footer slots to structure the drawer content. The default close button can be replaced using the close slot.
This is the body slot content.
Put any HTML here.
import { PlusDrawer, PlusButton, PlusIcon } from '@plusui/react';import { useRef } from 'react';
export default () => { const drawerRef = useRef<HTMLPlusDrawerElement>(null);
const showDrawer = () => drawerRef.current?.show(); const hideDrawer = () => drawerRef.current?.hide();
return ( <> <PlusButton onClick={showDrawer}>Open Custom Drawer</PlusButton>
<PlusDrawer ref={drawerRef}> <span slot="header"><strong>Custom Header</strong></span> <div slot="body"> This is the <em>body</em> slot content. <p>Put any HTML here.</p> </div> <div slot="footer"> <PlusButton kind="text" onClick={hideDrawer}>Close</PlusButton> <PlusButton>Save</PlusButton> </div> <PlusButton slot="close" kind="text" aria-label="Close" onClick={hideDrawer}> <PlusIcon iconName="xmark-large" /> </PlusButton> </PlusDrawer> </> );};import { Component, ViewChild, ElementRef } from '@angular/core';import type { PlusDrawer } from '@plusui/core';
@Component({ selector: 'app-drawer-slots', template: ` <plus-button (plus-click)="showDrawer()">Open Custom Drawer</plus-button>
<plus-drawer #drawer> <span slot="header"><strong>Custom Header</strong></span> <div slot="body"> This is the <em>body</em> slot content. <p>Put any HTML here.</p> </div> <div slot="footer"> <plus-button kind="text" (plus-click)="hideDrawer()">Close</plus-button> <plus-button>Save</plus-button> </div> <plus-button slot="close" kind="text" aria-label="Close" (plus-click)="hideDrawer()"> <plus-icon icon-name="xmark-large"></plus-icon> </plus-button> </plus-drawer> `})export class DrawerSlotsComponent { @ViewChild('drawer') drawerRef!: ElementRef<PlusDrawer>;
showDrawer() { this.drawerRef.nativeElement.show(); } hideDrawer() { this.drawerRef.nativeElement.hide(); }}<template> <plus-button @plus-click="showDrawer">Open Custom Drawer</plus-button>
<plus-drawer ref="drawerRef"> <span slot="header"><strong>Custom Header</strong></span> <div slot="body"> This is the <em>body</em> slot content. <p>Put any HTML here.</p> </div> <div slot="footer"> <plus-button kind="text" @plus-click="hideDrawer">Close</plus-button> <plus-button>Save</plus-button> </div> <plus-button slot="close" kind="text" aria-label="Close" @plus-click="hideDrawer"> <plus-icon icon-name="xmark-large"></plus-icon> </plus-button> </plus-drawer></template>
<script setup>import { ref } from 'vue';
const drawerRef = ref(null);
const showDrawer = () => drawerRef.value?.show();const hideDrawer = () => drawerRef.value?.hide();</script><script> import { onMount } from 'svelte';
let drawerRef; let openButtonRef; let closeButtonRef;
onMount(() => { if (openButtonRef && closeButtonRef && drawerRef) { openButtonRef.addEventListener('plus-click', () => drawerRef.show()); closeButtonRef.addEventListener('plus-click', () => drawerRef.hide());
const closeSlotButton = drawerRef.querySelector('[slot="close"]'); if (closeSlotButton) { closeSlotButton.addEventListener('plus-click', () => drawerRef.hide()); } } });</script>
<plus-button bind:this={openButtonRef}>Open Custom Drawer</plus-button>
<plus-drawer bind:this={drawerRef}> <span slot="header"><strong>Custom Header</strong></span> <div slot="body"> This is the <em>body</em> slot content. <p>Put any HTML here.</p> </div> <div slot="footer"> <plus-button bind:this={closeButtonRef} kind="text">Close</plus-button> <plus-button>Save</plus-button> </div> <plus-button slot="close" kind="text" aria-label="Close"> <plus-icon icon-name="xmark-large"></plus-icon> </plus-button></plus-drawer><plus-button id="open-slots-html">Open Custom Drawer</plus-button>
<plus-drawer id="drawer-slots-html"> <span slot="header"><strong>Custom Header</strong></span> <div slot="body"> This is the <em>body</em> slot content. <p>Put any HTML here.</p> </div> <div slot="footer"> <plus-button kind="text" id="close-slots-html">Close</plus-button> <plus-button>Save</plus-button> </div> <plus-button slot="close" kind="text" aria-label="Close"> <plus-icon icon-name="xmark-large"></plus-icon> </plus-button></plus-drawer>
<script> const drawerSlots = document.getElementById('drawer-slots-html'); document.getElementById('open-slots-html').addEventListener('plus-click', () => drawerSlots.show()); document.getElementById('close-slots-html').addEventListener('plus-click', () => drawerSlots.hide());
drawerSlots.querySelector('[slot="close"]').addEventListener('plus-click', () => drawerSlots.hide());</script>Visual Sections
Section titled “Visual Sections”Layout & Spacing
Section titled “Layout & Spacing”
Light & Dark Mode
Section titled “Light & Dark Mode”
Accessibility (a11y)
Section titled “Accessibility (a11y)”- Keyboard Behavior:
- When the drawer is open, pressing
Escapewill close it. - Focus is typically managed within the drawer. When opened, focus should ideally move to the first focusable element inside the drawer or the close button. When closed, focus should return to the element that triggered the drawer.
- When the drawer is open, pressing
- Screen Reader:
- The component uses
role="dialog"andaria-modal="true"to indicate its purpose and that interaction should be limited to the drawer when open. aria-hiddenis toggled based on theisOpenstate.- An
aria-label="Drawer"is provided by default. It’s recommended to provide a more specific label viaaria-labelledbypointing to the header content if possible, or by settingaria-labeldirectly on theplus-drawerelement if the header slot is not used or needs overriding. - The close button has an
aria-label="Close drawer".
- The component uses
- Required Developer Actions:
- Ensure the drawer has a clear focus management strategy. While the component handles basic Esc key closing, managing focus trapping and return focus might require additional application-level logic, especially in complex scenarios.
- Provide meaningful content within the
header,body, andfooterslots. - If the default
aria-label="Drawer"is not descriptive enough for the specific use case, provide a more informative label usingaria-labeloraria-labelledbyon theplus-drawerelement.
Design System
Section titled “Design System”API Reference
Section titled “API Reference”Properties
Section titled “Properties”| Name | Type | Default | Description | Required |
|---|---|---|---|---|
size | 'sm' | 'md' | 'lg' | 'md' | Controls the width (left/right) or height (top/bottom) of the drawer. | No |
isOpen | boolean | false | Whether the drawer is currently open. Use show()/hide() methods. | No |
orientation | 'left' | 'right' | 'top' | 'bottom' | 'right' | The edge from which the drawer slides in. | No |
animationDuration | number | 300 | The duration of the open/close animation in milliseconds. | No |
Methods
Section titled “Methods”| Name | Parameters | Returns | Description |
|---|---|---|---|
show() | — | void | Opens the drawer. |
hide() | — | void | Closes the drawer. |
Events
Section titled “Events”| Name | Payload Type | Description |
|---|---|---|
plus-drawer-before-show | CustomEvent<void> | Fired just before the show animation starts. |
plus-drawer-show | CustomEvent<void> | Fired after the show animation completes. |
plus-drawer-before-hide | CustomEvent<void> | Fired just before the hide animation starts. |
plus-drawer-hide | CustomEvent<void> | Fired after the hide animation completes. |
| Name | Description |
|---|---|
header | Content for the drawer’s header area. |
body | The main content area of the drawer. |
footer | Content for the drawer’s footer area. |
close | Custom content for the close button (replaces default icon). |
default (unnamed) | Alternative slot for the main content (appended after body). |
CSS Shadow Parts
Section titled “CSS Shadow Parts”| Part | Description |
|---|---|
container | The main container element (host + overlay). |
overlay | The backdrop overlay element. |
drawer | The sliding drawer panel itself. |
header | The header section of the drawer. |
body | The main content body of the drawer. |
footer | The footer section of the drawer. |
close-button | The close button element. |
Join the Community
Plus UI is built by the community. Join us on our platforms to contribute, get help, and stay up to date.