Custom Layouts
By default, the detail form renders all properties in a single column, top to bottom. Override getView() to create custom layouts with columns, cards, tabs, and panels.
Basic Layout
The getView() method returns a layout built with $this->fromLayout():
| use System\View\Elements;
use System\View\ViewNode;
public function getView(?string $context = null): ?ViewNode
{
return $this->fromLayout([
'title', // property name as string
'description',
'due_date',
]);
}
|
Returning null uses the default layout (all fields, one column).
Rows — Side by Side Fields
Place properties next to each other in a row using an array:
| return $this->fromLayout([
'title',
['start_date', 'end_date'], // these two sit side by side
'description',
]);
|
Columns — Proportional Widths
Use Elements::columns() with Elements::col() for proportional column layouts (12-column grid):
| return $this->fromLayout([
Elements::columns()->add(
Elements::col(8)->add( // 8/12 width (left)
'title',
'reference',
'value'
),
Elements::col(4)->add( // 4/12 width (right)
'type',
'party',
'period'
)
),
['start_date', 'end_date'], // full-width row below
]);
|
Cards — Visual Grouping
Wrap fields in a card for visual separation:
| return $this->fromLayout([
Elements::columns()->add(
Elements::col(8)->add(
Elements::card()->add( // card with border
'title',
'reference',
'value'
)
),
Elements::col(4)->add(
Elements::card()->add(
'type',
'party'
)
)
),
]);
|
Tabs — Organized Sections
Use tabs to organize many fields into logical groups:
| return $this->fromLayout([
'title',
Elements::tabs('main')
->add('Details', // tab label
Elements::row('type', 'party', 'reference'),
'value'
)
->add('Dates',
Elements::row('start_date', 'end_date'),
'period'
)
->add('Documents',
'document',
'amendments'
),
]);
|
Panels — Collapsible Sections
| return $this->fromLayout([
'subject',
'content',
Elements::panel('Internal Notes', key: 'notes')
->add('note', 'todo'),
]);
|
HasMany in Layouts
Reference hasMany properties by name. They render as inline lists:
| return $this->fromLayout([
'title',
'content',
['reminders', 'feedbacks'], // two hasMany lists side by side
'attachments',
]);
|
Complete Example
Here's a real-world layout combining all elements:
| public function getView(?string $context = null): ?ViewNode
{
return $this->fromLayout([
// Two-column header
Elements::columns()->add(
Elements::col(8)->add(
Elements::card()->add(
'title',
'reference',
'value'
)
),
Elements::col(4)->add(
Elements::card()->add(
'type',
'party',
'period'
)
)
),
// Date row
Elements::columns()->add(
Elements::col(8)->add('start_date', 'end_date'),
),
// Child records
['reminders', 'amendments'],
// Bottom section
'document',
'todo',
]);
}
|
Tip
If getView() returns null, the framework renders all properties in declaration order. You only need a custom layout when the default order or single-column presentation isn't ideal.