How to Use Tailwind CSS in Magento 2 Hyvä Theme

How to Use Tailwind CSS in Magento 2 Hyvä Theme

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.

~8kb
typical Hyvä CSS output after purge
3.5×
faster frontend builds vs. Luma / Blank
100
Lighthouse scores achievable with Hyvä + Tailwind
v3.4
Tailwind version bundled in Hyvä 1.3+

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.

UTL
Core

Utility-First Speed

Style directly in your .phtml templates. No switching context between HTML and CSS files.

PGE
Performance

Tiny Production CSS

Tailwind's JIT purge removes unused classes. Final stylesheet is typically under 10kb vs 300kb+ for Luma.

DSN
Design System

Consistent Tokens

Define colours, spacing, typography once in tailwind.config.js. Every template uses the same system.

DX
Dev Experience

Great DX

Instant class suggestions in VS Code, zero-config setup in Hyvä, and predictable responsive modifiers.

ALP
Alpine.js

Alpine Synergy

Alpine's x-bind:class works perfectly with Tailwind utility strings toggle states cleanly in templates.

EXT
Extendable

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-module to 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.

app/design/frontend/Hyva/default/web/tailwind/ Directory
# 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

  1. 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.
  2. Install Node dependencies Run npm install (or yarn) inside web/tailwind/. This installs Tailwind CSS, PostCSS, and autoprefixer from the locked package.json.
  3. Run the development build watcher Execute npm run watch. Tailwind JIT will watch your .phtml files and rebuild the CSS instantly when classes change.
  4. Extend tailwind.config.js with your design tokens Add your brand colours, font families, custom spacing, and breakpoints inside the theme.extend block. Never replace the defaults extend them.
  5. Run the production build before deployment Execute npm run build to generate a minified, purged CSS file for production. Commit the compiled web/css/styles.css to 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:

web/tailwind/tailwind.config.js JavaScript
/** @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"),
  ],
};
      
!
Content paths are critical. If a .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.

Magento_Catalog/templates/product/view/addtocart.phtml PHTML
<!-- 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.

Purged Broken Dynamic class concatenation

"text-" . $color . "-500" Tailwind never sees the full class name. It gets purged.

✓ Safe Works Full class names or safelist

Use full class strings "text-red-500", or add dynamic patterns to the safelist in config.

tailwind.config.js safelist example JavaScript
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.

web/tailwind/styles.css CSS
/* 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;
  }
}
      
Use @layer components wisely. The component layer sits between base and utilities in specificity meaning you can always override a component class with a utility. This is intentional in Tailwind's design and keeps the system flexible.

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

1

Responsive Design with Tailwind Breakpoints

Mobile-first utility modifiers in .phtml templates

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.

Responsive product grid PHTML
<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>
          
Mobile-first sm: md: lg: xl: 2xl: Custom breakpoints via config
2

Alpine.js + Tailwind: Dynamic State Styling

x-bind:class patterns for interactive components

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.

Alpine.js accordion with Tailwind PHTML
<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>
          
x-bind:class x-transition rotate-180 opacity-0 / opacity-100
3

Adding Tailwind Plugins for Magento UI

Forms, typography, and aspect-ratio plugins

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.

Install plugins Shell
# Inside web/tailwind/ directory
npm install -D @tailwindcss/forms \
               @tailwindcss/typography \
               @tailwindcss/aspect-ratio
          
@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.css or tailwind.config.js.
  • Forgetting to add new module paths to content new Magento modules with .phtml templates must be included in the content array 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 to theme.extend instead.
  • Not committing the compiled CSS production servers typically don't have Node installed. Always commit web/css/styles.css to your repository.
  • Mixing @apply with responsive prefixes incorrectly you cannot use responsive prefixes inside @apply. Apply base styles in @layer components, then use responsive modifiers directly in templates.
Tailwind in Hyvä isn't just a styling choice it's a fundamental shift in how you think about Magento frontend. Once your team is fluent in utility classes, you'll never want to go back to the LESS compilation cycle of Luma.

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.

Need a Hyvä Expert?

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.

Hyvä Child Theme Setup
Tailwind Design System
Alpine.js Components
Custom Module Templates
Production Build Pipeline
Luma → Hyvä Migration


Loading...

Talk to an Expert

Request a Free Quote and expert consultation.