Open demo

CSS code

HTML
<svg width="0" height="0" style="position:absolute" aria-hidden="true" focusable="false">
  <symbol id="sh-i-x" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M6.5 6.5l11 11M17.5 6.5l-11 11"/></symbol>
  <symbol id="sh-i-share" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M12 14.5v-11M8 7.5l4-4 4 4M7 10.5H6a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-6a2 2 0 0 0-2-2h-1"/></symbol>
  <symbol id="sh-i-sliders" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M4 7h10M18 7h2M4 17h4M12 17h8"/><circle cx="16" cy="7" r="2"/><circle cx="10" cy="17" r="2"/></symbol>
  <symbol id="sh-i-pin" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M12 21s-6.5-5.6-6.5-11a6.5 6.5 0 0 1 13 0c0 5.4-6.5 11-6.5 11z"/><circle cx="12" cy="10" r="2.3"/></symbol>
  <symbol id="sh-i-file" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M14 3.5H7.5a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2h9a2 2 0 0 0 2-2V8z"/><path d="M14 3.5V8h4.5M9 13h6M9 16.5h4"/></symbol>
  <symbol id="sh-i-link" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M10 14a4 4 0 0 0 5.7 0l3-3a4 4 0 0 0-5.7-5.7l-1.2 1.2"/><path d="M14 10a4 4 0 0 0-5.7 0l-3 3a4 4 0 0 0 5.7 5.7l1.2-1.2"/></symbol>
  <symbol id="sh-i-mail" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="3.5" y="5.5" width="17" height="13" rx="2"/><path d="m4 7 8 6 8-6"/></symbol>
  <symbol id="sh-i-chat" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M20 11.5a7.5 7.5 0 0 1-11 6.6l-4.5 1.4 1.3-4A7.5 7.5 0 1 1 20 11.5z"/></symbol>
  <symbol id="sh-i-nearby" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="1.8"/><path d="M8.5 8.5a5 5 0 0 0 0 7M15.5 8.5a5 5 0 0 1 0 7M5.6 5.6a9 9 0 0 0 0 12.8M18.4 5.6a9 9 0 0 1 0 12.8"/></symbol>
  <symbol id="sh-i-download" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M12 4v11M7.5 10.5 12 15l4.5-4.5M5 19.5h14"/></symbol>
  <symbol id="sh-i-copy" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="8.5" y="8.5" width="11" height="11" rx="2"/><path d="M15.5 8.5v-2a2 2 0 0 0-2-2h-7a2 2 0 0 0-2 2v7a2 2 0 0 0 2 2h2"/></symbol>
  <symbol id="sh-i-folder" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M3.5 7.5a2 2 0 0 1 2-2h4l2 2.5h7a2 2 0 0 1 2 2v7.5a2 2 0 0 1-2 2h-13a2 2 0 0 1-2-2z"/><path d="M9.5 13.5h5.5M13 11.5l2 2-2 2"/></symbol>
  <symbol id="sh-i-trash" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M4.5 7h15M10 4h4M6.5 7l.8 11.2a2 2 0 0 0 2 1.8h5.4a2 2 0 0 0 2-1.8L17.5 7M10 10.5v6M14 10.5v6"/></symbol>
  <symbol id="sh-i-chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m6.5 9.5 5.5 5.5 5.5-5.5"/></symbol>
</svg>

<div><span class="lbl">Bottom sheet (action sheet)</span>
<div class="btns"><button class="sh-btn is-primary" type="button" data-open="sh-share" aria-haspopup="dialog"><svg aria-hidden="true"><use href="#sh-i-share"/></svg>Share file</button></div></div>

<div><span class="lbl">Side sheet</span>
<div class="btns"><button class="sh-btn" type="button" data-open="sh-filters" aria-haspopup="dialog"><svg aria-hidden="true"><use href="#sh-i-sliders"/></svg>Filters</button></div></div>

<div><span class="lbl">Sheet on mobile, dialog on desktop</span>
<div class="btns"><button class="sh-btn" type="button" data-open="sh-address" aria-haspopup="dialog"><svg aria-hidden="true"><use href="#sh-i-pin"/></svg>Edit delivery address</button></div></div>

<!-- 1. Bottom sheet (action sheet) -->
<dialog class="sh sh-bottom" id="sh-share" aria-labelledby="sh-share-title">
  <form class="sh-form" method="dialog">
    <span class="sh-handle" aria-hidden="true"></span>
    <div class="sh-head">
      <span class="sh-icon" aria-hidden="true"><svg><use href="#sh-i-file"/></svg></span>
      <div class="sh-titles"><h2 class="sh-title" id="sh-share-title">Share “Q3 roadmap.pdf”</h2><p class="sh-sub">2.4 MB · edited 2 hours ago</p></div>
    </div>
    <div class="sh-body">
      <ul class="sh-targets">
        <li><button class="sh-target" value="copy"><span aria-hidden="true"><svg><use href="#sh-i-link"/></svg></span>Copy link</button></li>
        <li><button class="sh-target" value="email"><span aria-hidden="true"><svg><use href="#sh-i-mail"/></svg></span>Email</button></li>
        <li><button class="sh-target" value="messages"><span aria-hidden="true"><svg><use href="#sh-i-chat"/></svg></span>Messages</button></li>
        <li><button class="sh-target" value="nearby"><span aria-hidden="true"><svg><use href="#sh-i-nearby"/></svg></span>Nearby</button></li>
      </ul>
      <ul class="sh-actions">
        <li><button class="sh-action" value="download"><svg aria-hidden="true"><use href="#sh-i-download"/></svg>Download</button></li>
        <li><button class="sh-action" value="duplicate"><svg aria-hidden="true"><use href="#sh-i-copy"/></svg>Duplicate</button></li>
        <li><button class="sh-action" value="move"><svg aria-hidden="true"><use href="#sh-i-folder"/></svg>Move to folder</button></li>
        <li><button class="sh-action is-danger" value="delete"><svg aria-hidden="true"><use href="#sh-i-trash"/></svg>Delete file</button></li>
      </ul>
    </div>
    <div class="sh-foot"><button class="sh-btn" value="cancel">Cancel</button></div>
  </form>
</dialog>

<!-- 2. Side sheet -->
<dialog class="sh sh-side" id="sh-filters" aria-labelledby="sh-filters-title">
  <form class="sh-form" method="dialog">
    <div class="sh-head">
      <div class="sh-titles"><h2 class="sh-title" id="sh-filters-title">Filters</h2><p class="sh-sub">Menswear · 146 products</p></div>
      <button class="sh-x" value="close" aria-label="Close filters"><svg aria-hidden="true"><use href="#sh-i-x"/></svg></button>
    </div>
    <div class="sh-body" tabindex="0" role="region" aria-label="Filter options">
      <fieldset class="sh-group">
        <legend class="sh-legend">Category</legend>
        <div>
          <label class="sh-check"><input type="checkbox" name="category" value="jackets" checked>Jackets<span class="sh-count">42</span></label>
          <label class="sh-check"><input type="checkbox" name="category" value="knitwear" checked>Knitwear<span class="sh-count">31</span></label>
          <label class="sh-check"><input type="checkbox" name="category" value="shirts" checked>Shirts<span class="sh-count">55</span></label>
          <label class="sh-check"><input type="checkbox" name="category" value="trousers">Trousers<span class="sh-count">18</span></label>
        </div>
      </fieldset>
      <fieldset class="sh-group">
        <legend class="sh-legend">Size</legend>
        <div class="sh-chips">
          <label class="sh-chip"><input type="checkbox" name="size" value="xs"><span>XS</span></label>
          <label class="sh-chip"><input type="checkbox" name="size" value="s"><span>S</span></label>
          <label class="sh-chip"><input type="checkbox" name="size" value="m" checked><span>M</span></label>
          <label class="sh-chip"><input type="checkbox" name="size" value="l" checked><span>L</span></label>
          <label class="sh-chip"><input type="checkbox" name="size" value="xl"><span>XL</span></label>
        </div>
      </fieldset>
      <fieldset class="sh-group">
        <legend class="sh-legend">Price</legend>
        <div class="sh-range">
          <div class="sh-field"><label for="sh-price-min">Min (£)</label><input class="sh-input" id="sh-price-min" name="min" type="number" inputmode="numeric" min="0" step="5" value="40"></div>
          <div class="sh-field"><label for="sh-price-max">Max (£)</label><input class="sh-input" id="sh-price-max" name="max" type="number" inputmode="numeric" min="0" step="5" value="180"></div>
        </div>
      </fieldset>
      <fieldset class="sh-group">
        <legend class="sh-legend">Availability</legend>
        <label class="sh-row"><span><b>In stock only</b><small>Hide items that ship in 2+ weeks</small></span><input class="sh-switch" type="checkbox" role="switch" name="stock" checked></label>
      </fieldset>
    </div>
    <div class="sh-foot">
      <button class="sh-btn" type="button" data-clear>Clear all</button>
      <button class="sh-btn is-primary" value="apply">Show 128 results</button>
    </div>
  </form>
</dialog>

<!-- 3. Sheet on mobile, dialog on desktop -->
<dialog class="sh sh-adapt" id="sh-address" aria-labelledby="sh-address-title">
  <form class="sh-form" method="dialog">
    <span class="sh-handle" aria-hidden="true"></span>
    <div class="sh-head">
      <span class="sh-icon" aria-hidden="true"><svg><use href="#sh-i-pin"/></svg></span>
      <div class="sh-titles"><h2 class="sh-title" id="sh-address-title">Edit delivery address</h2><p class="sh-sub">Order HL-48213 · arrives Thu 2 Oct</p></div>
    </div>
    <div class="sh-body">
      <div class="sh-fields">
        <div class="sh-field is-wide"><label for="sh-name">Full name</label><input class="sh-input" id="sh-name" name="name" autocomplete="name" value="Maya Lindqvist"></div>
        <div class="sh-field is-wide"><label for="sh-line1">Address line 1</label><input class="sh-input" id="sh-line1" name="line1" autocomplete="address-line1" value="Flat 4, 27 Wharf Road"></div>
        <div class="sh-field"><label for="sh-city">City</label><input class="sh-input" id="sh-city" name="city" autocomplete="address-level2" value="Manchester"></div>
        <div class="sh-field"><label for="sh-postcode">Postcode</label><input class="sh-input" id="sh-postcode" name="postcode" autocomplete="postal-code" value="M3 4LN"></div>
        <div class="sh-field is-wide"><label for="sh-country">Country</label>
          <div class="sh-select"><select class="sh-input" id="sh-country" name="country" autocomplete="country">
            <option value="IE">Ireland</option><option value="GB" selected>United Kingdom</option><option value="NL">Netherlands</option><option value="SE">Sweden</option>
          </select><svg aria-hidden="true"><use href="#sh-i-chevron"/></svg></div>
        </div>
      </div>
    </div>
    <div class="sh-foot">
      <button class="sh-btn" value="cancel">Cancel</button>
      <button class="sh-btn is-primary" value="save">Save address</button>
    </div>
  </form>
</dialog>
CSS
/* Bottom and side sheets on the native <dialog> element. showModal() puts the sheet in the top layer,
   keeps focus inside it, closes it on Esc and makes the page behind inert. The UA makes a modal dialog
   position:fixed with inset:0, so the margins below are what pin each sheet to an edge. */
.sh-btn{--sh-accent:#6d28d9;display:inline-flex;align-items:center;justify-content:center;gap:8px;min-height:42px;padding:9px 16px;border:1px solid #cbd5e1;border-radius:10px;
  background:#fff;color:#0f172a;font-family:inherit;font-size:.9rem;font-weight:600;line-height:1.2;text-decoration:none;cursor:pointer;transition:background-color .15s,border-color .15s,box-shadow .15s}
.sh-btn:hover{background:#f5f3ff;border-color:#a78bfa}
.sh-btn:focus-visible{outline:2px solid var(--sh-accent);outline-offset:2px}
.sh-btn.is-primary{background:var(--sh-accent);border-color:var(--sh-accent);color:#fff;box-shadow:0 6px 16px -8px rgba(109,40,217,.7)}
.sh-btn.is-primary:hover{background:#5b21b6;border-color:#5b21b6}
.sh-btn svg{width:18px;height:18px;flex:none}

.sh{--sh-accent:#6d28d9;--sh-ink:#0f172a;--sh-muted:#475569;--sh-line:#ebe7f5;--sh-tint:#f5f3ff;--sh-tint-2:#ede9fe;--sh-field:#8e89a8;--sh-radius:24px;
  max-width:none;max-height:none;padding:0;border:0;background:#fff;color:var(--sh-ink);font-size:.95rem;line-height:1.55;overflow:hidden;overscroll-behavior:contain;
  box-shadow:0 0 0 1px rgba(30,27,75,.06),0 -16px 48px -16px rgba(30,27,75,.4)}
.sh[open]{display:flex;flex-direction:column}
.sh::backdrop{background:rgba(24,20,56,.5);backdrop-filter:blur(2px)}
.sh[open]::backdrop{animation:sh-fade .3s ease-out}
@keyframes sh-up{from{transform:translateY(100%)}}
@keyframes sh-left{from{transform:translateX(100%)}}
@keyframes sh-pop{from{opacity:0;transform:translateY(10px) scale(.97)}}
@keyframes sh-fade{from{opacity:0}}
html:has(.sh:modal){overflow:hidden} /* stop the page scrolling behind an open sheet */

/* Bottom sheet: pinned to the bottom edge at every width, full width on phones */
.sh-bottom{width:min(560px,100%);max-height:calc(100dvh - 24px);margin:auto auto 0;border-radius:var(--sh-radius) var(--sh-radius) 0 0}
.sh-bottom[open]{animation:sh-up .34s cubic-bezier(.22,1,.36,1)}
/* Side sheet: full height on the right edge */
.sh-side{width:min(400px,100%);height:100%;margin:0 0 0 auto;border-radius:20px 0 0 20px;box-shadow:0 0 0 1px rgba(30,27,75,.06),-16px 0 48px -16px rgba(30,27,75,.4)}
.sh-side[open]{animation:sh-left .34s cubic-bezier(.22,1,.36,1)}
/* Adaptive: a bottom sheet on phones, a centred dialog from 640px */
.sh-adapt{width:100%;max-height:calc(100dvh - 24px);margin:auto auto 0;border-radius:var(--sh-radius) var(--sh-radius) 0 0}
.sh-adapt[open]{animation:sh-up .34s cubic-bezier(.22,1,.36,1)}

.sh-form{display:flex;flex-direction:column;flex:1;min-height:0;margin:0}
.sh-handle{flex:none;display:block;width:40px;height:5px;margin:10px auto 0;border-radius:999px;background:#d4cfe6}
.sh-head{flex:none;display:flex;align-items:center;gap:14px;padding:14px 24px 16px}
.sh-titles{flex:1;min-width:0}
.sh-title{margin:0;font-size:1.1rem;font-weight:700;letter-spacing:-.01em;line-height:1.35;overflow-wrap:anywhere}
.sh-sub{margin:2px 0 0;color:var(--sh-muted);font-size:.85rem}
.sh-icon{flex:none;display:grid;place-items:center;width:44px;height:44px;border-radius:12px;background:linear-gradient(135deg,var(--sh-tint),var(--sh-tint-2));color:var(--sh-accent)}
.sh-icon svg{width:22px;height:22px}
.sh-x{flex:none;display:grid;place-items:center;width:36px;height:36px;margin:-4px -8px 0 0;padding:0;border:0;border-radius:10px;background:transparent;color:var(--sh-muted);cursor:pointer;
  transition:background-color .15s,color .15s}
.sh-x:hover{background:var(--sh-tint);color:var(--sh-ink)}
.sh-x:focus-visible{outline:2px solid var(--sh-accent);outline-offset:1px}
.sh-x svg{width:20px;height:20px}
.sh-body{flex:1 1 auto;min-height:0;overflow-y:auto;overscroll-behavior:contain}
.sh-body:focus-visible{outline:2px solid var(--sh-accent);outline-offset:-4px}
.sh-foot{flex:none;display:flex;flex-wrap:wrap;justify-content:flex-end;gap:10px;padding:16px 24px;border-top:1px solid var(--sh-line);background:#fff}

/* 1. Action sheet: share targets, an action list and a Cancel button */
.sh-targets{list-style:none;display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:4px;margin:0;padding:4px 14px 14px}
.sh-target{display:flex;flex-direction:column;align-items:center;gap:8px;width:100%;padding:8px 2px;border:0;border-radius:14px;background:none;color:var(--sh-ink);
  font:inherit;font-size:.8rem;font-weight:600;line-height:1.25;cursor:pointer}
.sh-target span{display:grid;place-items:center;width:54px;height:54px;border-radius:50%;background:var(--sh-tint);color:var(--sh-accent);transition:background-color .15s}
.sh-target svg{width:24px;height:24px}
.sh-target:hover span{background:var(--sh-tint-2)}
.sh-target:focus-visible{outline:2px solid var(--sh-accent);outline-offset:-2px}
.sh-actions{list-style:none;margin:0;padding:8px 12px;border-top:1px solid var(--sh-line)}
.sh-action{display:flex;align-items:center;gap:14px;width:100%;min-height:48px;padding:10px 12px;border:0;border-radius:12px;background:none;color:var(--sh-ink);
  font:inherit;font-weight:500;text-align:left;cursor:pointer;transition:background-color .15s}
.sh-action svg{width:20px;height:20px;flex:none;color:var(--sh-muted)}
.sh-action:hover{background:var(--sh-tint)}
.sh-action:focus-visible{outline:2px solid var(--sh-accent);outline-offset:-2px}
.sh-action.is-danger,.sh-action.is-danger svg{color:#b91c1c}
.sh-action.is-danger:hover{background:#fef2f2}
.sh-bottom .sh-foot{padding:12px 16px calc(16px + env(safe-area-inset-bottom))}
.sh-bottom .sh-foot .sh-btn{width:100%;min-height:48px}

/* 2. Side sheet: filters */
.sh-side .sh-head{align-items:flex-start;padding:20px 24px 16px;border-bottom:1px solid var(--sh-line)}
.sh-side .sh-body{padding:0 24px}
.sh-group{margin:0;padding:18px 0;border:0;border-bottom:1px solid var(--sh-line);min-width:0}
.sh-group:last-child{border-bottom:0}
.sh-legend{float:left;width:100%;margin:0 0 10px;padding:0;color:var(--sh-ink);font-size:.75rem;font-weight:700;letter-spacing:.08em;text-transform:uppercase}
.sh-legend+*{clear:left}
.sh-check{display:flex;align-items:center;gap:12px;padding:7px 0;cursor:pointer}
.sh-check input{flex:none;width:18px;height:18px;margin:0;accent-color:var(--sh-accent);cursor:pointer}
.sh-check input:focus-visible{outline:2px solid var(--sh-accent);outline-offset:2px}
.sh-count{margin-left:auto;min-width:34px;padding:1px 8px;border-radius:999px;background:var(--sh-tint);color:#5b21b6;font-size:.75rem;font-weight:600;text-align:center}
.sh-chips{display:flex;flex-wrap:wrap;gap:8px}
.sh-chip input{position:absolute;width:1px;height:1px;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0;opacity:0}
.sh-chip span{display:grid;place-items:center;min-width:54px;height:40px;padding:0 14px;border:1px solid var(--sh-field);border-radius:999px;background:#fff;color:var(--sh-ink);
  font-size:.875rem;font-weight:600;cursor:pointer;transition:background-color .15s,border-color .15s,color .15s}
.sh-chip span:hover{background:var(--sh-tint)}
.sh-chip input:checked+span{border-color:var(--sh-accent);background:var(--sh-accent);color:#fff}
.sh-chip input:focus-visible+span{outline:2px solid var(--sh-accent);outline-offset:2px}
.sh-range{display:grid;grid-template-columns:1fr 1fr;gap:12px}
.sh-field label{display:block;margin-bottom:6px;color:var(--sh-muted);font-size:.8rem;font-weight:600}
.sh-input{display:block;width:100%;height:42px;margin:0;padding:8px 12px;border:1px solid var(--sh-field);border-radius:10px;background:#fff;color:var(--sh-ink);font:inherit;font-size:.9rem;
  transition:border-color .15s,box-shadow .15s}
.sh-input:focus{outline:none;border-color:var(--sh-accent);box-shadow:0 0 0 3px #ddd6fe}
.sh-row{display:flex;align-items:center;gap:16px}
.sh-row b{display:block;font-weight:600}
.sh-row small{display:block;color:var(--sh-muted);font-size:.8rem}
.sh-switch{flex:none;appearance:none;width:44px;height:26px;margin:0 0 0 auto;border:0;border-radius:999px;cursor:pointer;
  background:var(--sh-field) radial-gradient(circle,#fff 0 9px,transparent 9.5px) 2px 50%/22px 22px no-repeat;transition:background-color .2s,background-position .2s}
.sh-switch:checked{background-color:var(--sh-accent);background-position:calc(100% - 2px) 50%}
.sh-switch:focus-visible{outline:2px solid var(--sh-accent);outline-offset:2px}
.sh-side .sh-foot{flex-wrap:nowrap;padding:16px 24px calc(16px + env(safe-area-inset-bottom));box-shadow:0 -8px 20px -14px rgba(30,27,75,.35)}
.sh-side .sh-foot .sh-btn{flex:none;white-space:nowrap}
.sh-side .sh-foot .sh-btn.is-primary{flex:1 1 auto}

/* 3. Adaptive address form */
.sh-fields{display:grid;grid-template-columns:1fr 1fr;gap:14px 12px;padding:4px 24px 22px}
.sh-fields .is-wide{grid-column:1/-1}
.sh-select{position:relative}
.sh-select select{appearance:none;padding-right:40px;cursor:pointer}
.sh-select svg{position:absolute;right:12px;top:50%;width:18px;height:18px;margin-top:-9px;color:var(--sh-muted);pointer-events:none}
.sh-adapt .sh-foot{padding-bottom:calc(16px + env(safe-area-inset-bottom))}

@media (min-width:640px){
  .sh-adapt{width:min(480px,calc(100% - 32px));max-height:calc(100dvh - 56px);margin:auto;border-radius:16px;box-shadow:0 0 0 1px rgba(30,27,75,.06),0 28px 64px -18px rgba(30,27,75,.45)}
  .sh-adapt[open]{animation:sh-pop .22s cubic-bezier(.2,.8,.2,1)}
  .sh-adapt .sh-handle{display:none}
  .sh-adapt .sh-head{padding-top:22px}
  .sh-adapt .sh-foot{padding-bottom:16px;background:#faf9fe}
}
@media (max-width:600px){
  .sh-head,.sh-side .sh-head{padding-left:18px;padding-right:18px}
  .sh-side .sh-body{padding:0 18px}
  .sh-fields{padding-left:18px;padding-right:18px}
  .sh-foot,.sh-side .sh-foot{padding-left:18px;padding-right:18px}
  .sh-foot .sh-btn{flex:1 1 auto}
  .sh-targets{padding-left:8px;padding-right:8px}
}
@media (max-width:440px){.sh-side{border-radius:0}}
@media (prefers-reduced-motion:reduce){
  .sh[open],.sh[open]::backdrop{animation:none}
  .sh-btn,.sh-x,.sh-target span,.sh-action,.sh-chip span,.sh-input,.sh-switch{transition:none}
}
JavaScript
// Open a sheet from any button with data-open="dialog-id". showModal() traps focus, closes on Esc
// and makes the page behind inert. Buttons inside <form method="dialog"> close it with no script.
document.querySelectorAll('[data-open]').forEach(function (btn) {
  var dialog = document.getElementById(btn.getAttribute('data-open'));
  if (!dialog) return;
  btn.addEventListener('click', function () {
    dialog.showModal();
    dialog.addEventListener('close', function () { btn.focus(); }, { once: true }); // focus back to the trigger
  });
  // A click on the backdrop lands on the <dialog> itself (its form fills the box), so close it
  dialog.addEventListener('click', function (e) { if (e.target === dialog) dialog.close(); });
});
// "Clear all" in the filters sheet: untick every box and empty the price fields; the sheet stays open
document.querySelectorAll('[data-clear]').forEach(function (btn) {
  btn.addEventListener('click', function () {
    btn.form.querySelectorAll('input').forEach(function (el) {
      if (el.type === 'checkbox') el.checked = false; else el.value = '';
    });
  });
});

Bootstrap 5 code

Requires: Bootstrap 5.3.8 CSS, Bootstrap 5.3.8 JS bundle

HTML
<svg width="0" height="0" style="position:absolute" aria-hidden="true" focusable="false">
  <symbol id="sh-i-x" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M6.5 6.5l11 11M17.5 6.5l-11 11"/></symbol>
  <symbol id="sh-i-share" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M12 14.5v-11M8 7.5l4-4 4 4M7 10.5H6a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-6a2 2 0 0 0-2-2h-1"/></symbol>
  <symbol id="sh-i-sliders" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M4 7h10M18 7h2M4 17h4M12 17h8"/><circle cx="16" cy="7" r="2"/><circle cx="10" cy="17" r="2"/></symbol>
  <symbol id="sh-i-pin" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M12 21s-6.5-5.6-6.5-11a6.5 6.5 0 0 1 13 0c0 5.4-6.5 11-6.5 11z"/><circle cx="12" cy="10" r="2.3"/></symbol>
  <symbol id="sh-i-file" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M14 3.5H7.5a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2h9a2 2 0 0 0 2-2V8z"/><path d="M14 3.5V8h4.5M9 13h6M9 16.5h4"/></symbol>
  <symbol id="sh-i-link" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M10 14a4 4 0 0 0 5.7 0l3-3a4 4 0 0 0-5.7-5.7l-1.2 1.2"/><path d="M14 10a4 4 0 0 0-5.7 0l-3 3a4 4 0 0 0 5.7 5.7l1.2-1.2"/></symbol>
  <symbol id="sh-i-mail" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="3.5" y="5.5" width="17" height="13" rx="2"/><path d="m4 7 8 6 8-6"/></symbol>
  <symbol id="sh-i-chat" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M20 11.5a7.5 7.5 0 0 1-11 6.6l-4.5 1.4 1.3-4A7.5 7.5 0 1 1 20 11.5z"/></symbol>
  <symbol id="sh-i-nearby" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="1.8"/><path d="M8.5 8.5a5 5 0 0 0 0 7M15.5 8.5a5 5 0 0 1 0 7M5.6 5.6a9 9 0 0 0 0 12.8M18.4 5.6a9 9 0 0 1 0 12.8"/></symbol>
  <symbol id="sh-i-download" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M12 4v11M7.5 10.5 12 15l4.5-4.5M5 19.5h14"/></symbol>
  <symbol id="sh-i-copy" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="8.5" y="8.5" width="11" height="11" rx="2"/><path d="M15.5 8.5v-2a2 2 0 0 0-2-2h-7a2 2 0 0 0-2 2v7a2 2 0 0 0 2 2h2"/></symbol>
  <symbol id="sh-i-folder" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M3.5 7.5a2 2 0 0 1 2-2h4l2 2.5h7a2 2 0 0 1 2 2v7.5a2 2 0 0 1-2 2h-13a2 2 0 0 1-2-2z"/><path d="M9.5 13.5h5.5M13 11.5l2 2-2 2"/></symbol>
  <symbol id="sh-i-trash" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M4.5 7h15M10 4h4M6.5 7l.8 11.2a2 2 0 0 0 2 1.8h5.4a2 2 0 0 0 2-1.8L17.5 7M10 10.5v6M14 10.5v6"/></symbol>
</svg>

<div><span class="lbl">Bottom sheet (action sheet)</span>
<div class="d-flex flex-wrap gap-2"><button class="btn sh-btn btn-primary" type="button" data-bs-toggle="modal" data-bs-target="#sh-share"><svg aria-hidden="true"><use href="#sh-i-share"/></svg>Share file</button></div></div>

<div><span class="lbl">Side sheet</span>
<div class="d-flex flex-wrap gap-2"><button class="btn sh-btn" type="button" data-bs-toggle="modal" data-bs-target="#sh-filters"><svg aria-hidden="true"><use href="#sh-i-sliders"/></svg>Filters</button></div></div>

<div><span class="lbl">Sheet on mobile, dialog on desktop</span>
<div class="d-flex flex-wrap gap-2"><button class="btn sh-btn" type="button" data-bs-toggle="modal" data-bs-target="#sh-address"><svg aria-hidden="true"><use href="#sh-i-pin"/></svg>Edit delivery address</button></div></div>

<!-- 1. Bottom sheet (action sheet) -->
<div class="modal fade sh sh-bottom" id="sh-share" tabindex="-1" aria-labelledby="sh-share-title" aria-hidden="true">
  <div class="modal-dialog">
    <div class="modal-content">
      <span class="sh-handle" aria-hidden="true"></span>
      <div class="modal-header">
        <span class="sh-icon" aria-hidden="true"><svg><use href="#sh-i-file"/></svg></span>
        <div class="flex-grow-1"><h2 class="modal-title" id="sh-share-title">Share “Q3 roadmap.pdf”</h2><p class="sh-sub">2.4 MB · edited 2 hours ago</p></div>
      </div>
      <div class="modal-body">
        <ul class="list-unstyled sh-targets">
          <li><button type="button" class="sh-target" data-bs-dismiss="modal"><span aria-hidden="true"><svg><use href="#sh-i-link"/></svg></span>Copy link</button></li>
          <li><button type="button" class="sh-target" data-bs-dismiss="modal"><span aria-hidden="true"><svg><use href="#sh-i-mail"/></svg></span>Email</button></li>
          <li><button type="button" class="sh-target" data-bs-dismiss="modal"><span aria-hidden="true"><svg><use href="#sh-i-chat"/></svg></span>Messages</button></li>
          <li><button type="button" class="sh-target" data-bs-dismiss="modal"><span aria-hidden="true"><svg><use href="#sh-i-nearby"/></svg></span>Nearby</button></li>
        </ul>
        <div class="list-group list-group-flush sh-actions">
          <button type="button" class="list-group-item list-group-item-action sh-action" data-bs-dismiss="modal"><svg aria-hidden="true"><use href="#sh-i-download"/></svg>Download</button>
          <button type="button" class="list-group-item list-group-item-action sh-action" data-bs-dismiss="modal"><svg aria-hidden="true"><use href="#sh-i-copy"/></svg>Duplicate</button>
          <button type="button" class="list-group-item list-group-item-action sh-action" data-bs-dismiss="modal"><svg aria-hidden="true"><use href="#sh-i-folder"/></svg>Move to folder</button>
          <button type="button" class="list-group-item list-group-item-action sh-action is-danger" data-bs-dismiss="modal"><svg aria-hidden="true"><use href="#sh-i-trash"/></svg>Delete file</button>
        </div>
      </div>
      <div class="modal-footer"><button type="button" class="btn sh-btn" data-bs-dismiss="modal">Cancel</button></div>
    </div>
  </div>
</div>

<!-- 2. Side sheet -->
<div class="modal fade sh sh-side" id="sh-filters" tabindex="-1" aria-labelledby="sh-filters-title" aria-hidden="true">
  <div class="modal-dialog modal-dialog-scrollable">
    <div class="modal-content">
      <div class="modal-header">
        <div class="flex-grow-1"><h2 class="modal-title" id="sh-filters-title">Filters</h2><p class="sh-sub">Menswear · 146 products</p></div>
        <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close filters"></button>
      </div>
      <div class="modal-body" tabindex="0" role="region" aria-label="Filter options">
        <fieldset class="sh-group">
          <legend>Category</legend>
          <div class="form-check sh-check"><input class="form-check-input" type="checkbox" id="sh-cat-jackets" name="category" value="jackets" checked><label class="form-check-label" for="sh-cat-jackets">Jackets</label><span class="badge rounded-pill sh-count">42</span></div>
          <div class="form-check sh-check"><input class="form-check-input" type="checkbox" id="sh-cat-knitwear" name="category" value="knitwear" checked><label class="form-check-label" for="sh-cat-knitwear">Knitwear</label><span class="badge rounded-pill sh-count">31</span></div>
          <div class="form-check sh-check"><input class="form-check-input" type="checkbox" id="sh-cat-shirts" name="category" value="shirts" checked><label class="form-check-label" for="sh-cat-shirts">Shirts</label><span class="badge rounded-pill sh-count">55</span></div>
          <div class="form-check sh-check"><input class="form-check-input" type="checkbox" id="sh-cat-trousers" name="category" value="trousers"><label class="form-check-label" for="sh-cat-trousers">Trousers</label><span class="badge rounded-pill sh-count">18</span></div>
        </fieldset>
        <fieldset class="sh-group">
          <legend>Size</legend>
          <div class="d-flex flex-wrap gap-2">
            <input type="checkbox" class="btn-check" id="sh-size-xs" name="size" value="xs" autocomplete="off"><label class="btn sh-chip" for="sh-size-xs">XS</label>
            <input type="checkbox" class="btn-check" id="sh-size-s" name="size" value="s" autocomplete="off"><label class="btn sh-chip" for="sh-size-s">S</label>
            <input type="checkbox" class="btn-check" id="sh-size-m" name="size" value="m" autocomplete="off" checked><label class="btn sh-chip" for="sh-size-m">M</label>
            <input type="checkbox" class="btn-check" id="sh-size-l" name="size" value="l" autocomplete="off" checked><label class="btn sh-chip" for="sh-size-l">L</label>
            <input type="checkbox" class="btn-check" id="sh-size-xl" name="size" value="xl" autocomplete="off"><label class="btn sh-chip" for="sh-size-xl">XL</label>
          </div>
        </fieldset>
        <fieldset class="sh-group">
          <legend>Price</legend>
          <div class="row sh-range">
            <div class="col-6"><label class="form-label" for="sh-price-min">Min (£)</label><input class="form-control" id="sh-price-min" name="min" type="number" inputmode="numeric" min="0" step="5" value="40"></div>
            <div class="col-6"><label class="form-label" for="sh-price-max">Max (£)</label><input class="form-control" id="sh-price-max" name="max" type="number" inputmode="numeric" min="0" step="5" value="180"></div>
          </div>
        </fieldset>
        <fieldset class="sh-group">
          <legend>Availability</legend>
          <div class="form-check form-switch sh-toggle">
            <label class="form-check-label" for="sh-stock"><b>In stock only</b><small>Hide items that ship in 2+ weeks</small></label>
            <input class="form-check-input" type="checkbox" role="switch" id="sh-stock" name="stock" checked>
          </div>
        </fieldset>
      </div>
      <div class="modal-footer">
        <button type="button" class="btn sh-btn" data-clear>Clear all</button>
        <button type="button" class="btn sh-btn btn-primary" data-bs-dismiss="modal">Show 128 results</button>
      </div>
    </div>
  </div>
</div>

<!-- 3. Sheet on mobile, dialog on desktop -->
<div class="modal fade sh sh-adapt" id="sh-address" tabindex="-1" aria-labelledby="sh-address-title" aria-hidden="true">
  <div class="modal-dialog">
    <div class="modal-content">
      <span class="sh-handle" aria-hidden="true"></span>
      <div class="modal-header">
        <span class="sh-icon" aria-hidden="true"><svg><use href="#sh-i-pin"/></svg></span>
        <div class="flex-grow-1"><h2 class="modal-title" id="sh-address-title">Edit delivery address</h2><p class="sh-sub">Order HL-48213 · arrives Thu 2 Oct</p></div>
      </div>
      <div class="modal-body">
        <div class="row sh-fields">
          <div class="col-12"><label class="form-label" for="sh-name">Full name</label><input class="form-control" id="sh-name" name="name" autocomplete="name" value="Maya Lindqvist"></div>
          <div class="col-12"><label class="form-label" for="sh-line1">Address line 1</label><input class="form-control" id="sh-line1" name="line1" autocomplete="address-line1" value="Flat 4, 27 Wharf Road"></div>
          <div class="col-6"><label class="form-label" for="sh-city">City</label><input class="form-control" id="sh-city" name="city" autocomplete="address-level2" value="Manchester"></div>
          <div class="col-6"><label class="form-label" for="sh-postcode">Postcode</label><input class="form-control" id="sh-postcode" name="postcode" autocomplete="postal-code" value="M3 4LN"></div>
          <div class="col-12"><label class="form-label" for="sh-country">Country</label>
            <select class="form-select" id="sh-country" name="country" autocomplete="country">
              <option value="IE">Ireland</option><option value="GB" selected>United Kingdom</option><option value="NL">Netherlands</option><option value="SE">Sweden</option>
            </select>
          </div>
        </div>
      </div>
      <div class="modal-footer">
        <button type="button" class="btn sh-btn" data-bs-dismiss="modal">Cancel</button>
        <button type="button" class="btn sh-btn btn-primary" data-bs-dismiss="modal">Save address</button>
      </div>
    </div>
  </div>
</div>
CSS
/* Sheets built on Bootstrap's Modal plugin (not Offcanvas): custom classes re-anchor .modal-dialog to the
   bottom or right edge and swap the fade-down transform for a slide in from that edge. */
.sh-btn{--bs-btn-padding-x:16px;--bs-btn-padding-y:9px;--bs-btn-font-size:.9rem;--bs-btn-font-weight:600;--bs-btn-line-height:1.2;--bs-btn-border-radius:10px;
  --bs-btn-bg:#fff;--bs-btn-color:#0f172a;--bs-btn-border-color:#cbd5e1;--bs-btn-hover-bg:#f5f3ff;--bs-btn-hover-color:#0f172a;--bs-btn-hover-border-color:#a78bfa;
  --bs-btn-active-bg:#ede9fe;--bs-btn-active-color:#0f172a;--bs-btn-active-border-color:#a78bfa;
  display:inline-flex;align-items:center;justify-content:center;gap:8px;min-height:42px}
.sh-btn.btn-primary{--bs-btn-bg:#6d28d9;--bs-btn-color:#fff;--bs-btn-border-color:#6d28d9;--bs-btn-hover-bg:#5b21b6;--bs-btn-hover-color:#fff;--bs-btn-hover-border-color:#5b21b6;
  --bs-btn-active-bg:#4c1d95;--bs-btn-active-color:#fff;--bs-btn-active-border-color:#4c1d95;box-shadow:0 6px 16px -8px rgba(109,40,217,.7)}
.sh-btn:focus-visible{outline:2px solid #6d28d9;outline-offset:2px;box-shadow:none}
.sh-btn svg{width:18px;height:18px;flex:none}

.sh{--bs-modal-margin:0;--bs-modal-color:#0f172a;--bs-modal-bg:#fff;--bs-modal-border-width:0;--bs-modal-border-radius:24px;--bs-modal-inner-border-radius:24px;
  --bs-modal-header-padding:14px 24px 16px;--bs-modal-header-border-width:0;--bs-modal-header-border-color:#ebe7f5;--bs-modal-title-line-height:1.35;
  --bs-modal-footer-bg:#fff;--bs-modal-footer-border-color:#ebe7f5;font-size:.95rem;line-height:1.55}
.sh .modal-content{overflow:hidden;box-shadow:0 0 0 1px rgba(30,27,75,.06),0 -16px 48px -16px rgba(30,27,75,.4)}
.modal-backdrop{--bs-backdrop-bg:#181438;--bs-backdrop-opacity:.5;--bs-backdrop-zindex:1050;backdrop-filter:blur(2px)}
/* Slide in from the edge. The sheet itself stays opaque while it slides out, then Bootstrap hides it. */
.modal.sh.fade .modal-dialog{transition:transform .34s cubic-bezier(.22,1,.36,1)}
.modal.sh.fade.show{transition:none}
.modal.sh.fade:not(.show){transition:opacity .25s step-end}
.modal.sh.fade:not(.show) .modal-dialog{transition-duration:.25s;transition-timing-function:cubic-bezier(.4,0,1,1)}
.modal.sh-bottom.fade:not(.show) .modal-dialog,.modal.sh-adapt.fade:not(.show) .modal-dialog{transform:translateY(100%)}
.modal.sh-side.fade:not(.show) .modal-dialog{transform:translateX(100%)}

.sh-handle{flex:none;display:block;width:40px;height:5px;margin:10px auto 0;border-radius:999px;background:#d4cfe6}
.sh .modal-header{gap:14px}
.sh .modal-header .flex-grow-1{min-width:0}
.sh .modal-title{font-size:1.1rem;font-weight:700;letter-spacing:-.01em;overflow-wrap:anywhere}
.sh-sub{margin:2px 0 0;color:#475569;font-size:.85rem}
.sh-icon{flex:none;display:grid;place-items:center;width:44px;height:44px;border-radius:12px;background:linear-gradient(135deg,#f5f3ff,#ede9fe);color:#6d28d9}
.sh-icon svg{width:22px;height:22px}
.sh .modal-header .btn-close{flex:none;width:36px;height:36px;margin:-4px -8px 0 auto;padding:0;border-radius:10px;background-size:13px;opacity:.75}
.sh .btn-close:hover{background-color:#f5f3ff;opacity:1}
.sh .btn-close:focus-visible{outline:2px solid #6d28d9;outline-offset:1px;box-shadow:none;opacity:1}
.sh .modal-body{min-height:0;padding:0;overflow-y:auto;overscroll-behavior:contain}
.sh .modal-body:focus-visible{outline:2px solid #6d28d9;outline-offset:-4px}
.sh .modal-footer{flex-wrap:wrap;gap:10px;padding:16px 24px}
.sh .modal-footer>*{margin:0}

/* 1. Bottom sheet: pinned to the bottom edge at every width, full width on phones */
.sh-bottom{--bs-modal-width:560px}
.sh-bottom .modal-dialog{display:flex;align-items:flex-end;min-height:100%;margin:0 auto}
.sh-bottom .modal-content{max-height:calc(100dvh - 24px);border-bottom-right-radius:0;border-bottom-left-radius:0}
.sh-targets{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:4px;margin:0;padding:4px 14px 14px}
.sh-target{display:flex;flex-direction:column;align-items:center;gap:8px;width:100%;padding:8px 2px;border:0;border-radius:14px;background:none;color:#0f172a;
  font-size:.8rem;font-weight:600;line-height:1.25}
.sh-target span{display:grid;place-items:center;width:54px;height:54px;border-radius:50%;background:#f5f3ff;color:#6d28d9;transition:background-color .15s}
.sh-target svg{width:24px;height:24px}
.sh-target:hover span{background:#ede9fe}
.sh-target:focus-visible{outline:2px solid #6d28d9;outline-offset:-2px}
.sh-actions{--bs-list-group-border-width:0;--bs-list-group-color:#0f172a;--bs-list-group-action-color:#0f172a;--bs-list-group-action-hover-color:#0f172a;
  --bs-list-group-action-hover-bg:#f5f3ff;--bs-list-group-action-active-bg:#ede9fe;--bs-list-group-action-active-color:#0f172a;--bs-list-group-item-padding-x:12px;--bs-list-group-item-padding-y:10px;
  padding:8px 12px;border-top:1px solid #ebe7f5}
.sh-action{display:flex;align-items:center;gap:14px;min-height:48px;border-radius:12px!important;font-weight:500}
.sh-action svg{width:20px;height:20px;flex:none;color:#475569}
.sh-action:focus-visible{outline:2px solid #6d28d9;outline-offset:-2px}
.sh-action.is-danger,.sh-action.is-danger:hover,.sh-action.is-danger svg{color:#b91c1c}
.sh-action.is-danger:hover{background:#fef2f2}
.sh-bottom .modal-footer{padding:12px 16px calc(16px + env(safe-area-inset-bottom))}
.sh-bottom .modal-footer .sh-btn{width:100%;min-height:48px}

/* 2. Side sheet: full height on the right edge */
.sh-side .modal-dialog{width:min(400px,100%);max-width:none;height:100%;margin:0 0 0 auto}
.sh-side .modal-content{height:100%;max-height:none;border-radius:20px 0 0 20px;box-shadow:0 0 0 1px rgba(30,27,75,.06),-16px 0 48px -16px rgba(30,27,75,.4)}
.sh-side .modal-header{align-items:flex-start;padding:20px 24px 16px;border-bottom:1px solid #ebe7f5}
.sh-side .modal-body{padding:0 24px}
.sh-group{margin:0;padding:18px 0;border-bottom:1px solid #ebe7f5;min-width:0}
.sh-group:last-child{border-bottom:0}
.sh-group legend{margin:0 0 10px;padding:0;color:#0f172a;font-size:.75rem;font-weight:700;line-height:1.5;letter-spacing:.08em;text-transform:uppercase}
.sh .form-check-input{border-color:#8e89a8}
.sh .form-check-input:checked{background-color:#6d28d9;border-color:#6d28d9}
.sh .form-check-input:focus{border-color:#8e89a8;box-shadow:none}
.sh .form-check-input:checked:focus{border-color:#6d28d9}
.sh .form-check-input:focus-visible{outline:2px solid #6d28d9;outline-offset:2px}
.sh-check{display:flex;align-items:center;gap:12px;min-height:0;margin:0;padding:7px 0}
.sh-check .form-check-input{float:none;flex:none;width:18px;height:18px;margin:0;cursor:pointer}
.sh-check .form-check-label{flex:1;cursor:pointer}
.sh-count{min-width:34px;padding:3px 8px;background:#f5f3ff;color:#5b21b6;font-size:.75rem;font-weight:600}
.sh-chip{--bs-btn-padding-x:14px;--bs-btn-padding-y:0;--bs-btn-font-size:.875rem;--bs-btn-font-weight:600;--bs-btn-border-radius:999px;--bs-btn-bg:#fff;--bs-btn-color:#0f172a;
  --bs-btn-border-color:#8e89a8;--bs-btn-hover-bg:#f5f3ff;--bs-btn-hover-color:#0f172a;--bs-btn-hover-border-color:#8e89a8;--bs-btn-active-bg:#6d28d9;--bs-btn-active-color:#fff;
  --bs-btn-active-border-color:#6d28d9;display:grid;place-items:center;min-width:54px;height:40px}
.btn-check:focus-visible+.sh-chip{outline:2px solid #6d28d9;outline-offset:2px;box-shadow:none}
.sh .form-label{display:block;margin-bottom:6px;color:#475569;font-size:.8rem;font-weight:600}
.sh .form-control,.sh .form-select{height:42px;padding:8px 12px;border-color:#8e89a8;border-radius:10px;color:#0f172a;font-size:.9rem}
.sh .form-select{padding-right:40px;background-position:right 12px center}
.sh .form-control:focus,.sh .form-select:focus{border-color:#6d28d9;box-shadow:0 0 0 3px #ddd6fe}
.sh-toggle{display:flex;align-items:center;gap:16px;min-height:0;margin:0;padding:0}
.sh-toggle .form-check-label b{display:block;font-weight:600}
.sh-toggle .form-check-label small{display:block;color:#475569;font-size:.8rem}
.sh .form-switch .form-check-input,.sh .form-switch .form-check-input:focus{--bs-form-switch-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2.8' fill='%23fff'/%3e%3c/svg%3e")}
.sh-toggle .form-check-input{float:none;flex:none;width:44px;height:26px;margin:0 0 0 auto;border:0;background-color:#8e89a8;cursor:pointer}
.sh-toggle .form-check-input:checked{background-color:#6d28d9}
.sh-side .modal-footer{flex-wrap:nowrap;padding:16px 24px calc(16px + env(safe-area-inset-bottom));box-shadow:0 -8px 20px -14px rgba(30,27,75,.35)}
.sh-side .modal-footer .sh-btn{flex:none;white-space:nowrap}
.sh-side .modal-footer .sh-btn.btn-primary{flex:1 1 auto}

/* 3. Adaptive: a bottom sheet on phones, a centred dialog from 640px */
.sh-adapt{--bs-modal-width:480px}
.sh-adapt .modal-dialog{display:flex;align-items:flex-end;min-height:100%;max-width:none;margin:0}
.sh-adapt .modal-content{max-height:calc(100dvh - 24px);border-bottom-right-radius:0;border-bottom-left-radius:0}
.sh-fields{--bs-gutter-x:12px;--bs-gutter-y:14px}
.sh-range{--bs-gutter-x:12px;--bs-gutter-y:0}
.sh-adapt .modal-body{padding:4px 24px 22px}
.sh-adapt .modal-footer{padding-bottom:calc(16px + env(safe-area-inset-bottom))}

@media (min-width:640px){
  .sh-adapt{--bs-modal-footer-bg:#faf9fe}
  .sh-adapt .modal-dialog{align-items:center;min-height:calc(100% - 56px);max-width:var(--bs-modal-width);margin:28px auto}
  .sh-adapt .modal-content{max-height:none;border-radius:16px;box-shadow:0 0 0 1px rgba(30,27,75,.06),0 28px 64px -18px rgba(30,27,75,.45)}
  .modal.sh-adapt.fade .modal-dialog{transition-duration:.22s;transition-timing-function:cubic-bezier(.2,.8,.2,1)}
  .modal.sh-adapt.fade:not(.show) .modal-dialog{transform:translateY(10px) scale(.97)}
  .modal.sh-adapt.fade:not(.show){transition:opacity .22s}
  .modal.sh-adapt.fade.show{transition:opacity .15s linear}
  .sh-adapt .sh-handle{display:none}
  .sh-adapt .modal-header{padding-top:22px}
  .sh-adapt .modal-footer{padding-bottom:16px}
}
@media (max-width:600px){
  .sh{--bs-modal-header-padding:14px 18px 16px}
  .sh-side .modal-header{padding-left:18px;padding-right:18px}
  .sh-side .modal-body{padding:0 18px}
  .sh-adapt .modal-body{padding-left:18px;padding-right:18px}
  .sh .modal-footer{padding-left:18px;padding-right:18px}
  .sh .modal-footer .sh-btn{flex:1 1 auto}
  .sh-targets{padding-left:8px;padding-right:8px}
}
@media (max-width:440px){.sh-side .modal-content{border-radius:0}}
@media (prefers-reduced-motion:reduce){
  .modal.sh.fade,.modal.sh.fade:not(.show),.modal.sh.fade:not(.show) .modal-dialog,.modal.sh-adapt.fade:not(.show),.modal.sh-adapt.fade.show,.modal.sh.fade .modal-dialog,.sh-btn,.sh-chip,.sh-target span{transition:none}
}
JavaScript
// "Clear all" in the filters sheet: untick every box and empty the price fields; the sheet stays open
document.querySelectorAll('[data-clear]').forEach(function (btn) {
  btn.addEventListener('click', function () {
    btn.closest('.modal-content').querySelectorAll('input').forEach(function (el) {
      if (el.type === 'checkbox') el.checked = false; else el.value = '';
    });
  });
});

Tailwind 4 code

Requires: Tailwind CSS 4

HTML
<svg width="0" height="0" style="position:absolute" aria-hidden="true" focusable="false">
  <symbol id="sh-i-x" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M6.5 6.5l11 11M17.5 6.5l-11 11"/></symbol>
  <symbol id="sh-i-share" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M12 14.5v-11M8 7.5l4-4 4 4M7 10.5H6a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-6a2 2 0 0 0-2-2h-1"/></symbol>
  <symbol id="sh-i-sliders" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M4 7h10M18 7h2M4 17h4M12 17h8"/><circle cx="16" cy="7" r="2"/><circle cx="10" cy="17" r="2"/></symbol>
  <symbol id="sh-i-pin" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M12 21s-6.5-5.6-6.5-11a6.5 6.5 0 0 1 13 0c0 5.4-6.5 11-6.5 11z"/><circle cx="12" cy="10" r="2.3"/></symbol>
  <symbol id="sh-i-file" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M14 3.5H7.5a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2h9a2 2 0 0 0 2-2V8z"/><path d="M14 3.5V8h4.5M9 13h6M9 16.5h4"/></symbol>
  <symbol id="sh-i-link" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M10 14a4 4 0 0 0 5.7 0l3-3a4 4 0 0 0-5.7-5.7l-1.2 1.2"/><path d="M14 10a4 4 0 0 0-5.7 0l-3 3a4 4 0 0 0 5.7 5.7l1.2-1.2"/></symbol>
  <symbol id="sh-i-mail" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="3.5" y="5.5" width="17" height="13" rx="2"/><path d="m4 7 8 6 8-6"/></symbol>
  <symbol id="sh-i-chat" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M20 11.5a7.5 7.5 0 0 1-11 6.6l-4.5 1.4 1.3-4A7.5 7.5 0 1 1 20 11.5z"/></symbol>
  <symbol id="sh-i-nearby" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="1.8"/><path d="M8.5 8.5a5 5 0 0 0 0 7M15.5 8.5a5 5 0 0 1 0 7M5.6 5.6a9 9 0 0 0 0 12.8M18.4 5.6a9 9 0 0 1 0 12.8"/></symbol>
  <symbol id="sh-i-download" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M12 4v11M7.5 10.5 12 15l4.5-4.5M5 19.5h14"/></symbol>
  <symbol id="sh-i-copy" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="8.5" y="8.5" width="11" height="11" rx="2"/><path d="M15.5 8.5v-2a2 2 0 0 0-2-2h-7a2 2 0 0 0-2 2v7a2 2 0 0 0 2 2h2"/></symbol>
  <symbol id="sh-i-folder" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M3.5 7.5a2 2 0 0 1 2-2h4l2 2.5h7a2 2 0 0 1 2 2v7.5a2 2 0 0 1-2 2h-13a2 2 0 0 1-2-2z"/><path d="M9.5 13.5h5.5M13 11.5l2 2-2 2"/></symbol>
  <symbol id="sh-i-trash" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M4.5 7h15M10 4h4M6.5 7l.8 11.2a2 2 0 0 0 2 1.8h5.4a2 2 0 0 0 2-1.8L17.5 7M10 10.5v6M14 10.5v6"/></symbol>
  <symbol id="sh-i-chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m6.5 9.5 5.5 5.5 5.5-5.5"/></symbol>
</svg>

<div><span class="mb-3 block text-[.72rem] tracking-[.12em] text-[#5b6474] uppercase">Bottom sheet (action sheet)</span>
<div class="flex flex-wrap gap-2.5"><button class="inline-flex min-h-[42px] cursor-pointer items-center justify-center gap-2 rounded-[10px] border px-4 py-[9px] text-[.9rem] leading-[1.2] font-semibold no-underline transition-[background-color,border-color,box-shadow] duration-150 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#6d28d9] motion-reduce:transition-none border-[#6d28d9] bg-[#6d28d9] text-white shadow-[0_6px_16px_-8px_rgba(109,40,217,.7)] hover:border-[#5b21b6] hover:bg-[#5b21b6]" type="button" data-open="sh-share" aria-haspopup="dialog"><svg class="size-[18px] flex-none" aria-hidden="true"><use href="#sh-i-share"/></svg>Share file</button></div></div>

<div><span class="mb-3 block text-[.72rem] tracking-[.12em] text-[#5b6474] uppercase">Side sheet</span>
<div class="flex flex-wrap gap-2.5"><button class="inline-flex min-h-[42px] cursor-pointer items-center justify-center gap-2 rounded-[10px] border px-4 py-[9px] text-[.9rem] leading-[1.2] font-semibold no-underline transition-[background-color,border-color,box-shadow] duration-150 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#6d28d9] motion-reduce:transition-none border-[#cbd5e1] bg-white text-[#0f172a] hover:border-[#a78bfa] hover:bg-[#f5f3ff]" type="button" data-open="sh-filters" aria-haspopup="dialog"><svg class="size-[18px] flex-none" aria-hidden="true"><use href="#sh-i-sliders"/></svg>Filters</button></div></div>

<div><span class="mb-3 block text-[.72rem] tracking-[.12em] text-[#5b6474] uppercase">Sheet on mobile, dialog on desktop</span>
<div class="flex flex-wrap gap-2.5"><button class="inline-flex min-h-[42px] cursor-pointer items-center justify-center gap-2 rounded-[10px] border px-4 py-[9px] text-[.9rem] leading-[1.2] font-semibold no-underline transition-[background-color,border-color,box-shadow] duration-150 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#6d28d9] motion-reduce:transition-none border-[#cbd5e1] bg-white text-[#0f172a] hover:border-[#a78bfa] hover:bg-[#f5f3ff]" type="button" data-open="sh-address" aria-haspopup="dialog"><svg class="size-[18px] flex-none" aria-hidden="true"><use href="#sh-i-pin"/></svg>Edit delivery address</button></div></div>

<!-- 1. Bottom sheet (action sheet) -->
<dialog class="max-w-none overflow-hidden overscroll-contain border-0 bg-white p-0 text-[.95rem] leading-[1.55] text-[#0f172a] backdrop:bg-[rgba(24,20,56,.5)] backdrop:backdrop-blur-[2px] open:flex open:flex-col motion-safe:open:backdrop:animate-[sh-fade_.3s_ease-out] mx-auto mt-auto mb-0 max-h-[calc(100dvh-24px)] w-[min(560px,100%)] rounded-t-3xl shadow-[0_0_0_1px_rgba(30,27,75,.06),0_-16px_48px_-16px_rgba(30,27,75,.4)] motion-safe:open:animate-[sh-up_.34s_cubic-bezier(.22,1,.36,1)]" id="sh-share" aria-labelledby="sh-share-title">
  <form class="m-0 flex min-h-0 flex-1 flex-col" method="dialog">
    <span class="mx-auto mt-2.5 block h-[5px] w-10 flex-none rounded-full bg-[#d4cfe6]" aria-hidden="true"></span>
    <div class="flex flex-none items-center gap-3.5 px-6 pt-3.5 pb-4 max-[600px]:px-[18px]">
      <span class="grid size-11 flex-none place-items-center rounded-xl bg-[linear-gradient(135deg,#f5f3ff,#ede9fe)] text-[#6d28d9]" aria-hidden="true"><svg class="size-[22px]"><use href="#sh-i-file"/></svg></span>
      <div class="min-w-0 flex-1"><h2 class="m-0 text-[1.1rem] leading-[1.35] font-bold tracking-[-.01em] [overflow-wrap:anywhere]" id="sh-share-title">Share “Q3 roadmap.pdf”</h2><p class="mt-0.5 mb-0 text-[.85rem] text-[#475569]">2.4 MB · edited 2 hours ago</p></div>
    </div>
    <div class="min-h-0 flex-auto overflow-y-auto overscroll-contain">
      <ul class="m-0 grid list-none grid-cols-4 gap-1 px-3.5 pt-1 pb-3.5 max-[600px]:px-2">
        <li><button class="group flex w-full cursor-pointer flex-col items-center gap-2 rounded-[14px] border-0 bg-transparent px-0.5 py-2 text-[.8rem] leading-[1.25] font-semibold text-[#0f172a] focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-[#6d28d9]" value="copy"><span class="grid size-[54px] place-items-center rounded-full bg-[#f5f3ff] text-[#6d28d9] transition-colors duration-150 group-hover:bg-[#ede9fe] motion-reduce:transition-none" aria-hidden="true"><svg class="size-6"><use href="#sh-i-link"/></svg></span>Copy link</button></li>
        <li><button class="group flex w-full cursor-pointer flex-col items-center gap-2 rounded-[14px] border-0 bg-transparent px-0.5 py-2 text-[.8rem] leading-[1.25] font-semibold text-[#0f172a] focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-[#6d28d9]" value="email"><span class="grid size-[54px] place-items-center rounded-full bg-[#f5f3ff] text-[#6d28d9] transition-colors duration-150 group-hover:bg-[#ede9fe] motion-reduce:transition-none" aria-hidden="true"><svg class="size-6"><use href="#sh-i-mail"/></svg></span>Email</button></li>
        <li><button class="group flex w-full cursor-pointer flex-col items-center gap-2 rounded-[14px] border-0 bg-transparent px-0.5 py-2 text-[.8rem] leading-[1.25] font-semibold text-[#0f172a] focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-[#6d28d9]" value="messages"><span class="grid size-[54px] place-items-center rounded-full bg-[#f5f3ff] text-[#6d28d9] transition-colors duration-150 group-hover:bg-[#ede9fe] motion-reduce:transition-none" aria-hidden="true"><svg class="size-6"><use href="#sh-i-chat"/></svg></span>Messages</button></li>
        <li><button class="group flex w-full cursor-pointer flex-col items-center gap-2 rounded-[14px] border-0 bg-transparent px-0.5 py-2 text-[.8rem] leading-[1.25] font-semibold text-[#0f172a] focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-[#6d28d9]" value="nearby"><span class="grid size-[54px] place-items-center rounded-full bg-[#f5f3ff] text-[#6d28d9] transition-colors duration-150 group-hover:bg-[#ede9fe] motion-reduce:transition-none" aria-hidden="true"><svg class="size-6"><use href="#sh-i-nearby"/></svg></span>Nearby</button></li>
      </ul>
      <ul class="m-0 list-none border-t border-[#ebe7f5] px-3 py-2">
        <li><button class="flex min-h-12 w-full cursor-pointer items-center gap-3.5 rounded-xl border-0 bg-transparent px-3 py-2.5 text-left font-medium transition-colors duration-150 focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-[#6d28d9] motion-reduce:transition-none text-[#0f172a] hover:bg-[#f5f3ff]" value="download"><svg class="size-5 flex-none text-[#475569]" aria-hidden="true"><use href="#sh-i-download"/></svg>Download</button></li>
        <li><button class="flex min-h-12 w-full cursor-pointer items-center gap-3.5 rounded-xl border-0 bg-transparent px-3 py-2.5 text-left font-medium transition-colors duration-150 focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-[#6d28d9] motion-reduce:transition-none text-[#0f172a] hover:bg-[#f5f3ff]" value="duplicate"><svg class="size-5 flex-none text-[#475569]" aria-hidden="true"><use href="#sh-i-copy"/></svg>Duplicate</button></li>
        <li><button class="flex min-h-12 w-full cursor-pointer items-center gap-3.5 rounded-xl border-0 bg-transparent px-3 py-2.5 text-left font-medium transition-colors duration-150 focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-[#6d28d9] motion-reduce:transition-none text-[#0f172a] hover:bg-[#f5f3ff]" value="move"><svg class="size-5 flex-none text-[#475569]" aria-hidden="true"><use href="#sh-i-folder"/></svg>Move to folder</button></li>
        <li><button class="flex min-h-12 w-full cursor-pointer items-center gap-3.5 rounded-xl border-0 bg-transparent px-3 py-2.5 text-left font-medium transition-colors duration-150 focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-[#6d28d9] motion-reduce:transition-none text-[#b91c1c] hover:bg-[#fef2f2]" value="delete"><svg class="size-5 flex-none text-[#b91c1c]" aria-hidden="true"><use href="#sh-i-trash"/></svg>Delete file</button></li>
      </ul>
    </div>
    <div class="flex flex-none border-t border-[#ebe7f5] bg-white px-4 pt-3 pb-[calc(16px+env(safe-area-inset-bottom))]"><button class="inline-flex min-h-12 cursor-pointer items-center justify-center gap-2 rounded-[10px] border px-4 py-[9px] text-[.9rem] leading-[1.2] font-semibold no-underline transition-[background-color,border-color,box-shadow] duration-150 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#6d28d9] motion-reduce:transition-none border-[#cbd5e1] bg-white text-[#0f172a] hover:border-[#a78bfa] hover:bg-[#f5f3ff] w-full" value="cancel">Cancel</button></div>
  </form>
</dialog>

<!-- 2. Side sheet -->
<dialog class="max-w-none overflow-hidden overscroll-contain border-0 bg-white p-0 text-[.95rem] leading-[1.55] text-[#0f172a] backdrop:bg-[rgba(24,20,56,.5)] backdrop:backdrop-blur-[2px] open:flex open:flex-col motion-safe:open:backdrop:animate-[sh-fade_.3s_ease-out] my-0 mr-0 ml-auto h-full max-h-none w-[min(400px,100%)] rounded-l-[20px] max-[440px]:rounded-none shadow-[0_0_0_1px_rgba(30,27,75,.06),-16px_0_48px_-16px_rgba(30,27,75,.4)] motion-safe:open:animate-[sh-left_.34s_cubic-bezier(.22,1,.36,1)]" id="sh-filters" aria-labelledby="sh-filters-title">
  <form class="m-0 flex min-h-0 flex-1 flex-col" method="dialog">
    <div class="flex flex-none items-start gap-3.5 border-b border-[#ebe7f5] px-6 pt-5 pb-4 max-[600px]:px-[18px]">
      <div class="min-w-0 flex-1"><h2 class="m-0 text-[1.1rem] leading-[1.35] font-bold tracking-[-.01em] [overflow-wrap:anywhere]" id="sh-filters-title">Filters</h2><p class="mt-0.5 mb-0 text-[.85rem] text-[#475569]">Menswear · 146 products</p></div>
      <button class="-mt-1 -mr-2 grid size-9 flex-none cursor-pointer place-items-center rounded-[10px] border-0 bg-transparent p-0 text-[#475569] transition-colors duration-150 hover:bg-[#f5f3ff] hover:text-[#0f172a] focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-[#6d28d9] motion-reduce:transition-none" value="close" aria-label="Close filters"><svg class="size-5" aria-hidden="true"><use href="#sh-i-x"/></svg></button>
    </div>
    <div class="min-h-0 flex-auto overflow-y-auto overscroll-contain px-6 max-[600px]:px-[18px] focus-visible:outline-2 focus-visible:-outline-offset-4 focus-visible:outline-[#6d28d9]" tabindex="0" role="region" aria-label="Filter options">
      <fieldset class="m-0 min-w-0 border-b border-[#ebe7f5] py-[18px] last:border-b-0">
        <legend class="float-left mb-2.5 w-full p-0 text-[.75rem] font-bold tracking-[.08em] text-[#0f172a] uppercase">Category</legend>
        <div class="clear-left">
          <label class="flex cursor-pointer items-center gap-3 py-[7px]"><input class="size-[18px] flex-none cursor-pointer accent-[#6d28d9] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#6d28d9]" type="checkbox" name="category" value="jackets" checked>Jackets<span class="ml-auto min-w-[34px] rounded-full bg-[#f5f3ff] px-2 py-px text-center text-[.75rem] font-semibold text-[#5b21b6]">42</span></label>
          <label class="flex cursor-pointer items-center gap-3 py-[7px]"><input class="size-[18px] flex-none cursor-pointer accent-[#6d28d9] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#6d28d9]" type="checkbox" name="category" value="knitwear" checked>Knitwear<span class="ml-auto min-w-[34px] rounded-full bg-[#f5f3ff] px-2 py-px text-center text-[.75rem] font-semibold text-[#5b21b6]">31</span></label>
          <label class="flex cursor-pointer items-center gap-3 py-[7px]"><input class="size-[18px] flex-none cursor-pointer accent-[#6d28d9] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#6d28d9]" type="checkbox" name="category" value="shirts" checked>Shirts<span class="ml-auto min-w-[34px] rounded-full bg-[#f5f3ff] px-2 py-px text-center text-[.75rem] font-semibold text-[#5b21b6]">55</span></label>
          <label class="flex cursor-pointer items-center gap-3 py-[7px]"><input class="size-[18px] flex-none cursor-pointer accent-[#6d28d9] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#6d28d9]" type="checkbox" name="category" value="trousers">Trousers<span class="ml-auto min-w-[34px] rounded-full bg-[#f5f3ff] px-2 py-px text-center text-[.75rem] font-semibold text-[#5b21b6]">18</span></label>
        </div>
      </fieldset>
      <fieldset class="m-0 min-w-0 border-b border-[#ebe7f5] py-[18px] last:border-b-0">
        <legend class="float-left mb-2.5 w-full p-0 text-[.75rem] font-bold tracking-[.08em] text-[#0f172a] uppercase">Size</legend>
        <div class="clear-left flex flex-wrap gap-2">
          <label><input class="peer sr-only" type="checkbox" name="size" value="xs"><span class="grid h-10 min-w-[54px] cursor-pointer place-items-center rounded-full border border-[#8e89a8] bg-white px-3.5 text-[.875rem] font-semibold text-[#0f172a] transition-colors duration-150 hover:bg-[#f5f3ff] peer-checked:border-[#6d28d9] peer-checked:bg-[#6d28d9] peer-checked:text-white peer-focus-visible:outline-2 peer-focus-visible:outline-offset-2 peer-focus-visible:outline-[#6d28d9] motion-reduce:transition-none">XS</span></label>
          <label><input class="peer sr-only" type="checkbox" name="size" value="s"><span class="grid h-10 min-w-[54px] cursor-pointer place-items-center rounded-full border border-[#8e89a8] bg-white px-3.5 text-[.875rem] font-semibold text-[#0f172a] transition-colors duration-150 hover:bg-[#f5f3ff] peer-checked:border-[#6d28d9] peer-checked:bg-[#6d28d9] peer-checked:text-white peer-focus-visible:outline-2 peer-focus-visible:outline-offset-2 peer-focus-visible:outline-[#6d28d9] motion-reduce:transition-none">S</span></label>
          <label><input class="peer sr-only" type="checkbox" name="size" value="m" checked><span class="grid h-10 min-w-[54px] cursor-pointer place-items-center rounded-full border border-[#8e89a8] bg-white px-3.5 text-[.875rem] font-semibold text-[#0f172a] transition-colors duration-150 hover:bg-[#f5f3ff] peer-checked:border-[#6d28d9] peer-checked:bg-[#6d28d9] peer-checked:text-white peer-focus-visible:outline-2 peer-focus-visible:outline-offset-2 peer-focus-visible:outline-[#6d28d9] motion-reduce:transition-none">M</span></label>
          <label><input class="peer sr-only" type="checkbox" name="size" value="l" checked><span class="grid h-10 min-w-[54px] cursor-pointer place-items-center rounded-full border border-[#8e89a8] bg-white px-3.5 text-[.875rem] font-semibold text-[#0f172a] transition-colors duration-150 hover:bg-[#f5f3ff] peer-checked:border-[#6d28d9] peer-checked:bg-[#6d28d9] peer-checked:text-white peer-focus-visible:outline-2 peer-focus-visible:outline-offset-2 peer-focus-visible:outline-[#6d28d9] motion-reduce:transition-none">L</span></label>
          <label><input class="peer sr-only" type="checkbox" name="size" value="xl"><span class="grid h-10 min-w-[54px] cursor-pointer place-items-center rounded-full border border-[#8e89a8] bg-white px-3.5 text-[.875rem] font-semibold text-[#0f172a] transition-colors duration-150 hover:bg-[#f5f3ff] peer-checked:border-[#6d28d9] peer-checked:bg-[#6d28d9] peer-checked:text-white peer-focus-visible:outline-2 peer-focus-visible:outline-offset-2 peer-focus-visible:outline-[#6d28d9] motion-reduce:transition-none">XL</span></label>
        </div>
      </fieldset>
      <fieldset class="m-0 min-w-0 border-b border-[#ebe7f5] py-[18px] last:border-b-0">
        <legend class="float-left mb-2.5 w-full p-0 text-[.75rem] font-bold tracking-[.08em] text-[#0f172a] uppercase">Price</legend>
        <div class="clear-left grid grid-cols-2 gap-3">
          <div><label class="mb-1.5 block text-[.8rem] font-semibold text-[#475569]" for="sh-price-min">Min (£)</label><input class="block h-[42px] w-full rounded-[10px] border border-[#8e89a8] bg-white px-3 py-2 text-[.9rem] text-[#0f172a] transition-[border-color,box-shadow] duration-150 focus:border-[#6d28d9] focus:shadow-[0_0_0_3px_#ddd6fe] focus:outline-none motion-reduce:transition-none" id="sh-price-min" name="min" type="number" inputmode="numeric" min="0" step="5" value="40"></div>
          <div><label class="mb-1.5 block text-[.8rem] font-semibold text-[#475569]" for="sh-price-max">Max (£)</label><input class="block h-[42px] w-full rounded-[10px] border border-[#8e89a8] bg-white px-3 py-2 text-[.9rem] text-[#0f172a] transition-[border-color,box-shadow] duration-150 focus:border-[#6d28d9] focus:shadow-[0_0_0_3px_#ddd6fe] focus:outline-none motion-reduce:transition-none" id="sh-price-max" name="max" type="number" inputmode="numeric" min="0" step="5" value="180"></div>
        </div>
      </fieldset>
      <fieldset class="m-0 min-w-0 border-b border-[#ebe7f5] py-[18px] last:border-b-0">
        <legend class="float-left mb-2.5 w-full p-0 text-[.75rem] font-bold tracking-[.08em] text-[#0f172a] uppercase">Availability</legend>
        <label class="clear-left flex items-center gap-4"><span><b class="block font-semibold">In stock only</b><small class="block text-[.8rem] text-[#475569]">Hide items that ship in 2+ weeks</small></span><input class="ml-auto h-[26px] w-11 flex-none cursor-pointer appearance-none rounded-full bg-[#8e89a8] bg-[radial-gradient(circle,#fff_0_9px,transparent_9.5px)] bg-[length:22px_22px] bg-[position:2px_50%] bg-no-repeat transition-[background-color,background-position] duration-200 checked:bg-[#6d28d9] checked:bg-[position:calc(100%-2px)_50%] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#6d28d9] motion-reduce:transition-none" type="checkbox" role="switch" name="stock" checked></label>
      </fieldset>
    </div>
    <div class="flex flex-none gap-2.5 border-t border-[#ebe7f5] bg-white px-6 pt-4 pb-[calc(16px+env(safe-area-inset-bottom))] shadow-[0_-8px_20px_-14px_rgba(30,27,75,.35)] max-[600px]:px-[18px]">
      <button class="inline-flex min-h-[42px] cursor-pointer items-center justify-center gap-2 rounded-[10px] border px-4 py-[9px] text-[.9rem] leading-[1.2] font-semibold no-underline transition-[background-color,border-color,box-shadow] duration-150 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#6d28d9] motion-reduce:transition-none border-[#cbd5e1] bg-white text-[#0f172a] hover:border-[#a78bfa] hover:bg-[#f5f3ff] flex-none whitespace-nowrap" type="button" data-clear>Clear all</button>
      <button class="inline-flex min-h-[42px] cursor-pointer items-center justify-center gap-2 rounded-[10px] border px-4 py-[9px] text-[.9rem] leading-[1.2] font-semibold no-underline transition-[background-color,border-color,box-shadow] duration-150 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#6d28d9] motion-reduce:transition-none border-[#6d28d9] bg-[#6d28d9] text-white shadow-[0_6px_16px_-8px_rgba(109,40,217,.7)] hover:border-[#5b21b6] hover:bg-[#5b21b6] flex-auto whitespace-nowrap" value="apply">Show 128 results</button>
    </div>
  </form>
</dialog>

<!-- 3. Sheet on mobile, dialog on desktop -->
<dialog class="max-w-none overflow-hidden overscroll-contain border-0 bg-white p-0 text-[.95rem] leading-[1.55] text-[#0f172a] backdrop:bg-[rgba(24,20,56,.5)] backdrop:backdrop-blur-[2px] open:flex open:flex-col motion-safe:open:backdrop:animate-[sh-fade_.3s_ease-out] mx-auto mt-auto mb-0 max-h-[calc(100dvh-24px)] w-full rounded-t-3xl shadow-[0_0_0_1px_rgba(30,27,75,.06),0_-16px_48px_-16px_rgba(30,27,75,.4)] motion-safe:open:animate-[sh-up_.34s_cubic-bezier(.22,1,.36,1)] min-[640px]:m-auto min-[640px]:max-h-[calc(100dvh-56px)] min-[640px]:w-[min(480px,calc(100%-32px))] min-[640px]:rounded-2xl min-[640px]:shadow-[0_0_0_1px_rgba(30,27,75,.06),0_28px_64px_-18px_rgba(30,27,75,.45)] min-[640px]:motion-safe:open:animate-[sh-pop_.22s_cubic-bezier(.2,.8,.2,1)]" id="sh-address" aria-labelledby="sh-address-title">
  <form class="m-0 flex min-h-0 flex-1 flex-col" method="dialog">
    <span class="mx-auto mt-2.5 block h-[5px] w-10 flex-none rounded-full bg-[#d4cfe6] min-[640px]:hidden" aria-hidden="true"></span>
    <div class="flex flex-none items-center gap-3.5 px-6 pt-3.5 pb-4 max-[600px]:px-[18px] min-[640px]:pt-[22px]">
      <span class="grid size-11 flex-none place-items-center rounded-xl bg-[linear-gradient(135deg,#f5f3ff,#ede9fe)] text-[#6d28d9]" aria-hidden="true"><svg class="size-[22px]"><use href="#sh-i-pin"/></svg></span>
      <div class="min-w-0 flex-1"><h2 class="m-0 text-[1.1rem] leading-[1.35] font-bold tracking-[-.01em] [overflow-wrap:anywhere]" id="sh-address-title">Edit delivery address</h2><p class="mt-0.5 mb-0 text-[.85rem] text-[#475569]">Order HL-48213 · arrives Thu 2 Oct</p></div>
    </div>
    <div class="min-h-0 flex-auto overflow-y-auto overscroll-contain">
      <div class="grid grid-cols-2 gap-x-3 gap-y-3.5 px-6 pt-1 pb-[22px] max-[600px]:px-[18px]">
        <div class="col-span-2"><label class="mb-1.5 block text-[.8rem] font-semibold text-[#475569]" for="sh-name">Full name</label><input class="block h-[42px] w-full rounded-[10px] border border-[#8e89a8] bg-white px-3 py-2 text-[.9rem] text-[#0f172a] transition-[border-color,box-shadow] duration-150 focus:border-[#6d28d9] focus:shadow-[0_0_0_3px_#ddd6fe] focus:outline-none motion-reduce:transition-none" id="sh-name" name="name" autocomplete="name" value="Maya Lindqvist"></div>
        <div class="col-span-2"><label class="mb-1.5 block text-[.8rem] font-semibold text-[#475569]" for="sh-line1">Address line 1</label><input class="block h-[42px] w-full rounded-[10px] border border-[#8e89a8] bg-white px-3 py-2 text-[.9rem] text-[#0f172a] transition-[border-color,box-shadow] duration-150 focus:border-[#6d28d9] focus:shadow-[0_0_0_3px_#ddd6fe] focus:outline-none motion-reduce:transition-none" id="sh-line1" name="line1" autocomplete="address-line1" value="Flat 4, 27 Wharf Road"></div>
        <div><label class="mb-1.5 block text-[.8rem] font-semibold text-[#475569]" for="sh-city">City</label><input class="block h-[42px] w-full rounded-[10px] border border-[#8e89a8] bg-white px-3 py-2 text-[.9rem] text-[#0f172a] transition-[border-color,box-shadow] duration-150 focus:border-[#6d28d9] focus:shadow-[0_0_0_3px_#ddd6fe] focus:outline-none motion-reduce:transition-none" id="sh-city" name="city" autocomplete="address-level2" value="Manchester"></div>
        <div><label class="mb-1.5 block text-[.8rem] font-semibold text-[#475569]" for="sh-postcode">Postcode</label><input class="block h-[42px] w-full rounded-[10px] border border-[#8e89a8] bg-white px-3 py-2 text-[.9rem] text-[#0f172a] transition-[border-color,box-shadow] duration-150 focus:border-[#6d28d9] focus:shadow-[0_0_0_3px_#ddd6fe] focus:outline-none motion-reduce:transition-none" id="sh-postcode" name="postcode" autocomplete="postal-code" value="M3 4LN"></div>
        <div class="col-span-2"><label class="mb-1.5 block text-[.8rem] font-semibold text-[#475569]" for="sh-country">Country</label>
          <div class="relative"><select class="block h-[42px] w-full rounded-[10px] border border-[#8e89a8] bg-white px-3 py-2 text-[.9rem] text-[#0f172a] transition-[border-color,box-shadow] duration-150 focus:border-[#6d28d9] focus:shadow-[0_0_0_3px_#ddd6fe] focus:outline-none motion-reduce:transition-none cursor-pointer appearance-none pr-10" id="sh-country" name="country" autocomplete="country">
            <option value="IE">Ireland</option><option value="GB" selected>United Kingdom</option><option value="NL">Netherlands</option><option value="SE">Sweden</option>
          </select><svg class="pointer-events-none absolute top-1/2 right-3 -mt-[9px] size-[18px] text-[#475569]" aria-hidden="true"><use href="#sh-i-chevron"/></svg></div>
        </div>
      </div>
    </div>
    <div class="flex flex-none flex-wrap justify-end gap-2.5 border-t border-[#ebe7f5] bg-white px-6 py-4 max-[600px]:px-[18px] pb-[calc(16px+env(safe-area-inset-bottom))] min-[640px]:bg-[#faf9fe] min-[640px]:pb-4">
      <button class="inline-flex min-h-[42px] cursor-pointer items-center justify-center gap-2 rounded-[10px] border px-4 py-[9px] text-[.9rem] leading-[1.2] font-semibold no-underline transition-[background-color,border-color,box-shadow] duration-150 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#6d28d9] motion-reduce:transition-none border-[#cbd5e1] bg-white text-[#0f172a] hover:border-[#a78bfa] hover:bg-[#f5f3ff] max-[600px]:flex-auto" value="cancel">Cancel</button>
      <button class="inline-flex min-h-[42px] cursor-pointer items-center justify-center gap-2 rounded-[10px] border px-4 py-[9px] text-[.9rem] leading-[1.2] font-semibold no-underline transition-[background-color,border-color,box-shadow] duration-150 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#6d28d9] motion-reduce:transition-none border-[#6d28d9] bg-[#6d28d9] text-white shadow-[0_6px_16px_-8px_rgba(109,40,217,.7)] hover:border-[#5b21b6] hover:bg-[#5b21b6] max-[600px]:flex-auto" value="save">Save address</button>
    </div>
  </form>
</dialog>
CSS
/* Slide-in keyframes for the sheets and a fade for the backdrop (used through motion-safe:open:animate-[…]) */
@keyframes sh-up { from { transform: translateY(100%); } }
@keyframes sh-left { from { transform: translateX(100%); } }
@keyframes sh-pop { from { opacity: 0; transform: translateY(10px) scale(.97); } }
@keyframes sh-fade { from { opacity: 0; } }
/* Stop the page scrolling behind an open sheet */
html:has(dialog:modal) { overflow: hidden; }
JavaScript
// Open a sheet from any button with data-open="dialog-id". showModal() traps focus, closes on Esc
// and makes the page behind inert. Buttons inside <form method="dialog"> close it with no script.
document.querySelectorAll('[data-open]').forEach(function (btn) {
  var dialog = document.getElementById(btn.getAttribute('data-open'));
  if (!dialog) return;
  btn.addEventListener('click', function () {
    dialog.showModal();
    dialog.addEventListener('close', function () { btn.focus(); }, { once: true }); // focus back to the trigger
  });
  // A click on the backdrop lands on the <dialog> itself (its form fills the box), so close it
  dialog.addEventListener('click', function (e) { if (e.target === dialog) dialog.close(); });
});
// "Clear all" in the filters sheet: untick every box and empty the price fields; the sheet stays open
document.querySelectorAll('[data-clear]').forEach(function (btn) {
  btn.addEventListener('click', function () {
    btn.form.querySelectorAll('input').forEach(function (el) {
      if (el.type === 'checkbox') el.checked = false; else el.value = '';
    });
  });
});

About this set

Three modal sheets that slide in from the edge of the screen. The bottom sheet is an action sheet for a file: a grab handle, the file name and size, four round share targets (Copy link, Email, Messages, Nearby), a list of actions ending in a red Delete file, and a full-width Cancel. It stays on the bottom edge at every width, full width on phones and 560px wide on desktop. The side sheet is a filter panel for a shop: full height on the right, with category checkboxes and counts, size chips, a price range, an in-stock switch and a footer that stays visible while the options scroll. The third is a delivery address form that is a bottom sheet on phones and a centred dialog from 640px. Use a bottom sheet for short lists of actions, a side sheet for filters or details the user checks against the page, and the adaptive form when one dialog has to suit thumbs and mice. The plain and Tailwind versions use the native dialog element opened with showModal(), placed on an edge with margin overrides; the Bootstrap version keeps the Modal plugin and swaps its fade for a slide. Focus stays inside, Esc and the backdrop close them, and the page behind does not scroll.

What’s included

  • Bottom, right-side and adaptive sheets built on a modal dialog
  • Slide-in from the edge, turned off under reduced motion
  • Safe-area padding under the last button for phones with a home indicator
  • Scrolling filter body with a footer that stays visible
  • Bootstrap version uses the Modal plugin, re-anchored with a few custom classes

Accessibility

Each sheet is a modal dialog labelled by its visible heading; the grab handle is decorative and hidden from assistive technology. Focus stays inside while a sheet is open, Esc closes it and focus returns to the button that opened it. Every button, chip, checkbox, switch and field has a visible focus outline and a text label, and the scrolling filter list is a labelled region that can be focused and scrolled from the keyboard. All slide and fade animations stop when the user prefers reduced motion.

Customise it

In the plain version change --sh-accent, --sh-tint and --sh-field on .sh, and the widths in .sh-bottom (560px) and .sh-side (400px). In Bootstrap set --bs-modal-width on .sh-bottom and .sh-adapt and the --bs-btn-* variables on .sh-btn. In Tailwind swap the #6d28d9 values and the w-[min(560px,100%)] and w-[min(400px,100%)] classes.