ddtcorex/maestro-skills/magento2-hyva-dev

Trust tier 3
  • community
  • ext:[{"type":"template","description":"Custom templates for code generation"}]
Source check pending

Checking that the SKILL.md file is still in the repository.

This skill should be used when the user asks to "create a Hyvä theme", "set up a child theme", "build an Alpine.js component", "make this CSP-compliant", "add Tailwind CSS classes", "convert Luma to Hyvä", "migrate Knockout to Alpine", work on "Hyvä checkout" or "Hyvä React components", or handle "Tailwind configuration" or "CSP nonce registration". Provides expert Hyvä theme development for Magento 2. This is a SPECIALIZED skill for Hyvä-specific patterns. DEPENDENT on magento2-dev-core for PHP/backend patterns.

Install

npx vskill@latest install ddtcorex/maestro-skills/magento2-hyva-dev

Semi-extensibleMentions customization, but needs skill-specific knowledge. Read the extensible skills standard

SKILL.md

Magento 2 Hyvä Developer

Hyvä is a modern Magento 2 frontend framework with dramatically simplified JavaScript and CSS. This skill covers Hyvä-specific patterns.

Related Skills

REQUIRED BACKGROUND: Load magento2-dev-core first — it defines the PHP/backend patterns (DI, escaping, repositories) this skill assumes for any ViewModel or backend code behind a Hyvä template.

Hyvä and Luma (magento2-frontend-dev) are mutually exclusive theme stacks — check the theme's theme.xml parent (Hyva/default/Hyva/reset vs Magento/blank) and composer.json for hyva-themes/* packages before assuming either applies. Hybrid projects (e.g. dual-stack) keep both: app/design/frontend/<Vendor>/hyva_* → this skill, app/design/frontend/<Vendor>/luma_child → magento2-frontend-dev. Pair with govard-magento for the container/CLI side.

Detect the project's actual setup first

Otherwise: read theme.xml parent, <theme>/web/tailwind/package.json, tailwind.config.js or tailwind-source.css+hyva.config.json, and composer.lock — verify, don't memorize table (verified 2026-08-26).

Hyvä/Tailwind conventions vary a lot by project age — check before applying a pattern:

  • Tailwind version: v4 uses CSS-based config and hyva.config.json design tokens; v2/v3 use tailwind.config.js. Check web/tailwind/package.json.
  • CSP build: Hyva/default-csp vs the plain Hyva/default/Hyva/reset parent in theme.xml. Applying CSP-only nonce patterns to a non-CSP theme (or vice versa) wastes effort.
  • Parent theme: Hyva/reset (built from scratch) vs Hyva/default (full starter) changes how much markup/CSS already exists to extend rather than rewrite.

Hyvä vs Luma Comparison

AspectLumaHyvä
JavaScript~200 resources (RequireJS/Knockout)2 resources (Alpine.js)
CSSLESS-basedTailwind CSS
Bundle Size500KB+<50KB
Core Web VitalsChallengingOptimized
Learning CurveSteepGentle
MaintenanceComplexSimple

Theme Structure

Creating a Child Theme

Always copy web/ from the parent theme rather than creating it from scratch — it carries the Tailwind config and build tooling the theme needs:

bash
mkdir -p app/design/frontend/<Vendor>/<Theme>/web
cp -r vendor/hyva-themes/magento2-default-theme/web/* app/design/frontend/<Vendor>/<Theme>/web/
# For a CSP theme, copy from magento2-default-theme-csp instead

Then add registration.php, theme.xml (parent: Hyva/default, Hyva/reset, or Hyva/default-csp), and composer.json, install Tailwind deps and build, then bin/magento setup:upgrade && bin/magento cache:flush to pick up the new theme.

app/design/frontend/Vendor/Theme/
├── registration.php
├── theme.xml
├── composer.json
├── package.json
├── tailwind.config.js
├── package.json
├── web/
│ ├── tailwind/
│ │ ├── base/ # Preflight, resets
│ │ ├── components/ # Reusable components
│ │ │ ├── buttons.css
│ │ │ ├── forms.css
│ │ │ └── messages.css
│ │ ├── utilities/ # Custom utilities
│ │ └── theme/ # Page-specific
│ └── js/
│ └── alpinejs/ # Alpine components
├── layout/
│ └── default.xml
└── templates/
└── ...

CSP (Content Security Policy) Compliance

Critical: PCI-DSS 4.0 (Required since April 2025)

Payment pages MUST NOT use:

  • unsafe-eval CSP directive
  • unsafe-inline CSP directive
CSP Nonce Registration

Every inline script MUST register with CSP:

php
<?php
/** @var Hyva\Theme\ViewModel\HyvaCsp $hyvaCsp */
use Hyva\Theme\ViewModel\HyvaCsp;
?>
<!-- Register script before using nonce -->
<?php $hyvaCsp->registerInlineScript() ?>
<script nonce="<?= $cspNonce ?>">
// CSP-compliant code
</script>
$hyvaCsp is ambient — never demand an assignment. The Hyvä theme
provides the $hyvaCsp view model to every template: a /** @var */
docblock (as above) is the complete setup, no
$viewModels->require(HyvaCsp::class) needed. isset($hyvaCsp) guards
are the correct pattern for templates that must also render without
Hyvä/CSP — never flag them as dead code, and never claim isset() is
always false from template text alone. If CSP registration genuinely
looks unwired, check layout XML / theme wiring first (a missing call to
a mechanism the project doesn't have is "not applicable", not a finding).
CSP-Compatible Alpine.js Patterns

WRONG (CSP violations):

html
<!-- These patterns break CSP -->
<button @click="count++">Add</button>
<span x-show="!loading">Ready</span>
<input :value="name" @input="name = $event.target.value">

CORRECT (CSP-compliant):

html
<!-- Use methods for mutations -->
<button @click="increment">Add</button>
<span x-show="isNotLoading">Ready</span>
<input :value="name" @input="updateName">
javascript
function initComponent() {
return {
count: 0,
loading: true,
name: '',
increment() {
this.count++;
},
isNotLoading() {
return !this.loading;
},
updateName(event) {
this.name = event.target.value;
}
}
}
window.addEventListener('alpine:init', () => {
Alpine.data('initComponent', initComponent);
}, {once: true})
Registering Alpine Components
php
<?php
/** @var Hyva\Theme\ViewModel\HyvaCsp $hyvaCsp */
?>
<script>
function initProductSlider() {
return {
products: [],
currentIndex: 0,
init() {
// Initialization
},
next() {
this.currentIndex = (this.currentIndex + 1) % this.products.length;
},
prev() {
this.currentIndex = (this.currentIndex - 1 + this.products.length) % this.products.length;
}
}
}
window.addEventListener('alpine:init', () => Alpine.data('initProductSlider', initProductSlider), {once: true})
</script>
<?php $hyvaCsp->registerInlineScript() ?>

Alpine.js Component Structure

Basic Component
javascript
// web/js/alpinejs/Example.js
function initExample() {
return {
// Observable state
isOpen: false,
items: [],
selectedId: null,
// Computed (reactive)
get hasItems() {
return this.items.length > 0;
},
// Methods
toggle() {
this.isOpen = !this.isOpen;
},
select(id) {
this.selectedId = id;
},
// Lifecycle
init() {
// Called when component initializes
this.loadData();
},
loadData() {
fetch('/api/data')
.then(res => res.json())
.then(data => this.items = data);
}
}
}
window.addEventListener('alpine:init', () => Alpine.data('initExample', initExample), {once: true})
Template Usage
html
<div x-data="initExample">
<button @click="toggle">Toggle</button>
<div x-show="isOpen">
<template x-for="item in items" :key="item.id">
<div @click="select(item.id)" :class="{ 'selected': selectedId === item.id }">
<span x-text="item.name"></span>
</div>
</template>
</div>
</div>
<script>
function initExample() {
// ... component logic
}
window.addEventListener('alpine:init', () => Alpine.data('initExample', initExample), {once: true})
</script>
<?php $hyvaCsp->registerInlineScript() ?>
Passing Data from PHP
php
<?php
/** @var \Magento\Framework\Escaper $escaper */
/** @var \Hyva\Theme\ViewModel\HyvaCsp $hyvaCsp */
$productsJson = json_encode($products, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT);
?>
<div x-data="initProductList"
data-products="<?= $escaper->escapeHtmlAttr($productsJson) ?>">
</div>
<script>
function initProductList() {
return {
products: [],
init() {
this.products = JSON.parse(this.$root.dataset.products || '[]');
}
}
}
window.addEventListener('alpine:init', () => Alpine.data('initProductList', initProductList), {once: true})
</script>
<?php $hyvaCsp->registerInlineScript() ?>

Hyvä Utilities

Hyvä provides global utilities via the hyva object:

Form Handling
javascript
// Get form key
hyva.getFormKey()
// Submit form via POST
hyva.postForm({
action: '/checkout',
data: { product_id: 123, qty: 1 }
})
// Alternative with fetch
async function submitForm(url, data) {
const formKey = hyva.getFormKey();
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
},
body: JSON.stringify({ ...data, form_key: formKey })
});
return response.json();
}
Cookies
javascript
hyva.getCookie('customer_segment')
hyva.setCookie('recent_viewed', productId, 30)
Formatting
javascript
hyva.formatPrice(price, showSign)
hyva.str('Hello {0}', name)
hyva.safeParseNumber(value)
DOM Manipulation
javascript
hyva.replaceDomElement('#target', '<span>New content</span>')
hyva.trapFocus(modalElement)
Events
javascript
// After Alpine initialization
hyva.alpineInitialized(function() {
console.log('Alpine ready');
})

View Models

Prefer view models (Hyva\Theme\Model\ViewModelInterface / Magento's ArgumentInterface) over blocks for passing data to templates — they keep PHP logic out of the theme directory (which should hold only templates, layout, i18n, and web/ assets) and in a proper app/code module where Magento's DI can autoload the class.

php
// app/code/Vendor/Module/ViewModel/ProductInfo.php
declare(strict_types=1);
namespace Vendor\Module\ViewModel;
use Hyva\Theme\Model\ViewModelInterface;
use Magento\Framework\View\LayoutInterface;
class ProductInfo implements ViewModelInterface
{
public function __construct(
private readonly LayoutInterface $layout
) {}
public function isInStock(): bool
{
$product = $this->layout->getBlock('product.info')->getProduct();
return $product && $product->isInStock();
}
}
xml
<!-- layout/default.xml -->
<referenceBlock name="product.info">
<arguments>
<argument name="product_info_view_model" xsi:type="object">Vendor\Module\ViewModel\ProductInfo</argument>
</arguments>
</referenceBlock>
php
<?php /** @var \Vendor\Module\ViewModel\ProductInfo $productInfoViewModel */ ?>
<?php if ($productInfoViewModel->isInStock()): ?>
<button class="btn-cart">Add to Cart</button>
<?php endif; ?>

Tailwind CSS

Tailwind v4 (CSS-based config)

Newer Hyvä themes use Tailwind v4, which drops tailwind.config.js for a CSS-based config plus a hyva.config.json design-token file — check web/tailwind/package.json first, the two configs are not interchangeable.

css
/* web/tailwind/tailwind-source.css */
@import "tailwindcss";
@theme {
--color-primary: oklch(46% 0.2 265);
--spacing-xs: 0.5rem;
}
@layer components {
.btn-primary {
@apply bg-primary text-white px-4 py-2 rounded;
}
}
json
// hyva.config.json
{
"tokens": {
"src": "hyva.design.tokens.json",
"format": "default",
"cssSelector": "@theme"
}
}

Generate tokens/sources with npx hyva-sources / npx hyva-tokens rather than hand-rolling them. Alpine gating: Tailwind v4 sources come from CSS @source directives and hyva.config.json tailwind.include/exclude; editing hyva.config.json on a v3 setup is a silent no-op — verify web/tailwind/package.json tailwindcss major before touching either config.

This is the start of the file. Read the full SKILL.md on GitHub.

Security

  • Security scanPassed, 95 of 100Pattern checks on the skill's files.
  • Model reviewPassed

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

7-day trend score
0

Not refreshed yet.

Extension points

  • templateCustom templates for code generation

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/ddtcorex/maestro-skills/magento2-hyva-dev/badge)](https://verified-skill.com/skills/ddtcorex/maestro-skills/magento2-hyva-dev)

Something wrong with this skill or its page? Tell us.