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 09 · Patterns, live preview and upkeep
Pattern: attributes to CSS classes
Patterns, live preview and upkeepPage 39 of 45Updated
A helper that turns a component's attribute areas (selects, multiselects and switches) into one class string, given in full in Smarty and Twig, with the rules for naming option keys and switches so the classes come out right.
Who this is for. Front-end developers building component templates on a design system with modifier classes. After this page you can add the helper to a site and give any component its classes from its attribute areas in one call.
What the helper replaces
An attribute area holds a setting rather than content: a style, a width, an alignment, a switch that reverses the order of columns (Attributes: settings, not content). Almost every attribute ends up as a class on the component's outer element. Written by hand, that is an {if} per attribute in every component template, and each one is a place to forget a case. The helper takes the list of attribute areas once and returns the class string.
What it takes and returns
Parameter
What it holds
component
The component being rendered.
classes
Classes applied whatever the areas say, such as the block's own name. Default: none.
attributes
Names of attribute areas whose value becomes a class: a select gives its option key, a multiselect each of its keys, a text area its text.
flags
Names of switch areas. When a switch is on, the area's own name is added as a class.
var
Smarty only: the name of the variable that receives the result. Default: classes.
Empty values are dropped, so an attribute the author left unset adds nothing. The result is the classes joined with single spaces.
Name the options and switches after the classes
The helper writes values out as they are stored, with no lookup table in between, so the naming is done in the admin:
A select or multiselect attribute's option keys are the class names. A select called componentWidth whose option keys are width--narrow and width--wide outputs one of those two strings; the option's label is what the author sees. See Select options, keys and design tokens.
A switch flag is named after its class: a switch area called columns--reverse adds the class columns--reverse when it is on.
A select stores its option key, so the keys are a contract with the stylesheet. Changing a key later is a rename: records saved before the change still hold the old key.
The helper in both engines
The Smarty version is an include, taken whole from a production site. The Twig version is constructed to do the same thing; no site we reviewed had one. It is a macro because a Twig macro cannot set a variable in the template that calls it, so it returns the string instead.
Template code
{* templates/includes/elements/attributes-to-css.html
$component the component being rendered
$classes classes applied unconditionally, default []
$attributes area names: text (select) becomes one class, values (multiselect) become several
$flags switch area names; the area's own name is the class when it is on
$var output variable name, 'classes' by default *}
{strip}
{if !empty($component)}
{$classes = $classes|default:[]}
{$attributes = $attributes|default:[]}
{if !empty($attributes)}
{foreach $attributes as $attribute}
{$values = $component->getAreaValues($attribute)}
{if !empty($values)}
{$classes = array_merge($classes, $values)}
{else}
{$classes[] = $component->getAreaText($attribute)}
{/if}
{/foreach}
{/if}
{if !empty($flags)}
{foreach $flags as $flag}
{if !empty($component->getAreaText($flag))}{$classes[] = $flag}{/if}
{/foreach}
{/if}
{$outputVariable = $var|default:'classes'}
{assign var=$outputVariable value=implode(' ', array_filter($classes)) scope="parent"}
{/if}
{/strip}
{# templates/macros/css.twig
component the component being rendered
base classes applied unconditionally
attributes area names: text (select) becomes one class, values (multiselect) become several
flags switch area names; the area's own name is the class when it is on #}
{% macro classes(component, base, attributes, flags) %}
{%- set list = base|default([]) -%}
{%- for attribute in attributes|default([]) -%}
{%- set values = component.getAreaValues(attribute) -%}
{%- set list = values is not empty ? list|merge(values) : list|merge([component.getAreaText(attribute)]) -%}
{%- endfor -%}
{%- for flag in flags|default([]) -%}
{%- if component.getAreaText(flag) is not empty -%}{%- set list = list|merge([flag]) -%}{%- endif -%}
{%- endfor -%}
{{- list|filter(c => c)|join(' ') -}}
{% endmacro %}
Calling it from a component
In Smarty the helper writes its result into the template that included it ({assign … scope="parent"}), so after the include the component prints {$classes}. In Twig the component imports the macro and prints what it returns where the class goes. Both calls below give a columns component its block classes, three attribute classes and one flag.
A Smarty include sees the variables of the template that includes it. When one template calls the helper twice, the second call would otherwise start from the first call's $classes (by then a string, not a list) and its $attributes and $flags. Give each call its own classes (even classes=[]), attributes and flags, and the second one its own var, such as var='innerClasses'. The Twig macro has its own scope and needs none of this.
Why it reads getAreaText(), not the shorthand
The helper reads each area with getAreaText() and getAreaValues(), never with the shorthand. In live preview, the shorthand and getAreaHtml() wrap every text area in a <span class="live-preview-container" …> marker so that the author can edit the value in place; getAreaText() adds that marker only to rich text. A marker inside a class attribute would break the markup in the preview, so any value used in an attribute is read with getAreaText(). The helper is therefore safe in live preview. Nothing is lost by an attribute not being editable in place: it is a setting, chosen in the form.
Content areas keep the shorthand: the heading inside the same component is still {$component->heading}. Which calls keep in-place editing, and which lose it, is on Live preview and testing in-place editing.