Comprehensive guidance on integrating Tailwind CSS and JavaScript in Hyvä Themes, including configuration merging, module registration, and build processes for Magento 2.
This skill provides comprehensive guidance on integrating Tailwind CSS and JavaScript in Hyvä Themes, including configuration merging, module registration, and build processes for Magento 2.
Hyvä Themes uses a sophisticated Tailwind CSS build system that:
twProps utility for dynamic themingapp/design/frontend/Vendor/ThemeName/
├── web/
│ ├── css/
│ │ └── styles.css # Compiled output (git-ignored)
│ ├── js/
│ │ └── custom.js # Custom JavaScript
│ └── tailwind/
│ ├── package.json # NPM dependencies
│ ├── postcss.config.js # PostCSS configuration
│ ├── tailwind.config.js # Main Tailwind config
│ ├── tailwind-source.css # Source CSS
│ ├── tailwind.browser-jit.css # Browser JIT styles (optional)
│ ├── tailwind.browser-jit-config.js # Browser JIT config (optional)
│ └── components/ # Component-specific CSS
│ ├── typography.css
│ ├── button.css
│ ├── forms.css
│ └── ...
Navigate to your theme's web/tailwind directory:
cd app/design/frontend/Vendor/ThemeName/web/tailwind
npm install @hyva-themes/hyva-modules
For Hyvä 1.1.14+: This package is included by default.
Update your theme's tailwind.config.js to use mergeTailwindConfig:
const { twProps, mergeTailwindConfig } = require('@hyva-themes/hyva-modules');
const colors = require('tailwindcss/colors');
/** @type {import('tailwindcss').Config} */
module.exports = mergeTailwindConfig({
content: [
// Current theme's phtml and layout XML files
'../../**/*.phtml',
'../../*/layout/*.xml',
'../../*/page_layout/override/base/*.xml',
// Parent theme (for child themes)
// '../../../../../../../vendor/hyva-themes/magento2-default-theme/**/*.phtml',
// '../../../../../../../vendor/hyva-themes/magento2-default-theme/*/layout/*.xml',
// app/code modules (if using custom modules)
// '../../../../../../../app/code/**/*.phtml',
],
theme: {
extend: {
fontFamily: {
sans: ['Segoe UI', 'Helvetica Neue', 'Arial', 'sans-serif']
},
colors: twProps({
primary: {
lighter: colors.blue['600'],
DEFAULT: colors.blue['700'],
darker: colors.blue['800']
},
secondary: {
lighter: colors.gray['100'],
DEFAULT: colors.gray['200'],
darker: colors.gray['300']
}
}),
backgroundColor: twProps({
container: {
lighter: '#ffffff',
DEFAULT: '#fafafa',
darker: '#f5f5f5'
}
}),
textColor: ({ theme }) => ({
...twProps({
primary: {
lighter: colors.gray['700'],
DEFAULT: colors.gray['800'],
darker: colors.gray['900']
}
}, 'text')
}),
minHeight: {
'a11y': '44px',
'screen-25': '25vh',
'screen-50': '50vh',
'screen-75': '75vh'
},
container: {
center: true,
padding: '1.5rem'
}
}
},
plugins: [
require('@tailwindcss/forms'),
require('@tailwindcss/typography')
]
});
Key Features:
mergeTailwindConfig() - Wraps config to enable automatic module mergingtwProps() - Generates CSS variables for dynamic themingcontent array - Specifies files to scan for Tailwind classesconst { postcssImportHyvaModules } = require('@hyva-themes/hyva-modules');
module.exports = {
plugins: [
postcssImportHyvaModules({
excludeDirs: [] // Optionally exclude specific module directories
}),
require('postcss-import'),
require('tailwindcss/nesting'),
require('tailwindcss'),
require('autoprefixer')
]
};
Purpose:
postcssImportHyvaModules - Automatically imports CSS from registered modulestailwindcss/nesting - Enables CSS nesting support (required for JIT)autoprefixer - Adds vendor prefixes for browser compatibility@import 'tailwindcss/base';
@import 'tailwindcss/components';
@import 'tailwindcss/utilities';
/* Component imports */
@import 'components/typography.css';
@import 'components/button.css';
@import 'components/forms.css';
@import 'components/cart.css';
@import 'components/product-page.css';
/* Custom utilities */
@layer utilities {
.text-balance {
text-wrap: balance;
}
}
app/code/Vendor/ModuleName/
├── registration.php
├── module.xml
├── etc/
│ └── hyva-themes.json (optional, for non-compatibility modules)
└── view/frontend/
├── templates/
│ └── custom-component.phtml
├── web/
│ ├── css/
│ │ └── custom-styles.css
│ └── js/
│ └── custom-script.js
└── tailwind/
└── tailwind.config.js
module.exports = {
content: [
'../templates/**/*.phtml',
'../layout/**/*.xml'
],
theme: {
extend: {
colors: {
'module-primary': '#3b82f6'
}
}
}
};
Important Notes:
tailwind.config.js file location${themeDirRequire} to reference theme's node_modules:const colors = require(`${themeDirRequire}/tailwindcss/colors`);
module.exports = {
theme: {
extend: {
colors: {
brand: colors.blue['600']
}
}
}
};
@layer components {
.custom-component {
@apply bg-module-primary text-white p-4 rounded;
}
}
The app/etc/hyva-themes.json file automatically regenerates when running:
bin/magento setup:upgradebin/magento module:enablebin/magento module:disablebin/magento hyva:config:generateExample hyva-themes.json:
{
"extensions": [
{
"src": "app/code/Vendor/ModuleName"
},
{
"src": "vendor/hyva-themes/magento2-some-module/src"
}
]
}
For non-compatibility modules, register via event observer:
etc/frontend/events.xml:
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd">
<event name="hyva_config_generate_before">
<observer name="Vendor_ModuleName"
instance="Vendor\ModuleName\Observer\RegisterModuleForHyvaConfig"/>
</event>
</config>
Observer/RegisterModuleForHyvaConfig.php:
<?php
declare(strict_types=1);
namespace Vendor\ModuleName\Observer;
use Magento\Framework\Component\ComponentRegistrar;
use Magento\Framework\Event\Observer;
use Magento\Framework\Event\ObserverInterface;
class RegisterModuleForHyvaConfig implements ObserverInterface
{
public function __construct(
private ComponentRegistrar $componentRegistrar
) {}
public function execute(Observer $event): void
{
$config = $event->getData('config');
$extensions = $config->hasData('extensions')
? $config->getData('extensions')
: [];
$moduleName = implode('_', array_slice(explode('\\', __CLASS__), 0, 2));
$path = $this->componentRegistrar->getPath(
ComponentRegistrar::MODULE,
$moduleName
);
$extensions[] = ['src' => substr($path, strlen(BP) + 1)];
$config->setData('extensions', $extensions);
}
}
# From theme's web/tailwind directory
cd app/design/frontend/Vendor/ThemeName/web/tailwind
npm install
npm run build-dev
# Generate Hyvä config
ddev exec bin/magento hyva:config:generate
# Build Tailwind CSS (minified)
npm --prefix app/design/frontend/Vendor/ThemeName/web/tailwind run build-prod
npm --prefix app/design/frontend/Vendor/ThemeName/web/tailwind run watch
PROXY_URL="https://ntotank.ddev.site" npm --prefix app/design/frontend/Uptactics/nto/web/tailwind run browser-sync
For child themes, use Tailwind's presets to inherit parent configuration:
const { mergeTailwindConfig } = require('@hyva-themes/hyva-modules');
const parentTheme = require('../../../../../../../vendor/hyva-themes/magento2-default-theme/web/tailwind/tailwind.config.js');
/** @type {import('tailwindcss').Config} */
module.exports = mergeTailwindConfig({
presets: [parentTheme], // Import parent theme config
content: [
'../../**/*.phtml',
'../../*/layout/*.xml',
// Parent theme paths
'../../../../../../../vendor/hyva-themes/magento2-default-theme/**/*.phtml'
],
theme: {
extend: {
// Override or extend parent theme settings
colors: {
'child-primary': '#ef4444'
}
}
}
});
The twProps utility generates CSS variables for dynamic theming:
const { twProps } = require('@hyva-themes/hyva-modules');
module.exports = mergeTailwindConfig({
theme: {
extend: {
colors: twProps({
primary: {
lighter: '#3b82f6',
DEFAULT: '#2563eb',
darker: '#1d4ed8'
}
})
}
}
});
Generated CSS:
:root {
--color-primary-lighter: #3b82f6;
--color-primary: #2563eb;
--color-primary-darker: #1d4ed8;
}
Usage in CSS:
.btn {
background-color: var(--color-primary);
}
Usage in Tailwind classes:
<button class="bg-primary text-white">Click Me</button>
For CMS content with dynamic Tailwind classes:
const colors = require('tailwindcss/colors');
module.exports = {
theme: {
container: {
center: true
},
extend: {
colors: {
'my-gray': '#888877',
primary: {
lighter: colors.purple['300'],
DEFAULT: colors.purple['800'],
darker: colors.purple['900']
}
}
}
}
};
{
"tailwindBrowserJitConfigPath": "../../../../../app/design/frontend/Vendor/ThemeName/web/tailwind/tailwind.browser-jit-config.js",
"tailwindBrowserJitCssPath": "../../../../../app/design/frontend/Vendor/ThemeName/web/tailwind/tailwind.browser-jit.css"
}
Add to the end of tailwind.config.js:
// Merge browser JIT config if it exists
if (require('fs').existsSync('./tailwind.browser-jit-config.js')) {
function isObject(item) {
return (item && typeof item === 'object' && !Array.isArray(item));
}
function mergeDeep(target, ...sources) {
if (!sources.length) return target;
const source = sources.shift();
if (isObject(target) && isObject(source)) {
for (const key in source) {
if (isObject(source[key])) {
if (!target[key]) Object.assign(target, { [key]: {} });
mergeDeep(target[key], source[key]);
} else {
Object.assign(target, { [key]: source[key] });
}
}
}
return mergeDeep(target, ...sources);
}
mergeDeep(module.exports, require('./tailwind.browser-jit-config.js'));
}
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
<type name="Hyva\CompatModuleFallback\Observer\HyvaThemeHyvaConfigGenerateBefore">
<arguments>
<argument name="exclusions" xsi:type="array">
<item name="Hyva_VendorModule" xsi:type="boolean">true</item>
</argument>
</arguments>
</type>
</config>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd">
<event name="hyva_config_generate_before">
<observer name="ObserverName" disabled="true"/>
</event>
</config>
const { postcssImportHyvaModules } = require('@hyva-themes/hyva-modules');
module.exports = {
plugins: [
postcssImportHyvaModules({
excludeDirs: [
'vendor/hyva-themes/magento2-hyva-checkout/src',
'app/code/Vendor/ExcludedModule'
]
}),
// ... other plugins
]
};
To force include specific classes that Tailwind might not detect:
module.exports = mergeTailwindConfig({
safelist: [
'mt-10',
'md:mt-20',
{
pattern: /^(bg|text|border)-(primary|secondary)/,
variants: ['hover', 'focus']
}
],
// ... rest of config
});
var config = {
map: {
'*': {
'customModule': 'Vendor_ModuleName/js/custom-module'
}
},
paths: {
'customLib': 'Vendor_ModuleName/js/lib/custom-library'
},
shim: {
'customLib': {
deps: ['jquery']
}
}
};
// view/frontend/web/js/alpine/custom-component.js
export default function customComponent() {
return {
isOpen: false,
toggle() {
this.isOpen = !this.isOpen;
},
init() {
console.log('Component initialized');
}
};
}
Usage in templates:
<div x-data="customComponent()">
<button @click="toggle">Toggle</button>
<div x-show="isOpen">Content</div>
</div>
<script>
require(['Vendor_ModuleName/js/alpine/custom-component'], function(customComponent) {
window.customComponent = customComponent;
});
</script>
# Nuclear cache clear
ddev exec rm -rf var/cache/* var/page_cache/* var/view_preprocessed/* pub/static/*
ddev exec bin/magento cache:flush
ddev exec bin/magento hyva:config:generate
# Rebuild CSS
cd app/design/frontend/Vendor/ThemeName/web/tailwind
npm run build-prod
app/etc/hyva-themes.json contains your moduleddev exec bin/magento hyva:config:generate
tailwind.config.js path: view/frontend/tailwind/tailwind.config.jspostcss.config.js includes postcssImportHyvaModulescontent array in tailwind.config.jssafelist for dynamically generated classesUse ${themeDirRequire} instead of direct require():
// ❌ Wrong
const colors = require('tailwindcss/colors');
// ✅ Correct
const colors = require(`${themeDirRequire}/tailwindcss/colors`);
hyva:config:generateFor the NTOTank project (current directory structure):
Theme Location: app/design/frontend/Uptactics/nto/
Tailwind Directory: app/design/frontend/Uptactics/nto/web/tailwind/
Build Commands:
# Development
ddev exec composer build-tailwind
# Production
ddev exec composer build-prod
# Watch mode
ddev exec composer watch
Custom Modules Integration:
Uptactics_NtoTheme - Core theme moduleProxiBlue_SearchDynaTable - Search filtering with Alpine.jsUptactics_ToolbarFilters - Custom category filtersEnsure all custom modules have proper Tailwind configs at:
app/code/Uptactics/ModuleName/view/frontend/tailwind/tailwind.config.js