@use 'sprucecss/scss/spruce' as *;
.drawer {
--drawer-inline-size: 24rem;
background: transparent;
block-size: 100dvb;
border: 0;
border-radius: 0;
box-shadow: none;
inset-inline: auto 0;
margin: 0;
max-block-size: none;
max-inline-size: 100vi;
overflow: visible;
padding: 0;
&::backdrop {
background-color: color('overlay');
}
@include motion-safe {
--drawer-offset: 100%;
transition: translate 0.3s cubic-bezier(0.32, 0.72, 0, 1), overlay 0.3s allow-discrete, display 0.3s allow-discrete;
translate: var(--drawer-offset) 0;
&[open] {
translate: 0 0;
@starting-style {
translate: var(--drawer-offset) 0;
}
}
&::backdrop {
opacity: 0;
transition: opacity 0.3s, overlay 0.3s allow-discrete, display 0.3s allow-discrete;
}
&[open]::backdrop {
opacity: 1;
@starting-style {
opacity: 0;
}
}
}
&__panel {
background-color: color('background');
block-size: 100%;
border-inline-start: config('border-width-base', $display) solid color('border');
box-shadow: shadow('xl');
display: flex;
flex-direction: column;
inline-size: min(var(--drawer-inline-size), 100vi);
}
&__header,
&__footer {
align-items: center;
display: flex;
gap: spacer('s');
padding: spacer('s') spacer('m');
}
&__header {
border-block-end: config('border-width-base', $display) solid color('border');
justify-content: space-between;
}
&__title {
font-size: font-size('h4');
margin: 0;
}
&__body {
flex-grow: 1;
overflow-y: auto;
overscroll-behavior: contain;
padding: spacer('m');
@include layout-stack('m');
}
&__footer {
border-block-start: config('border-width-base', $display) solid color('border');
justify-content: flex-end;
}
}
[dir='rtl'] .drawer {
--drawer-offset: -100%;
}
html:has(.drawer[open]) {
overflow: hidden;
scrollbar-gutter: stable;
}
<button class="btn btn--primary" type="button" data-drawer-open="drawer-cart" aria-haspopup="dialog">Open drawer</button>
<dialog class="drawer" id="drawer-cart" aria-labelledby="drawer-cart-title">
<div class="drawer__panel">
<header class="drawer__header">
<h2 class="drawer__title" id="drawer-cart-title">Filters</h2>
<button class="btn btn--ghost-secondary btn--icon btn--sm" type="button" data-drawer-close aria-label="Close">
<svg class="btn__icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true" focusable="false"><path d="M18 6 6 18M6 6l12 12"/></svg>
</button>
</header>
<div class="drawer__body">
<fieldset>
<legend>Category</legend>
<div class="form-group form-group--vertical-check">
<label class="form-check"><input class="form-check__control" type="checkbox" checked> <span class="form-label form-check__label">Backpacks</span></label>
<label class="form-check"><input class="form-check__control" type="checkbox"> <span class="form-label form-check__label">Accessories</span></label>
<label class="form-check"><input class="form-check__control" type="checkbox"> <span class="form-label form-check__label">Outdoor</span></label>
</div>
</fieldset>
<div class="form-group">
<label class="form-label" for="drawer-price">Maximum price</label>
<input class="form-range" id="drawer-price" type="range" min="0" max="500" value="250">
</div>
</div>
<footer class="drawer__footer">
<button class="btn btn--outline-primary" type="button" data-drawer-close>Reset</button>
<button class="btn btn--primary" type="button" data-drawer-close>Show 24 results</button>
</footer>
</div>
</dialog>
(() => {
document.querySelectorAll('[data-drawer-open]').forEach((opener) => {
const drawer = document.getElementById(opener.dataset.drawerOpen);
if (!drawer) {
return;
}
let pressedOnBackdrop = false;
opener.addEventListener('click', (event) => {
event.preventDefault();
if (!drawer.open) {
drawer.showModal();
}
});
drawer.addEventListener('pointerdown', (event) => {
pressedOnBackdrop = event.target === drawer;
});
drawer.addEventListener('click', (event) => {
const backdropClick = event.target === drawer && pressedOnBackdrop;
if (backdropClick || event.target.closest('[data-drawer-close]')) {
drawer.close();
}
pressedOnBackdrop = false;
});
});
})();