Sheet
UI::SheetA panel that slides in from an edge of the screen.
# VelvetKit
## Sheet (`UI::Sheet`)
A panel that slides in from an edge of the screen.
Add it: `bin/rails generate velvetkit:add sheet`
Also adds: `icon`, `spinner`, `button`, `dialog`
Stimulus: `ui--dialog`
### Arguments
```ruby
UI::Sheet.new(class: nil, **html_attrs)
```
Slots: `with_trigger`, `with_body`, `with_content`, `with_title`, `with_description`, `with_footer`
### Notes
UI::Sheet — side-anchored modal panel backed by the native <dialog> element.
Reuses the ui--dialog controller and follows the same compound API as
UI::Dialog, but anchors the panel to an edge of the viewport instead of
centering it.
Compound:
UI::Sheet
UI::Sheet::Trigger — click-to-open wrapper (inherits from Dialog::Trigger)
UI::Sheet::Content — the <dialog> + fixed panel; inline title / description / footer
### Example: Right
```erb
<%= render(UI::Sheet.new) do |sheet| %>
<% sheet.with_trigger { "Open right sheet" } %>
<% sheet.with_content(side: :right) do |content| %>
<% content.with_title { "Notifications" } %>
<% content.with_description { "Manage your notification preferences." } %>
<div class="space-y-3 text-sm text-muted-foreground">
<p>Right sheets work well for settings and secondary tasks.</p>
<p>They reuse the dialog controller but anchor the panel to a side.</p>
</div>
<% end %>
<% end %>
```
### Example: Left
```erb
<%= render(UI::Sheet.new) do |sheet| %>
<% sheet.with_trigger(variant: :secondary) { "Open left sheet" } %>
<% sheet.with_content(side: :left) do |content| %>
<% content.with_title { "Project filters" } %>
<% content.with_description { "Filter and refine your current project list." } %>
<div class="space-y-3 text-sm text-muted-foreground">
<p>Left sheets are useful for navigation and faceted filtering.</p>
</div>
<% end %>
<% end %>
```
### Example: Top
```erb
<%= render(UI::Sheet.new) do |sheet| %>
<% sheet.with_trigger(variant: :outline) { "Open top sheet" } %>
<% sheet.with_content(side: :top) do |content| %>
<% content.with_title { "Command palette" } %>
<% content.with_description { "Quick actions and navigation in a compact top sheet." } %>
<%= render(UI::Input.new(type: :search, placeholder: "Search commands")) %>
<% end %>
<% end %>
```
### Example: Bottom
```erb
<%= render(UI::Sheet.new) do |sheet| %>
<% sheet.with_trigger(variant: :secondary) { "Open bottom sheet" } %>
<% sheet.with_content(side: :bottom) do |content| %>
<% content.with_title { "Mobile actions" } %>
<% content.with_description { "Bottom sheets are suited to mobile-friendly action lists." } %>
<% content.with_footer do %>
<%= render(UI::Sheet::Close.new(variant: :outline)) { "Dismiss" } %>
<%= render(UI::Button.new) { "Continue" } %>
<% end %>
<p class="text-sm text-muted-foreground">This panel anchors to the bottom edge with a capped height.</p>
<% end %>
<% end %>
```
bin/rails generate velvetkit:add sheet
- Stimulus
- ui--dialog
- Installs
- Nothing
Examples
Right
right.html.erb
<%= render(UI::Sheet.new) do |sheet| %>
<% sheet.with_trigger { "Open right sheet" } %>
<% sheet.with_content(side: :right) do |content| %>
<% content.with_title { "Notifications" } %>
<% content.with_description { "Manage your notification preferences." } %>
<div class="space-y-3 text-sm text-muted-foreground">
<p>Right sheets work well for settings and secondary tasks.</p>
<p>They reuse the dialog controller but anchor the panel to a side.</p>
</div>
<% end %>
<% end %>
Left
left.html.erb
<%= render(UI::Sheet.new) do |sheet| %>
<% sheet.with_trigger(variant: :secondary) { "Open left sheet" } %>
<% sheet.with_content(side: :left) do |content| %>
<% content.with_title { "Project filters" } %>
<% content.with_description { "Filter and refine your current project list." } %>
<div class="space-y-3 text-sm text-muted-foreground">
<p>Left sheets are useful for navigation and faceted filtering.</p>
</div>
<% end %>
<% end %>
Top
top.html.erb
<%= render(UI::Sheet.new) do |sheet| %>
<% sheet.with_trigger(variant: :outline) { "Open top sheet" } %>
<% sheet.with_content(side: :top) do |content| %>
<% content.with_title { "Command palette" } %>
<% content.with_description { "Quick actions and navigation in a compact top sheet." } %>
<%= render(UI::Input.new(type: :search, placeholder: "Search commands")) %>
<% end %>
<% end %>
Bottom
bottom.html.erb
<%= render(UI::Sheet.new) do |sheet| %>
<% sheet.with_trigger(variant: :secondary) { "Open bottom sheet" } %>
<% sheet.with_content(side: :bottom) do |content| %>
<% content.with_title { "Mobile actions" } %>
<% content.with_description { "Bottom sheets are suited to mobile-friendly action lists." } %>
<% content.with_footer do %>
<%= render(UI::Sheet::Close.new(variant: :outline)) { "Dismiss" } %>
<%= render(UI::Button.new) { "Continue" } %>
<% end %>
<p class="text-sm text-muted-foreground">This panel anchors to the bottom edge with a capped height.</p>
<% end %>
<% end %>
API
Arguments
UI::Sheet.new(class: nil, **html_attrs)
Slots:
with_trigger
with_body
with_content
with_title
with_description
with_footer
Notes
UI::Sheet — side-anchored modal panel backed by the native <dialog> element.
Reuses the ui--dialog controller and follows the same compound API as
UI::Dialog, but anchors the panel to an edge of the viewport instead of
centering it.
Compound:
UI::Sheet
UI::Sheet::Trigger — click-to-open wrapper (inherits from Dialog::Trigger)
UI::Sheet::Content — the <dialog> + fixed panel; inline title / description / footer