Usage
<%= render "components/drawer/provider", default_open: drawer_open? do %>
<%= render "components/drawer", state: drawer_state do %>
<%= render "components/drawer/header" do %>
<h2 class="text-lg font-semibold">Drawer Title</h2>
<% 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 %>
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" with aria-modal and a configurable aria_label. Focus moves into the panel on open and returns to the previously focused element on close.
- While closed, the panel is
aria-hidden and inert, 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/content |
Scrollable middle section |
| drawer/footer |
Bottom section for actions |
| 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.