UI Components in the Checkout
The checkout component tree, the jsLayout array it is built from, and how to read that tree before you change it.
jsLayout > components > checkout > children > steps > children > <step> > children > ...Everything the shopper sees in checkout comes from one nested PHP array called jsLayout. It is declared in layout XML, merged across modules and themes like any other layout, serialized to JSON by the checkout block, and handed to Magento_Ui/js/core/app, which instantiates one component per node.
How a node is described
A node's useful keys are component (the RequireJS path to the JavaScript class), config (values passed to it, including template), children, sortOrder and displayArea. Nesting decides the name: the shipping form is checkout.steps.shipping-step.shippingAddress. The client-side registry stores every instance under that dotted name, which is how one component finds another and how you find either from the console.
Read it before changing it
The file worth reading is vendor/magento/module-checkout/view/frontend/layout/checkout_index_index.xml. Under steps it has exactly two children in Magento 2.4.9: shipping-step with sortOrder 1 and billing-step with 2. Other modules add to the same tree, so confirm what the merge produced rather than what one file says. Adobe Commerce with B2B installed adds further nodes here; the mechanism is unchanged.
What trips people up
A child's displayArea must match a getRegion() call in its parent's template. If it does not, the component is created and never rendered, and nothing in the page or the console says so. After editing layout XML, run bin/magento cache:flush.
Example
<!-- app/code/Vendor/DeliveryNote/view/frontend/layout/checkout_index_index.xml -->
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
<body>
<referenceBlock name="checkout.root">
<arguments>
<argument name="jsLayout" xsi:type="array">
<item name="components" xsi:type="array">
<item name="checkout" xsi:type="array">
<item name="children" xsi:type="array">
<item name="steps" xsi:type="array">
<item name="children" xsi:type="array">
<item name="billing-step" xsi:type="array">
<item name="children" xsi:type="array">
<item name="payment" xsi:type="array">
<item name="children" xsi:type="array">
<item name="afterMethods" xsi:type="array">
<item name="children" xsi:type="array">
<item name="delivery-note-hint" xsi:type="array">
<item name="component" xsi:type="string">Vendor_DeliveryNote/js/view/note-hint</item>
<item name="displayArea" xsi:type="string">afterMethods</item>
<item name="sortOrder" xsi:type="string">10</item>
</item>
</item>
</item>
</item>
</item>
</item>
</item>
</item>
</item>
</item>
</item>
</item>
</argument>
</arguments>
</referenceBlock>
</body>
</page>
When to use it
- Add a message or a small form under the payment methods list.
- Find the dotted name of a core checkout component so another component can import from it.
- Remove a stock address field such as company or fax.
- Audit which third-party modules have injected components into checkout.
More examples
Dumping the merged tree
A layout processor receives the array after every module and theme has merged into it, which makes it the only honest place to see what checkout actually contains. Register it as an item in the layoutProcessors argument of Magento\Checkout\Block\Onepage in etc/frontend/di.xml, run bin/magento cache:flush, load checkout, then read var/log/debug.log. Remove it once you know the shape; it logs on every checkout request.
<?php
// app/code/Vendor/DeliveryNote/Block/Checkout/TreeDumper.php
declare(strict_types=1);
namespace Vendor\DeliveryNote\Block\Checkout;
use Magento\Checkout\Block\Checkout\LayoutProcessorInterface;
use Psr\Log\LoggerInterface;
class TreeDumper implements LayoutProcessorInterface
{
public function __construct(
private readonly LoggerInterface $logger
) {
}
public function process($jsLayout)
{
$steps = $jsLayout['components']['checkout']['children']['steps']['children'] ?? [];
foreach ($steps as $code => $step) {
$this->logger->debug(sprintf(
'checkout step %s sortOrder=%s children=%s',
$code,
$step['sortOrder'] ?? 'none',
implode(',', array_keys($step['children'] ?? []))
));
}
return $jsLayout;
}
}
Walking the registry from the console
The registry is the live counterpart of the jsLayout array. get() returns undefined for a component that has not been created yet, which is itself the answer when you expected one; async() runs the callback once it registers. Use this to check whether a node you added exists at all before you start debugging its template.
// Browser console, on /checkout
require(['uiRegistry'], function (registry) {
var payment = registry.get('checkout.steps.billing-step.payment');
console.log(payment.name); // full dotted name
console.log(payment.getTemplate()); // template it renders
console.log(payment.elems().map(function (e) { // its live children
return e.name;
}));
// For a component that may not exist yet:
registry.async('checkout.steps.shipping-step.shippingAddress')(function (shipping) {
console.log(shipping.isFormInline);
});
});
Removing a core node
Removing a field from the shipping address fieldset is a PHP array unset, not a layout <remove> directive, because the node lives inside a block argument rather than in the page's block tree. Reach for this when a client wants a stock checkout field gone. Check the field is not required by a shipping carrier first, or the address will fail validation server-side with no message in the form.
<?php
// app/code/Vendor/DeliveryNote/Block/Checkout/RemoveCompany.php
declare(strict_types=1);
namespace Vendor\DeliveryNote\Block\Checkout;
use Magento\Checkout\Block\Checkout\LayoutProcessorInterface;
class RemoveCompany implements LayoutProcessorInterface
{
public function process($jsLayout)
{
unset(
$jsLayout['components']['checkout']['children']['steps']['children']
['shipping-step']['children']['shippingAddress']['children']
['shipping-address-fieldset']['children']['company']
);
return $jsLayout;
}
}
Discussion