Claude-skill-registry hyva-tailwind-integration
Comprehensive guidance on integrating Tailwind CSS and JavaScript in Hyvä Themes, including configuration merging, module registration, and build processes for Magento 2.
git clone https://github.com/majiayu000/claude-skill-registry
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/hyva-tailwind-integration" ~/.claude/skills/majiayu000-claude-skill-registry-hyva-tailwind-integration && rm -rf "$T"
skills/data/hyva-tailwind-integration/SKILL.mdHyvä Theme Tailwind CSS & JS Integration Skill
Purpose
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.
When to Use This Skill
- Setting up a new Hyvä theme with Tailwind CSS
- Creating Hyvä compatibility modules with custom styles
- Configuring Tailwind config merging across modules
- Understanding the Hyvä Tailwind build system
- Troubleshooting CSS compilation issues
- Implementing theme inheritance with proper Tailwind configuration
Overview of Hyvä Tailwind Architecture
Hyvä Themes uses a sophisticated Tailwind CSS build system that:
- Automatically merges Tailwind configurations from multiple modules
- Scans templates across theme, parent themes, and modules for CSS classes
- Supports CSS variables via the
utility for dynamic themingtwProps - Enables module-specific styling without theme modifications
Key Components
- @hyva-themes/hyva-modules - NPM package for config/CSS merging
- hyva-themes.json - Registry of modules with Tailwind configs
- tailwind.config.js - Theme-level Tailwind configuration
- tailwind-source.css - Source CSS with @tailwind directives
Directory Structure
app/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 │ └── ...
Setup Process
1. Install Hyvä Modules Package (Themes < 1.1.14)
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.
2. Configure tailwind.config.js
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:
- Wraps config to enable automatic module mergingmergeTailwindConfig()
- Generates CSS variables for dynamic themingtwProps()
array - Specifies files to scan for Tailwind classescontent
3. Configure postcss.config.js
const { 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:
- Automatically imports CSS from registered modulespostcssImportHyvaModules
- Enables CSS nesting support (required for JIT)tailwindcss/nesting
- Adds vendor prefixes for browser compatibilityautoprefixer
4. Source CSS Structure (tailwind-source.css)
@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; } }
Module Integration
Creating a Compatibility Module with Tailwind Config
1. Module Directory Structure
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
2. Module Tailwind Config (view/frontend/tailwind/tailwind.config.js)
module.exports = { content: [ '../templates/**/*.phtml', '../layout/**/*.xml' ], theme: { extend: { colors: { 'module-primary': '#3b82f6' } } } };
Important Notes:
- Paths are relative to the
file locationtailwind.config.js - Use
to reference theme's node_modules:${themeDirRequire}
const colors = require(`${themeDirRequire}/tailwindcss/colors`); module.exports = { theme: { extend: { colors: { brand: colors.blue['600'] } } } };
3. Module CSS (view/frontend/web/css/source/module.css)
@layer components { .custom-component { @apply bg-module-primary text-white p-4 rounded; } }
Registering Modules in hyva-themes.json
The
app/etc/hyva-themes.json file automatically regenerates when running:
bin/magento setup:upgradebin/magento module:enablebin/magento module:disablebin/magento hyva:config:generate
Example hyva-themes.json:
{ "extensions": [ { "src": "app/code/Vendor/ModuleName" }, { "src": "vendor/hyva-themes/magento2-some-module/src" } ] }
Manual Module Registration
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); } }
Build Commands
Development Build
# From theme's web/tailwind directory cd app/design/frontend/Vendor/ThemeName/web/tailwind npm install npm run build-dev
Production Build
# 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
Watch Mode (Development)
npm --prefix app/design/frontend/Vendor/ThemeName/web/tailwind run watch
With BrowserSync
PROXY_URL="https://ntotank.ddev.site" npm --prefix app/design/frontend/Uptactics/nto/web/tailwind run browser-sync
Theme Inheritance & Presets
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' } } } });
CSS Variables with twProps
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>
Browser JIT (Just-In-Time) Compilation
For CMS content with dynamic Tailwind classes:
tailwind.browser-jit-config.js
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'] } } } } };
Optional Configuration (etc/cms-tailwind-jit-theme-config.json)
{ "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" }
Merge Browser JIT into Main Config
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')); }
Excluding Modules
Exclude from Compatibility Module Registry (di.xml)
<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>
Exclude from Event Observer Registration (frontend/events.xml)
<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>
Exclude from CSS Merging (postcss.config.js)
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 ] };
Safelist Classes
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 });
JavaScript Loading
Module JavaScript (requirejs-config.js)
var config = { map: { '*': { 'customModule': 'Vendor_ModuleName/js/custom-module' } }, paths: { 'customLib': 'Vendor_ModuleName/js/lib/custom-library' }, shim: { 'customLib': { deps: ['jquery'] } } };
Alpine.js Components
// 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>
Troubleshooting
CSS Not Updating
# 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
Module Config Not Merging
- Check
contains your moduleapp/etc/hyva-themes.json - Regenerate config:
ddev exec bin/magento hyva:config:generate - Verify module's
path:tailwind.config.jsview/frontend/tailwind/tailwind.config.js - Check
includespostcss.config.jspostcssImportHyvaModules
Missing CSS Classes
- Add paths to
array incontenttailwind.config.js - Use
for dynamically generated classessafelist - Check Tailwind is scanning XML files if classes are in layout XML
Node Module Errors in Module Config
Use
${themeDirRequire} instead of direct require():
// ❌ Wrong const colors = require('tailwindcss/colors'); // ✅ Correct const colors = require(`${themeDirRequire}/tailwindcss/colors`);
Best Practices
- Use mergeTailwindConfig() - Always wrap theme config for module merging
- Use twProps() for colors - Generates CSS variables for dynamic theming
- Organize CSS by components - Keep component styles in separate files
- Use safelist sparingly - Only for truly dynamic classes
- Regenerate config after module changes - Run
hyva:config:generate - Use presets for child themes - Inherit parent theme configuration
- Document custom utilities - Add comments for team understanding
- Version lock @hyva-themes/hyva-modules - Prevent breaking changes
- Test build in production mode - Catch purging issues early
- Use Browser JIT for CMS content - Enable dynamic styling in admin-managed content
Project-Specific Implementation
For 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:
- Core theme moduleUptactics_NtoTheme
- Search filtering with Alpine.jsProxiBlue_SearchDynaTable
- Custom category filtersUptactics_ToolbarFilters
Ensure all custom modules have proper Tailwind configs at:
app/code/Uptactics/ModuleName/view/frontend/tailwind/tailwind.config.js