Add a Form Layout
A form layout is another way of rendering the shipped Form root, hyva_form. The Form
Builder ships two layouts, Single page and Multi-step, and a module adds its own with one
template plus one registry entry on Hyva\FormBuilder\Model\Config\Source\FormLayouts. This
guide builds an accordion layout that renders each Form Section as its own panel.
A layout is enough when the form is still Form Sections holding fields, just laid out
differently. The shipped root keeps owning everything else: the Form Section children, the
component-side settings from the shared metadata includes, and the whole submission
pipeline. When the form is structurally different, with different children, settings of its
own, or its own <form> envelope, you need a new root instead.
Create a Custom Form Root covers that advanced path.
Prerequisites
You should be comfortable with Magento DI (di.xml array arguments) and Hyvä
templates. A layout needs no component declaration, because it renders the shipped
hyva_form root. The Form Builder ships as hyva-themes/commerce-module-form-builder;
your module should depend on it. The Architecture page explains how the
root's settings reach the submission pipeline.
How Form Layouts Work
The shipped root's Layout field is a variants field whose options come from the
FormLayouts registry. Each option's value is a template path. Choosing a layout stores
that path on the root's variants property, and the editor and the storefront both render
the form through that template.
The same registry feeds the new-form dialog's type step. Every registered layout appears
there as a card, ahead of any third-party root, and choosing one seeds a hyva_form root
set to that layout with a first Form Section inside it. So one di.xml item makes a layout
available in both places, with no plugin.
On the storefront, Hyva\FormBuilder\Block\Form checks the stored value against the
registered layouts before calling setTemplate(). A blank or unregistered value falls back
to FormLayouts::DEFAULT_LAYOUT, the Single page template, so a form whose layout module
is later removed still renders, as a single page.
The Layout field itself is opt-in
hyva_form gets a Layout field because its declaration lists
Hyva_FormBuilder::etc/hyva_cms/form_root_layout_metadata.json in content.includes,
ahead of the shared form_root_content_metadata.json. A custom root doesn't get one
unless it includes that file too. Without it, the root has no Layout value, so the
storefront renders it through the Single page layout. See
Create a Custom Form Root.
Step 1: Register the Layout
Add an item to the layouts array argument on FormLayouts in your module's di.xml. The
shipped single_page and multi_step items in module-form-builder/src/etc/di.xml have the
same shape:
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
<type name="Hyva\FormBuilder\Model\Config\Source\FormLayouts">
<arguments>
<argument name="layouts" xsi:type="array">
<item name="accordion" xsi:type="array">
<item name="value" xsi:type="string">Acme_AccordionLayout::elements/accordion_form.phtml</item>
<item name="label" xsi:type="string" translate="true">Accordion</item>
<item name="description" xsi:type="string" translate="true">Every section collapsed into its own panel, opened one at a time.</item>
<item name="icon" xsi:type="string">Acme_AccordionLayout::images/accordion-form.svg</item>
<item name="sort_order" xsi:type="number">30</item>
</item>
</argument>
</arguments>
</type>
</config>
Magento merges array arguments by item name, so accordion slots in alongside the two
shipped layouts without colliding. Each item carries five keys:
value- the template path. It's what gets stored on the root when the layout is chosen, and what the storefront block checks before rendering. An item with a blankvalueis skipped.labelanddescription- the Layout field's option text and the type card in the new-form dialog. Both are translated.icon- the type card's image, resolved as a view file in the admin area, so the SVG lives atview/adminhtml/web/images/accordion-form.svgin your module.sort_order- the position among the layouts, ascending. The shipped pair use 10 and 20.
Step 2: Ship the Layout Template
The value path resolves like any Magento template, so
Acme_AccordionLayout::elements/accordion_form.phtml lives at
view/frontend/templates/elements/accordion_form.phtml in your module.
A layout template is a complete root template. It renders the whole <form> envelope for
hyva_form, not just the sections, so start from a copy of whichever shipped layout is
closer rather than from scratch:
module-form-builder/src/view/frontend/templates/elements/single_page_form.phtml- every section stacked on one page, one submit button.multi_step_form.phtmlbeside it - one section at a time, adding the progress bar, the step indicator and Back and Next navigation.
The two files differ only in their section handling and navigation markup. Everything else in them is shared envelope that your layout keeps.
What a Layout Template Renders
Read either shipped file end to end before adapting it. A layout template keeps these pieces:
- The three render paths. The branch near the top of the file resolves the storefront
(
$block->getPublishedForm()returns aPublishedForm), the form's own full-screen preview (live submissions), and the editor canvas (an inert submit button). Keep the branch as it is. The table in Ship the Root Template explains each path. - The
<form>wired to the form script.x-data="hyvaFormBuilder",@submit.prevent="submitForm", and the<script type="application/json" data-initial-state>element carrying the state seed fromFormRenderer. Inside it sit the hiddenform_keyandform_identifierinputs, the storefront-only honeypot input, the reCAPTCHA fragments, thedata-hyva-form-error="_form"region for form-level errors, and the editor-canvas callouts. The root's CSS Classes value arrives as$block->getData('classes')and goes on the<form>tag. - The Form Section children, stamped. Each
form_stepchild gets astep_indexand aform_layoutbefore it renders throughChildElementRenderer::renderChildHtml(). - The success surface. The
data-hyva-form-result="success"element carryingx-ref="result"is where the form script paints the success message or Success Content block. Drop it, or itsx-ref, and a successful submission leaves the customer looking at nothing. - The form script, once. The file ends with
$formRenderer->renderFormScriptHtml($block), after the closing</form>.
Stamping the Form Section Children
form_step.phtml reads the form_layout value its parent stamps. multi_step wires the
section into the wizard, showing only the current one and giving a blank section the
label "Step N". Any other value shows every section and leaves a blank label without a legend.
The index keeps each section's data-step-index in step with the DOM.
An accordion shows every section and does its own collapsing, so it stamps the same values as the Single page layout:
$indexedChildren = [];
$sectionCounter = 0;
foreach ($children as $child) {
$childArray = (array)$child;
if (($childArray['component'] ?? '') === 'form_step') {
$childArray['step_index'] = $sectionCounter;
// Anything other than multi_step renders every section, which the accordion wants.
$childArray['form_layout'] = 'single_page';
$sectionCounter++;
}
$indexedChildren[] = $childArray;
}
The panel markup then wraps each rendered child. The shared form script knows only the Multi-step layout's step state, so opening and closing panels is up to your own markup.
Passing the Block to the Form Script
renderFormScriptHtml() takes the calling block, and the shipped templates pass $block.
The script then renders on the caller's layout: the page layout on the storefront, and in
the editor preview a layout other than the shared one the editor's sidebar render depends
on. A named layout block dedupes the script, so several forms on one page still emit it
once. Keep the argument when you copy the call.
Reference Implementation
In the module-form-builder package:
src/etc/di.xml- theFormLayoutswiring for the two shipped layouts.src/Model/Config/Source/FormLayouts.php- the registry,DEFAULT_LAYOUT, and the check the storefront makes.src/Block/Form.php- how the stored layout is validated beforesetTemplate().src/ViewModel/Adminhtml/ContentSetup/FormTypes.php- how layouts become the new-form dialog's type cards.src/view/frontend/templates/elements/single_page_form.phtmlandmulti_step_form.phtml- the two shipped layout templates.
src/view/frontend/templates/elements/form_step.phtml- how a section reads the stampedform_layoutandstep_index.
Related Topics
- Create a Custom Form Root - the advanced path, for a form that's structurally different rather than laid out differently.
- Building Forms - choosing a layout in the setup dialog and on the Form root.
- Creating Components - the Hyvä CMS component model the Form root builds on.