Skip to content

Build a compatibility module

A compatibility module keeps theme-specific code out of your reusable components. Your PHP class can serve several storefronts; a small companion module provides the layout, Alpine, CSS, or template changes needed by one theme.

Install an existing integration first. For Hyvä, use magewirephp/magewire-hyva-theme. It already coordinates the Alpine loader and bridges Magento flash messages. The example below adds one reusable Hyvä behavior on top of that package.

Package the integration

Create a Composer package with this structure:

vendor/module-magewire-hyva-extras/
├── composer.json
└── src/
    ├── registration.php
    ├── etc/module.xml
    └── view/frontend/
        ├── layout/default_hyva.xml
        └── templates/js/magewire-disclosure.phtml

The dependencies make the package installable in any project that uses Magewire 3 and the maintained Hyvä integration:

composer.json
{
  "name": "vendor/module-magewire-hyva-extras",
  "description": "Reusable Magewire UI behavior for Hyvä",
  "type": "magento2-module",
  "require": {
    "magewirephp/magewire": "^3.7.2",
    "magewirephp/magewire-hyva-theme": "^3.1"
  },
  "autoload": {
    "files": ["src/registration.php"]
  }
}

Register the Magento module and sequence it after the package whose layout nodes you extend:

src/registration.php
<?php

use Magento\Framework\Component\ComponentRegistrar;

ComponentRegistrar::register(
    ComponentRegistrar::MODULE,
    'Vendor_MagewireHyvaExtras',
    __DIR__
);
src/etc/module.xml
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
    <module name="Vendor_MagewireHyvaExtras">
        <sequence>
            <module name="Magewirephp_Magewire"/>
            <module name="Magewirephp_MagewireHyvaTheme"/>
            <module name="Hyva_Theme"/>
        </sequence>
    </module>
</config>

For a project-local module in app/code, use the same registration.php and etc/module.xml layout. Composer metadata matters when you distribute it.

If the package restyles Magewire's own UI, require magewirephp/magewire ^3.7 (and magewirephp/magewire-hyva-theme ^3.1.2 on Hyvä). Magewire 3.7.0 replaced the notifier, loading icon, and exception markup with the classes documented in Core styles.

Register shared browser behavior

The Hyvä integration uses the default_hyva layout handle. Add a block to Magewire's Alpine component container; the block inherits the Magewire view model from the root layout block:

src/view/frontend/layout/default_hyva.xml
<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <body>
        <referenceContainer name="magewire.alpinejs.components">
            <block name="vendor.magewire.hyva.disclosure"
                   template="Vendor_MagewireHyvaExtras::js/magewire-disclosure.phtml"/>
        </referenceContainer>
    </body>
</page>

Wrap inline JavaScript in a Script fragment so Magewire can supply the CSP nonce or hash. This follows the pattern used by the maintained Hyvä package:

src/view/frontend/templates/js/magewire-disclosure.phtml
<?php

/** @var \Magento\Framework\View\Element\Template $block */
$magewireViewModel = $block->getData('view_model');
$fragment = $magewireViewModel->utils()->fragment();
?>
<?php $script = $fragment->make()->script()->start() ?>
<script>
    document.addEventListener('alpine:init', () => {
        Alpine.data('magewireDisclosure', () => ({
            open: false,
            toggle() { this.open = ! this.open }
        }));
    }, { once: true });
</script>
<?php $script->end() ?>

Any component template in the project can now use the same browser behavior:

view/frontend/templates/magewire/counter.phtml
<div x-data="magewireDisclosure">
    <button type="button" x-on:click="toggle" x-bind:aria-expanded="open">
        <?= $escaper->escapeHtml(__('Show counter')) ?>
    </button>

    <div x-show="open" x-cloak>
        <span><?= $escaper->escapeHtml(__('Count: %1', $magewire->count)) ?></span>
        <button type="button" wire:click="increment">
            <?= $escaper->escapeHtml(__('Increase')) ?>
        </button>
    </div>
</div>

The disclosure state stays in Alpine; the count stays in the Magewire component. That division lets several PHP components use the same browser behavior without copying scripts into every PHTML file.

Install the package with Composer, enable Vendor_MagewireHyvaExtras if needed, run bin/magento setup:upgrade, and rebuild the Hyvä frontend assets if the package also contributes Tailwind sources.

Adapt another theme

For a theme without a maintained Magewire package, start with the same Magento module structure. Use a layout handle that your theme actually adds; the default_hyva handle above belongs to Hyvä and is not a Magento convention for every theme. A general default.xml applies wherever the module is enabled, which matters in stores with more than one active theme.

The first integration decision is script ownership. Magewire bundles Alpine. Your module must place the Magewire loader at the right layout node and coordinate the theme's own Alpine asset so one instance starts on a page. The Hyvä loader layout and script template are concrete references. Then add only the CSS and browser bridges your theme needs. Since Magewire 3.7, core ships complete styles for its own UI, so a new integration can adjust the core style tokens instead of supplying notifier or loading-indicator CSS. Keep PHP business services in the feature module; put theme behavior in this companion module.

Extend and replace layout output

Add a child to an existing container when you want additional output:

<referenceContainer name="magewire.features">
    <block name="vendor.magewire.theme-bridge"
           template="Vendor_MagewireHyvaExtras::js/theme-bridge.phtml"/>
</referenceContainer>

Change an existing block's template when you need a replacement:

<referenceBlock name="magewire.ui-components.notifier"
                template="Vendor_MagewireHyvaExtras::magewire/notifier.phtml"/>

The replacement template must provide the behavior expected by that block. For the notifier, keep the classes and attributes listed under Notifier markup so the core styles and theme tokens still apply. Merely placing a PHTML file at the same relative path in your module does not replace another module's template. Prefer adding a child or a small feature bridge when it meets the need; replacing a whole core template ties your package to its internal markup.

If your integration registers a Magewire Feature or Mechanism, add its collection item in etc/frontend/di.xml. For admin extensions use etc/adminhtml/di.xml. Magento's area configuration can replace collection arrays declared in global etc/di.xml.

Check both page types

Test one page with a Magewire component and one page without one. Confirm the component updates, Alpine initializes once, CSP permits your script, and the theme still works when Magewire does not drive the page. Test again with Magento full-page cache enabled. To check style overrides, open the UI workbench at /magewire/playwright/ui, which renders the real notifier, loading icon, and exception markup. See Alpine loading, Layout nodes, Core styles, and Tailwind for each integration point.