← Back to blog

Hyvä vs Luma in 2026: What Changes for a Magento Developer

Rodrigo Biazzi

Most new Magento 2 frontend projects now start with a choice between the Luma stack that ships with Magento and Hyvä. Comparisons usually focus on Lighthouse scores. This one is about the daily work: what a developer writes, debugs and maintains on each stack, and what that means for extensions, checkout and budgets.

Two very different frontend stacks

Luma, and its parent Blank, is the frontend Magento 2 shipped with from the start. It is built on:

  • RequireJS for loading JavaScript modules
  • jQuery and jQuery UI widgets
  • KnockoutJS and UI components for dynamic parts, including the mini cart and the whole checkout
  • LESS compiled by Magento's static content deployment

Hyvä replaces all of that on the storefront. A Hyvä theme uses:

  • Alpine.js for interactivity, written inline in templates
  • Tailwind CSS, compiled with a Node build step inside the theme
  • No RequireJS, no jQuery and no Knockout on the storefront
  • The same Magento backend: layout XML, blocks, view models, PHP templates and customer data sections all still apply

The last point matters. Hyvä is not a headless or PWA approach. It is a regular Magento theme that renders HTML on the server. Everything you know about layout handles, blocks, plugins and caching carries over. What changes is the JavaScript and CSS layer, and with it how much code runs in the browser.

Writing a component on each stack

Luma: a JavaScript widget or UI component

On Luma, a small interactive element usually means a template, a JavaScript file registered as a RequireJS module, and an x-magento-init script tag to bind them.

<div class="stock-alert" data-mage-init='{"Acme_StockAlert/js/stock-alert": {"productId": <?= (int) $block->getProductId() ?>, "url": "<?= $escaper->escapeUrl($block->getUrl('stockalert/subscribe')) ?>"}}'>
    <button type="button" class="action primary"><?= $escaper->escapeHtml(__('Notify me')) ?></button>
</div>
define(['jquery', 'mage/cookies'], function ($) {
    'use strict';

    return function (config, element) {
        $(element).on('click', 'button', function () {
            $.post(config.url, {product: config.productId, form_key: $.mage.cookies.get('form_key')});
        });
    };
});

Changing existing behaviour on Luma often means a RequireJS mixin, declared in requirejs-config.js:

var config = {
    config: {
        mixins: {
            'Magento_Checkout/js/action/place-order': {
                'Acme_Checkout/js/action/place-order-mixin': true
            }
        }
    }
};

Mixins are powerful, but tracing which file wraps which module across dozens of extensions is slow work, and static content deployment has to run before you see a change in production mode.

Hyvä: Alpine.js in the template

On Hyvä the same feature lives in one template. State and behaviour sit next to the markup.

<?php
/** @var \Magento\Framework\View\Element\Template $block */
/** @var \Magento\Framework\Escaper $escaper */
$productId = (int) $block->getData('product_id');
?>
<div x-data="initStockAlert()">
    <button type="button" class="btn btn-primary" x-on:click="subscribe()" x-bind:disabled="sent">
        <?= $escaper->escapeHtml(__('Notify me')) ?>
    </button>
    <p x-show="sent" x-cloak><?= $escaper->escapeHtml(__('We will email you.')) ?></p>
</div>
<script>
    function initStockAlert() {
        return {
            sent: false,
            subscribe() {
                fetch('<?= $escaper->escapeJs($block->getUrl('stockalert/subscribe')) ?>', {
                    method: 'POST',
                    headers: {'Content-Type': 'application/x-www-form-urlencoded'},
                    body: new URLSearchParams({
                        product: '<?= $productId ?>',
                        form_key: hyva.getFormKey()
                    })
                }).then(() => { this.sent = true; });
            }
        };
    }
</script>

There is no build step for the JavaScript and nothing to register. The Tailwind classes do need the CSS build, which picks up classes from your templates. For most developers who know PHP and some modern JavaScript, this is quicker to read and to change than the Luma equivalent.

Private content

Customer data sections work the same on the server side. On the client, Hyvä dispatches a private-content-loaded event on the window when section data is ready, and components listen for it:

<div x-data="{ cartCount: 0 }"
     x-on:private-content-loaded.window="cartCount = $event.detail.data.cart ? $event.detail.data.cart.summary_count : 0">
    <span x-text="cartCount"></span>
</div>

That means your full page cache strategy stays the same. Pages are still cached as shared HTML and personal data is still loaded from customer/section/load.

Styling: LESS versus Tailwind

Luma styling means overriding LESS variables and mixins from Magento_Theme and each module's web/css/source files, inheriting from Blank or Luma. Small changes are easy. Large redesigns end up fighting the parent theme's specificity and you ship a lot of CSS you never use.

In Hyvä you style with utility classes in the templates and configure design tokens in the Tailwind configuration inside the theme's web/tailwind folder. The build scans templates for classes and only outputs what is used, so the CSS stays small. The trade off is that design changes mean editing templates, and a developer who has never used Tailwind needs some time to get comfortable with it.

A Hyvä child theme declares its parent in theme.xml like any Magento theme:

<theme xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:noNamespaceSchemaLocation="urn:magento:framework:Config/etc/theme.xsd">
    <title>Acme Store</title>
    <parent>Hyva/default</parent>
</theme>

View models and the Hyvä registry

Hyvä encourages view models over block classes. Besides passing them as layout arguments, every Hyvä template gets a $viewModels variable, an instance of Hyva\Theme\Model\ViewModelRegistry, that lets a template ask for any view model directly:

<?php
/** @var \Hyva\Theme\Model\ViewModelRegistry $viewModels */
$heroicons = $viewModels->require(\Hyva\Theme\ViewModel\HeroiconsOutline::class);
?>
<?= $heroicons->shoppingCartHtml('w-6 h-6') ?>

It is convenient, and it removes a lot of layout XML. Use it with care in templates that are shared widely, because dependencies are less visible than layout arguments.

Extensions: the real cost

This is where most of the planning time goes. Any Magento extension with frontend JavaScript written for Luma (RequireJS, jQuery, Knockout) will not work on a Hyvä storefront as is. The backend part of the extension works, but its frontend needs a Hyvä version.

The options for each extension are:

  • The vendor already ships Hyvä support, either built in or as a separate package.
  • A compatibility module exists in the Hyvä compatibility module tracker or from the community.
  • You write the compatibility module yourself: new templates with Alpine.js, sometimes new layout XML, reusing the extension's backend.
  • The page that needs the extension falls back to Luma, through Hyvä's Luma theme fallback for specific routes.

Before committing to Hyvä, list every extension with frontend output and check each one. Extensions that only touch the admin, APIs, cron or the database are not affected at all. On a store with many frontend extensions, the compatibility work can be larger than the theme work itself.

Checkout

The Luma checkout is a Knockout single page application built from UI components and configured through checkout_index_index.xml layout and JavaScript. Customising it means layout processors, mixins and Knockout templates. Many payment and shipping extensions ship code for exactly this checkout.

On a Hyvä store you have two main paths:

  • Luma checkout fallback. The checkout keeps using the Luma stack while the rest of the store runs on Hyvä. Payment extensions work as they do today. You still maintain Luma styling for the checkout pages.
  • Hyvä Checkout. A separate commercial product from Hyvä built on Magewire, a server driven component library. Customisations are written mostly in PHP and templates. Payment and shipping methods need Hyvä Checkout support from their vendors or a compatibility module.

Licensing and pricing of the Hyvä theme and its commercial products have changed over time, so check the current terms on the Hyvä website before you estimate a project.

Debugging and tooling

  • On Luma, frontend bugs often live in the interaction between RequireJS modules, mixins and Knockout bindings. Browser dev tools and source maps help, but static content deployment and the pub/static cache are frequent sources of confusion. When in doubt, clear pub/static/frontend and var/view_preprocessed on a development machine.
  • On Hyvä, most behaviour is visible in the template that renders it. Alpine.js dev tools for the browser show component state. The common issue is a Tailwind class that does not appear in the CSS because the build did not scan the file that uses it, so check the content paths in the Tailwind configuration first.

Both stacks use the same backend tools: layout debugging, template hints (bin/magento dev:template-hints:enable), the profiler and the cache commands.

Which one to choose

For a new build or a full redesign, Hyvä is usually the stronger default, as long as the extension list has been checked and the checkout path is decided. The storefront is lighter, frontend changes are faster to make and the code is easier for new developers to follow.

Luma still makes sense when a store depends heavily on extensions that have no Hyvä support, when there is no budget for a frontend rebuild and the current theme works, or when the team has deep Knockout and UI component experience and no time to learn a new stack. Its performance can be improved too, with careful JavaScript bundling, fewer extensions on the storefront and proper caching, although it will not reach the same baseline as Hyvä.

Whatever you pick, the backend skills are the same. Layout XML, view models, plugins, service contracts and full page cache work in both, and that is where most of the Magento specific knowledge lives.

FAQ

Is Hyvä a headless or PWA frontend?

No. Hyvä is a regular Magento theme rendered on the server. Layout XML, blocks, view models and full page cache work as usual. It replaces the RequireJS, jQuery, Knockout and LESS layer with Alpine.js and Tailwind CSS.

Do Luma extensions work on a Hyvä store?

Their backend parts do. Frontend parts built on RequireJS, jQuery or Knockout need a Hyvä compatible version, a compatibility module, or a Luma fallback for the pages that use them.

Can I use the default Magento checkout with Hyvä?

Yes. The Luma checkout can be kept through a fallback while the rest of the store uses Hyvä. The alternative is Hyvä Checkout, a separate product built on Magewire.

Do I need Node.js to work on a Hyvä theme?

Yes, for the Tailwind CSS build inside the theme's web/tailwind folder. Alpine.js components themselves are written in the templates and need no build step.

I am available for freelance Magento and Adobe Commerce work, from module development and performance audits to migrations and Hyvä builds. If that sounds useful, get in touch.

Related articles