Regions and attributes
Introduction
Themes decide the markup. Regions decide the attributes on it: classes, data- attributes, ARIA, placeholders. Changing a class should not mean rewriting a theme, and rewriting a theme should not mean setting every class again.
Regions
A region is a named part of the rendered form. Each one has an attribute variable for templates, such as {label_attributes} for the label region.
| Level | Regions |
|---|---|
| Once per form | form, form_content, meta_wrapper, page_progress, the error summary (error_wrapper, error_heading, error_list, error_item) and the success notice (success_wrapper, success_message) |
| Once per page or section | page, page_heading, page_description, section, section_heading, section_description, and each page's navigation (page_nav, page_back, page_next, page_submit) |
| Once per field | field_wrapper, field, label, instructions, error, options_wrapper |
| Once per choice | option, option_label |
The complete list, read from the add-on's source: error, error_heading, error_item, error_list, error_wrapper, field, field_wrapper, form, form_content, instructions, label, meta_wrapper, option, option_label, options_wrapper, page, page_back, page_description, page_heading, page_nav, page_next, page_progress, page_save, page_submit, section, section_description, section_heading, success_message, success_wrapper.
Where attributes come from
Several layers can set attributes on a region. They apply in this order, and later wins:
- Computed. What Formidable needs: a field's
id,name,typeandvalue, a label'sfor, thedata-formidable-*hooks the script uses, and the form'saction,methodandenctype. - Fieldtype group, then fieldtype, set under Registry. These reach every field of that kind.
- The site, on Configuration › Templating: the house style.
- The form, on its Templating tab.
- The field, on its Templating tab.
- Tag parameters, for one render: first
REGION:attribute:NAME=for every instance, thenfield:HANDLE:REGION:attribute:NAME=for one field.
The closer a setting is to the one thing it affects, the later it applies. A class set on a fieldtype reaches every field of that type. The same attribute set on one field beats it for that field alone.
class appends. Everything else replaces.
Site: class="field"
Form: class="mb-4"
Result: class="field mb-4"
Formidable puts classes on things too, such as a grid span on a wrapper or the selected state on an option, so adding mb-4 should not silently drop them. To replace instead, start the value with =:
Form: class="=mb-4"
Result: class="mb-4"
In the control panel, this is the Replace the classes from above checkbox. Every other attribute is simply replaced by a later layer.
Leaving a layer empty inherits
A layer that sets nothing for a region contributes nothing, and the layers above show through. There is no separate "inherit" switch. The control panel's editor always shows what is inherited from above, with its source.
In the control panel: Templating
Every level (Registry fieldtypes, Configuration, a form, a field) edits its regions on its Templating tab:
- A map of the form on the left: form, error summary, success notice, pages, sections, a field and its parts, and the buttons, nested as they render. Click the part to style.
- Classes as chips, with the inherited ones shown first and muted.
- Other attributes, one row each. Add attribute suggests attributes that make sense for that element, such as
placeholderandpatternfor an input ornovalidatefor the form, and accepts any other valid name, such asdata-*orhx-post. - Result: the opening tag the part will render with, so a change can be seen as you make it.
From a template: tag parameters
The same layers, for one render. One grammar works everywhere:
REGION:attribute:NAME="value" every instance of the region
field:HANDLE:REGION:attribute:NAME="value" one field only
field:HANDLE:attribute:NAME="value" one field's input (short for field:HANDLE:field:attribute:NAME)
Region parameters on the tag
Example{exp:formidable:form
form_handle="contact_us"
only="first_name|email_address"
label:attribute:class="form-label"
field:first_name:field_wrapper:attribute:class="=name-row"
}
{fields}{field_template}{/fields}
{/exp:formidable:form}
…
<div class="formidable-form-content">
<div data-formidable-columns="12" data-formidable-page="1" data-formidable-section="s_1_1" class="name-row col-span-1 flex flex-col space-y-1 lg:col-span-12">
<label class="formidable-field-label form-label font-semibold" for="first_name">First Name</label>
<span class="formidable-field-instructions text-sm text-grey-300" id="first_name-instructions">We prefer your legal name</span>
<input id="first_name" name="first_name" value="" type="text" aria-describedby="first_name-instructions" class="block w-full rounded-md border border-gray-300 px-4 py-2 shadow-sm">
</div>
<div data-formidable-columns="12" data-formidable-page="1" data-formidable-section="s_1_1" class="col-span-1 flex flex-col space-y-1 lg:col-span-12">
<label class="formidable-field-label form-label font-semibold" for="email_address">Email Address</label>
<input id="email_address" name="email_address" value="" type="email" placeholder="[email protected]" class="block w-full rounded-md border border-gray-300 px-4 py-2 shadow-sm">
…
Reading the output:
label:attribute:class="form-label"was appended to every label, after the label's ownformidable-field-labelclass.field:email_address:attribute:placeholder=reached only the email input.field:first_name:field_wrapper:attribute:class="=name-row"replaced the classes the layers had set for that wrapper. The Tailwind classes after it (col-span-1 flex …) are still there, because the Tailwind theme writes them in its own markup, with{field_wrapper_attributes:bag class="…"}. A theme's own markup is applied last. To remove those classes, copy the theme's_fieldpart and change it.
Template variables use underscores ({label_attributes}) rather than colons, because : is EE's modifier separator. Tag parameters use colons throughout.
Values can use field variables
An attribute value on a field-level region may use that field's variables, from the control panel or from a tag:
field_wrapper:attribute:class="col-span-{field_columns} field--{field_handle}"
Each field substitutes its own values. An unknown variable is left as written, so a typo is visible instead of silently producing an empty class. Substituted values are escaped for use in an attribute.
Narrowing in the template: the :bag modifier
Any attribute variable can be narrowed or added to where it is used. only= and except= take pipe-separated attribute names. Any other parameter is layered on as an attribute, with class appending as usual.
The :bag modifier
Example{exp:formidable:form form_handle="contact_us" only="email_address"}
<form {form_attributes:bag except="data-formidable-disable-submit"}>
{form_meta}
{fields}
<label {label_attributes:bag class="form-label"}>{field_name}</label>
<input {field_attributes:bag only="id|name|type|value"}>
{/fields}
<button type="submit">Send</button>
</form>
{/exp:formidable:form}
<form id="contact_us" name="contact_us" method="post" action="https://example.com/index.php?ACT=101">
…
<label class="formidable-field-label form-label" for="email_address">Email Address</label>
<input id="email_address" name="email_address" value="" type="email">
<button type="submit">Send</button>
</form>
except="data-formidable-disable-submit" dropped that attribute from the form. class="form-label" was appended to the label's own class. only= kept just the input attributes that were named.
Accessibility, computed for you
Each field's instructions and error message get ids derived from the input's id (email_address-instructions, email_address-error). The input points at them with aria-describedby, so a screen reader reads them when the input is focused, and it carries aria-invalid="true" while it has an error.
- No instructions means no instructions id. The error id appears only while there is an error and the theme renders the error with
{error_attributes}. - An
aria-describedbyyou set on the field is kept, and the computed ids are added after it. Anidyou set on the instructions or error region replaces the computed one, and the input follows it. - A group of inputs is named by its label. Radio buttons, checkboxes, image choices, ratings, scales, a matrix, and a name's or address's parts have no single input for
<label for>to point at. So the field's label gets an id (size-label) and nofor. The options wrapper getsrole="radiogroup"(radios) orrole="group",aria-labelledbythat id, the descriptions and invalid state, and the field's id, so a link to#sizelands on the group. A required radio group also getsaria-required="true". Formidable recognises a group by your markup: a part that uses{options_wrapper_attributes}and never writes{field_attributes}on an element. - Required fields are marked.
{field_label}ends with<span class="formidable-required-mark" aria-hidden="true">*</span>when the field is required, and{field_required_mark}prints the same mark wherever you write your own label. Screen readers hear "required" from the input itself. A "require if" rule adds and removes the mark as the visitor types. - The error summary is
role="alert", focusable (tabindex="-1") and focused when the page comes back with errors, and each error in the shipped themes links to its field. Success messages arerole="status", and the page progress list is named "Form progress".
Every checkbox and radio in the shipped themes sits inside its <label>, so the whole label is the click target, and the shipped stylesheets' colours for stars, scale buttons, image cards and switches are at least 3:1 against white. Signatures are drawn with a pointer, so a form that must be completed by keyboard alone should offer another way to sign.
{field_attributes} always includes aria-describedby for a field that has instructions. If your template prints the instructions itself, give that element the matching id, {field_handle}-instructions, as in Write your own field markup. Otherwise the reference points at nothing.