Skip to content

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
<?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.

yaml
columns:
    title:
        label: Title

    slug:
        label: Address
        format: /blog/%s

    keywords:
        label: Keywords
        default: None

Number ​

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.

yaml
columns:
    title:
        label: Title

    views:
        label: Views
        type: number
        format: '%d views'

    comments_count:
        label: Comments
        type: number
        relation: comments
        relationCount: true

Switch ​

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.

yaml
columns:
    title:
        label: Title

    published:
        label: Live
        type: switch
        options:
            0: Not published
            1: Published

Color picker ​

colorpicker shows a hex color as a swatch, such as a color saved by the color picker form widget.

yaml
columns:
    title:
        label: Title

    featured_color:
        label: Color
        type: colorpicker

Selectable ​

selectable shows the label of the option a value picked, and takes its options the same ways a dropdown field does:

  • options lists 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 as getCountryOptions(). The method is called on each record, so the states follow the country of their own row.
  • optionsMethod names another model method, or a static method as Class::method.
  • optionsPreset uses a built-in set, such as timezones (see option presets).

An array value, like the features of a sample, shows the label of every picked option.

yaml
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
php
/**
 * 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.

yaml
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:i

Relative 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.

yaml
columns:
    title:
        label: Title

    published_at:
        label: Published
        type: timetense

    published_since:
        label: Since
        type: timesince
        valueFrom: published_at

Summary ​

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.

yaml
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.

yaml
columns:
    title:
        label: Title

    featured_image:
        label: Image
        type: image
        width: 96
        height: 54
        options:
            mode: crop
        sortable: false

Files ​

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.

yaml
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: false

Linkage ​

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.

yaml
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: _blank

Partial ​

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.

yaml
columns:
    title:
        label: Title

    tags:
        label: Tags
        type: partial
        path: ~/resources/views/reference/lists/columns/_column_tags.php
        relationWith: tags
        sortable: false
php
<?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:

  • valueFrom reads another attribute in place of the column name. With a relation, it reads that attribute of the related record and eager loads the relation.
  • displayFrom shows another attribute, such as an accessor, while searching and sorting still use the column's own value.
yaml
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.

yaml
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: right

Column properties ​

Every column takes these properties. A column given as a string, such as title: Title, is a text column with that label.

PropertyDescription
labelthe column header. Default: the column name in StudlyCase.
shortLabela shorter label for the header, in place of label.
typehow the value displays, one of the column types. Default: text.
defaultshown in place of an empty value.
searchableincludes the column in the list search. Default: false.
sortablelets a click on the header sort the list. Default: true.
sortableDefaultsorts 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.
invisiblestarts the column unchecked in the list setup, so it is hidden until the visitor shows it. Default: false.
hiddendefines the column without ever showing it, not even in the list setup. Default: false.
clickablefalse leaves the cell out of the row link. Default: true.
selectraw SQL for the value, or with relation a column of the related table. @ stands for the table name.
valueFromreads the value from another attribute, or with relation from an attribute of the related record.
displayFromshows another attribute, while searching and sorting use the column's own value.
relationa relation of the model to read the value from, with select or valueFrom.
relationCountshows the number of related records. Needs relation. Default: false.
relationWitha relation, or an array of them, eager loaded with the list query. Dot notation loads nested relations.
formata format for the value, used by the text, number and date types.
widththe column width, such as 10% or 50px.
alignaligns the cells: left, right or center.
cssClassa class added to the cells.
headCssClassa class added to the header cell.
tooltipan icon with a tip after the header label, as text or an array with title, icon, placement and isHtml.
permissionsgate abilities the visitor needs to see the column, as a string or an array where any one is enough, checked with Laravel's Gate.
ordera number for the column's position. Default: 100, 200 and so on, in the order of the definitions.
afterplaces the column after the named column.
beforeplaces the column before the named column.

Column types ​

TypeShowsOptions
textthe value, escaped. An array of plain values is joined with commas.format
numberthe value like text, aligned to the right.format
switcha tick or a cross for a boolean, with a label on hover.options
colorpickera swatch of a hex color.
selectablethe label of the picked option, or of each picked option in an array.options, optionsMethod, optionsPreset
datetimea date and time, in the viewer's timezone.format, useTimezone
datea date, as stored.format, useTimezone
timea time, as stored.format, useTimezone
timetensethe day and time, such as Today at 12:49, in the viewer's timezone.useTimezone
timesincethe time passed, such as 3 days ago.useTimezone
summarythe text without HTML, shortened.limitChars, limitWords, endChars
imagethumbnails of attached images, or of image paths and addresses.width, height, limit, options
filean icon for each attached file.limit
linkagethe value as a link.linkUrl, linkText, attributes
partiala 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 ​

OptionDescription
formattext and number: a sprintf pattern for the value, such as '%d views'. datetime, date and time: a PHP date format.
useTimezonethe 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.
optionsswitch: 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.
optionsMethodselectable: a model method that returns the options, or a static method as Class::method. Default: get{Column}Options(), called on each record.
optionsPresetselectable: a built-in option set, such as timezones.
limitCharssummary: the number of characters to keep. Default: 40.
limitWordssummary: the number of words to keep, in place of limitChars.
endCharssummary: added to a shortened value. Default: ....
widthimage: the thumbnail width in pixels. Default: 68.
heightimage: the thumbnail height in pixels. Default: 68.
limitimage and file: the most files shown in a cell. Default: 3.
linkUrllinkage: the address, where :name takes an attribute of the record.
linkTextlinkage: the link text, in place of the value.
attributeslinkage: HTML attributes for the link, such as target: _blank.
pathpartial: the file to render, as a ~/ path or a namespaced view.