Hyvä Theme Customization: Tailwind, Overrides & the Build Pipeline
Customize a Hyvä theme without breaking the production build: the four layers a change can live in, the Tailwind pipeline end to end, and the purge bug that bites every team exactly once.
Hyvä deletes the part of Magento's frontend that made it slow: no RequireJS, no Knockout, no jQuery, no LESS pipeline. In their place sits a stack any front-end developer already knows — Tailwind CSS for styling and Alpine.js for the ~27 KB of interactivity the storefront actually needs.
That swap is why a tuned Hyvä storefront ships single-digit JavaScript payloads and posts Lighthouse performance scores in the 90s where Luma struggles to clear 30. But it also moves the customization surface. The muscle memory from Luma — override a .phtml, recompile LESS, fight the cascade — is half-wrong here. Get the new mental model right and Hyvä is the most pleasant theme layer Magento has ever had. Get it wrong and you'll spend an afternoon wondering why a button is unstyled in production but perfect on your laptop.
This is the practical, upgrade-safe guide to customizing a Hyvä theme in 2026: the four places a change can live, the Tailwind build pipeline end to end, and the purge gotcha that bites every team exactly once.
The four places a customization can live
Before you touch anything, decide where the change belongs. Hyvä gives you four layers, and the right answer is almost always “the highest layer that does the job,” because higher layers are less code, easier to read, and survive upgrades cleanly.
| You want to change… | Put it here | Survives composer update? |
|---|---|---|
| Brand colors, fonts, spacing scale, breakpoints | Design tokens — web/tailwind/tailwind.config.js theme.extend | Yes — it's in your child theme |
| How one element looks on one page | Utility classes directly in the template's markup | Yes — if the template is yours |
| Markup / structure of a Hyvä block | Template override — copy the .phtml into your child theme | Yes — theme fallback, vendor untouched |
| Reusable feature across themes / projects | Custom module with its own templates & layout XML | Yes — versioned separately |
Layer 1 — design tokens in tailwind.config.js
A Hyvä child theme lives where every Magento theme lives — app/design/frontend/Panth/default/ — and the Tailwind project sits inside it at web/tailwind/:
app/design/frontend/Panth/default/
├─ theme.xml
├─ registration.php
└─ web/
└─ tailwind/
├─ tailwind.config.js ← your tokens live here
├─ package.json
└─ css/
└─ styles.css ← @tailwind + a few @layer rules
Brand changes — colors, fonts, the spacing scale, custom breakpoints — belong in theme.extend. Extend rather than replace, so you keep Tailwind's defaults and Hyvä's base styling:
// web/tailwind/tailwind.config.js
module.exports = {
theme: {
extend: {
colors: {
primary: {
DEFAULT: '#f97316', // brand orange
dark: '#ea580c',
soft: '#fff4ed',
},
},
fontFamily: {
// Hyva preloads the font; the class just references it
sans: ['"DM Sans"', 'ui-sans-serif', 'system-ui'],
},
},
},
};
Now bg-primary, text-primary-dark, and font-sans exist as real utilities everywhere — templates, modules, even CMS blocks. One token, applied site-wide, zero markup forks. This is the layer most “theme customization” tickets should actually resolve to.
Layer 2 — the build pipeline (and the purge that eats your CSS)
Tailwind doesn't ship a giant stylesheet with every possible class. It scans your files, finds the classes you actually used, and generates only those. That is what keeps the CSS small — and it's also the source of the single most common Hyvä support ticket. Day to day you run two commands from the theme's web/tailwind/ folder:
cd app/design/frontend/Panth/default/web/tailwind
npm ci # once, installs Tailwind + the Hyva preset
npm run watch # dev: rebuilds in < 100ms on every save
npm run build-prod # prod: one minified, purged styles.css
In watch mode Tailwind keeps a generous, unpurged stylesheet, so everything looks right on your laptop. build-prod is different — it purges every class it cannot find in a scanned file. If a class only appears somewhere Tailwind isn't looking, it's deleted from the production CSS, and the element renders unstyled only in production. Here is the typical “works on my machine” trace:
# dev: looks perfect
$ npm run watch
rebuilding... done in 84ms <- full class set present
# prod: the button is suddenly naked
$ npm run build-prod
Done in 11.2s <- purged: bg-primary never seen in a scanned file
$ bin/magento setup:static-content:deploy -f en_US
The cause is always the content array — the list of globs Tailwind scans. The default covers the theme and core modules, but not your custom module's templates unless you register them (Layer 4). And no glob can catch a class you build at runtime, because the string never appears in source:
<!-- PURGED: 'bg-' + color is assembled at render time, never seen as a literal -->
<div class="bg-<?= $status ?>-100">…</div>
<!-- SAFE: full class names appear as literal strings Tailwind can scan -->
<div class="<?= $status === 'ok' ? 'bg-green-100' : 'bg-red-100' ?>">…</div>
When a dynamic class is genuinely unavoidable, add it to the safelist so the prod build keeps it:
// tailwind.config.js
module.exports = {
safelist: ['bg-green-100', 'bg-red-100', 'bg-yellow-100'],
// ...
};
Element styled in watch but unstyled after build-prod? It's never a cascade problem and never a deploy-cache problem — it's purge. Either the class lives in a file outside your content globs (fix the globs / register the module), or it's a runtime-concatenated string (write the full class name, or safelist it). Check the generated styles.css for the literal class before you touch anything else.
Layer 3 — overriding a Hyvä template the right way
When you truly need different markup — not just different styling — override the template through Magento's theme fallback. You copy the vendor file into your child theme at the same module-relative path and edit your copy. The original is never touched, so the override survives composer update (see our Hyvä compatibility guide for the inverse problem — making a Luma module render at all).
# vendor original (NEVER edit this):
vendor/hyva-themes/magento2-default-theme/Magento_Catalog/templates/product/price/final_price.phtml
# your override (edit this, same path under your theme):
app/design/frontend/Panth/default/Magento_Catalog/templates/product/price/final_price.phtml
Theme fallback resolves your file first, falls back to the vendor file for everything you didn't override. Two rules keep this clean:
1. Copy the WHOLE template, then edit. A partial copy renders a partial block.
2. Override the SMALLEST template that contains your change. Overriding a big
container to tweak one child means you now own (and must maintain) the whole
container forever. Find the leaf .phtml that actually renders the markup.
For interactive behavior inside that markup, reach for Alpine before anything else — the patterns are covered in 10 Alpine.js patterns every Hyvä developer needs. Resist the urge to bolt jQuery back on; it defeats the entire point of the theme.
Layer 4 — modules and the hyva-modules registry
A reusable feature belongs in its own module with its own .phtml templates and layout XML — standard Magento. The Hyvä-specific step is making Tailwind scan those module templates so their classes aren't purged. You don't hand-edit the theme's content array for every module; instead each module ships a small Tailwind config fragment, and Hyvä's hyva-modules helper aggregates them into the theme build:
# from the theme's web/tailwind directory: regenerate the merged module list
# after installing or scaffolding a module that ships frontend templates
npx hyva-modules
# it writes/updates tailwind.config.js's module imports so your module's
# templates are part of the content globs Tailwind scans on build-prod
Skip this step and your module looks perfect in watch (unpurged) and unstyled in production (purged) — the exact Layer-2 symptom, now caused by an unregistered module rather than a runtime class. It's the same disease; this is just where it comes from when the markup lives in app/code/Panth/….
The deploy sequence, in order
On production the Tailwind build is a step you own — setup:static-content:deploy copies whatever CSS is on disk; it does not run Tailwind for you. Forget the build and you deploy stale (or empty) styles. The correct order:
cd app/design/frontend/Panth/default/web/tailwind
npm ci
npm run build-prod # generate the purged CSS
cd - # back to Magento root
bin/magento setup:static-content:deploy -f en_US # publish it
bin/magento cache:flush
If a CSS change deploys but the old styling still serves, it's almost always a stale static cache or a CDN holding the old styles.css — bump the file or purge the edge, don't re-debug Tailwind. Need this done properly on your store? That's exactly what our Hyvä theme development service ships.
Frequently asked questions
Do I need to know Tailwind to customize a Hyvä theme?
For anything beyond a token tweak, yes — Tailwind is the styling layer in Hyvä; there is no LESS to fall back to. The good news is the surface area is small and the docs are excellent. If you can read utility classes (flex, gap-4, text-primary, md:grid-cols-3) you can do 90% of day-to-day Hyvä work. Brand colors and fonts are a five-line edit in tailwind.config.js; you only reach for deeper Tailwind when you build genuinely custom components.
Why do my styles work locally but disappear in production?
That's Tailwind's purge, not a Magento cache bug. npm run watch keeps an unpurged stylesheet so everything renders on your laptop; npm run build-prod strips every class it can't find in a scanned file. The class is either in a file outside your content globs (register the module with npx hyva-modules, or widen the globs) or it's assembled at runtime as a string Tailwind can't see (write the full class name, or add it to safelist). Confirm by grepping the generated styles.css for the literal class.
Can I edit files under vendor/hyva-themes/ directly?
No. Anything under vendor/ is overwritten on the next composer update, and your change vanishes silently. Override templates by copying them into your child theme at the same module-relative path (theme fallback resolves yours first), and put style changes in your theme's tailwind.config.js or template classes. The whole point of the four-layer model is that none of your work lives in vendor/.
How do I override just one Hyvä template?
Find the smallest .phtml that renders the markup you want to change, copy the entire file from vendor/hyva-themes/magento2-default-theme/<Module>/templates/… into app/design/frontend/<Vendor>/<theme>/<Module>/templates/… at the identical path, then edit your copy. Copy the whole template (a partial copy renders a partial block) and override the leaf, not a big container you'd then have to maintain forever.
Is Hyvä still Tailwind-based in 2026, or has it moved to something else?
Still Tailwind — it remains the core styling layer, paired with Alpine.js for interactivity and Magewire for server-driven reactive components. The build pipeline (npm run watch / build-prod from the theme's web/tailwind directory) and the purge behavior described here are unchanged in practice. If you're on an older Tailwind major, the config keys in this guide (theme.extend, content, safelist) are stable across versions; only a few defaults differ.
Want a Hyvä storefront built — or a Luma store migrated — without the purge surprises and upgrade-day forks? I build production Hyvä themes the upgrade-safe way: tokens over overrides, registered modules, a clean build pipeline, and a measurable Lighthouse baseline you can hold me to. Adobe-Certified Magento + Hyvä developer (cert 2021) — fixed-fee $499 audit · $2,499 sprint · ~Nh @ $25/hr. See Hyvä theme development service.
Book a Hyvä build