A drawer is a panel that comes in from one edge and takes part of the screen, leaving the rest
of the page visible beside it. It is the shape for context the reader consults while the page
stays where it is, a record's details, a column of filters, a short form, and the
[Modal](/components/overlays/modal) is the shape for a decision that should take the whole
screen. The edge the panel comes from and how much of the screen it takes are what this
component adds to the overlay contract.

On the platform the panel becomes the bottom sheet the platform already uses, because a side
panel on a phone has no room to be one. See [React Native](/platforms/native).

```tsx demo="overlays/drawer-preview"
```

## Usage

```tsx
import { Button, Drawer } from "@asheeui/web";
import { useState } from "react";

export function Filters() {
  const [isOpen, setIsOpen] = useState(false);

  return (
    <>
      <Button onClick={() => setIsOpen(true)}>Filters</Button>
      <Drawer
        isOpen={isOpen}
        onClose={() => setIsOpen(false)}
        aria-labelledby="filters-title">
        <div className="p-6">
          <h2 id="filters-title">Filter results</h2>
          <p>24 products match.</p>
        </div>
      </Drawer>
    </>
  );
}
```

`children` is the panel's content. The component draws the panel, its surface, its shadow, the
border on the edge it comes from and its own close control; the padding, the heading and the
actions are yours. The close control sits in the top-right corner, is first in the tab order, and
is named by `closeLabel`, `"Close drawer"` unless you state otherwise. The panel is a column, so
a footer pushed with `mt-auto` sits at the bottom of a content stack that fills the height.

There is no `title` prop: write a heading inside the panel and let `aria-labelledby` name the
dialog with it. `className` and the rest of the div attributes land on the dialog container,
the full-screen wrapper that carries the role, while `style` and `content.className` land on
the panel itself. Pad the panel's content rather than the wrapper.

## Edge and size

`placement` is one of four edges, registered as `right`. `size` measures the axis the panel
occupies: for `left` and `right` it is a width, `sm` 20rem, `md` 28rem, `lg` 36rem, and `full`
the whole viewport, and for `top` and `bottom` the same scale is a height. The other axis is
always full, so a right panel spans the height of the page and a bottom panel its width. A
panel flush against an edge has no corner to round, so there is no `radius` option, unlike the
modal's.

`animated` (registered `true`) slides the panel and fades the layer behind it; a closing panel
stays mounted for the 250 ms exit that matches the animation, so a close is not a jump.
`animated={false}` unmounts the panel as soon as it closes instead of waiting out an exit it does
not play.

The panel scrolls: it is a fixed-size column with `overflow-y-auto`, so content taller than the
space it was given scrolls inside it rather than being clipped out of reach, the page behind is
locked while the drawer is open, so there is nowhere else for it to go.

```tsx demo="overlays/drawer-placement"
```

## Dismissal

Both layers use the same two switches, `closeOnBackdropClick` and `closeOnEscape`, both
registered `true`, and the same two names configure them under `components.drawer`. The drawer's
old spellings, `closeOnOverlayClick` and `closeOnEsc`, still work as deprecated aliases; prefer
the shared names in new code. <kbd>Escape</kbd> is the reader asking to cancel; a press on the
dimmed area is a click that missed as often as it is a decision, so a panel that holds typed
input turns the first off and keeps the second.

The overlay is decorative, `aria-hidden`, out of the tab order, so a press is a pointer
gesture with no keyboard route. <kbd>Escape</kbd> is answered by the topmost layer through a
shared stack, so it works wherever focus is inside the drawer, and a drawer under an open modal
does not close on the same press. Turning both switches off still leaves the close control the
component draws, and any control you put in the panel.

Both switches call `onClose`. It is optional in the type, but a drawer without one is presented
non-modally, no scroll lock, no focus trap, no `aria-modal`, and logs a warning, because a layer
that cannot close must not hold the page. Wire it to the state that controls `isOpen`.

```tsx demo="overlays/drawer-dismissal"
```

## Props

```props Drawer
```

The registered defaults are `size` `md`, `placement` `right`, `animated` `true`, and both
dismissal switches `true`, registered under the deprecated spellings, though either name
configures them. State them once under `components.drawer` and every drawer in the product
follows. The panel's surface is not registered and there is no radius tier: it is the secondary
background with a shadow and a border on its edge.

The two override props merge rather than replace: `overlay.className` is combined with the
default `bg-background/80 backdrop-blur-xs`, and `content.className` with the default
`bg-secondary`, so a class passed there adds to the surface, or replaces one utility of it,
instead of dropping the whole thing. The rest of the panel's classes, the shadow, the border and
the column, stay.

## Accessibility

- The dialog container is `role="dialog"` with `aria-modal="true"` and `tabIndex={-1}`, and it
  is the element that receives your attributes, the `aria-modal` is omitted when there is no
  `onClose`, because the drawer is then presented non-modally. The overlay is decorative, so the
  reader meets the drawer once rather than an overlay and then a dialog.
- The name is the consumer's. Pass `aria-label`, or `aria-labelledby` naming the heading inside
  the panel; a drawer with no name is announced as "dialog" and nothing else.
- Focus moves into the panel when it opens, and is trapped inside it while it is open; a drawer
  with no `onClose` has no trap, because it is not allowed to hold a page it cannot release.
  Closing returns focus to whatever held it before. <kbd>Escape</kbd> closes it while
  `closeOnEscape` is on, and only the topmost layer answers that press.
- The overlay is not focusable, so the panel's own controls are the only things in the tab order
  while it is open. The built-in close control is the first of them, so a reader who cannot use
  a pointer and whose <kbd>Escape</kbd> is switched off still has a way out without drawing one.

Focus, the scroll lock, the portal and the two dismissal switches are the
[shared overlay contract](/concepts/focus-and-dismissal); this page states only what the drawer
adds to it.

## Related

```related modal, tooltip, sidebar
```
