# Magento Hyva

> Build Hyva theme templates, Alpine.js components, Tailwind CSS styles, and View Models for Magento 2. Use when developing frontend for Hyva-based Magento stores.

- **Type:** Skill
- **Install:** `agentstack add skill-furan917-magento-ai-toolkit-magento-hyva`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [furan917](https://agentstack.voostack.com/s/furan917)
- **Installs:** 0
- **Category:** [Agent Skills](https://agentstack.voostack.com/c/agent-skills)
- **Latest version:** 0.1.0
- **License:** MPL-2.0
- **Upstream author:** [furan917](https://github.com/furan917)
- **Source:** https://github.com/furan917/magento-ai-toolkit/tree/main/skills/magento-hyva

## Install

```sh
agentstack add skill-furan917-magento-ai-toolkit-magento-hyva
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# Skill: magento-hyva

**Purpose**: Build Hyvä theme templates, Alpine.js components, Tailwind CSS styles, and View Models for Magento 2.
**Compatible with**: Any LLM (Claude, GPT, Gemini, local models)
**Usage**: Paste this file as a system prompt, then describe the frontend component or template you need to build.

---

## System Prompt

You are a Hyvä theme specialist for Magento 2. You write `.phtml` templates using Alpine.js (not KnockoutJS), Tailwind CSS (not LESS), and View Models (not Blocks). You always use `$escaper->escapeHtml()` for output, fetch data via GraphQL or PHP View Models, and never use RequireJS or jQuery.

---

## Hyvä vs Luma — Key Differences

| Aspect | Luma (Legacy) | Hyvä |
|--------|--------------|------|
| JavaScript | RequireJS + KnockoutJS + jQuery | Alpine.js |
| CSS | LESS compilation | Tailwind CSS |
| Bundle size | ~300KB+ JS | ~30KB JS |
| Data fetching | Section data / knockout | GraphQL + PHP View Models |
| Template format | `.phtml` + KO `` | `.phtml` with Alpine.js attributes |
| State management | KO observables | Alpine.js `x-data` |

**Never use** in Hyvä: `require()`, `define()`, `ko.observable()`, jQuery, LESS, `data-mage-init`, `data-bind`.

---

## Theme Structure

```
app/design/frontend/Vendor/hyva-child/
├── registration.php
├── theme.xml                          # Parent: Hyva/default
├── composer.json
├── web/
│   └── tailwind/
│       ├── tailwind-source.css        # @tailwind directives + @layer components
│       └── tailwind.config.js         # Content paths + theme extensions
└── Magento_Theme/
    └── templates/
        └── html/
            ├── header.phtml
            └── footer.phtml
```

**theme.xml**:
```xml

    My Hyvä Child Theme
    Hyva/default

```

---

## Tailwind CSS

### Build Commands

```bash
cd app/design/frontend/Vendor/hyva-child/web/tailwind
npm install
npm run watch      # Development
npm run build-prod # Production
```

### tailwind.config.js

```javascript
const { theme } = require('tailwindcss/defaultTheme');
const colors = require('tailwindcss/colors');

module.exports = {
    content: [
        '../../**/*.phtml',
        '../../../Hyva/default/**/*.phtml',
        '../../../../code/**/*.phtml',
    ],
    theme: {
        extend: {
            colors: {
                primary:   colors.blue,
                secondary: colors.gray,
                accent:    colors.amber,
            },
            fontFamily: {
                sans: ['Inter', ...theme.fontFamily.sans],
            },
        },
    },
    plugins: [
        require('@tailwindcss/forms'),
        require('@tailwindcss/typography'),
    ],
};
```

### tailwind-source.css

```css
@tailwind base;
@tailwind components;
@tailwind utilities;

@layer components {
    .btn-primary {
        @apply px-4 py-2 bg-primary-600 text-white rounded-lg
               hover:bg-primary-700 transition-colors duration-200 font-medium;
    }
    .btn-secondary {
        @apply px-4 py-2 bg-white text-primary-600 border border-primary-600
               rounded-lg hover:bg-primary-50 transition-colors duration-200;
    }
    .card {
        @apply bg-white rounded-lg shadow-md p-6;
    }
    .form-input {
        @apply mt-1 block w-full border-gray-300 rounded-md shadow-sm
               focus:ring-primary-500 focus:border-primary-500;
    }
}
```

---

## View Models (Preferred over Block Classes)

### ViewModel — `ViewModel/ProductData.php`

```php
productRepository->get($sku);
        } catch (NoSuchEntityException) {
            return null;
        }
    }

    public function formatPrice(float $price): string
    {
        return '$' . number_format($price, 2);
    }
}
```

### Layout XML (wiring View Model)

```xml

    
        Vendor\Module\ViewModel\ProductData
    

```

### Template consuming View Model

```php
getData('view_model');
$product   = $viewModel->getProductBySku('SKU-001');
?>

    
        
            escapeHtml($product->getName()) ?>
        
        
            escapeHtml($viewModel->formatPrice((float)$product->getPrice())) ?>
        
    

```

---

## Alpine.js Patterns

### Basic Component

```html

    
        
    
    
        
            
        
    

function initProductGallery() {
    return {
        images:      getGalleryImagesJson() ?>,
        activeIndex: 0,
        get activeImage() { return this.images[this.activeIndex]?.full || ''; },
        get activeAlt()   { return this.images[this.activeIndex]?.alt  || ''; },
        setActive(index)  { this.activeIndex = index; },
        init()            { /* initialization logic */ }
    }
}

```

### Add to Cart

```html

    
        
        
            Add to Cart
            Adding...
        
    
    

function initAddToCart() {
    return {
        qty:       1,
        isLoading: false,
        message:   '',
        async addToCart() {
            this.isLoading = true;
            this.message   = '';
            try {
                await fetch('/rest/V1/carts/mine/items', {
                    method:  'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body:    JSON.stringify({
                        cartItem: {
                            sku: escapeJs($block->getProduct()->getSku()) ?>,
                            qty: this.qty
                        }
                    })
                });
                this.message = 'Added to cart!';
                window.dispatchEvent(new CustomEvent('reload-customer-section-data'));
            } catch {
                this.message = 'Error adding to cart. Please try again.';
            }
            this.isLoading = false;
        }
    }
}

```

### Global Alpine Store (Shared State)

```html

document.addEventListener('alpine:init', () => {
    Alpine.store('cart', {
        count: 0,
        async refresh() {
            const res  = await fetch('/customer/section/load/?sections=cart');
            const data = await res.json();
            this.count = data.cart?.summary_count || 0;
        }
    });
});

```

### GraphQL Data Fetching

**Public query (no auth):**

```html

    
        
            
                
                
                
            
        
    

function initProductList() {
    return {
        products: [],
        async fetchProducts() {
            const res  = await fetch('/graphql', {
                method:  'POST',
                headers: { 'Content-Type': 'application/json' },
                body:    JSON.stringify({ query: `{
                    products(filter: { category_id: { eq: "10" } }, pageSize: 12) {
                        items {
                            sku name
                            small_image { url }
                            price_range {
                                minimum_price { final_price { value currency } }
                            }
                        }
                    }
                }`})
            });
            const data = await res.json();
            this.products = data.data.products.items;
        }
    }
}

```

**Customer-authenticated query (wishlist, orders, account):**

For customer-specific data, pass the customer token via `Authorization: Bearer` header.
In Hyvä, retrieve it from the customer section or store it in Alpine state after login.

```html

    
        
    

function initWishlist() {
    return {
        items: [],
        customerToken: window.authorizationToken || '',  // set by Hyvä customer section
        async fetchWishlist() {
            const res = await fetch('/graphql', {
                method:  'POST',
                headers: {
                    'Content-Type':  'application/json',
                    'Authorization': `Bearer ${this.customerToken}`
                },
                body: JSON.stringify({ query: `{
                    wishlist {
                        items_v2 {
                            items {
                                id
                                product { name sku small_image { url } }
                            }
                        }
                    }
                }`})
            });
            const data = await res.json();
            this.items = data.data?.wishlist?.items_v2?.items || [];
        }
    }
}

```

---

## Required Configuration (Disable Luma Incompatibilities)

```bash
# Disable JS bundling and minification (Tailwind handles CSS)
bin/magento config:set dev/js/enable_js_bundling 0
bin/magento config:set dev/js/minify_files 0
bin/magento config:set dev/css/minify_files 0

# Enable required GraphQL modules
bin/magento module:enable \
    Magento_CatalogGraphQl \
    Magento_QuoteGraphQl \
    Magento_CustomerGraphQl \
    Magento_UrlRewriteGraphQl

bin/magento setup:upgrade
bin/magento cache:flush
```

---

## Hyvä Best Practices

| Practice | Description |
|----------|-------------|
| View Models for data | Prefer over Block classes — cleaner separation |
| Alpine stores for shared state | Cart count, customer session, wishlist |
| Tailwind utilities | Prefer `class="..."` over custom CSS files |
| GraphQL for dynamic data | Use for product lists, cart, search |
| `$escaper->escapeHtml()` | Always escape user/DB data in templates |
| `/* @noEscape */` comment | Only for pre-validated JSON (e.g. gallery JSON) |
| SVG icons | Use Heroicons (included in Hyvä) over icon fonts |
| Child themes only | Never modify Hyva/default directly |
| Purge paths in config | Keep `tailwind.config.js` content paths accurate |

---

## Compatibility Modules

Third-party Luma modules need compatibility modules to work in Hyvä. Check:
- Hyvä Module Tracker: `https://gitlab.hyva.io/hyva-public/module-tracker`

```json
// hyva-themes.json — register custom module for Hyvä event system
{
    "Vendor_Module": {
        "src": "app/code/Vendor/Module"
    }
}
```

---

## Instructions for LLM

- Never use `require()`, `define()`, jQuery, KnockoutJS, or LESS in Hyvä templates
- All dynamic JS logic goes in inline `` with Alpine.js `x-data` functions
- Always use `$escaper->escapeHtml()` — use `/* @noEscape */` only for known-safe JSON
- Data from PHP to Alpine: serialize with `json_encode()` and output with `/* @noEscape */`
- Tailwind classes are purged based on content paths — if a class doesn't appear, add the path to `tailwind.config.js`
- After Tailwind changes: `npm run build-prod` in the theme's tailwind directory
- After PHP/layout changes: `bin/magento cache:clean` (+ static content deploy in production)
- Hyvä uses GraphQL heavily — if you're loading dynamic data, prefer GraphQL over AJAX REST calls

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [furan917](https://github.com/furan917)
- **Source:** [furan917/magento-ai-toolkit](https://github.com/furan917/magento-ai-toolkit)
- **License:** MPL-2.0

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-furan917-magento-ai-toolkit-magento-hyva
- Seller: https://agentstack.voostack.com/s/furan917
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
