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.
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: 1The 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.
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.
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.
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 fieldadaptive 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.
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-3Collapsible 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.
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 levelComments
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.
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: trueTooltips
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.
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-questionInput 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.
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.
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:
.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().
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:
/**
* 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().
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::listCurrenciesRead-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.
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: GymDisabled 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.
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: DetailedPreview 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.
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
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.
| Option | Description |
|---|---|
| defaultTab | the tab for fields with no tab. Default: Misc. |
| activeTab | the tab that opens first, by name or by position counted from 1. Default: the first tab. |
| icons | icon classes for the tabs, keyed by tab name. |
| lazy | the names of tabs to load the first time they open. |
| stretch | makes the tabs fill the height of the page. Default: the secondary tabs stretch when there are any, otherwise the primary tabs. |
| linkable | reflects the open tab in the URL hash, so a link can open it. Default: true. |
| cssClass | a class on the tab container. |
| paneCssClass | a class on the tab panes, as a string for every pane, or a list keyed by tab name or position. |
| identifiers | ids 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.
| Option | Description |
|---|---|
| horizontalMode | puts each label in a column beside its field. Default: false. |
| surveyMode | renders the tabs one after another under headings. Default: false. |
| previewMode | shows every field as uneditable text. Default: false. |