App shell
The frame every screen composes into: an icon-collapsible sidebar with grouped nav, badges and a user footer, plus a topbar with breadcrumb that answers the collapsed state, and a stat-card content grid.
Installation
Blocks are copied source, not library calls — the generator writes this
exact template into app/views/blocks/
and from there it's yours to edit. The preview below renders the same
source the copy-in produces.
bin/rails g poetry:block app-shell
Default
<%= poetry_sidebar(collapsible: :icon) do |shell| %>
<% shell.with_nav do %>
<%= poetry_sidebar_header do %>
<%# The collapse-aware header shape (a menu button, never a bare
div): at icon width the button shrinks to the tile and the
truncating spans disappear with it. %>
<%= poetry_sidebar_menu do %>
<%= poetry_sidebar_menu_item do %>
<%= poetry_sidebar_menu_button(size: :lg) do %>
<div class="flex aspect-square size-8 items-center justify-center rounded-lg bg-sidebar-primary text-sidebar-primary-foreground">
<%= poetry_icon(name: :"audio-waveform", class: "size-4") %>
</div>
<div class="grid flex-1 text-left text-sm leading-tight">
<span class="truncate font-semibold">Meridian</span>
<span class="truncate text-xs">Operations</span>
</div>
<% end %>
<% end %>
<% end %>
<% end %>
<%= poetry_sidebar_content do %>
<%= poetry_sidebar_group do %>
<%= poetry_sidebar_group_label { "Platform" } %>
<%= poetry_sidebar_menu do %>
<%= poetry_sidebar_menu_item do %>
<%= poetry_sidebar_menu_button(href: "/dashboard", active: true) do %>
<%= poetry_icon(name: :"layout-dashboard") %><span>Dashboard</span>
<% end %>
<% end %>
<%= poetry_sidebar_menu_item do %>
<%= poetry_sidebar_menu_button(href: "/orders") do %>
<%= poetry_icon(name: :package) %><span>Orders</span>
<% end %>
<%= poetry_sidebar_menu_badge do %>12<% end %>
<% end %>
<%= poetry_sidebar_menu_item do %>
<%= poetry_sidebar_menu_button(href: "/customers") do %>
<%= poetry_icon(name: :users) %><span>Customers</span>
<% end %>
<% end %>
<% end %>
<% end %>
<%= poetry_sidebar_group do %>
<%= poetry_sidebar_group_label { "Resources" } %>
<%= poetry_sidebar_menu do %>
<%= poetry_sidebar_menu_item do %>
<%= poetry_sidebar_menu_button(href: "/docs") do %>
<%= poetry_icon(name: :"book-open") %><span>Documentation</span>
<% end %>
<% end %>
<%= poetry_sidebar_menu_item do %>
<%= poetry_sidebar_menu_button(href: "/support") do %>
<%= poetry_icon(name: :"life-buoy") %><span>Support</span>
<% end %>
<% end %>
<% end %>
<% end %>
<% end %>
<%= poetry_sidebar_footer do %>
<%# The footer rides the same collapse-aware shape as the header: at
icon width the menu button shrinks to the avatar tile and the
truncating spans clip with it instead of spilling into the inset. %>
<%= poetry_sidebar_menu do %>
<%= poetry_sidebar_menu_item do %>
<% profile = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2NCA2NCI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJnIiB4MT0iMCIgeTE9IjAiIHgyPSIxIiB5Mj0iMSI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjNjM2NmYxIi8+PHN0b3Agb2Zmc2V0PSIuNTUiIHN0b3AtY29sb3I9IiM4YjVjZjYiLz48c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiNlYzQ4OTkiLz48L2xpbmVhckdyYWRpZW50PjwvZGVmcz48cmVjdCB3aWR0aD0iNjQiIGhlaWdodD0iNjQiIGZpbGw9InVybCgjZykiLz48Y2lyY2xlIGN4PSIzMiIgY3k9IjI1IiByPSIxMCIgZmlsbD0iI2ZmZiIgb3BhY2l0eT0iLjkiLz48cGF0aCBkPSJNMTIgNjRjMi0xMyAxMC0xOSAyMC0xOXMxOCA2IDIwIDE5eiIgZmlsbD0iI2ZmZiIgb3BhY2l0eT0iLjkiLz48L3N2Zz4=" %>
<%= poetry_sidebar_menu_button(size: :lg) do %>
<%= poetry_avatar(src: profile, label: "Matt Solt", class: "size-8 rounded-lg") { "MS" } %>
<div class="grid flex-1 text-left text-sm leading-tight">
<span class="truncate font-semibold">Matt Solt</span>
<span class="truncate text-xs">matt@roboruby.com</span>
</div>
<% end %>
<% end %>
<% end %>
<% end %>
<%= poetry_sidebar_rail %>
<% end %>
<% shell.with_inset do %>
<header class="flex h-14 shrink-0 items-center gap-2 border-b px-4 transition-[height] group-has-data-[collapsible=icon]/sidebar-wrapper:h-12">
<%= poetry_sidebar_trigger %>
<%= poetry_separator(orientation: :vertical, class: "h-4") %>
<%= poetry_breadcrumb do |crumb| %>
<% crumb.with_item("Meridian", href: "/") %>
<% crumb.with_item("Dashboard") %>
<% end %>
</header>
<div class="flex flex-col gap-6 p-6">
<div class="flex flex-wrap items-end justify-between gap-4">
<div class="space-y-1">
<h1 class="text-2xl font-semibold tracking-tight">Dashboard</h1>
<p class="text-sm text-muted-foreground">Today across the Meridian workspace.</p>
</div>
<%= poetry_button(variant: :outline) do |button| %>
<% button.with_leading { poetry_icon(name: :download) } %>
Export
<% end %>
</div>
<div class="grid gap-4 md:grid-cols-3">
<%= poetry_card(title_tag: :h2) do |card| %>
<% card.with_title { "Revenue" } %>
<% card.with_description { "Last 30 days" } %>
<div class="text-2xl font-semibold tabular-nums">$48,210</div>
<p class="text-xs text-muted-foreground">+12.4% from the previous period</p>
<% end %>
<%= poetry_card(title_tag: :h2) do |card| %>
<% card.with_title { "Open orders" } %>
<% card.with_description { "Awaiting fulfillment" } %>
<div class="text-2xl font-semibold tabular-nums">42</div>
<p class="text-xs text-muted-foreground">12 due before Friday</p>
<% end %>
<%= poetry_card(title_tag: :h2) do |card| %>
<% card.with_title { "On-time rate" } %>
<% card.with_description { "Trailing quarter" } %>
<div class="text-2xl font-semibold tabular-nums">98.2%</div>
<p class="text-xs text-muted-foreground">Target is 97.5%</p>
<% end %>
</div>
<%= poetry_card(title_tag: :h2) do |card| %>
<% card.with_title { "Recent activity" } %>
<% card.with_description { "The latest changes across orders and customers." } %>
<ul class="space-y-3 text-sm">
<li class="flex items-center justify-between gap-4">
<span>ORD-1042 marked fulfilled by Matt</span>
<span class="text-xs text-muted-foreground tabular-nums">2m ago</span>
</li>
<li class="flex items-center justify-between gap-4">
<span>Northwind Traders added a shipping address</span>
<span class="text-xs text-muted-foreground tabular-nums">18m ago</span>
</li>
<li class="flex items-center justify-between gap-4">
<span>Refund approved for ORD-1040</span>
<span class="text-xs text-muted-foreground tabular-nums">1h ago</span>
</li>
</ul>
<% end %>
</div>
<% end %>
<% end %>
Styling
A block has no styling surface of its own — every element in the copied
template is a Poetry component carrying its usual
data-slot parts, and because you own
the source, utility classes can be edited directly in place. Per-part
contracts live on the composed components' pages: