Field Layout Elements
In addition to content fields, developers can add a handful of other built-in field layout elements to their field layouts to optimize the author’s attention or productivity. Craft uses field layout elements for titles, some address components, alt text on assets, native user attributes, and more. They also power “UI” elements like horizontal rules, breaks, and templates.
Plugins can supplement the built-in field layout elements by creating and registering a class that extends craft\base\FieldLayoutElement (opens new window). Broadly speaking, field layout elements come in two flavors:
- Fields — Features that enhance (or are essential to) collecting data for an element. Title fields, users’ Email field, and Commerce’s Variants nested element management interface are examples of fields. Fields typically extend craft\fieldlayoutelements\BaseField (opens new window) (or one of its subclasses).
- UI — Optional features that can be added any number of times, and are not part of the critical authoring path. The built-in Template and Tip components are examples of UI elements, and are based on craft\fieldlayoutelements\BaseUiElement (opens new window) (or one of its subclasses).
All layout elements are rendered in the context of an element, and support user and element conditions in addition to their own custom settings. We’ll look at how you can adopt these behaviors via your class’s inheritance, public properties, and configuration.
Plugins that provide field types do not need to maintain their own field layout element! Instead, they should return HTML appropriate to the editing context, which will be wrapped in an instance of craft\fieldlayoutelements\CustomField (opens new window).
Field layout elements are only used to build the body of the element editor. The sidebar (or “metadata” column), header, and toolbar are managed by each element type, individually, but can be customized using events.
# Class + Registration
Create a new class that extends craft\base\FieldLayoutElement (opens new window) (or one of the other existing types). Our example will display some sort of read-only status information from an external source:
namespace myplugin\fieldlayoutelements;
use Craft;
use craft\base\ElementInterface;
use craft\fieldlayoutelements\BaseUiElement;
use craft\helpers\Html;
class SynchronizationStatus extends BaseUiElement
{
public function selectorLabel(): string
{
return Craft::t('site', 'Synchronization Status');
}
public function formHtml(?ElementInterface $element = null, bool $static = false): ?string
{
return Html::tag('div', 'Hello, world!');
}
protected function selectorIcon(): ?string
{
return 'satellite-dish';
}
}
This is only the minimum implementation for a UI field layout element. Explore deeper customization options in the base definitions and settings sections.
To register a field layout element, add the appropriate event listener:
- For native fields, use craft\models\FieldLayout::EVENT_DEFINE_NATIVE_FIELDS (opens new window);
- For UI elements, use craft\models\FieldLayout::EVENT_DEFINE_UI_ELEMENTS (opens new window);
use craft\events\DefineFieldLayoutElementsEvent;
use craft\models\FieldLayout;
use yii\base\Event;
Event::on(
FieldLayout::class,
FieldLayout::EVENT_DEFINE_NATIVE_FIELDS,
function(DefineFieldLayoutFieldsEvent $event) {
/** @var FieldLayout $layout */
$layout = $event->sender;
if ($layout->type === MyElementType::class) {
$event->fields[] = MyNativeField::class;
}
}
);
In the first example (registering a “native field”), we perform a check to make sure the layout element is only available in layout designers that target a specific element type.
# Base Definitions
Craft includes a handful of abstract types that can be extended to leverage specific features and provide hints as to the layout element’s purpose:
- craft\fieldlayoutelements\BaseUiElement (opens new window) — A static component that typically does not correspond to a specific element property or accept input. Its appearance and output can still
- craft\fieldlayoutelements\BaseField (opens new window) — Common features for native and custom fields, including a label, instructions, tip, warning, whether or not input is “required,” and whether its underlying attribute can be rendered in element cards or provide thumbnails. HTML inputs that are rendered into layout elements that extend this class are automatically factored in to change tracking, and their values are sent to Craft when saving an element.
- craft\fieldlayoutelements\BaseNativeField (opens new window) — A subclass of
BaseField
that is intended for use with native element attributes (or an attribute provided via a behavior). - craft\fieldlayoutelements\TextField (opens new window) — General-purpose input element suitable for most scalar values. You can customize many of the underlying HTML element’s attributes via class properties—see below for an example.
Somewhat counterintuitively, the craft\fieldlayoutelements\CustomField (opens new window) class is not intended to be extended. Craft automatically wraps each custom field instance with this layout element, exposing
# Ad-Hoc Elements
In some cases, you may not need to create a dedicated class at all! Suppose we were creating a custom element type to manage currencies and exchange rates—the element type might have a property like $symbol
to hold a three-character ISO code, which we want to let administrators edit alongside other custom fields. Instead of a custom class, we could register a suitable layout element based on the TextField
class:
namespace myplugin;
use craft\events\DefineFieldLayoutElementsEvent;
use craft\fieldlayoutelements\TextField;
use craft\models\FieldLayout;
use yii\base\Event;
use myplugin\elements\Currency;
Event::on(
FieldLayout::class,
FieldLayout::EVENT_DEFINE_NATIVE_FIELDS,
function(DefineFieldLayoutFieldsEvent $event) {
/** @var FieldLayout $layout */
$layout = $event->sender;
if ($layout->type === Currency::class) {
$event->fields[] = [
'class' => TextField::class,
'type' => 'text',
'title' => Craft::t('my-plugin', 'Currency Symbol'),
'maxlength' => 3,
'placeholder' => 'USD',
'autocorrect' => false,
// ...
];
}
}
);
You can register any value compatible with Craft::createObject() (opens new window), including strings (like the fully-qualified class name in the previous section), a config object (as above), or an already- instantiated object.
# Settings + Features
For a comprehensive list of methods and properties you can use to customize a field layout element’s behavior, refer to the classes that extend craft\base\FieldLayoutElement (opens new window). The most important divide in feature sets is between UI Elements (classes that extend craft\fieldlayoutelements\BaseUiElement (opens new window)) and Fields (classes that extend craft\fieldlayoutelements\BaseField (opens new window)). Features available to one or the other are noted.
# Cosmetic
These settings exist primarily to improve the usability and accessibility of fields and UI elements in field layouts.
# Selector Label
The label displayed when viewing a field layout element in the selector palette, and when it appears within a field layout designer.
protected function selectorLabel(): string
# Icon
An icon that helps identify layout elements of the same type. In mosts cases, this return value should be consistent—but Craft makes use of it to bubble up dynamic icons (opens new window) from custom fields.
protected function selectorIcon(): ?string
# Width
By default, field layout elements are full-width. Opt-in to customizable width using hasCustomWidth()
:
public function hasCustomWidth(): bool
{
return true;
}
# Indicators (Fields)
To make field layout elements more useful at-a-glance, they support icon-based “indicators.” Craft handles many built-in features (like fields marked as Required, or elements with configured conditions), but you can supplement them with your own indicators.
protected function selectorIndicators(): array
{
// Preserve built-in indicators:
$indicators = parent::selectorIndicators();
if ($this->someCustomSetting) {
$indicators[] = [
'label' => Craft::t('my-plugin', 'This field layout element may behave differently!'),
'icon' => 'triangle-exclamation',
'iconColor' => 'fuchsia',
];
}
return $indicators;
}
# Label and Instructions (Fields)
Bind assistive text to a native field input.
public ?string $label = null;
public ?string $instructions = null;
Override the defaultLabel()
and defaultInstructions()
methods instead, if you wish to allow users to customize labels and instructions—or to provide translatable defaults.
# Tips + Warnings (Fields)
In addition to labels and instructions, fields can provide a tip
and warning
box below their markup.
public ?string $tip = null;
public ?string $warning = null;
These strings should be plain text or Markdown; HTML is encoded before parsing.
# Functional
These settings govern the actual behavior and configurability of your field layout element, throughout the system.
# Multi-instance
Whether your field layout element can be added to a layout more than once. Typically, native fields do not support this, as they represent an underlying element property. Most UI elements do, except in cases where multiple instances would be redundant or confusing. Consider that semi-dynamic UI elements may be rendered at different times—say, in response to a new tab becoming visible—and therefore show different information.
public function isMultiInstance(): bool
{
return true;
}
# Settings Support
If your field layout element has aspects that should be configurable on a per-instance basis, you should return true
from hasSettings()
:
public function hasSettings()
{
return true;
}
It is then your responsibility to render additional settings HTML:
protected function settingsHtml(): ?string
{
return Cp::lightswitchFieldHtml([
'label' => Craft::t('my-plugin', 'Refresh automatically?'),
'instructions' => Craft::t('my-plugin', 'Periodically refresh the sync status while the element editor is open and in the foreground.'),
'id' => 'autoRefresh',
'name' => 'autoRefresh',
'on' => $this->autoRefresh,
]);
}
This markup is prepended to any default settings, like the user and element condition builders. Craft hides the Settings action for a field layout element if it doesn’t support conditions and doesn’t explicitly return true
from hasSettings()
.
# Conditions
All field layout elements support conditions, by default. Opt-out by returning false
from a conditional()
method:
protected function conditional(): bool
{
return false;
}
# Mandatory (Fields)
You can force a field layout element to be present in any field layout it is supported in by marking it as “mandatory:”
public function mandatory(): bool
{
return false;
}
If a mandatory field layout element has not been explicitly added to a field layout via a field layout designer, Craft injects it at the bottom of the first tab (when rendering the layout) to ensure it is available to authors.
# Requirable (Fields)
When added to a layout, you can include a Make required/optional action (along with a corresponding indicator). The state of this selection is available when rendering a field layout element as the required
property, and can be used in your inputHtml()
method.
# Thumbnail + Card Support (Fields)
Field layout elements also underpin Craft’s handling of element chips and cards.
To make your field layout element available for selection as element thumbnails, implement the thumbable()
and thumbHtml()
methods:
public function thumbable(): bool
{
return true;
}
public function thumbHtml(ElementInterface $element, int $size): ?string
{
$value = $element->{$this->attribute()};
return Html::tag('div', Cp::fallbackIconSvg($value), ['class' => 'cp-icon']);
}
Any other kind of value can be exposed to element cards by returning true
from a previewable()
method:
public function previewable(): bool
{
return true;
}
You must then implement previewHtml()
to return a representation of your data. By default, Craft will attempt to access a property of the element corresponding to your field layout element’s attribute
.
public function previewHtml(ElementInterface $element): string
{
return Html::tag('code', Html::encode($element->{$this->attribute()}));
}
# Fieldset (Fields)
Wrap the input HTML in a fieldset
, and use special handling for labels, instructions, and errors.
protected function useFieldset(): bool
{
return true;
}