Drawer

A panel that slides in from an edge and keeps the page behind it visible, for work that is a detour rather than a new destination.

Accessibility

What Drawer does for you, and what it leaves to the page around it.

What it renders

The panel is a <div role="dialog" aria-modal="true">. The kit changes none of this. It only sets the close icon from your icon pack.
  • aria-modal is always "true", even with the mask turned off. A maskless drawer still announces itself as modal while the page behind it stays usable, so avoid that combination for anything a screen reader user depends on.
  • The close button is a real button, labeled “Close” in the active locale. A custom closeIcon that carries its own aria-label keeps it.
  • The mask is not a button. Clicking it closes the drawer, but the keyboard cannot reach it, so Escape and the close button are the keyboard ways out.

Naming the drawer

title is the drawer's accessible name: the dialog is labeled by the title element. With no title, nothing names it.
  • A drawer with no title is announced as “dialog” and nothing else.
  • With no title, no extra and closable={false}, there is no header, and the body carries its own heading. Point aria-labelledby at that heading's id, or pass an aria-label.
  • Your own aria-labelledby wins over the title, so you can name the drawer after something in the body.
  • Name it after the record, not the action: “Invoice 4021” says more than “Details”.

Focus on open, and where it returns

focusable.trap and focusable.focusTriggerAfterClose control focus, and both are on for a normal drawer with a mask.
  • On open, focus moves to the drawer itself, not to its first field. The reader hears the drawer's name first, and Tab then walks the panel from the top.
  • On close, focus goes back to whatever had it before, usually the button that opened the drawer.
  • If that button is gone, focus is lost. When the drawer's work removes the row that opened it, move focus yourself, to the list or to whatever replaced the row.
  • destroyOnHidden does not change where focus returns.

The focus trap and Escape

Focus is trapped only when there is a mask and the drawer renders over the page. With mask={false} or getContainer={false}, Tab leaves the panel. Pass focusable={{ trap: true }} to keep the trap, or accept that the drawer is part of the page and not a dialog.
  • Escape closes the drawer unless keyboard={false}. Only the top drawer responds, so a stack closes one level per press.
  • With keyboard={false} and mask={{ closable: false }}, a visible button is the only way out for a keyboard user. Put it in the footer.
  • Keep dismiss buttons in the header or the footer, where a screen reader user will look for them.

Scroll locking, and what a maskless drawer gives up

The page behind the drawer stops scrolling only while the mask is up and the drawer renders over the page. mask={false} and getContainer={false} both leave the page scrolling.
Such a drawer loses the focus trap, the scroll lock and the click-away close together. That is fine for an inspector next to the page, but it is no longer a dialog. Place it next to the content it belongs to in the reading order, and give it its own heading.

Contrast and motion

The drawer's colors come from your theme. Check the panel text against the panel background, and the header divider and close icon, which use secondary colors.
  • mask.blur does not make the panel easier to read. The dimming does that.
  • A drawer over a dark page still uses its own theme, not the colors of the page behind it.
  • Turn animation off with ThemeScope theme={{ token: { motion: false } }} for readers who ask for reduced motion, and put your own animations inside the body behind motion-safe:.