Accessibility
Drawer from @prepared911/ui-drawer is a sliding panel rendered in a portal. It is not a modal dialog: there is no backdrop, no default focus trap, and the rest of the page remains in the tab order unless you add behavior yourself. Treat accessibility as a shared contract between the component (what it guarantees) and your app (labels, focus, and announcements).
useEscapeClose). Pass closeOnEscape={false} and/or onEscapeKeyDown when a parent owns layered Escape (for example a popover above the drawer). You do not need to wire onKeyDown for Escape on the drawer root.clickOutsideToClose is true, a click outside the drawer panel closes it. Default is false; enable it only when that dismissal model matches your UX.onOpen and onClose run when the drawer transitions between closed and open so you can coordinate focus or analytics.AnimatePresence) with a 300 ms ease-in-out transition; the package does not currently read prefers-reduced-motion for that animation.resizable, the drag handle exposes aria-label="Drag to resize" and a tooltip; resize remains pointer-driven.DrawerHeader close control: When passing onClose to DrawerHeader, the dismiss control is an IconButton with a localized accessible name (via closeTitle or the default close string).DrawerPortal's target or document.body, which affects page landmarks unless you wrap content with explicit roles and labels (see below).aria-label, aria-labelledby, or role on the outer container. Wrap primary content in a landmark or region and associate a visible title, or set aria-label on a wrapper Box when there is no visible heading.tabIndex={-1} for programmatic focus), then manage order inside your content.aria-modal="true"; the package does not supply those; use a dedicated dialog pattern or add a trap library and test thoroughly.aria-live region or an equivalent pattern; the drawer does not announce by itself.DrawerHeader renders a prominent title with CoreText; heading level is not a prop today. Ensure the surrounding page's heading outline still makes sense (there is an internal note in source about semantic order for drawer titles vs page h2/h3).esc) closes an open drawer while closeOnEscape is true (the default). Call event.preventDefault() from onEscapeKeyDown to keep the drawer open.onClose is provided.Opening should send focus into the drawer; closing should restore focus to the trigger. Use the onOpen and onClose callbacks provided by Drawer to coordinate these moves. Defer focus calls that depend on animation until after the panel is present in the DOM (for example requestAnimationFrame or a short delay aligned with the 300 ms motion duration).
If the first focusable control is not the right starting point, focus a wrapper with tabIndex={-1} and an aria-label that matches the drawer purpose, then move to inner controls as needed.
Use onOpen to move focus into the drawer once the animation completes:
Use a ref to capture the trigger element and restore focus when the drawer closes:
Choose a role that matches how the drawer is used:
role="complementary" when the panel supplements the main content (side detail, filters, related info).role="region" with a required accessible name when the panel is a distinct section but not strictly "complementary."Pair with aria-labelledby pointing at a stable id for the visible title, or aria-label when the title is not visible. DrawerHeader does not set an id on the title element, so you need to provide one yourself when using aria-labelledby.
The simplest approach when the label matches a known string:
When the drawer title is dynamic or you prefer to reference it directly, add your own id to a visible element:
Avoid relying on aria-hidden on the whole panel to mean "closed"—the drawer unmounts its open branch when closed; drive visibility from open state and focus, not by hiding an inert duplicate tree.
Announce meaningful transitions when users cannot infer them from focus alone. Use a visually hidden live region alongside the drawer:
Keep announcements short and non-interrupting (aria-live="polite") unless there is a safety-critical reason for assertive output.
The drawer's slide animation does not currently consult prefers-reduced-motion. If you must respect system settings before the library adds support, you can override the transition duration in your own SCSS using the drawer class:
Prefer reducing duration rather than removing visibility of open state. Avoid relying on motion for essential feedback.
DrawerHeader uses a small IconButton for dismiss; touch targets should meet your minimum size expectations; the stylesheet enlarges the chevron icon for visibility.resizable adds a wide hit target along the inner edge; still primarily pointer-oriented.width / minWidth / maxWidth so narrow viewports do not clip content or trap horizontal overflow; pair with DrawerContent (scrollable body) for long content.When a right-anchored drawer sits beside a DataTable, pass horizontalScrollSpacing with the open drawer's width in pixels so the table's horizontal scroll region gains enough padding that columns are not permanently hidden behind the drawer. That preserves keyboard and pointer access to columns that would otherwise sit under the panel.
Use useDrawerStore to read the current width:
aria-modal: Not set by the component; add it yourself if your UX is dialog-like.body can separate the drawer from main landmark structure; explicit role + labeling on your wrapper mitigates confusion in rotor/landmark navigation.DrawerHeader; validate against each page's outline.On this page