Skip to content
v1.0.3

Drawer

A panel that slides in from a screen edge — detail views, filters, multi-step forms. Enters from right (default), left, or bottom, locks body scroll while open, and closes on overlay click, Esc, or ×. Use a Dialog for short blocking decisions.

bash
jlds add drawer

Usage

html
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/jarooda/jlds@main/registry/css/index.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/jarooda/jlds@main/registry/css/drawer.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/jarooda/jlds@main/registry/css/button.css">
<!-- behavior layer: slide-in, scroll-lock + focus-trap (needs util.js) -->
<script src="https://cdn.jsdelivr.net/gh/jarooda/jlds@main/registry/js/core.js" defer></script>
<script src="https://cdn.jsdelivr.net/gh/jarooda/jlds@main/registry/js/util.js" defer></script>
<script src="https://cdn.jsdelivr.net/gh/jarooda/jlds@main/registry/js/drawer.js" defer></script>

<button class="jl-btn jl-btn--secondary" data-drawer-trigger data-drawer-target="#filters">Filters</button>

<div class="jl-drawer__overlay" id="filters" data-side="right" hidden>
  <div class="jl-drawer" role="dialog" aria-modal="true" style="--_size: 380px">
    <div class="jl-drawer__header">
      <div class="jl-drawer__header-text">
        <div class="jl-drawer__title">Filters</div>
        <div class="jl-drawer__desc">Narrow down the results.</div>
      </div>
      <button type="button" class="jl-drawer__close" aria-label="Close">
        <svg viewBox="0 0 18 18" width="18" height="18" fill="none"><path d="M5 5l8 8M13 5l-8 8" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg>
      </button>
    </div>
    <div class="jl-drawer__body">…filter controls…</div>
    <div class="jl-drawer__footer">
      <button class="jl-btn jl-btn--secondary" data-drawer-close>Reset</button>
      <button class="jl-btn jl-btn--primary" data-drawer-close>Apply</button>
    </div>
  </div>
</div>
vue
<script setup lang="ts">
import { ref } from "vue"
import { Drawer } from "@/components/ui/drawer"
import { Button } from "@/components/ui/button"

const open = ref(false)
</script>

<template>
  <Button variant="secondary" @click="open = true">Filters</Button>
  <Drawer v-model:open="open" side="right" :size="380" title="Filters" description="Narrow down the results.">
    …filter controls…
    <template #footer>
      <Button variant="secondary" @click="open = false">Reset</Button>
      <Button @click="open = false">Apply</Button>
    </template>
  </Drawer>
</template>
tsx
import { useState } from "react"
import { Drawer } from "@/components/ui/drawer"
import { Button } from "@/components/ui/button"

const [open, setOpen] = useState(false)

<>
  <Button variant="secondary" onClick={() => setOpen(true)}>Filters</Button>
  <Drawer
    open={open}
    onClose={() => setOpen(false)}
    side="right"
    size={380}
    title="Filters"
    description="Narrow down the results."
    footer={
      <>
        <Button variant="secondary" onClick={() => setOpen(false)}>Reset</Button>
        <Button onClick={() => setOpen(false)}>Apply</Button>
      </>
    }
  >
    …filter controls…
  </Drawer>
</>

Sides

right (default) · left · bottom. Set size (number → px) for the panel width (left/right) or height (bottom).

Props

PropTypeDefaultDescription
openbooleanWhether it's shown (Vue: v-model:open)
onClose() => voidOverlay click / Esc / × (Vue: @close)
side"right" | "left" | "bottom""right"Edge the panel enters from
sizenumber | stringWidth (left/right) or height (bottom)
title / descriptionReact.ReactNodeHeader content
footerReact.ReactNodePinned footer (Vue: footer slot)
showClosebooleantrueShow the × button

CSS classes (HTML)

ClassPurpose
.jl-drawer__overlay + data-sideThe fixed backdrop + entry edge (start it hidden)
.jl-drawerThe sliding panel (set --_size for width/height)
.jl-drawer__header / __title / __desc / __closeHeader parts
.jl-drawer__body / __footerScrollable body and pinned footer

The behavior layer: [data-drawer-trigger] (+ optional data-drawer-target="#id") opens it; .jl-drawer__close / [data-drawer-close], overlay-click, and Esc close it. Scroll-lock + focus-trap come from JLDS.util.