Skip to content
TopClass UI

Drawer

A dialog that slides in from a logical edge, so it mirrors in Arabic with no code change. A swipe or the x closes it.

client - size not measured yet

Drawer

client - size not measured yet

A dialog that slides in from a logical edge, so it mirrors in Arabic with no code change. A swipe or the x closes it.

Install

Try it

placement
size

The code

drawer.tsx
import { Button } from 'topclassui/button'
import {

<DrawerRoot>
  <DrawerTrigger render={<Button variant="outline">Filters</Button>} />
  <DrawerPopup>
    <DrawerHeader>
      <DrawerTitle>Filters</DrawerTitle>
      <DrawerDescription>Narrow the list of projects.</DrawerDescription>
    </DrawerHeader>
    <DrawerBody className="grid content-start gap-2.5">
      <p>Status</p>
      <p>Owner</p>
      <p>Updated</p>
    </DrawerBody>
    <DrawerFooter>
      <DrawerClose render={<Button variant="outline">Reset</Button>} />
      <DrawerClose render={<Button>Apply</Button>} />
    </DrawerFooter>
  </DrawerPopup>
</DrawerRoot>

placement is logical: start opens from the left in English and from the right in Arabic. Never left or right.

Every state

5 states

Import from the package

topclassui/drawer
import { DrawerRoot, DrawerBody, DrawerClose, DrawerDescription, DrawerFooter, DrawerHeader, DrawerPopup, DrawerTitle, DrawerTrigger } from 'topclassui/drawer'

Install once:npm i topclassui@beta zod

Examples

  • For a panel that slides in from an edge of the screen: filters, navigation, a list to pick from on a phone.
  • placement on DrawerRoot: end (the default), start, top or bottom.
  • size on DrawerRoot is the widest a start or end sheet grows: 256, 288, 320, 384 or 480 px. Top and bottom sheets span the screen and grow to nine tenths of its height.
  • A swipe toward the sheet's own edge closes it once it is past half of the sheet. A finger starts anywhere on the sheet; a mouse starts on the header.
  • DrawerBody scrolls and the header and the footer stay put. Start with a DrawerHeader, which keeps the title clear of the x.
  • On a phone the sheet pads the notch and the home indicator, when the viewport meta has viewport-fit=cover.
end
start
bottom
top
sizes
with-form
long-body
long-content
non-modal
trap-focus
In Arabic, right to left

Props

DrawerRoot

The props of DrawerRoot
PropTypeDefaultLimitsDescription
childrenReactNode--The trigger and the popup. With no trigger, hold open in a client file.
placementstart | end | top | bottom"end"-DrawerRoot: the edge the sheet slides from; start and end follow the direction.
sizexs | sm | md | lg | xl | custom"md"-DrawerRoot: the widest a start or an end sheet grows, 256 to 480 px; custom sets no width.

DrawerBody

No props of its own: it takes the attributes of the element it renders.

DrawerClose

No props of its own: it takes the attributes of the element it renders.

DrawerDescription

No props of its own: it takes the attributes of the element it renders.

DrawerFooter

No props of its own: it takes the attributes of the element it renders.

DrawerHeader

No props of its own: it takes the attributes of the element it renders.

DrawerPopup

No props of its own: it takes the attributes of the element it renders.

DrawerTitle

No props of its own: it takes the attributes of the element it renders.

DrawerTrigger

No props of its own: it takes the attributes of the element it renders.

Keyboard and accessibility

The keyboard of Drawer
KeysWhat it does
EnterSpaceOn the trigger, opens the sheet. Focus lands on the sheet, and Tab goes to its first control.
TabShift+TabMove through the sheet. Focus stays inside it.
EscapeCloses a tooltip open inside first, then the sheet. Focus returns to the trigger.
  • Every sheet ends with an x that nothing removes: a swipe is a drag, and a drag needs a second way (WCAG 2.5.7).
  • DrawerTitle names the sheet. With no title, pass aria-label to DrawerPopup.
  • While DrawerBody scrolls it is a tab stop, so the keyboard reaches a long text with no control in it.

Right to left

start opens from the right and end from the left, and each closes by a swipe toward its own edge. Mount a TopClassProvider: without one the swipe follows left to right.

Server and client

A client component: it renders on the server and hydrates in the browser.

Files: index.tsx, client.tsx.

On the chip: client - size not measured yet.