Drawer
Overview
Use the Drawer family when you need a panel that slides in from the edge of the viewport — for mobile navigation, filter panels, quick settings, or any overlay content that should feel spatially connected to the screen edge. The family is composed of five components: a root panel, a remote trigger, a title, a description, and a close button. The trigger lives separately from the drawer — they connect by matching IDs, not by DOM nesting.
Authoring Structure
Important: DrawerTrigger is always placed separately from Drawer — they are siblings, not nested. They connect via matching IDs: identity.drawerId on the Drawer must match targeting.targetDrawerId on the Trigger.
DrawerTrigger (separate, anywhere on page)
Drawer (the sliding panel)
├── DrawerTitle
├── DrawerDescription (optional)
├── (your content)
└── DrawerClose
Placement Rules
| Component | Placement | Role |
|---|---|---|
| DrawerTrigger | Separate from Drawer. Anywhere on the page. | Button that opens the drawer by targeting its ID. |
| Drawer | Top-level. Can be anywhere in the DOM. | The sliding panel surface. Hosts title, description, content, and close. |
| DrawerTitle | Inside Drawer. | Provides accessible name via aria-labelledby. Renders as a heading. |
| DrawerDescription | Inside Drawer. Optional. | Provides accessible description via aria-describedby. |
| DrawerClose | Inside Drawer. | Button that dismisses the drawer. |
Quick Start
<OmeDrawerTrigger
targeting='{{"targetDrawerId":"mobile-menu"}}'
content='{{"label":"Menu"}}'
/>
<OmeDrawer
identity='{{"drawerId":"mobile-menu"}}'
settings='{{"direction":"bottom","dismissible":true}}'
>
{#slot default}
<OmeDrawerTitle>Navigation</OmeDrawerTitle>
<nav>
<a href="/home">Home</a>
<a href="/about">About</a>
<a href="/contact">Contact</a>
</nav>
<OmeDrawerClose>Close</OmeDrawerClose>
{/slot}
</OmeDrawer>
The trigger and drawer are siblings connected by matching IDs (mobile-menu). The trigger can be placed anywhere on the page — it does not need to be near the drawer.
<OmeDrawerTrigger
targeting='{{"targetDrawerId":"filter-panel"}}'
styling='{{"class":"btn btn--outline"}}'
>
{#slot default}Filters{/slot}
</OmeDrawerTrigger>
<OmeDrawer
identity='{{"drawerId":"filter-panel"}}'
settings='{{"direction":"right","dismissible":true}}'
styling='{{"class":"ome-drawer-default filter-panel"}}'
>
{#slot default}
<OmeDrawerTitle structure='{{"tag":"h3"}}'>Filters</OmeDrawerTitle>
<OmeDrawerDescription>Refine your search results.</OmeDrawerDescription>
<div class="filter-options">
<!-- filter content -->
</div>
<OmeDrawerClose styling='{{"class":"btn btn--icon"}}'>
{#slot default}×{/slot}
</OmeDrawerClose>
{/slot}
</OmeDrawer>
Use custom slot content when you need icons, badges, or any markup beyond a plain label in the trigger or close button.
Family Components
Drawer (Root)
The root component renders the sliding panel surface. It is targeted by one or more DrawerTrigger components via a shared ID. When opened, the drawer's DOM node is physically moved into a shared runtime host — it does not render at its authored position. On close, the node is restored to its original location using a Comment anchor.
Identity Props
| Prop | Type | Default | Description |
|---|---|---|---|
drawerId | string | "" | Unique ID that triggers use to target this drawer. Must match |
Settings Props
| Prop | Type | Default | Description |
|---|---|---|---|
direction | "bottom" | "right" | "top" | "left" | bottom | Edge the drawer slides from. |
defaultOpen | boolean | false | Opens the drawer automatically when the runtime initializes. |
dismissible | boolean | true | When |
Styling Props
| Prop | Type | Default | Description |
|---|---|---|---|
class | class | ome-drawer-default | CSS class applied to the drawer surface element. |
Direction Behavior
| Direction | Behavior |
|---|---|
bottom | Slides up from the bottom edge. A drag handle element is auto-generated for bottom-sheet interaction. |
right | Slides in from the right edge. No handle element. |
top | Slides down from the top edge. No handle element. |
left | Slides in from the left edge. No handle element. |
CSS Custom Properties
| Property | Default | Description |
|---|---|---|
--ome-drawer-overlay-color | rgba(0, 0, 0, 0.5) | Background color of the overlay behind the drawer. |
--ome-drawer-animation-duration | 250ms | Duration of the slide in/out transition. |
Key Behaviors
- One active drawer at a time — opening a drawer closes any currently open drawer.
- DOM portaling — the drawer node is moved into a shared runtime host while open and restored on close.
- Focus trap — Tab/Shift+Tab is trapped inside the drawer while open.
- Focus restoration — focus returns to the opener element when the drawer closes.
- Scroll lock — body scrolling is disabled while the drawer is open, with
scrollbar-gutter: stableto prevent layout shift.
Accessibility
- Drawer receives
role="dialog"andaria-modal="true". aria-labelledbyis wired to theDrawerTitleelement's ID.aria-describedbyis wired to theDrawerDescriptionelement's ID (if present).- The drag handle (bottom direction only) gets
aria-hidden="true". - IDs are auto-generated if not explicitly set.
DrawerTrigger
The trigger is a <button> that opens a specific drawer by targeting its ID. It is always placed separately from the Drawer — they are siblings in the editor, not nested. Multiple triggers can target the same drawer by sharing the same targetDrawerId.
Targeting Props
| Prop | Type | Default | Description |
|---|---|---|---|
targetDrawerId | string | "" | ID of the drawer this trigger should open. Must match |
Content Props
| Prop | Type | Default | Description |
|---|---|---|---|
label | string | "" | Fallback label text. Used when the trigger slot is empty to render a plain text label inside the button. |
Settings Props
| Prop | Type | Default | Description |
|---|---|---|---|
disabled | boolean | false | Disables the trigger so it cannot open the drawer. |
Styling Props
| Prop | Type | Default | Description |
|---|---|---|---|
class | class | ome-drawer-trigger-default | CSS class applied to the trigger button element. |
Trigger Accessibility
aria-haspopup="dialog"on the trigger button.aria-expandedreflects whether the targeted drawer is open.aria-controlspoints to the drawer element's ID.EnterandSpaceopen the targeted drawer.
DrawerTitle
Provides an accessible name for the drawer via heading semantics. The drawer wires aria-labelledby to this element's ID automatically.
Structure Props
| Prop | Type | Default | Description |
|---|---|---|---|
tag | string | h2 | HTML tag for the title element. Use an appropriate heading level for your document outline. |
Styling Props
| Prop | Type | Default | Description |
|---|---|---|---|
class | class | ome-drawer-title-default | CSS class applied to the title element. |
DrawerDescription
Provides an accessible description for the drawer. The drawer wires aria-describedby to this element's ID automatically. Optional — omit it if the drawer does not need a description.
Structure Props
| Prop | Type | Default | Description |
|---|---|---|---|
tag | string | p | HTML tag for the description element. |
Styling Props
| Prop | Type | Default | Description |
|---|---|---|---|
class | class | ome-drawer-description-default | CSS class applied to the description element. |
DrawerClose
A <button> that closes the active drawer. Place it inside the Drawer — typically at the top or bottom of the drawer content.
Settings Props
| Prop | Type | Default | Description |
|---|---|---|---|
disabled | boolean | false | Disables the close button. |
Styling Props
| Prop | Type | Default | Description |
|---|---|---|---|
class | class | ome-drawer-close-default | CSS class applied to the close button element. |
Keyboard Behavior
| Key | Context | Action |
|---|---|---|
Enter / Space | Trigger focused | Opens the targeted drawer. |
Escape | Drawer open | Closes the drawer (only when dismissible="true"). |
Tab | Drawer open | Moves focus to the next focusable element inside the drawer (trapped). |
Shift + Tab | Drawer open | Moves focus to the previous focusable element inside the drawer (trapped). |
Enter / Space | Close button focused | Closes the drawer. |
Common Mistakes
DrawerTrigger and Drawer must be siblings, not nested. The trigger targets the drawer by matching IDs (targetDrawerId = drawerId), not by DOM relationship. Nesting the trigger inside the drawer breaks the connection.
The trigger-to-drawer connection relies entirely on matching IDs. If identity.drawerId on the Drawer does not match targeting.targetDrawerId on the DrawerTrigger, the trigger will not open the drawer.
When a drawer opens, its DOM node is physically moved into a shared runtime host. It does not render where you placed it in the editor. On close, it is restored to its original position.
When direction="bottom", a drag handle element is auto-generated inside the drawer for bottom-sheet interaction. This handle has aria-hidden="true" and is not interactive via keyboard — it is a visual and touch affordance only.
FAQs
Can multiple triggers open the same drawer?
Yes. Set the same targetDrawerId on all triggers that should open the drawer. Each trigger independently targets the drawer by ID, so any number of triggers can point to the same drawer.
What is the difference between Dialog and Drawer?
Dialog is a centered modal surface with 9 placement options. Drawer slides in from a screen edge with 4 directions (bottom, right, top, left). Both trap focus and share the same one-active-at-a-time constraint — opening a drawer closes any open dialog, and vice versa.
Why does only the bottom direction have a handle?
The handle is a drag affordance specific to bottom-sheet interaction patterns. Users expect to be able to drag a bottom sheet down to dismiss it. Side and top drawers do not use this pattern, so no handle is generated for those directions.
Does the drawer animate?
Yes. The drawer uses a CSS transform transition for the slide-in/out animation. The duration is configurable via the --ome-drawer-animation-duration custom property (default: 250ms).
Can I have a drawer inside a dialog?
Yes, but only one can be active at a time across both families. Opening a drawer will close any currently open dialog, and opening a dialog will close any currently open drawer.