Drawer
wu-drawer styles a native <dialog> as a panel docked to an edge of the viewport. It opens and closes like the modal: commandfor with command="show-modal" and command="close", and Esc.
How to make a slide-out drawer without JavaScript
Section titled “How to make a slide-out drawer without JavaScript”A drawer is a dialog that sticks to the side of the screen. Give a button the id of the drawer and it opens, with no script to write. This uses the browser’s built-in dialog element, with commandfor and command on the buttons. Add animated if you want it to slide in. The full demo is under Inline end.
<button class="wu-btn" type="button" commandfor="my-drawer" command="show-modal">Filters</button><dialog class="wu-drawer" id="my-drawer" aria-label="Filters"> <div class="wu-drawer-body">Options go here.</div> <form class="wu-drawer-footer" method="dialog"> <button class="wu-btn brand">Done</button> </form></dialog>| Class, attribute or variable | Effect |
|---|---|
wu-drawer |
Root, on <dialog>. Full-height surface panel at the inline end, over a dimmed backdrop |
wu-drawer-header |
Part, on <header>. Title row with a line below |
wu-drawer-title |
Part. Large semibold title; link it with aria-labelledby |
wu-drawer-close |
Part, on a <button> with aria-label. Cross drawn in CSS |
wu-drawer-body |
Part. Scrolls on its own |
wu-drawer-footer |
Part, on <form method="dialog">. End-aligned buttons with a line above |
inline-start |
Docks to the inline start instead |
bottom |
Docks to the bottom, full width, with rounded top corners and room for the device’s bottom safe area |
closedby="any" |
Click outside also closes it |
animated |
Slides in from its edge and back out, opt-in |
--wu-drawer--Width |
Width of a side drawer, 24rem by default |
--wu-drawer--MaxHeight |
Maximum height of a bottom drawer, 75dvh by default |
--wu-drawer--Backdrop |
Backdrop color, black at 50% (70% in dark themes) by default |
The side modifiers are inline-start and bottom. The words start and end are core class names (flex alignment), so the kit does not use them as modifiers.
Inline end
Section titled “Inline end”<button class="wu-btn" type="button" commandfor="drawer-end" command="show-modal">Filters</button><dialog class="wu-drawer" id="drawer-end" aria-labelledby="drawer-end-title" closedby="any"> <header class="wu-drawer-header"> <h2 class="wu-drawer-title" id="drawer-end-title">Filters</h2> <button class="wu-drawer-close" type="button" commandfor="drawer-end" command="close" aria-label="Close"></button> </header> <div class="wu-drawer-body"> <div class="wu-field"> <label class="wu-field-label" for="drawer-end-status">Status</label> <select class="wu-select" id="drawer-end-status" form="drawer-end-form" autofocus> <option>Any</option> <option>Open</option> <option>Closed</option> </select> </div> </div> <form class="wu-drawer-footer" id="drawer-end-form" method="dialog"> <button class="wu-btn" type="reset">Reset</button> <button class="wu-btn brand" value="apply">Apply</button> </form></dialog>form="drawer-end-form" ties the select to the footer form, so type="reset" puts it back to its first value and leaves the drawer open, while Apply closes it.
Inline start and bottom
Section titled “Inline start and bottom”<div class="flex wrap gap-2"> <button class="wu-btn" type="button" commandfor="drawer-start" command="show-modal">Inline start</button> <button class="wu-btn" type="button" commandfor="drawer-bottom" command="show-modal">Bottom</button></div><dialog class="wu-drawer inline-start" id="drawer-start" aria-labelledby="drawer-start-title" closedby="any"> <header class="wu-drawer-header"> <h2 class="wu-drawer-title" id="drawer-start-title">Menu</h2> <button class="wu-drawer-close" type="button" commandfor="drawer-start" command="close" aria-label="Close"></button> </header> <div class="wu-drawer-body">Docked to the inline start: the left in left-to-right pages, the right in right-to-left pages.</div></dialog><dialog class="wu-drawer bottom" id="drawer-bottom" aria-labelledby="drawer-bottom-title" closedby="any"> <header class="wu-drawer-header"> <h2 class="wu-drawer-title" id="drawer-bottom-title">Share</h2> <button class="wu-drawer-close" type="button" commandfor="drawer-bottom" command="close" aria-label="Close"></button> </header> <div class="wu-drawer-body">Docked to the bottom, full width.</div></dialog>Animation
Section titled “Animation”The animated word slides the drawer in from its edge and back out, mirrored in right-to-left pages, and fades the backdrop. wu-animated on an ancestor turns it on for every drawer inside. Motion runs only under prefers-reduced-motion: no-preference.
<button class="wu-btn" type="button" commandfor="drawer-animated" command="show-modal">Open animated</button><dialog class="wu-drawer animated" id="drawer-animated" aria-labelledby="drawer-animated-title" closedby="any"> <header class="wu-drawer-header"> <h2 class="wu-drawer-title" id="drawer-animated-title">Activity</h2> <button class="wu-drawer-close" type="button" commandfor="drawer-animated" command="close" aria-label="Close"></button> </header> <div class="wu-drawer-body">No new activity.</div></dialog>Page scroll lock
Section titled “Page scroll lock”While a drawer is open with command="show-modal" (or showModal()), the page behind it does not scroll. The rule is CSS only: :root:has(.wu-drawer:modal) sets overflow: hidden on the page, and scrollbar-gutter: stable keeps the space of the page scrollbar, so the layout does not shift. A drawer opened with show() is not modal and leaves the page scrolling. The drawer body still scrolls on its own.
Right to left
Section titled “Right to left”The default drawer docks to the inline end, which is the left edge in right-to-left text, and inline-start docks to the right. The close button and the footer buttons move to the left too, and the animated slide comes from the drawer’s own edge.
<div dir="rtl"> <button class="wu-btn" type="button" commandfor="drawer-rtl" command="show-modal">فتح</button> <dialog class="wu-drawer animated" id="drawer-rtl" aria-labelledby="drawer-rtl-title" closedby="any"> <header class="wu-drawer-header"> <h2 class="wu-drawer-title" id="drawer-rtl-title">التفاصيل</h2> <button class="wu-drawer-close" type="button" commandfor="drawer-rtl" command="close" aria-label="إغلاق"></button> </header> <div class="wu-drawer-body">يلتصق بنهاية السطر: الحافة اليسرى في النص من اليمين إلى اليسار.</div> <form class="wu-drawer-footer" method="dialog"> <button class="wu-btn brand" value="done">تم</button> </form> </dialog></div>Accessibility
Section titled “Accessibility”command="show-modal"opens the drawer as a modal dialog: the rest of the page is inert and focus moves into the drawer.- Put
autofocuson the first field of a form drawer, as in the first example. Without it, focus goes to the first focusable element, usually the close button. - The bottom drawer pads its end by
env(safe-area-inset-bottom), which is above zero only on pages withviewport-fit=coverin the viewport meta tag. - Link the title with
aria-labelledbyand give the close button anaria-label. - Esc closes the drawer;
closedby="any"adds click outside.
Load only the drawer
Section titled “Load only the drawer”@use "@wovenui/components/drawer";The drawer needs no other component. The examples above also use the button, the field and the select.