When Hyvä Theme launched, it changed how Magento 2 frontend development works. By replacing the heavyweight Knockout.js + RequireJS stack with Alpine.js and Tailwind CSS, Hyvä delivered something rare in Magento: a frontend that developers actually enjoy working in.
But Tailwind in Hyvä has quirks. The build pipeline, the purge config, how you extend the design system, and how you handle custom components these are all specific to the Hyvä environment. This guide covers all of it, step by step.
Why Tailwind CSS Is the Right Fit for Hyvä
Hyvä's architecture is fundamentally different from Luma. Pages are rendered server-side with PHP templates (.phtml), and interactivity is handled with Alpine.js directly inside those templates. There is no JavaScript build step for your layout which means utility-first CSS like Tailwind fits perfectly.
Utility-First Speed
Style directly in your .phtml templates. No switching context between HTML and CSS files.
Tiny Production CSS
Tailwind's JIT purge removes unused classes. Final stylesheet is typically under 10kb vs 300kb+ for Luma.
Consistent Tokens
Define colours, spacing, typography once in tailwind.config.js. Every template uses the same system.
Great DX
Instant class suggestions in VS Code, zero-config setup in Hyvä, and predictable responsive modifiers.
Alpine Synergy
Alpine's x-bind:class works perfectly with Tailwind utility strings toggle states cleanly in templates.
Easy to Extend
Add custom utilities, plugins, and component classes with standard Tailwind config no Magento-specific APIs needed.
Prerequisites Before You Start
Make sure your environment meets these requirements before modifying the Tailwind setup:
-
Hyvä Theme 1.1.x or above installed Tailwind JIT mode requires Hyvä 1.1+. Run
composer show hyva-themes/magento2-theme-moduleto check your version. -
Node.js 18+ and npm/yarn available on your dev machine The Tailwind build runs via Node. You do not need Node on production the compiled CSS is what gets deployed.
-
A custom child theme created (do not edit Hyvä directly) Always work in a child theme. Editing vendor files means upgrades will overwrite your customisations.
-
Familiarity with Tailwind utility classes If you're new to Tailwind, read the official docs on utility classes and responsive prefixes first the Hyvä-specific config will make more sense.
Understanding Hyvä's Tailwind Build Pipeline
Hyvä ships with a complete Tailwind build pipeline inside the theme's web/tailwind/ directory. Understanding this structure is essential before you customise anything.
# Hyvä Tailwind directory structure web/ └── tailwind/ ├── tailwind.config.js # Main Tailwind config extend this ├── package.json # Node dependencies (Tailwind, PostCSS) ├── postcss.config.js # PostCSS pipeline config └── styles.css # Entry CSS @tailwind directives live here
The compiled output lands in web/css/styles.css this is what Hyvä loads in the frontend. You never edit the compiled file directly. All changes go through the tailwind/ source files, then you run the build.
Step-by-Step: Setting Up Tailwind in Your Hyvä Child Theme
-
Navigate to your child theme's tailwind directory Copy the entire
web/tailwind/folder from the Hyvä base theme into your child theme at the same path. This gives you a starting config you can override. -
Install Node dependencies Run
npm install(oryarn) insideweb/tailwind/. This installs Tailwind CSS, PostCSS, and autoprefixer from the locked package.json. -
Run the development build watcher Execute
npm run watch. Tailwind JIT will watch your.phtmlfiles and rebuild the CSS instantly when classes change. -
Extend tailwind.config.js with your design tokens Add your brand colours, font families, custom spacing, and breakpoints inside the
theme.extendblock. Never replace the defaults extend them. -
Run the production build before deployment Execute
npm run buildto generate a minified, purged CSS file for production. Commit the compiledweb/css/styles.cssto your repo.
Configuring tailwind.config.js for Magento 2
This is the most important file in your Tailwind setup. Here's an annotated example config for a Hyvä child theme:
/** @type {import('tailwindcss').Config} */ module.exports = { // ── Content paths: every file Tailwind should scan for classes ── content: [ "../templates/**/*.phtml", "../Magento_*/**/*.phtml", "../Hyva_*/**/*.phtml", "./../../../Hyva_*/**/templates/**/*.phtml", // Also scan JS files if you use Tailwind classes in Alpine components "../Magento_*/**/js/**/*.js", ], theme: { // extend keeps all Tailwind defaults, adds your brand tokens extend: { colors: { primary: "#0d9488", // your brand teal secondary: "#0c1a2e", accent: "#f59e0b", }, fontFamily: { sans: ["'DM Sans'", "sans-serif"], display: ["'Sora'", "sans-serif"], }, screens: { xs: "480px", // custom extra-small breakpoint }, }, }, // ── Plugins: official Tailwind plugins ── plugins: [ require("@tailwindcss/forms"), require("@tailwindcss/typography"), require("@tailwindcss/aspect-ratio"), ], };
.phtml file is not matched by your content array, its Tailwind classes will be purged from the production build and styles will silently disappear. Always add new module template paths when you create custom modules.
Using Tailwind Utility Classes in .phtml Templates
In Hyvä, you apply Tailwind classes directly in your PHP templates. There is no separate SCSS or CSS to maintain the markup is the style source.
<!-- Product card with Tailwind utility classes --> <div class="flex flex-col bg-white rounded-xl shadow-sm border border-gray-100 hover:shadow-md transition-shadow duration-200 overflow-hidden"> <!-- Product image --> <div class="aspect-w-1 aspect-h-1 bg-gray-50"> <?= $block->getImage($product, 'category_page_grid')->toHtml() ?> </div> <!-- Product info --> <div class="p-4 flex flex-col gap-2"> <h2 class="text-sm font-semibold text-gray-900 line-clamp-2 leading-snug"> <?= $escaper->escapeHtml($product->getName()) ?> </h2> <p class="text-primary font-bold text-base"> <?= $block->getProductPrice($product) ?> </p> <!-- Alpine.js + Tailwind class binding --> <button class="mt-auto w-full py-2 px-4 rounded-lg font-semibold text-sm transition-colors duration-200" x-bind:class="isAdded ? 'bg-green-500 text-white' : 'bg-primary text-white hover:bg-teal-700'" x-on:click="addToCart()"> <span x-text="isAdded ? 'Added ✓' : 'Add to Cart'"></span> </button> </div> </div>
Handling Dynamic Classes: The Safelist
Tailwind's JIT purge scans for static strings. If you build class names dynamically in PHP for example, constructing a colour class from a CMS setting Tailwind will not find it and will purge it.
"text-" . $color . "-500" Tailwind never sees the full class name. It gets purged.
Use full class strings "text-red-500", or add dynamic patterns to the safelist in config.
module.exports = { content: [/* ... */], // ── Safelist: classes that must survive purge ── safelist: [ // Protect all colour variants for a dynamic badge system { pattern: /^(bg|text|border)-(red|green|blue|amber|teal)-(100|500|700)$/, variants: ["hover", "focus"], }, // Protect specific utility classes used in JS-built markup "ring-2", "ring-primary", "scale-95", ], };
Writing Custom Component Classes with @apply
For repeated UI patterns buttons, badges, form inputs you can extract Tailwind utilities into reusable component classes using the @apply directive in your styles.css. This keeps your templates clean without sacrificing the Tailwind system.
/* Tailwind base directives */ @tailwind base; @tailwind components; @tailwind utilities; /* ── Custom component layer ── */ @layer components { /* Primary button reuses Tailwind utilities */ .btn-primary { @apply inline-flex items-center justify-center gap-2 px-5 py-2.5 rounded-lg font-semibold text-sm text-white bg-primary hover:bg-teal-700 active:scale-95 transition-all duration-150 cursor-pointer; } /* Secondary / ghost button */ .btn-ghost { @apply inline-flex items-center justify-center gap-2 px-5 py-2.5 rounded-lg font-semibold text-sm text-primary border border-primary hover:bg-teal-50 transition-colors duration-150; } /* Status badge */ .badge { @apply inline-block px-2.5 py-0.5 rounded-full text-xs font-bold uppercase tracking-wide; } /* Form input override Magento default inputs */ .form-input-hyva { @apply w-full rounded-lg border border-gray-300 bg-white px-3 py-2 text-sm text-gray-900 placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-primary focus:border-primary transition duration-150; } }
Tailwind Build Commands Quick Reference
| Command | When to use | Output |
|---|---|---|
npm run watch |
During active development rebuilds CSS on every file save | Unminified CSS |
npm run build |
Before deploying to staging or production | Minified + purged CSS |
npm run build-dev |
One-shot build without minification (useful for debug) | Readable, unpurged CSS |
npm install |
First setup, or after adding new Tailwind plugins | Installs dependencies |
Advanced Patterns for Magento 2 + Tailwind
Responsive Design with Tailwind Breakpoints
Tailwind's responsive modifiers work exactly the same in Hyvä's .phtml templates as they do in any HTML file. Use sm:, md:, lg:, and xl: prefixes to apply utilities at specific breakpoints. The default breakpoints are set in the Hyvä config and can be extended in your child theme's tailwind.config.js.
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5"> <?php foreach ($products as $product): ?> <!-- product card --> <?php endforeach; ?> </div>
Alpine.js + Tailwind: Dynamic State Styling
Alpine.js's x-bind:class directive accepts an object where keys are Tailwind class strings and values are boolean expressions. This lets you conditionally apply Tailwind classes based on component state perfect for cart interactions, accordions, mobile menus, and dropdowns.
<div x-data="{ open: false }" class="border border-gray-200 rounded-lg overflow-hidden"> <!-- Accordion trigger --> <button class="w-full flex items-center justify-between p-4 text-left font-semibold text-gray-900 hover:bg-gray-50 transition-colors" x-on:click="open = !open"> <span>Delivery Information</span> <!-- Animated chevron --> <svg class="w-5 h-5 text-gray-400 transition-transform duration-200" x-bind:class="{ 'rotate-180': open }" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" /> </svg> </button> <!-- Accordion content --> <div x-show="open" x-transition:enter="transition duration-200 ease-out" x-transition:enter-start="opacity-0 -translate-y-2" x-transition:enter-end="opacity-100 translate-y-0" class="px-4 pb-4 text-sm text-gray-600 leading-relaxed"> <?= $escaper->escapeHtml($block->getDeliveryInfo()) ?> </div> </div>
Adding Tailwind Plugins for Magento UI
Three Tailwind plugins are especially useful in a Magento 2 context. The forms plugin resets browser input styles so your checkout fields can be styled with utilities. The typography plugin adds the prose class for rendering CMS page content cleanly. The aspect-ratio plugin handles product image containers without JavaScript.
# Inside web/tailwind/ directory npm install -D @tailwindcss/forms \ @tailwindcss/typography \ @tailwindcss/aspect-ratio
Common Mistakes to Avoid
-
Editing the compiled CSS directly it gets overwritten on every build. All changes must go through
styles.cssortailwind.config.js. -
Forgetting to add new module paths to content new Magento modules with
.phtmltemplates must be included in thecontentarray or their classes will be purged. -
Using arbitrary values excessively
w-[347px]is fine occasionally, but if you need the same value repeatedly, add it totheme.extendinstead. -
Not committing the compiled CSS production servers typically don't have Node installed. Always commit
web/css/styles.cssto your repository. -
Mixing
@applywith responsive prefixes incorrectly you cannot use responsive prefixes inside@apply. Apply base styles in@layer components, then use responsive modifiers directly in templates.
Final Thoughts
Tailwind CSS in Hyvä Theme is one of the most developer-friendly setups available in the Magento ecosystem. The combination of utility-first styling, Alpine.js reactivity, and a lean build pipeline means you can build complex, performant storefronts significantly faster than with any Luma-based approach.
The key is getting the configuration right from the start: solid content paths, a well-structured theme.extend, and a deployment workflow that always runs npm run build before pushing to production. Get those three right and the rest follows naturally.
At Mavenbird, we've built and upgraded dozens of Hyvä stores with Tailwind. Whether you're starting from scratch, migrating from Luma, or cleaning up a messy Hyvä implementation, our team knows the patterns that work.
Let Mavenbird Build Your Hyvä + Tailwind Storefront
From Hyvä child theme setup to full Tailwind design systems, custom Alpine.js components, and performance-tuned production builds we handle the whole frontend stack.