Skip to content

Tables

The list page of a resource is described by collection(). In code the class is Anvil\Collection\Collection: it holds the columns, filters, sorting, page sizes and how records look as table rows and as cards. Users switch between table and cards, search, filter, sort, hide columns and save views.

The example is the people list of AnvilIAM (shortened): a computed name with a second line, icons on dates, counts of related records, a hint coloured like the record, and cards with several areas.

use Anvil\Collection\Collection;
use Anvil\Collection\Columns\CountColumn;
use Anvil\Collection\Columns\TextColumn;
use Anvil\Collection\Filters\DateRangeFilter;
use Anvil\Collection\Filters\SelectFilter;
public function collection(Collection $collection): Collection
{
return $collection
->columns([
TextColumn::make('name')
->state(fn ($person): string => $this->recordTitle($person))
->queryColumn('people.last_name', 'people.first_name')->sortable()->searchable()
->description('cost_center'),
TextColumn::make('personnel_number')->sortable()->searchable()->link('hr'),
TextColumn::make('entry_date')->date()->icon('box-arrow-in-right')->sortable(),
TextColumn::make('exit_date')->date()->icon('box-arrow-right')->sortable()->toggleable(),
TextColumn::make('hint')
->state(static fn ($person): ?string => self::hint($person)) // "joins in 3 days"
->color(static fn ($hint, $person): ?string => self::tone($person)),
CountColumn::make('open_tasks')->label('Open tasks')
->relation(FulfilmentTaskModel::class, 'person_id', static fn (Model $tasks) => $tasks->where('status', 'open'))
->icon('list-check'),
TextColumn::make('status')
->labels(['planned' => 'Planned', 'active' => 'Active', 'left' => 'Left'])
->badge(['active' => 'success', 'planned' => 'info', 'left' => 'secondary']),
])
->filters([
SelectFilter::make('status')->options(['planned' => 'Planned', 'active' => 'Active', 'left' => 'Left']),
DateRangeFilter::make('entry_date'),
])
// Blue: joins soon; orange: has left.
->tone(static fn ($person): ?string => self::tone($person))
->card(
title: 'name',
description: 'personnel_number',
badges: ['status'],
meta: ['entry_date', 'exit_date'],
rows: [['hint'], ['open_tasks']],
)
->defaultSort('name')
->perPage(25, [10, 25, 50, 100]);
}
Part Described by Details
Columns columns([...]) Columns
Filters filters([...]) Filters
Search searchable() columns Search
Cards card(...) Cards
Actions per record, bulk actions, export Resource::actions(), bulkActions(), $exportable Actions in lists
Key figures above the list Resource::listWidgets() Widgets above the list
Remembered layout, saved views, presets presets([...]) Saved views
Permissions, policies, validation of requests Security
Option Default Effect
columns([...]) – Columns: TextColumn, CountColumn or own columns
filters([...]) [] Filters in the toolbar
defaultSort('name', 'asc') newest first (UUIDv7) Sort when the list is opened; the column must be sortable()
perPage(25, [10, 25, 50, 100]) 25 Page size and the sizes users can choose
views([Collection::VIEW_TABLE, Collection::VIEW_CARDS], default: …) table and cards, table first Available views; the switch is shown with more than one
card(title:, description:, badges:, meta:, rows:, fields:, view:) first column as title, other columns as fields Content of the cards, see Cards
tone(fn ($record) => 'warning') none Colour of a record as table row and card, see Colours and icons
rowActions(table: 3, cards: 2) 3 and 2 Actions shown as buttons per record, see Actions in lists
presets([...]) none Views defined in code, see Presets
emptyState('No invoices yet.') “No records found.” Text of an empty list

views() restricts the list to one view or sets the default:

$collection->views([Collection::VIEW_TABLE, Collection::VIEW_CARDS], default: Collection::VIEW_CARDS);
  • Sort by the header of a sortable() column; a second click reverses the order.
  • Search the searchable() columns with words, phrases and *.
  • Filter with the controls of the toolbar; active filters are counted and Reset filters clears them.
  • Page through the records and choose the page size.
  • Switch between table and cards, hide toggleable() columns.
  • Save the current state as a view, or open a preset.
  • Open a record: the first column of a row (and the card title) links to the detail page if the user may view the record and the column has no link of its own.

Every change reloads only the list (htmx), not the page. Changing the search, a filter or the page size starts on page 1 again. What the user wants to keep is saved as a view.

Relations on the detail page (see Related records) show the collection of the related resource with the same columns, filters and actions, restricted to the records of the parent. They offer the column menu, but neither remember the layout nor offer saved views or widgets.