Skip to content
  1. Keyline UI
  2. Drawer
Keyline UIComponents
  • Get started
    • Installation
    • Theming
    • Modes
    • Configuration
    • Composition
  • Actions
    • Button
    • Button group
    • Split button
    • Toggle
    • Toggle group
    • Toolbar
    • Menu
    • Menubar
    • Context menu
  • Inputs
    • Field and form
    • Checkbox
    • Checkbox group
    • Radio group
    • Switch
    • Slider
    • Segmented
    • Select
    • Native select
    • Combobox
    • Multiselect
    • Number field
    • OTP field
    • Time field
    • Date picker and calendar
    • Date range
  • Data
    • Table
    • Chart
    • Stat and meter
    • Status and badge
    • Progress
    • Skeleton
    • Empty
    • Avatar
    • Kbd
    • Marker
  • Navigation
    • Tabs
    • Breadcrumb
    • Pagination
    • Nav
    • Navigation menu
    • Command menu
  • Overlays
    • Dialog
    • Alert dialog
    • Drawer
    • Sheet
    • Popover
    • Hover card
    • Tooltip
    • Toast
    • Notice
  • Layout
    • App shell
    • Sidebar
    • Panel
    • Resizable
    • Scroll area
    • Accordion
    • Collapsible
    • Carousel
    • Inspector
    • Item
    • Direction
  • Conversation
    • Message
    • Bubble
    • Message scroller
    • Attachment
    • Questionnaire
  • Blocks
    • Shipments

Drawer

A panel that slides in from an edge, made for touch. Drag it and it follows your finger; let go past halfway, or flick, and it leaves. A swipe that starts in a scrolled body scrolls first. Escape and Close always work, and the grip is only a hint.

Loading the live example…

"use client"

import { useState } from "react"
import { Button } from "@/components/keyline/ui/button"
import { Drawer, DrawerBody, DrawerClose, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerTitle, DrawerTrigger } from "@/components/keyline/ui/drawer"

const STOPS = [
  ["9:40", "Left the warehouse"],
  ["10:15", "Scranton Business Park, 40 cases"],
  ["11:05", "Lackawanna County, 120 cases"],
  ["12:30", "Lunch at Cugino's"],
  ["13:20", "Vance Refrigeration, held: no one to sign"],
  ["14:10", "Blue Cross of Pennsylvania, 60 cases"],
]

export function TruckRoute() {
  const [called, setCalled] = useState(false)

  return (
    <div className="grid gap-4">
      <Drawer>
        <DrawerTrigger render={<Button className="justify-self-start" />}>Darryl&apos;s route</DrawerTrigger>
        <DrawerContent size="min(70vh, 480px)">
          <DrawerHeader>
            <DrawerTitle>Darryl&apos;s route today</DrawerTitle>
            <DrawerDescription>Swipe down, press Escape or Close to put it away.</DrawerDescription>
          </DrawerHeader>
          <DrawerBody>
            <ol className="m-0 grid list-none gap-3 p-0 text-[14px]">
              {STOPS.map(([at, what]) => (
                <li key={at} className="grid grid-cols-[3.5rem_1fr] gap-3">
                  <span className="font-kl-mono text-[13px] text-kl-ink-2">{at}</span>
                  <span>{what}</span>
                </li>
              ))}
            </ol>
          </DrawerBody>
          <DrawerFooter>
            <DrawerClose render={<Button layout="cell" />}>Close</DrawerClose>
            <DrawerClose render={<Button layout="cell" variant="primary" onClick={() => setCalled(true)} />}>Call Bob Vance</DrawerClose>
          </DrawerFooter>
        </DrawerContent>
      </Drawer>
      <p role="status" className="m-0 min-h-5 text-[14px] text-kl-ink-2">
        {called ? "Calling Vance Refrigeration about the held delivery." : ""}
      </p>
    </div>
  )
}

Installation

npx shadcn@latest add @keyline-ui/drawer

Run it where your components.json is. The first time, map the @keyline-ui registry: one command, in Installation.

Usage

import { Drawer, DrawerBody, DrawerClose, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerTitle, DrawerTrigger } from "@/components/keyline/ui/drawer"
<Drawer>
  <DrawerTrigger render={<Button />}>Darryl's route</DrawerTrigger>
  <DrawerContent size="min(70vh, 480px)">
    <DrawerHeader>
      <DrawerTitle>Darryl's route today</DrawerTitle>
    </DrawerHeader>
    <DrawerBody>…</DrawerBody>
    <DrawerFooter>
      <DrawerClose render={<Button layout="cell" />}>Close</DrawerClose>
    </DrawerFooter>
  </DrawerContent>
</Drawer>

Props

PropTypeDefaultDescription
side"bottom" | "end" | "start"Default "bottom"On Drawer: the edge it comes from. end mirrors in right-to-left text.
sizenumber | stringDefault —On DrawerContent: height from the bottom, width from a side. Pixels or any CSS length.
hideGripbooleanDefault falseHides the grip cell. The swipe still works anywhere on the drawer.
open · onOpenChangebooleanDefault —On Drawer: control it.

Built on Base UI Drawer. For a panel on a wide screen that doesn't need the swipe, use Sheet.

Sheet 50 of 73PreviousAlert dialogNextSheet
On this page