Guide 04 · Developer docs

Input types, layouts, components and images

The platform's central abstraction, in one line: an input type defines what an editor can enter, a layout area binds one input type to a named slot, a layout groups areas and points at a template, a record or component holds the content for each area, and the template reads it back. Read this book straight after the template languages.

L1–L3
Unit 04 · Layout areas, groups and attributes

Attributes: settings, not content

Marking a layout area as an attribute moves it out of the main editing form, so authors see content first and settings second. Attributes of a whole block, attributes attached to one field, the rule for choosing, and a worked image with alt text, crop and caption attached to it.

Who this is for. Content modellers designing editing forms, and developers rendering the settings that end up in attributes. After this page you can decide which areas are settings, attach a setting to the field it qualifies, and read it in a template.

What marking an area as an attribute does

A layout area with Attribute switched on stops being an ordinary field. Its editor moves behind an Attributes control, so the main form shows content and the settings wait one click away:

  • on a record such as a page, attributes of the whole layout appear on their own Attributes tab;
  • on a component, they open from an Attributes button on the component;
  • an attribute attached to an area opens from an Attributes icon beside that area's field.

Use attributes for styling and behaviour: a background, an alignment, a width, a "hide on mobile" switch, an image's crop. Nothing changes for the template: an attribute is an area like any other, read by its own name.

Attribute of the block, or of one field

An attribute with no attachment configures the whole block. An attribute attached to an area configures that one field. The rule for choosing: would the setting still make sense if that field were empty? A section's background applies whether or not the section has an image, so it belongs to the block. An image's crop means nothing without the image, so it is attached to the image.

Attaching an attribute to an area

Set Attribute attachment on the attribute to choose the area it hangs off. Three rules, enforced on save:

  • only areas that have been saved can be chosen, so save the parent area first, then attach ("Only areas that have been saved can be selected");
  • the parent must not be an attribute itself ("An attribute can only be attached to a layout area that is not an attribute");
  • switching Attribute off on an area clears its attachment.

An attached attribute always moves into its parent's group, and an attribute of the whole block is moved out of any group, when the layout is saved.

Worked example: an image with alt text, crop and caption

A media block whose image carries its own settings, and whose background belongs to the block:

AreaInput typeAttributeAttached to
imageImageNo
imageAltTextYesimage
imageCropA select: fit::Fill the frame, auto::Show the whole imageYesimage
imageCaptionTextYesimage
backgroundA select of background classesYes(the block)

The author sees the image field with an Attributes icon beside it holding the alt text, crop and caption, and the background behind the component's Attributes button. The template reads each by name:

Template code templates/components/media.html
{$image = $component->getAreaImage('image')}
{if !$image->isBlank()}
  {$alt = $component->getAreaText('imageAlt')|escape}
  {$crop = $component->getAreaText('imageCrop')|default:'fit'}
  {$caption = $component->getAreaText('imageCaption')}
  <figure class="media {$component->getAreaText('background')}">
    {$image->getResized(800, 450, $crop)->getHtml(['alt' => $alt, 'loading' => 'lazy'])}
    {if $caption}<figcaption>{$caption|escape}</figcaption>{/if}
  </figure>
{/if}
{% set image = component.getAreaImage('image') %}
{% if not image.isBlank() %}
  {% set alt = component.getAreaText('imageAlt')|e %}
  {% set crop = component.getAreaText('imageCrop')|default('fit') %}
  {% set caption = component.getAreaText('imageCaption') %}
  <figure class="media {{ component.getAreaText('background') }}">
    {{ image.getResized(800, 450, crop).getHtml({'alt': alt, 'loading': 'lazy'}) }}
    {% if caption %}<figcaption>{{ caption|e }}</figcaption>{% endif %}
  </figure>
{% endif %}

The template reads these values with getAreaText() rather than the shorthand {$component->imageCaption} because each goes into an attribute, a method argument or a test, where the shorthand's live-preview <span> does not belong. The crop keys are resize strategies: fit fills the 800 by 450 frame and crops what overflows, auto fits the whole image inside it. The alt text is escaped before it goes in, because getHtml() prints attribute values as they are. The other strategies are on The seven resize strategies.

A component that does not know which attributes its designer attached can read them all at once, keyed by name, with getAreaAttributesContentOrDefault('image'): see Introspection, raw content and attributes. Turning a list of attribute selects into one class string is Pattern: attributes to CSS classes.

Go deeper

Was this page helpful?

Your answer goes to the docs team.