Columns
The Lists widget shows the records of a model as a table, with a column for each entry in its columns definition, given as a YAML file path or an array. The column type decides how a value displays, and each type takes options of its own. Adding a list to a page, with the routes and handlers it needs, is covered in Building a List Page.
The lists on this page
Each example on this page is a small list of the seeded posts or samples, built in a loop from its own YAML file. customPageName: false keeps the page number out of the address, so every list on the page pages on its own.
<?php
namespace App\Controllers\Reference\ListsController;
use App\Models\Post;
use App\Models\Sample;
use Illuminate\Support\Str;
use Larajax\Ui\Widgets\Lists;
/**
* ColumnsExamples builds the small lists on the Columns page, one for each column example.
*/
trait ColumnsExamples
{
/**
* makeColumnLists builds a list of posts or samples for each example, with the columns from the example's YAML file.
*
* @return array<string, Lists>
*/
protected function makeColumnLists(): array
{
$examples = [
'text' => Post::class,
'number' => Post::class,
'switch' => Post::class,
'colorpicker' => Post::class,
'selectable' => Sample::class,
'dates' => Post::class,
'relative' => Post::class,
'summary' => Post::class,
'image' => Post::class,
'files' => Sample::class,
'linkage' => Post::class,
'partial' => Post::class,
'relations' => Post::class,
'headers' => Post::class,
];
$lists = [];
foreach ($examples as $name => $modelClass) {
$lists[$name] = Lists::make(
alias: Str::camel($name).'List',
model: new $modelClass,
columns: "~/resources/ui/reference/lists/columns/{$name}.yaml",
recordsPerPage: 4,
customPageName: false,
);
}
return $lists;
}
}Text
text is the default type, used when a column sets no type. It escapes the value, and an array of plain values shows its items separated by commas, like the keywords here. format is a sprintf pattern for the value, so /blog/%s puts the slug inside an address, and default shows in place of an empty value.
columns:
title:
label: Title
slug:
label: Address
format: /blog/%s
keywords:
label: Keywords
default: NoneNumber
number takes format like a text column and aligns its cells to the right. Given a relation and relationCount: true, it shows the number of related records, counted in the list query, so the column still sorts.
columns:
title:
label: Title
views:
label: Views
type: number
format: '%d views'
comments_count:
label: Comments
type: number
relation: comments
relationCount: trueSwitch
switch shows a boolean as a tick or a cross. Hovering the icon shows a label for the value: options gives the labels for false and true, in that order, and without it they are Yes and No.
columns:
title:
label: Title
published:
label: Live
type: switch
options:
0: Not published
1: PublishedColor picker
colorpicker shows a hex color as a swatch, such as a color saved by the color picker form widget.
columns:
title:
label: Title
featured_color:
label: Color
type: colorpickerSelectable
selectable shows the label of the option a value picked, and takes its options the same ways a dropdown field does:
optionslists them in the YAML. An option written as[label, color]shows a colored dot before the label, and[label, icon]shows the icon, or an image when the second item is a path.- With no
options, the column calls the model method named after it,get{Column}Options(), such asgetCountryOptions(). The method is called on each record, so the states follow the country of their own row. optionsMethodnames another model method, or a static method asClass::method.optionsPresetuses a built-in set, such astimezones(see option presets).
An array value, like the features of a sample, shows the label of every picked option.
columns:
name:
label: Name
status:
label: Status
type: selectable
options:
draft: [Draft, '#94a3b8']
active: [Active, '#22c55e']
archived: [Archived, '#ef4444']
priority:
label: Priority
type: selectable
options:
low: [Low, ph ph-arrow-down]
normal: [Normal, ph ph-minus]
high: [High, ph ph-arrow-up]
country:
label: Country
type: selectable
state:
label: State
type: selectable
timezone:
label: Timezone
type: selectable
optionsPreset: timezones
features:
label: Features
type: selectable
options:
wifi: Wi-Fi
parking: Parking
pool: Pool
gym: Gym/**
* getCountryOptions returns the countries that start the dependent dropdown chain.
*
* @return array<string, string>
*/
public function getCountryOptions(): array
{
return [
'au' => 'Australia',
'nz' => 'New Zealand',
'us' => 'United States',
];
}
/**
* getStateOptions returns the states of the picked country, refreshed through dependsOn.
*
* @return array<string, string>
*/
public function getStateOptions(): array
{
return array_map(
fn (array $state) => $state['name'],
static::listRegions()[$this->country] ?? []
);
}
/**
* getCityOptions returns the cities of the picked state, refreshed through dependsOn.
*
* @return array<string, string>
*/
public function getCityOptions(): array
{
return static::listRegions()[$this->country][$this->state]['cities'] ?? [];
}Date and time
datetime, date and time show a date value. Without a format, a datetime reads like Sat, Mar 14, 2026 9:30 AM, a date like Mar 14, 2026 and a time like 9:30 AM. format takes a PHP date format in place of the default.
A datetime converts the value to the viewer's timezone, set with Ui::resolveTimezoneUsing(), while date and time show the value as stored. useTimezone changes either one: true converts a date or time, and false keeps a datetime as stored.
columns:
title:
label: Title
published_at:
label: Published
type: datetime
review_on:
label: Review
type: date
format: D j M Y
share_time:
label: Share Time
type: time
format: H:iRelative time
timetense names the day before the time, such as Today at 12:49, Yesterday at 18:46 or 14 Mar 2026 at 09:30, and timesince counts the time passed, such as 3 days ago. Both columns here show the publish date: the second has a name of its own and reads the date through valueFrom. A timetense column converts to the viewer's timezone like a datetime, and useTimezone changes either type.
columns:
title:
label: Title
published_at:
label: Published
type: timetense
published_since:
label: Since
type: timesince
valueFrom: published_atSummary
summary strips the HTML from a value and shortens it to 40 characters, or to limitChars. limitWords keeps a number of whole words instead. endChars replaces the ... added to a shortened value, and a value short enough to show in full gets nothing added.
columns:
title:
label: Title
excerpt:
label: Excerpt
type: summary
content:
label: Content
type: summary
limitWords: 8
endChars: ' (more)'Image
image shows a thumbnail of an attached image, from an attachOne or attachMany relation, or of an image path or address held in the column. width and height size the thumbnail, 68 by 68 pixels by default, and options passes resize options to it, such as mode: crop. An attachment has no database column to sort by, so the column sets sortable: false.
columns:
title:
label: Title
featured_image:
label: Image
type: image
width: 96
height: 54
options:
mode: crop
sortable: falseFiles
On an attachMany relation, an image column shows up to limit thumbnails, 3 by default, as small squares when no size is set. file shows an icon for the type of each attached file, with the file name as its tip, also up to limit.
A file column with clickable: false links each icon to its file. In a clickable cell, a click belongs to the row link, so the icons are not links.
columns:
name:
label: Name
gallery:
label: Gallery
type: image
limit: 3
sortable: false
brochure:
label: Brochure
type: file
sortable: false
attachments:
label: Attachments
type: file
limit: 2
clickable: false
sortable: falseLinkage
linkage shows the value as a link to linkUrl, where :name takes that attribute of the record, such as :category_id. A path becomes an address through Ui::url(), while a full address is left alone, and a scheme link that points nowhere shows as plain text. linkText replaces the link text, so a column with no value of its own, like Edit here, still shows a link. attributes adds HTML attributes to the link, such as target: _blank.
Without linkUrl, the value is the address, and a value that is an array of [url, text], such as one returned by an accessor, sets both. Only relative addresses and the http, https, ftp, ftps, mailto, tel and sms schemes become links, and any other value shows as text. A linkage cell keeps its own link, so it is never part of the row link.
columns:
title:
label: Title
category:
label: Category
type: linkage
relation: category
select: name
linkUrl: categories/:category_id/edit
edit_link:
label: Edit
type: linkage
linkUrl: posts/:id/edit
linkText: Edit post
sortable: false
attributes:
target: _blankPartial
partial renders a file of your own in each cell, which receives the $record, its $value and the $column. path is a file path, where ~ stands for the app root, or a namespaced view such as admin::lists.tags. relationWith eager loads a relation with the list query, here the tags, so the partial does not run a query for every row. Give a partial with links of its own clickable: false.
columns:
title:
label: Title
tags:
label: Tags
type: partial
path: ~/resources/views/reference/lists/columns/_column_tags.php
relationWith: tags
sortable: false<?php if ($value && $value->count()): ?>
<div class="d-flex flex-wrap gap-1">
<?php foreach ($value as $tag): ?>
<span class="badge badge-subtle theme-secondary">
<?= e($tag->name) ?>
</span>
<?php endforeach ?>
</div>
<?php else: ?>
<span class="fg-3">
Untagged
</span>
<?php endif ?>Values from relations and SQL
A column with a relation and a select reads a column of the related record inside the list query, so it can be searched like any other. On a relation with many records, such as the tags, the values are joined with commas. A column name in brackets, such as category[slug], reads an attribute of the related record, or a key of an array value, after the query has run, so it can be neither searched nor sorted.
select without a relation is raw SQL for the value, where @ stands for the table name, as in length(@title).
Two more options pick the value:
valueFromreads another attribute in place of the column name. With arelation, it reads that attribute of the related record and eager loads the relation.displayFromshows another attribute, such as an accessor, while searching and sorting still use the column's own value.
columns:
title:
label: Title
category_name:
label: Category
relation: category
select: name
tag_names:
label: Tags
relation: tags
select: name
sortable: false
category[slug]:
label: Category Slug
title_length:
label: Title Length
type: number
select: length(@title)Headers and cells
shortLabel replaces the label in the header, while the list setup keeps showing the full label. tooltip adds an icon after the label with a tip, given as text or as an array with a title, an icon (an info icon by default), a placement (top, right, bottom or left, default top) and isHtml to show the title as HTML.
width sets the column width, as a percentage or in pixels, and a single column without one takes the space left over. align aligns the cells to the left, right or center. cssClass adds a class to the cells and headCssClass adds one to the header.
columns:
title:
label: Title
width: 40%
tooltip: Shown as the heading of the post.
views:
label: Views this year
shortLabel: Views
type: number
align: center
status:
label: Status
cssClass: text-capitalize
headCssClass: fst-italic
tooltip:
title: Only <strong>approved</strong> posts can go live.
isHtml: true
icon: ph ph-question
placement: rightColumn properties
Every column takes these properties. A column given as a string, such as title: Title, is a text column with that label.
| Property | Description |
|---|---|
| label | the column header. Default: the column name in StudlyCase. |
| shortLabel | a shorter label for the header, in place of label. |
| type | how the value displays, one of the column types. Default: text. |
| default | shown in place of an empty value. |
| searchable | includes the column in the list search. Default: false. |
| sortable | lets a click on the header sort the list. Default: true. |
| sortableDefault | sorts the list by this column until the visitor picks another, as asc or desc, when the list sets no defaultSort. Use it on one column only. |
| invisible | starts the column unchecked in the list setup, so it is hidden until the visitor shows it. Default: false. |
| hidden | defines the column without ever showing it, not even in the list setup. Default: false. |
| clickable | false leaves the cell out of the row link. Default: true. |
| select | raw SQL for the value, or with relation a column of the related table. @ stands for the table name. |
| valueFrom | reads the value from another attribute, or with relation from an attribute of the related record. |
| displayFrom | shows another attribute, while searching and sorting use the column's own value. |
| relation | a relation of the model to read the value from, with select or valueFrom. |
| relationCount | shows the number of related records. Needs relation. Default: false. |
| relationWith | a relation, or an array of them, eager loaded with the list query. Dot notation loads nested relations. |
| format | a format for the value, used by the text, number and date types. |
| width | the column width, such as 10% or 50px. |
| align | aligns the cells: left, right or center. |
| cssClass | a class added to the cells. |
| headCssClass | a class added to the header cell. |
| tooltip | an icon with a tip after the header label, as text or an array with title, icon, placement and isHtml. |
| permissions | gate abilities the visitor needs to see the column, as a string or an array where any one is enough, checked with Laravel's Gate. |
| order | a number for the column's position. Default: 100, 200 and so on, in the order of the definitions. |
| after | places the column after the named column. |
| before | places the column before the named column. |
Column types
| Type | Shows | Options |
|---|---|---|
| text | the value, escaped. An array of plain values is joined with commas. | format |
| number | the value like text, aligned to the right. | format |
| switch | a tick or a cross for a boolean, with a label on hover. | options |
| colorpicker | a swatch of a hex color. | |
| selectable | the label of the picked option, or of each picked option in an array. | options, optionsMethod, optionsPreset |
| datetime | a date and time, in the viewer's timezone. | format, useTimezone |
| date | a date, as stored. | format, useTimezone |
| time | a time, as stored. | format, useTimezone |
| timetense | the day and time, such as Today at 12:49, in the viewer's timezone. | useTimezone |
| timesince | the time passed, such as 3 days ago. | useTimezone |
| summary | the text without HTML, shortened. | limitChars, limitWords, endChars |
| image | thumbnails of attached images, or of image paths and addresses. | width, height, limit, options |
| file | an icon for each attached file. | limit |
| linkage | the value as a link. | linkUrl, linkText, attributes |
| partial | a file of your own. | path |
The types are fixed: any other type throws an error that names it. For a cell no type covers, use a partial column.
Type options
| Option | Description |
|---|---|
| format | text and number: a sprintf pattern for the value, such as '%d views'. datetime, date and time: a PHP date format. |
| useTimezone | the date types: true converts the value to the viewer's timezone and false shows it as stored. Default: true for datetime and timetense, false for the others. |
| options | switch: the labels for false and true. Default: Yes and No. selectable: the options, as value: label, value: [label, color] or value: [label, icon]. image: resize options for the thumbnails, such as mode: crop. |
| optionsMethod | selectable: a model method that returns the options, or a static method as Class::method. Default: get{Column}Options(), called on each record. |
| optionsPreset | selectable: a built-in option set, such as timezones. |
| limitChars | summary: the number of characters to keep. Default: 40. |
| limitWords | summary: the number of words to keep, in place of limitChars. |
| endChars | summary: added to a shortened value. Default: .... |
| width | image: the thumbnail width in pixels. Default: 68. |
| height | image: the thumbnail height in pixels. Default: 68. |
| limit | image and file: the most files shown in a cell. Default: 3. |
| linkUrl | linkage: the address, where :name takes an attribute of the record. |
| linkText | linkage: the link text, in place of the value. |
| attributes | linkage: HTML attributes for the link, such as target: _blank. |
| path | partial: the file to render, as a ~/ path or a namespaced view. |