Drawer
A modal panel that slides in from the right edge.
Overview
A record or a form the reader works on beside the page, with actions that stay in view.
Record
import { useState } from "react";
import { Button, Drawer, KeyValue, KeyValueList } from "@hackcove/ui";
function Example() {
const [open, setOpen] = useState(false);
return (
<>
<Button variant="outline" onClick={() => setOpen(true)}>
Open HC-65
</Button>
{open && (
<Drawer
title="HC-65 · Tabs"
onClose={() => setOpen(false)}
footer={
<>
<Button variant="ghost" onClick={() => setOpen(false)}>
Cancel
</Button>
<Button onClick={() => setOpen(false)}>Save</Button>
</>
}
>
<KeyValueList>
<KeyValue label="Status">In progress</KeyValue>
<KeyValue label="Owner">Jacobus</KeyValue>
</KeyValueList>
</Drawer>
)}
</>
);
}footer.import { useState } from "react";
import { Body, Button, Drawer } from "@hackcove/ui";
function Example() {
const [open, setOpen] = useState(false);
return (
<>
<Button variant="outline" onClick={() => setOpen(true)}>
Delete build
</Button>
{open && (
<Drawer
title="Delete build?"
size="sm"
onClose={() => setOpen(false)}
footer={<Button onClick={() => setOpen(false)}>Delete</Button>}
>
<Body>This removes the build and every snapshot in it.</Body>
</Drawer>
)}
</>
);
}Dialog: a full-height panel for one confirmation is more than it asks.Footer
import { useState } from "react";
import { Button, Drawer, Field, Input } from "@hackcove/ui";
function Example() {
const [open, setOpen] = useState(false);
return (
<>
<Button variant="outline" onClick={() => setOpen(true)}>
Rename
</Button>
{open && (
<Drawer
title="Rename"
size="sm"
onClose={() => setOpen(false)}
footer={<Button onClick={() => setOpen(false)}>Save</Button>}
>
<Field label="Name" htmlFor="drawer-rename">
<Input id="drawer-rename" defaultValue="Hack Cove" />
</Field>
</Drawer>
)}
</>
);
}footer, so Save stays in view while the body scrolls.import { useState } from "react";
import { Button, Drawer, Field, Input, Stack } from "@hackcove/ui";
function Example() {
const [open, setOpen] = useState(false);
return (
<>
<Button variant="outline" onClick={() => setOpen(true)}>
Rename
</Button>
{open && (
<Drawer title="Rename" size="sm" onClose={() => setOpen(false)}>
<Stack>
<Field label="Name" htmlFor="drawer-rename-body">
<Input id="drawer-rename-body" defaultValue="Hack Cove" />
</Field>
<Button onClick={() => setOpen(false)}>Save</Button>
</Stack>
</Drawer>
)}
</>
);
}Usage
- Render it only while it is open:
{open && <Drawer … onClose={() => setOpen(false)} />}. It opens itself on mount. - Use it for a record to read or edit, or a form with several fields; a yes-or-no question is a
Dialog. - The actions go in
footer, pinned under the body, which scrolls between the header and the footer. sizeissm,md(the default) orlg; choose by the content,smfor one field andlgfor a table.- Set
closeLabelto the reader's language when the page is not in English.
Accessibility
- A Drawer is the browser's own
<dialog>opened withshowModal(): the page behind goes inert, focus stays inside it and Esc closes it. - It is named by its
title, anh2, througharia-labelledby. - The header's close button is an
IconButtonnamed bycloseLabel("Close" by default); a click on the scrim closes it too. - Closing, or unmounting it, hands focus back to the element that opened it.
- The slide-in is switched off under
prefers-reduced-motion: reduce.
API
Drawer
A modal slide-over anchored to the right edge, on the browser's own <dialog>: it is opened with showModal() on mount, so render it only while it is open. The page behind goes inert, Esc closes it and focus stays inside; a click on the scrim (the <dialog> element itself, not its contents) closes it too. The header holds the title and a close button; the body scrolls between it and an optional pinned footer.
closeLabelThe accessible name of the close button.
- Type
string- Default
Close
footerActions pinned under the scrolling body, right-aligned.
- Type
ReactNode
onClose requiredCalled when the drawer closes: Esc, a click on the scrim or the close button.
- Type
() => void
sizemd (the default) holds a form or a record; sm is narrower and lg wider.
- Type
"sm" | "md" | "lg"- Default
md
title requiredThe drawer's accessible name, shown as its heading.
- Type
ReactNode