Skip to content

Layout ​

A form places its fields in up to three sections: the fields outside any tabs, the primary tabs and the secondary tabs. Within a section, span puts each field on a side or across the width, and a few properties shape how each field shows its label, comment and input. Form options switch the whole form to a horizontal, survey or preview layout.

Tabs ​

Fields under tabs are grouped into tabs by their tab property, in the order the tabs first appear. Fields with no tab land in defaultTab, which is Misc unless you set it. icons puts an icon beside a tab title, keyed by the tab name. Fields under the top level fields sit above the tabs and stay in view whichever tab is open. One YAML file can hold fields, tabs and secondaryTabs together.

yaml
fields:
    code:
        label: Code
        comment: Outside the tabs, so it stays above them on every tab.

tabs:
    defaultTab: Content

    icons:
        Content: ph ph-text-aa
        Pricing: ph ph-currency-dollar

    fields:
        headline:
            label: Headline
            comment: No tab is given, so it lands in the default tab.

        excerpt:
            label: Excerpt
            type: textarea
            size: tiny

        price:
            label: Price
            type: number
            tab: Pricing
            span: left
            step: 0.01

        quantity:
            label: Quantity
            type: number
            tab: Pricing
            span: right
            step: 1

The open tab is reflected in the URL hash, so a link or a reload opens the same tab. A tab whose fields are all hidden, by hidden or by a trigger, is hidden too. Tab options lists everything a tab section takes.

Lazy tabs ​

A tab named in lazy renders a loading indicator, then fetches its fields from the form's onLazyLoadTab handler the first time it opens. It suits tabs holding heavy widgets that most visits never look at. The tab that opens first is always rendered straight away. Until a lazy tab opens, its fields are not on the page, so saving the form leaves their values as they were.

yaml
tabs:
    lazy:
        - Notes

    fields:
        summary:
            label: Summary
            type: textarea
            size: small
            tab: Overview

        notes:
            label: Notes
            type: textarea
            tab: Notes
            comment: Rendered by the form's onLazyLoadTab handler when this tab first opens.

Secondary tabs ​

secondaryTabs renders a second tab set below the primary tabs, often for large content fields such as a body or description. It takes the same options as tabs. activeTab picks the tab that opens first, by name or by position counted from 1.

yaml
tabs:
    fields:
        email:
            label: Email
            type: email
            tab: Contact

secondaryTabs:
    activeTab: Body

    icons:
        Description: ph ph-article
        Body: ph ph-text-align-left

    fields:
        description:
            label: Description
            type: textarea
            size: small
            tab: Description

        body:
            label: Body
            type: textarea
            tab: Body
            comment: The secondary tabs open on Body, chosen by activeTab.

Spans ​

span places a field on the left or right half, or across the full width, which is the default. A left field followed by a right field share a row. auto takes the right half after a left field, and the left half otherwise, so a run of auto fields fills the rows in pairs. On narrow screens the halves stack.

yaml
fields:
    city:
        label: City
        span: left
        comment: span left

    postcode:
        label: Postcode
        span: right
        comment: span right

    delivery_address:
        label: Address
        span: full
        comment: span full, the default

    state:
        label: State
        type: dropdown
        span: auto
        comment: span auto, which lands on the left

    country:
        label: Country
        type: dropdown
        span: auto
        comment: span auto, which lands on the right after a left field

adaptive makes a field fill its tab, for a large editor such as the rich editor, and stretches the tab to the height of the page.

Rows ​

Fields with span: row that follow one another share one grid row, each sized by the column classes in its spanClass. Prefixed classes such as md:col-6 apply from that screen width up, so the fields stack on a phone.

yaml
fields:
    discount_code:
        label: Discount Code
        span: row
        spanClass: md:col-6

    discount_percent:
        label: Discount %
        type: number
        span: row
        spanClass: md:col-3

    keywords:
        label: Keywords
        span: row
        spanClass: md:col-3

Collapsible sections ​

A section field with the container attribute data-field-collapsible starts collapsed. Clicking the section shows or hides every field after it, up to the next section. The fields stay in the form while they are hidden, so they still save.

yaml
fields:
    _shipping_section:
        label: Shipping
        type: section
        comment: Click to show or hide the fields of this section.
        containerAttributes:
            data-field-collapsible: true

    delivery:
        label: Delivery
        type: dropdown
        span: left
        options:
            pickup: Pick up in store
            courier: Courier
            post: Post

    transport:
        label: Transport
        type: dropdown
        span: right
        options:
            road: Road
            air: Air
            sea: Sea

    _membership_section:
        label: Membership
        type: section
        containerAttributes:
            data-field-collapsible: true

    level:
        label: Level
        type: dropdown
        emptyOption: Pick a level

Comments ​

comment sits below a field and commentAbove between its label and the input. Both are escaped, unless commentHtml is on, which renders them as HTML. Labels, comments and placeholders pass through Laravel's __(), so they can be translation keys.

yaml
fields:
    name:
        label: Name
        span: left
        commentAbove: commentAbove sits between the label and the field.
        comment: comment sits below the field.

    slug:
        label: Slug
        span: right
        comment: With commentHtml, a comment can hold <strong>HTML markup</strong>.
        commentHtml: true

Tooltips ​

tooltip adds an icon beside the label that shows a hint on hover. As a string it is the hint text. As an array it takes title, the text, icon, an icon class in place of the default info icon, placement, which is top unless set to right, bottom or left, and isHtml, which renders the title as HTML.

yaml
fields:
    category:
        label: Category
        type: dropdown
        span: left
        optionsMethod: getCategoryOptions
        tooltip: The shelf the sample is listed on.

    priority:
        label: Priority
        type: dropdown
        span: right
        options:
            low: Low
            normal: Normal
            high: High
        tooltip:
            title: <strong>High</strong> ships first, <strong>Low</strong> ships last.
            isHtml: true
            placement: bottom
            icon: ph ph-question

Input attributes ​

attributes adds HTML attributes to the input itself, here a pattern, maxlength and title that the browser checks before the form is sent. A maxlength or pattern you set replaces the one a text or number field adds by default.

yaml
fields:
    reference:
        label: Reference
        placeholder: REF-1234
        attributes:
            pattern: 'REF-[0-9]{4}'
            maxlength: 8
            title: REF, a dash and four digits
        comment: The browser marks the field invalid until it matches REF-1234.

Container class and attributes ​

cssClass adds a class to the element that wraps the field, its label and its comment, which is the place to style one field differently. containerAttributes adds HTML attributes to the same element, such as a data- attribute for your own scripts.

yaml
fields:
    private_notes:
        label: Private Notes
        type: textarea
        size: tiny
        span: left
        cssClass: reference-forms-highlight
        comment: Highlighted by a class on the field's container.

    api_key:
        label: API Key
        span: right
        containerAttributes:
            data-secret: api-key
        comment: Inspect the wrapping element to find data-secret.

The demo app's stylesheet draws the highlight from the class:

css
.form-group.reference-forms-highlight {
    padding-inline-start: 0.75rem;
    border-inline-start: 3px solid var(--amber-brand);
}

Horizontal mode ​

The form option horizontalMode puts each label in a column beside its field, with the comment under the field. It belongs to the widget rather than the YAML, so you set it on the form with $form->horizontalMode = true, or pass horizontalMode: true to Form::make().

yaml
fields:
    plan:
        label: Plan
        type: radio
        inlineOptions: true
        options:
            starter: Starter
            team: Team
            business: Business

    status:
        label: Status
        type: balloon-selector
        options:
            draft: Draft
            active: Active
            archived: Archived

    timezone:
        label: Timezone
        type: dropdown
        optionsPreset: timezones
        comment: Labels sit to the left, comments under the field.

The page action builds this example and the next, then switches their modes on:

php
/**
 * layout displays the Layout page, where the horizontal and survey examples set their form options on the widget.
 */
public function layout(): View
{
    $forms = $this->makeExampleForms('forms/layout', [
        'tabs', 'lazy-tabs', 'secondary-tabs', 'spans', 'row-span', 'sections',
        'comments', 'tooltips', 'attributes', 'css-class',
        'horizontal', 'survey', 'read-only', 'disabled',
    ]);

    $forms['horizontal']->horizontalMode = true;
    $forms['survey']->surveyMode = true;

    return view('reference.forms.layout', $this->exampleVars() + [
        'standaloneForms' => $this->makeStandaloneForms('forms/layout', ['preview'], $this->referenceSample(), [
            'arrayName' => 'Preview',
            'previewMode' => true,
        ]),
    ]);
}

Survey mode ​

The form option surveyMode renders the tabs one after another, each under a heading, so the whole form reads top to bottom with nothing hidden behind a tab. You set it like horizontal mode, with $form->surveyMode = true or surveyMode: true passed to Form::make().

yaml
tabs:
    fields:
        audience:
            label: Audience
            type: dropdown
            tab: Who it is for
            span: left
            options:
                retail: Retail
                trade: Trade
                wholesale: Wholesale

        visibility:
            label: Visibility
            type: dropdown
            tab: Who it is for
            span: right
            options:
                public: Public
                members: Members
                private: Private

        currency:
            label: Currency
            type: dropdown
            tab: How it is sold
            optionsMethod: App\Models\Sample::listCurrencies

Read-only fields ​

readOnly stops a field from being changed while still posting its value, so saving the form keeps it. Text inputs get the readonly attribute, checkboxes, switches and radio lists ignore clicks, Select All and Select None leave a checkbox list alone, and a dropdown shows its value as text beside a hidden input that posts it.

yaml
fields:
    icon:
        label: Icon
        span: left
        readOnly: true

    stock_status:
        label: Stock Status
        type: dropdown
        span: right
        readOnly: true
        options:
            in_stock: [In stock, '#27ae60']
            low: [Low stock, '#f39c12']
            out: [Out of stock, '#e74c3c']
        comment: A read-only dropdown shows its value as text.

    has_discount:
        label: Offer a discount
        type: checkbox
        span: left
        readOnly: true

    is_featured:
        label: Featured
        type: switch
        span: right
        readOnly: true

    size:
        label: Size
        type: radio
        inlineOptions: true
        readOnly: true
        options:
            small: Small
            medium: Medium
            large: Large

    features:
        label: Features
        type: checkboxlist
        inlineOptions: true
        quickselect: true
        readOnly: true
        options:
            wifi: Wi-Fi
            parking: Parking
            pool: Pool
            gym: Gym

Disabled fields ​

disabled locks a field and leaves it out of the saved data, even if a value is posted for it, so the saved value stays as it was. hidden does the same without rendering the field at all.

yaml
fields:
    template:
        label: Template
        type: textarea
        size: tiny
        span: left
        disabled: true

    step_style:
        label: Step Style
        type: dropdown
        span: right
        disabled: true
        options:
            simple: Simple
            detailed: Detailed

Preview mode ​

The form option previewMode shows every field as plain, uneditable text, for a page that only displays a record, and passes the mode on to the form widgets in it. This form is built with Form::make(..., previewMode: true). On the demo page it also has an arrayName of its own, so its fields never collide with the editable examples, and Save leaves it out.

yaml
fields:
    name:
        label: Name
        span: left

    email:
        label: Email
        type: email
        span: right

    transport:
        label: Transport
        type: dropdown
        span: left
        options:
            road: [Road, ph ph-truck]
            air: [Air, ph ph-airplane-tilt]
            sea: [Sea, ph ph-boat]

    status:
        label: Status
        type: balloon-selector
        span: right
        options:
            draft: Draft
            active: Active
            archived: Archived

    description:
        label: Description
        type: textarea
        size: small

    features:
        label: Features
        type: checkboxlist
        span: left
        options:
            wifi: Wi-Fi
            parking: Parking
            pool: Pool
            gym: Gym

    is_featured:
        label: Featured
        type: switch
        span: right
php
<?php

namespace App\Controllers\Reference\FormsController;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
use Larajax\Ui\Widgets\Form;

/**
 * StandaloneForms builds example forms that Save leaves out, such as a preview or a form on a new record.
 */
trait StandaloneForms
{
    /**
     * makeStandaloneForms builds one form per example YAML file on the given record, passing the extra form options to each, where an arrayName keeps their inputs apart from the saved examples.
     *
     * @param  array<int, string>  $names
     * @param  array<string, mixed>  $options
     * @return array<string, Form>
     */
    protected function makeStandaloneForms(string $folder, array $names, Model $model, array $options = []): array
    {
        $forms = [];

        foreach ($names as $name) {
            $forms[$name] = Form::make(...[
                'alias' => 'standalone'.Str::studly($name),
                'model' => $model,
                'fields' => "~/resources/ui/reference/{$folder}/{$name}.yaml",
                ...$options,
            ]);
        }

        return $forms;
    }
}

Tab options ​

tabs and secondaryTabs take these options beside their fields.

OptionDescription
defaultTabthe tab for fields with no tab. Default: Misc.
activeTabthe tab that opens first, by name or by position counted from 1. Default: the first tab.
iconsicon classes for the tabs, keyed by tab name.
lazythe names of tabs to load the first time they open.
stretchmakes the tabs fill the height of the page. Default: the secondary tabs stretch when there are any, otherwise the primary tabs.
linkablereflects the open tab in the URL hash, so a link can open it. Default: true.
cssClassa class on the tab container.
paneCssClassa class on the tab panes, as a string for every pane, or a list keyed by tab name or position.
identifiersids for the tabs, keyed by tab name. A tab given notes gets the id tab-notes and its pane pane-notes, for scripts that show or hide it.

Form options ​

The layout options of the form widget, set with Form::make() or on the widget before it renders. The widget's other options are covered in Building a Form Page.

OptionDescription
horizontalModeputs each label in a column beside its field. Default: false.
surveyModerenders the tabs one after another under headings. Default: false.
previewModeshows every field as uneditable text. Default: false.