This tutorial extends from the previous one, which explained the $css() utility.
The (billing and/or shippping) address form is using CSS grids (of 12 columns) to put all fields in the right place. Each field is a Magento block (so, a <block> within the XML layout). And because the outer <div> HTML element (as found within the PHTML template for each field, like form/field.phtml) is using the $css() utility, this can be used to style each field.
For instance, the prefix field can be given a column span 2 in the 12 column grid system:
<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:View/Layout:etc/page_configuration.xsd">
<body>
<referenceBlock name="loki.checkout.shipping-step.shipping-address.prefix">
<arguments>
<argument name="css_classes" xsi:type="array">
<item name="block" xsi:type="array">
<item name="grid" xsi:type="string">col-span-2</item>
</item>
</argument>
</arguments>
</referenceBlock>
</body>
</page>
However, this becomes pretty cumbersome if you need to do this for all fields.
Likewise, by using the XML layout <move/> directive, you can move each field in the right position. And even worse, with <move/>, you need to make sure to choose the exact XML layout file, otherwise your own layout update might be overruled again with another.
The sort order of each child block can be set as well via the XML layout:
<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:View/Layout:etc/page_configuration.xsd">
<body>
<referenceBlock name="loki.checkout.shipping-step.shipping-address.prefix">
<arguments>
<argument name="sort_order" xsi:type="number">42</argument>
</arguments>
</referenceBlock>
</body>
</page>
But again, this becomes pretty cumbersome if you want to do this for every single block in an address.
Finally, you could hack your own order and col-span-* into TailwindCSS classes. Don't do this either: You will lack much flexibility.
There is a better way instead.
etc/loki_checkout.xml configuration fileThe module LokiCheckout_Core ships with a file etc/loki_checkout.xml which configures a sorting order and CSS grid classes. This looks similar to the following:
<?xml version="1.0" encoding="UTF-8" ?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:LokiCheckout_:etc/loki_checkout.xsd">
<grid_layouts>
<grid_layout name="default_grid">
<grid name="default">
<block alias="prefix" sortOrder="1" colSpan="6" mdColSpan="12" />
<block alias="firstname" sortOrder="2" colSpan="6" mdColSpan="12" />
<block alias="middlename" sortOrder="3" colSpan="6" mdColSpan="12" />
<block alias="lastname" sortOrder="3" colSpan="6" mdColSpan="12" />
...
</grid>
</grid_layout>
</grid_layouts>
</config>
This XML configuration is used by various other mechanisms to change the HTML of the child blocks of address, like with the PHTML template checkout/billing/address-form.phtml.
To make changes to the grid column spans and/or the sorting order, create your own Magento 2 module and add a new file etc/loki_checkout.xml to it. Next, make your changes:
<?xml version="1.0" encoding="UTF-8" ?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:LokiCheckout_:etc/loki_checkout.xsd">
<grid_layouts>
<grid_layout name="default_grid">
<grid name="default">
<block alias="prefix" sortOrder="1" colSpan="4" mdColSpan="12" />
<block alias="firstname" sortOrder="2" colSpan="4" mdColSpan="12" />
<block alias="middlename" sortOrder="3" colSpan="4" mdColSpan="12" />
<block alias="lastname" sortOrder="3" colSpan="4" mdColSpan="12" />
...
</grid>
</grid_layout>
</grid_layouts>
</config>
Make sure to clean the configuration cache afterwards.
bin/magento cache:clean config
The XML arguments colSpan and mdColSpan of the loki_checkout.xml file are added to every child of an address block. Commonly, these child blocks are Loki Field Components, that use a PHTML template Loki_FieldComponents::form/field.phtml which starts with an HTML element like the following:
<div class="<?= /* @noEscape */ $css('') ?>" style="<?= $escaper->escapeHtml($style()) ?>">
</div>
Here, the $css() utility (Loki\CssUtils\Util\CssClass) and the $style() utility (Loki\CssUtils\Util\CssStyle) allow for dynamically adding new CSS classes and CSS styles. This is precisely done via a LokiCheckout\Core\Util\Block\CssClassParser\GridClassParser and a LokiCheckout\Core\Util\Block\CssStyleParser\GridStyleParser. The GridClassParser adds the CSS class loki-col-span. The GridStyleParser adds the CSS variables --loki-col-span and --loki-md-col-span with the values defined in the loki_checkout.xml file.
The CSS class loki-col-span uses these CSS variables as follows:
.loki-col-span {
--loki-col-span: 12;
--_col: var(--loki-col-span);
grid-column: span var(--_col) / span var(--_col);
}
In real life, it is a bit more complex - see the global.css file for the full code.
At first, this mechanism might seem complex and unwanted. The values of colSpan and mdColSpan could also be just hard-coded into the PHTML templates themselves, right? However, grids in the Loki Checkout can dynamically change, depending upon the current Store View, the selected country and custom logic. The grid determines the field width, not the other way around.
Additionally, the usage of CSS variables is complex as well, but it avoids declaring every possible combination of col-span-* in the Tailwind build. It is better for performance.
The XML argument sortOrder of the loki_checkout.xml is, at first, picked up by another mechanism. However, in the end, it also uses the same approach of CSS classes and CSS variables.
First, the observer LokiCheckout\Core\Observer\SetSortOrder takes the XML argument sortOrder and sets this as a block argument sort_order (aka getSortOrder() / setSortOrder()).
Next, the PHTML template checkout/billing/address-form.phtml (for example) renders all child blocks (so, all fields) as follows:
use Loki\Components\Util\Block\ChildRenderer;
use Loki\CssUtils\Util\CssClass;
use Magento\Framework\Escaper;
use Magento\Framework\View\Element\Template;
/** @var Template $block */
/** @var Escaper $escaper */
/** @var CssClass $css */
/** @var ChildRenderer $childRenderer */
?>
<div class="<?= $escaper->escapeHtml($css('grid md:grid-cols-12 my-8 gap-4 md:gap-8')) ?>">
<?= /* @noEscape */ $childRenderer->all($block) ?>
</div>
Most importantly, the $childRenderer->all() is used (instead of a more regular $block->getChildHtml()). This renderer makes sure that all blocks are ordered by their sort_order before rendering. The HTML, as rendered by the browser, contains the right ordering of blocks.
On top of this, the GridClassParser also add a CSS classes order-(--loki-sort-order) to each child block (aka, each field). The Tailwind CSS class order-(--x) turns the --x (the CSS variable) into the value of the CSS rule order:
order: 42;
It is important to understand that this sets a CSS order which normally positions the block in the same way as it is position in the HTML. At first, this makes little sense. However, note that Loki Field Components can update their own value and then re-render other parts of the HTML as well. With this tech in mind, a custom implementation could re-order the tax field if the company field is filled. And because those component updates are AJAX-driven, the Loki Checkout changes the CSS order via JavaScript. It is a niche scenario but fully supported by the Loki Checkout.
The default grid layout is called default_grid, but other grid layouts can be created as well. For instance, the LokiCheckout_Nl module introduces a grid layout called dutch_grid and the LokiCheckout_PostcodeNl modules introduces a grid layout called postcode_nl_grid. Just search for any etc/loki_checkout.xml file in your system.
You can also add your own grid layout if you want by adding a etc/loki_checkout.xml to a custom Magento module:
<?xml version="1.0" encoding="UTF-8" ?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:LokiCheckout_:etc/loki_checkout.xsd">
<grid_layouts>
<grid_layout name="custom_grid">
...
</grid_layout>
</grid_layouts>
</config>
The grid layout is applied to a certain form via the XML layout.
For instance, the layout file loki_checkout_block_billing_address.xml could look as follows:
<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:View/Layout:etc/page_configuration.xsd">
<body>
<referenceBlock name="loki-checkout.billing-address.address-form">
<arguments>
<argument name="grid_layout" xsi:type="string">custom_grid</argument>
</arguments>
</referenceBlock>
</body>
</page>
And similarly, the layout file loki_checkout_block_shipping_address.xml could look as follows:
<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:View/Layout:etc/page_configuration.xsd">
<body>
<referenceBlock name="loki-checkout.shipping-address.address-form">
<arguments>
<argument name="grid_layout" xsi:type="string">custom_grid</argument>
</arguments>
</referenceBlock>
</body>
</page>
The etc/loki_checkoutxml file also allows for a grid layout to have different settings per country. For instance, take the following snippet from the core:
<?xml version="1.0" encoding="UTF-8" ?>
<config>
<grid_layouts>
<grid_layout name="default_grid" defaultMdColSpan="6">
<grid name="default">
...
<block alias="country_id" sortOrder="120" />
...
</grid>
<grid name="netherlands" countryId="NL" parent="default">
<block alias="country_id" mdColSpan="12"/>
</grid>
</grid_layout>
</grid_layouts>
</config>
Here, the grid layout default_grid contains a grid default that defines the country field to have a md:col-span-6 CSS class. However, when the country is NL (The Netherlands), this changes to md:col-span-12.
order-* classesEach field is ordered within the HTML following these grid layout settings. In other words, the default HTML document resembles the final ordering of all blocks. That being said, the same HTML contains Tailwind order-* classes.
These CSS classes only serve a single purpose: When the country is changed (for instance, from Germany to The Netherlands), the grid settings could lead into a different layout.