Hyva Alpine Component
Write CSP-compatible Alpine.js components for Hyvä themes in Magento 2. This skill should be used when the user wants to create Alpine components, add interactivity to Hyvä templates, write JavaScript for Hyvä themes, or needs help with Alpine.js patterns that work with Content Security Policy. Trigger phrases include "create alpine component", "add interactivity", "alpine for hyva", "x-data component", "csp compatibility", "csp compliant javascript".
- Skill ID
- hyva-themes/hyva-ai-tools/hyva-alpine-component
- Publisher
- hyva-themes
- Repository
- hyva-ai-tools
- Installs
- 0
- Files
- 1
- Synced
- Sep 23, 2026
Open any RiverX project, open the Skills panel in the chat, and search for this identifier. The files are fetched from the source repository at install time.
hyva-themes/hyva-ai-tools/hyva-alpine-componentInstalls these files- SKILL.md
What this skill tells the agent
Hyvä Alpine Component
Overview
This skill provides guidance for writing CSP-compatible Alpine.js components in Hyvä themes. Alpine CSP is a specialized Alpine.js build that operates without the unsafe-eval CSP directive, which is required for PCI-DSS 4.0 compliance on payment-related pages (mandatory from April 1, 2025).
Key principle: CSP-compatible code functions in both standard and Alpine CSP builds. Write all Alpine code using CSP patterns for future-proofing.
CSP Constraints Summary
| Capability | Standard Alpine | Alpine CSP |
|---|---|---|
| Property reads | x-show="open" | Same |
| Negation | x-show="!open" | Method: x-show="isNotOpen" |
| Mutations | @click="open = false" | Method: @click="close" |
| Method args | @click="setTab('info')" | Dataset: @click="setTab" data-tab="info" |
x-model | Available | Not supported - use :value + @input |
| Range iteration | x-for="i in 10" | Not supported |
Component Structure Pattern
Every Alpine component in Hyvä follows this structure:
<div x-data="initComponentName">
<!-- Template content -->
</div>
<script>
function initComponentName() {
return {
// Properties
propertyName: initialValue,
// Lifecycle
init() {
// Called when component initializes
},
// Methods for state access
isPropertyTrue() {
return this.propertyName === true;
},
// Methods for mutations
setPropertyValue() {
this.propertyName = this.$event.target.value;
}
}
}
window.addEventListener('alpine:init', () => Alpine.data('initComponentName', initComponentName), {once: true})
</script>
<?php $hyvaCsp->registerInlineScript() ?>Critical requirements:
- Register constructor with
Alpine.data()insidealpine:initevent listener - Use
{once: true}to prevent duplicate registrations - Call
$hyvaCsp->registerInlineScript()after every<script>block - Use
$escaper->escapeJs()for PHP values in JavaScript strings - Use
$escaper->escapeHtmlAttr()for data attributes (notescapeJs)
Constructor Functions
Basic Registration
function initMyComponent() {
return {
open: false
}
}
window.addEventListener('alpine:init', () => Alpine.data('initMyComponent', initMyComponent), {once: true})Why named global functions? Constructor functions are declared as named functions in global scope (not inlined in the Alpine.data() callback) so they can be proxied and extended in other templates. This is an extensibility feature of Hyvä Themes - other modules or child themes can wrap or override these functions before they are registered with Alpine.
Composing Multiple Objects
When combining objects (e.g., with hyva.modal), use spread syntax inside the constructor:
function initMyModal() {
return {
...hyva.modal.call(this),
...hyva.formValidation(this.$el),
customProperty: '',
customMethod() {
// Custom logic
}
};
}Use .call(this) to pass Alpine context to composed functions.
Property Access Patterns
Value Properties with Dot Notation
return {
item: {
is_visible: true,
title: 'Product'
}
}<span x-show="item.is_visible" x-text="item.title"></span>Transforming Values (Negation, Conditions)
CSP does not allow inline transformations. Create methods instead:
Wrong (CSP incompatible):
<span x-show="!item.deleted"></span>
<span x-text="item.title || item.value"></span>Correct:
<span x-show="isItemNotDeleted"></span>
<span x-text="itemLabel"></span>return {
item: { deleted: false, title: '', value: '' },
isItemNotDeleted() {
return !this.item.deleted;
},
itemLabel() {
return this.item.title || this.item.value;
}
}Negation Method Shorthand
For simple boolean negation, use bracket notation:
return {
deleted: false,
['!deleted']() {
return !this.deleted;
}
}<template x-if="!deleted">
<div>The item is present</div>
</template>Property Mutation Patterns
Extract Mutations to Methods
Wrong (CSP incompatible):
<button @click="open = !open">Toggle</button>Correct:
<button @click="toggle">Toggle</button>return {
open: false,
toggle() {
this.open = !this.open;
}
}Passing Arguments via Dataset
Wrong (CSP incompatible):
<button @click="selectItem(123)">Select</button>Correct:
<button @click="selectItem" data-item-id="<?= $escaper->escapeHtmlAttr($itemId) ?>">Select</button>return {
selected: null,
selectItem() {
this.selected = this.$el.dataset.itemId;
}
}Important: Use escapeHtmlAttr for data attributes, not escapeJs.
Accessing Event and Loop Variables in Methods
Methods can access Alpine's special properties:
return {
onInput() {
// Access event
const value = this.$event.target.value;
this.inputValue = value;
},
getItemUrl() {
// Access x-for loop variable
return `${BASE_URL}/product/id/${this.item.id}`;
}
}x-model Alternatives
x-model is not available in Alpine CSP. Use two-way binding patterns instead.
Text Inputs
<input type="text"
:value="username"
@input="setUsername">return {
username: '',
setUsername() {
this.username = this.$event.target.value;
}
}Number Inputs
Use hyva.safeParseNumber() for numeric values:
return {
quantity: 1,
setQuantity() {
this.quantity = hyva.safeParseNumber(this.$event.target.value);
}
}Textarea
<textarea @input="setComment" x-text="comment"></textarea>return {
comment: '',
setComment() {
this.comment = this.$event.target.value;
}
}Checkboxes
<input type="checkbox"
:checked="isSubscribed"
@change="toggleSubscribed">return {
isSubscribed: false,
toggleSubscribed() {
this.isSubscribed = this.$event.target.checked;
}
}Checkbox Arrays
<template x-for="option in options" :key="option.id">
<input type="checkbox"
:value="option.id"
:checked="isOptionSelected"
@change="toggleOption"
:data-option-id="option.id">
</template>return {
selectedOptions: [],
isOptionSelected() {
return this.selectedOptions.includes(this.option.id);
},
toggleOption() {
const optionId = this.$el.dataset.optionId;
const index = this.selectedOptions.indexOf(optionId);
if (index === -1) {
this.selectedOptions.push(optionId);
} else {
this.selectedOptions.splice(index, 1);
}
}
}Select Elements
<select @change="setCountry">
<template x-for="country in countries" :key="country.code">
<option :value="country.code"
:selected="isCountrySelected"
x-text="country.name"></option>
</template>
</select>return {
selectedCountry: '',
isCountrySelected() {
return this.selectedCountry === this.country.code;
},
setCountry() {
this.selectedCountry = this.$event.target.value;
}
}x-for Patterns
Basic Iteration
<template x-for="(product, index) in products" :key="index">
<div x-text="product.name"></div>
</template>Using Methods in Loops
More from hyva-themes
Use this skill in a project
Open any RiverX project, search the Skills panel for hyva-themes/hyva-ai-tools/hyva-alpine-component, and install it in one click.
