Fields layout API β
Per-option reference for the layout DSL β header, panel, card, sidebar, tabs, and tab. For task-oriented documentation and worked examples, see the Fields layout guide.
The field-level stacked and width options are documented in the field options reference. Everything below is declared inside a resource's fields method.
Header β
-> header
header(**args, &block)A resource-level item β declared as a standalone header call inside fields, not a field option (there is no header: keyword on individual fields). It marks where the page header chrome renders: cover image, title, description, avatar, discreet info, and control buttons.
You only declare header when you want it somewhere other than the top β e.g. to place a resource tool, card, or panel above it. Otherwise leave it out and Avo renders the header at the top for you.
- Arguments: ignored.
headeraccepts**argsbut nothing reads them β it carries no configuration. Its content comes entirely from the resource (title,description, cover, avatar, controls), soheaderpositions the chrome but can't customize it. - Behavior: declaring
headersuppresses the automatically-injected one; its position within thefieldsblock determines where it renders. - Default: if omitted, Avo auto-adds a header at the very top of the page. When a resource is embedded in a modal, the header is stripped from the edit view.
Not to be confused with heading, a field type for inline section titles inside a panel or form.
Panels β
-> panel
panel(title: nil, **args, &block)Groups related fields inside a titled container. Declared at the root level or inside tabs.
-> title
The panel's title, rendered at the top of the panel.
panel title: "User information" do
field :first_name, as: :text
field :last_name, as: :text
end- Type: String
- Default:
nil(untitled panel)
-> description
An auxiliary line rendered under the panel title.
panel title: "User information", description: "Some information about this user" do
field :first_name, as: :text
end- Type: String
- Default:
nil
-> visible
Dynamically controls the visibility of a panel and all its children at once, without setting visibility on each field.
panel title: "User information", visible: -> { resource.record.enabled? } do
field :first_name, as: :text
field :last_name, as: :text
end- Type: Boolean or Proc
- Default:
true
Panels also accept the standard view-visibility options (only_on, except_on, show_on, hide_on) β see field options.
-> card
card(title: nil, **args, &block)A lightweight grouping that sections fields visually without opening a full panel. Unlike sidebar, card can be placed at the root level, inside a panel, inside a sidebar, or inside a tab. Takes title and description (same as panel).
card title: "Personal information" do
field :first_name, as: :text
field :last_name, as: :text
endSidebar β
-> sidebar
sidebar(**args, &block)A narrow column that stacks compact fields beside the main content. Must be declared inside a panel (declaring it elsewhere raises). Wrap the fields in a card, or add a field that brings its own panel.
panel do
field :id, as: :id
sidebar do
card do
field :email, as: :gravatar, only_on: :show
field :active, as: :boolean, only_on: :show
end
end
end-> panel_wrapper
Controls whether Avo's panel styling (white_panel_classes) is applied to the sidebar's contents. Set it to false when rendering a custom tool inside a sidebar that should not carry the default panel chrome.
sidebar panel_wrapper: false do
tool Avo::ResourceTools::SidebarTool
end- Type: Boolean
- Default:
true
Tabs β
-> tabs
tabs(id: nil, title: nil, description: nil, **args, &block)The group block wrapping a set of tabs. title, description, and visible apply to both the group and each individual tab.
-> id
A unique identifier for the tab group. Required for durable (remembered across views) and bookmarkable tab selection β assign a distinct id to each group.
tabs id: :some_random_uniq_id do
field :posts, as: :has_many, show_on: :edit
end- Type: Symbol or String
- Default:
nil
-> title
On a tabs group, an overarching label for the whole collection. On a tab, the mandatory label shown on the tab switcher.
tabs title: "Tabs group title" do
tab title: "User information" do
# ...
end
end- Type: String
- Default:
nilfor the group; required for eachtab
-> description
Auxiliary text for the group or a tab. On a tab, it renders as the tooltip shown when hovering the switcher.
tab title: "User information", description: "Some information about this user" do
# ...
end- Type: String
- Default:
nil
-> visible
Controls the visibility of a whole tabs group or an individual tab.
tabs visible: -> { resource.record.enabled? } do
tab title: "General Information" do
panel do
field :name, as: :text
end
end
tab title: "Admin Information", visible: -> { current_user.is_admin? } do
panel do
field :role, as: :text
end
end
end- Type: Boolean or Proc
- Default:
true
-> tab
tab(title:, **args, &block)An individual tab inside a tabs group. title is required; it also accepts description and visible (see tabs), plus the two loading options below. Standalone fields placed directly in a tab are auto-wrapped in a card, so a panel or card is optional β add one only to attach a title or description.
-> lazy_load
Defers loading a tab's content until the tab is revealed, improving performance by fetching data only when needed. In form views it is automatically disabled to prevent data loss on submit.
tabs do
tab title: "Address", lazy_load: true do
# ...
end
end- Type: Boolean
- Default:
false(all tabs load immediately)
-> loading
Where lazy_load fetches a tab's content automatically when it's revealed, loading: :manual defers the fetch until the user clicks a Load button β useful for heavy tabs you don't want to load on every page view. Each manual tab gets its own button (per-tab gating); on click, the real content replaces the placeholder, and a failed request shows an inline error with a Retry button.
tabs do
tab title: "Orders", loading: :manual do
field :orders, as: :has_many
end
end- Type: Symbol or Hash
- Default: Unset β a tab renders eagerly (its content loads inline on cold start).
loading is the same mechanism used by association turbo-frame fields (has_one, has_many, has_and_belongs_to_many) β see the association loading option. The difference is the default: a tab with no loading: stays eager, whereas association frames fall back to the global default from config.associations (loading: :lazy, auto_load_for: 15.minutes out of the box). Setting loading: on the field or tab overrides that default.
Values β
| Value | Behavior |
|---|---|
:manual | Placeholder + Load button; fetches on click. Once opened, the tab is remembered for 15 minutes by default. |
{ mode: :manual } | Same as :manual. |
{ mode: :manual, auto_load_for: 5.minutes } | Manual with a custom sliding memory window β once opened, the tab auto-loads (no placeholder) on return visits for the given duration. |
{ auto_load_for: 5.minutes } | mode: is optional in a Hash β omit it and loading defaults to :manual, so this is the same as the row above. |
{ mode: :manual, auto_load_for: 0 } | Manual with no memory β the placeholder returns on every visit (0 or nil opts out). |
:lazy / { mode: :lazy } | Native lazy loading (equivalent to lazy_load: true). |
auto_load_for accepts an ActiveSupport::Duration (5.minutes) or a raw Integer of seconds (300); Hash keys may be symbols or strings.
Once the user opens a manual tab, Avo remembers it for the auto_load_for window (default 15 minutes) via a short-lived cookie scoped per record + tab, sliding the window forward on each return visit. When it lapses, the placeholder + Load button return.
tab title: "Orders", loading: { mode: :manual, auto_load_for: 5.minutes } do
field :orders, as: :has_many
endINFO
loading: :manual is purely additive β omitting it leaves every tab behaving exactly as before. Like lazy_load, manual loading is a Show-view concern and does not apply to form views.