Avo::UI::PanelComponent
The panel is one of the most used building blocks in Avo. It renders a titled container with an optional description, header controls, body, sidebar, and footer.
<%= render Avo::UI::PanelComponent.new(title: @product.name, description: @product.description) do |panel| %>
<% panel.with_controls do %>
<%= a_link(@product.link, icon: "tabler/outline/eye", style: :primary, color: :primary) do %>
View product
<% end %>
<% end %>
<% panel.with_card do %>
<div class="flex flex-col p-4 min-h-24 space-y-4">
<h3>Product information</h3>
<p>Style: shiny</p>
</div>
<% end %>
<% end %>
ui.panel(...) is shorthand for Avo::UI::PanelComponent.new(...) — both forms work.
Options
All options are optional. You may render a panel without any of them.
<%= render Avo::UI::PanelComponent.new do |panel| %>
<% panel.with_body do %>
Something here.
<% end %>
<% end %>-> title
The title of the panel, rendered at the top of the header.

- Type: String
-> description
A small line of text under the title that describes what the panel is about.

- Type: String
-> class
A list of CSS classes applied to the panel container.
<%= render Avo::UI::PanelComponent.new(title: "Panel", class: "ring-2 ring-blue-500") do |panel| %>
<% panel.with_body do %>
Something here.
<% end %>
<% end %>
- Type: String
-> data
A hash of data-* attributes forwarded to the panel container.
- Type: Hash
-> index
The item index, forwarded to the container as a data-item-index attribute. Used when panels are rendered as part of a collection (for example the Grid view).
- Type: Integer
-> content_focusable
When true, the panel body becomes a keyboard focus anchor: focusing it lets the user Tab into the fields and Shift+Tab back to the header controls.
- Type: Boolean
- Default:
false
Slots
The component exposes a few slots where you customize the content of specific areas.
-> header
Replaces the default header (title, description and controls) with your own markup. Use it when you need full control over the top of the panel.
<%= render Avo::UI::PanelComponent.new do |panel| %>
<% panel.with_header do %>
<%= render Avo::UI::PanelHeaderComponent.new(title: "Dashboard", description: "Everything at a glance") %>
<% end %>
<% end %>-> controls
A place for panel controls such as back, edit, delete, and detach buttons. The controls are automatically aligned to the right of the header and collapse under the title and description on narrow screens.
<%= render Avo::UI::PanelComponent.new(title: "Dashboard") do |panel| %>
<% panel.with_controls do %>
<%= a_link("/admin", icon: "tabler/outline/external-link", style: :primary, color: :primary) do %>
Admin
<% end %>
<% end %>
<% end %>
-> cover
A full-width area rendered at the very top of the panel, above the header. Handy for a cover image or banner.
<%= render Avo::UI::PanelComponent.new(title: "Product") do |panel| %>
<% panel.with_cover do %>
<%= image_tag @product.cover_url, class: "w-full h-40 object-cover" %>
<% end %>
<% end %>
-> body
The main slot of the component, where the bulk of the content is displayed flush in the panel (no card wrapper).
<%= render Avo::UI::PanelComponent.new(title: "Product information") do |panel| %>
<% panel.with_body do %>
<p>This content is rendered flush in the panel body. There is no card wrapping it, so it sits directly on the panel surface — use this slot when your content already brings its own card.</p>
<% end %>
<% end %>
-> card
Wraps the content in a bordered card automatically (an Avo::UI::CardComponent). Use this instead of body when you want the content to sit inside a distinct card surface.
The card body ships unpadded so wide content (tables, horizontal scrollers) can sit flush to the card edge. Opt into the standard body padding with padded: true (or the card--padded CSS modifier) when your content is free-form — forms, prose, custom tools:
<%= render Avo::UI::PanelComponent.new do |panel| %>
<% panel.with_card(padded: true) do %>
Something here.
<% end %>
<% end %>
-> sidebar
Shows content in a smaller area on the end side of the body.
<%= render Avo::UI::PanelComponent.new(title: "Product") do |panel| %>
<% panel.with_card do %>
Main content here.
<% end %>
<% panel.with_sidebar do %>
Something tiny here.
<% end %>
<% end %>
-> pre_bodies
Content rendered between the header and the body. Can be used more than once (via repeated with_pre_body calls); each entry is rendered in order.
<%= render Avo::UI::PanelComponent.new(title: "Product") do |panel| %>
<% panel.with_pre_body do %>
A notice above the body.
<% end %>
<% panel.with_card do %>
Main content here.
<% end %>
<% end %>
-> footer
The lowest area of the component, rendered under the body or card.
<%= render Avo::UI::PanelComponent.new(title: "Product") do |panel| %>
<% panel.with_card do %>
Main content here.
<% end %>
<% panel.with_footer do %>
Something at the bottom.
<% end %>
<% end %>