Drawer
Slide-out panel with overlay, cookie persistence, keyboard shortcut, and dialog accessibility. Server-rendered ERB with a small Stimulus controller.
Usage
<%= render "components/drawer/provider", default_open: drawer_open? do %>
<%= render "components/drawer", state: drawer_state do %>
<%= render "components/drawer/header" do %>
<%= render "components/drawer/title", text: "Drawer Title" %>
<% end %>
<%= render "components/drawer/content" do %>
Drawer content
<% end %>
<%= render "components/drawer/footer" do %>
Footer actions
<% end %>
<% end %>
<% end %>Examples
With Trigger
The trigger can live anywhere on the page — it finds the drawer through a Stimulus outlet and mirrors its state with aria-expanded.
<%= render "components/drawer/trigger" do %>Open Drawer<% end %>
<%= render "components/drawer/provider" do %>
<%= render "components/drawer" do %>
<!-- content -->
<% end %>
<% end %>Sections and Separators
Group the drawer body into stacked sections, divided by a separator.
<%= render "components/drawer/content" do %>
<%= render "components/drawer/section" do %>
<%= render "components/drawer/title", text: "Filters", tag: :h3 %>
<% end %>
<%= render "components/drawer/separator" %>
<%= render "components/drawer/section" do %>
<%# More rows %>
<% end %>
<% end %>drawer/separator renders the separator primitive, so it keeps the primitive's 1px track while the drawer part re-spaces it for the panel.
Left Side Drawer
<%= render "components/drawer", side: :left do %>
<!-- content -->
<% end %>Keyboard & Accessibility
- Cmd/Ctrl + D toggles the drawer (configurable via
keyboard_shortcut). - Escape closes it; clicking the backdrop closes it.
- The panel is a
role="dialog"witharia-modaland a configurablearia_label. Focus moves into the panel on open and returns to the previously focused element on close. - While closed, the panel is
aria-hiddenandinert, so the off-screen content is invisible to assistive technology and unreachable by keyboard.
API Reference
Provider
| Parameter | Type | Default | Description |
|---|---|---|---|
| id | String | "drawer-provider" | Element ID for stable morph matching |
| default_open | Boolean | false | Initial open state |
| cookie_name | String | "drawer_state" | Cookie for persistence |
| keyboard_shortcut | String | "d" | Toggle shortcut (Cmd/Ctrl + key) |
| css_classes | String | "" | Additional CSS classes |
| html_options | Hash | {} | Additional HTML attributes |
Drawer
| Parameter | Type | Default | Description |
|---|---|---|---|
| id | String | auto | Element ID (deterministic, derived from side) |
| state | Symbol | :closed | :open or :closed |
| side | Symbol | :right | :left or :right |
| aria_label | String | "Drawer" | Accessible name for the dialog panel |
| css_classes | String | "" | Additional CSS classes |
| html_options | Hash | {} | Additional HTML attributes |
Trigger
| Parameter | Type | Default | Description |
|---|---|---|---|
| icon_name | Symbol | nil | Optional icon for the toggle button |
| variant | Symbol | :default | Button variant |
| size | Symbol | :default | Button size |
| css_classes | String | "" | Additional CSS classes |
| html_options | Hash | {} | Additional HTML attributes |
Other Parts
| Partial | Description |
|---|---|
| drawer/header | Top section with title and built-in close button |
| drawer/title | Heading inside the header. text: / content:, tag: (default :h2) |
| drawer/description | Supporting line under the title. text: / content:, tag: (default :p) |
| drawer/content | Scrollable middle section |
| drawer/footer | Bottom section for actions |
| drawer/section | Groups related rows inside the content area. Container — pass a block |
| drawer/separator | Divider between sections. orientation: (default :horizontal) |
| drawer/close | Close button (X icon) |
Helper Methods
| Method | Description |
|---|---|
| drawer_state(cookie_name) | Returns :open or :closed from the cookie |
| drawer_open?(cookie_name) | Returns true if open |
| drawer_closed?(cookie_name) | Returns true if closed |
Turbo Drive
The drawer controller integrates with Turbo Drive to keep state correct across navigations:
- Cache teardown: the drawer closes and the backdrop hides before Turbo caches the page.
- Morph awareness: with
turbo_refresh_method_tag :morph, the drawer re-reads its cookie so client state survives the morph. - Persistence: open/closed state lives in a cookie and survives full page loads.