
Hyvä replaces Luma’s JavaScript stack rather than layering on top of it. RequireJS and data-mage-init give way to inline scripts inside templates, Knockout and Underscore give way to Alpine.js, and jQuery gives way to native JavaScript. For a Luma developer the syntax is the easy part. The harder adjustment is that components become self-contained instead of orchestrated.
Teams underestimate this. Agencies quote Hyvä migrations on template count and extension compatibility, then discover the frontend developers need two or three weeks to stop writing Luma patterns in Alpine syntax. Here is what actually changes, what to unlearn, and the helpers that shorten the curve.
The four substitutions
Hyvä’s guide to migrating JS and templates is direct about the mapping.
| Luma | Hyvä | What changes for you |
|---|---|---|
RequireJS, data-mage-init |
Inline scripts in .phtml |
No module loader, no async dependency graph |
| Knockout, Underscore | Alpine.js | x-for replaces x-magento-template blocks |
| jQuery | Native JavaScript | Most jQuery use has a direct native equivalent |
jQuery.data() |
element.dataset |
Cache values manually where you need to |
The pattern underneath is consistency: fewer layers, less indirection, and behaviour that lives next to the markup it controls.
What actually trips people up
The syntax is learnable in an afternoon. These are the habits that take longer.
Looking for the orchestration layer. In Luma, a component declares dependencies and the loader resolves them. Developers arriving in Hyvä look for the equivalent and there isn’t one, because components are self-contained by design. The instinct to build a shared registry so components can talk to each other is the first thing to resist. Where components genuinely need to coordinate, use events rather than reinventing a module system.
Global scope collisions. Because scripts are inline in templates, function names live in the global scope. Hyvä’s guidance is to give functions unique names. This feels primitive to anyone used to modules, and it matters: two developers who each write initSlider in different templates will produce a bug that only appears on pages where both render.
Registering components at the wrong time. Alpine components are registered on the alpine:init event:
document.addEventListener('alpine:init', () => {
Alpine.data('initMyComponent', initMyComponent)
});
Skip that and your component silently does nothing, which is a frustrating first day.
Getting event timing wrong. Two events matter. Use private-content-loaded when you depend on customer section data, and DOMContentLoaded when you depend on window.hyva. Choosing the wrong one produces intermittent bugs that pass locally and fail under real network conditions, which is the worst category to debug.
Reaching for jQuery out of habit. It is usually still available through a compatibility path, and using it defeats the purpose. Most of the weight Hyvä removes is exactly this.
The helpers worth knowing on day one
The window.hyva object is available on every page in a Hyvä theme and removes a surprising amount of boilerplate. Teams that do not know it exists reimplement it badly.
The ones that come up constantly:
formatPrice()for currency formatting that respects store configurationgetFormKey()for Magento’s security token, andpostForm()to submit hidden form data over AJAXgetCookie()andsetCookie(), which are consent-aware, plussetSessionCookie()andgetBrowserStorage()for safe storage accessreplaceDomElement()to swap a section with an AJAX response, andactivateScripts()to execute script tags inside dynamically inserted contentstr()andstrf()for parameterised translationtrapFocus()andreleaseFocus()for accessible modals and drawersalpineInitialized()to run code once Alpine is ready
The accessibility helpers deserve attention. Focus trapping is the sort of thing teams write badly under deadline pressure, and having a tested implementation available means there is no excuse for a modal that strands keyboard users.
There is also x-defer, a Hyvä Alpine plugin that postpones component initialisation. It is the right tool for below-the-fold interactivity, and it is one of the few places where a small change produces a measurable Core Web Vitals improvement.
One documentation caveat worth flagging to your team: the Alpine site documents version 3 only, so older tutorials found through search may not match what you are running.
The shape of a converted component
It helps to see what “self-contained” means in practice, because the phrase does a lot of work.
A Luma component typically spans three or four places. There is markup in a template, a data-mage-init or x-magento-init attribute wiring it up, a JavaScript module in a separate file, and often a Knockout template for anything that renders a list. Understanding one component means opening several files and holding the loader’s resolution order in your head.
The Hyvä equivalent puts the markup, the state, and the behaviour in one template. State is declared where the component starts, the markup binds to it directly, and list rendering happens with x-for inline rather than in a separate template block. A developer reading that file sees the whole component.
The benefit is obvious once you have debugged a few. The cost is less obvious and worth naming: templates get longer, and a component with substantial logic can feel cramped inside a .phtml file. Teams coming from a modular codebase often find this the least comfortable part of Hyvä. The answer is not to rebuild the module system. It is to keep components small enough that a single file is the right unit, and to extract genuinely shared logic into a helper registered on alpine:init rather than duplicating it.
That constraint is a feature more than a limitation. A component that has grown too large for its template is usually a component doing too much, and the discomfort is a useful signal.
Measuring whether the retraining worked
Two checks tell you whether a team has actually internalised the model rather than translating Luma patterns into Alpine syntax.
The first is bundle weight. If your JavaScript payload has not dropped substantially against the Luma baseline, something is wrong. Either compatibility shims are pulling old libraries back in, or the team is loading things it does not need. This is measurable on any page and worth checking early rather than at the end.
The second is how the team answers “how do these two components talk to each other?” A team that has adjusted reaches for events. A team that has not proposes a shared object to hold state across components, which is Luma’s model rebuilt by hand and will cost you on every future upgrade.
Neither check requires tooling. Both are worth running at the end of the third week, while there is still time for the answer to change how the migration is built.
A realistic ramp-up plan
For a team of Magento frontend developers with no Alpine experience, this sequence works.
Week one: read, do not build. Have each developer read the Hyvä JS migration guide and the window.hyva reference, then convert one small existing Luma component as an exercise. A newsletter signup or a simple toggle is ideal. The goal is the mental model, not delivery.
Week two: pair on a real component. Take something with actual state, such as a filter or an add-to-cart flow, and pair on it. This is where the self-contained model either clicks or does not.
Week three: set conventions before volume. Agree naming rules for global functions, where shared helpers live, which events you standardise on, and when to use x-defer. Doing this before the team writes fifty components saves an expensive cleanup later. This is the same discipline that keeps upgrades cheap, as we set out in what Hyvä theme development actually involves.
Ongoing: review for Luma habits specifically. In code review, look for jQuery use, ad hoc registries, generic function names, and components that reach into each other’s state. These are the tells, and catching them early is far cheaper than refactoring later.
A team doing this properly is productive in about three weeks and fluent in two months. Budget that into a migration plan rather than assuming existing Magento frontend experience transfers directly, because in this one area it largely does not.
Where this fits a migration
The JavaScript retraining runs in parallel with the two workstreams that usually dominate a Hyvä project: extension compatibility, which we covered in adapting third-party Magento extensions to Hyvä, and the build pipeline, covered in the Magento and Hyvä release pipeline guide.
The sequencing point worth making: start the team’s Alpine work before the migration proper, not during it. A team learning a new frontend model while under delivery pressure produces the code you will spend next year rewriting.
How Bemeir helps
Bemeir is a Brooklyn ecommerce agency and the USA’s first official Hyvä Gold Partner. We work both ways here: building Hyvä frontends outright, and working alongside in-house teams making this transition so their developers own the result rather than inheriting a black box.
Our Hyvä development services cover the build, the conventions, and the code review that keeps a young Hyvä codebase clean. That sits inside a wider Magento and Adobe Commerce development practice covering upgrades, B2B, and the integration layer. We also build on Shopify and Shopify Plus, Shopware, and BigCommerce, and module questions draw on our technology partner ecosystem.
More about the team is on the about Bemeir page, or start a conversation from the Bemeir homepage.
Frequently asked questions
Does Hyvä use jQuery?
Hyvä’s approach is to remove it. The migration guidance is explicit that most dependencies on libraries like jQuery can be replaced with native JavaScript, and a large part of the performance benefit comes from not shipping those libraries. A jQuery compatibility path may exist for third-party code, but new code written in a Hyvä theme should use native JavaScript.
What replaces Knockout and RequireJS in Hyvä?
Alpine.js replaces Knockout and Underscore for rendering and reactivity, with the x-for directive taking the place of <script type="x-magento-template"> blocks. RequireJS and data-mage-init are replaced by inline scripts declared directly in .phtml templates, which makes each component self-contained rather than assembled by a module loader at runtime.
How long does it take a Luma developer to become productive in Hyvä?
Roughly three weeks to productive and about two months to fluent, for a developer with solid Magento frontend experience. The syntax is quick to learn. The slower part is unlearning the orchestrated component model, since Hyvä components are self-contained and do not resolve dependencies through a loader. Plan the ramp-up before a migration starts rather than during it.
What is the window.hyva object?
A JavaScript utility available on every page of a Hyvä theme, providing helpers for common Magento frontend tasks. It covers price formatting, form keys and AJAX form posting, consent-aware cookies and browser storage, DOM replacement with script activation, parameterised translation, and focus trapping for accessibility. Knowing it exists prevents teams from reimplementing these things less reliably.
What is x-defer in Hyvä?
A custom Hyvä Alpine plugin that postpones initialisation of an Alpine component. It is used to keep non-critical JavaScript from running during initial page load, which helps Core Web Vitals. It suits below-the-fold interactivity, where deferring execution has no user-visible cost but does reduce the work the browser does before the page becomes responsive.





